1  // SPDX-License-Identifier: MIT
2  /*
3   * Copyright © 2023 Intel Corporation
4   */
5  
6  #include <linux/bitmap.h>
7  #include <linux/mutex.h>
8  
9  #include <drm/drm_managed.h>
10  
11  #include "regs/xe_guc_regs.h"
12  
13  #include "xe_assert.h"
14  #include "xe_gt_printk.h"
15  #include "xe_guc.h"
16  #include "xe_guc_db_mgr.h"
17  #include "xe_guc_types.h"
18  
19  /**
20   * DOC: GuC Doorbells
21   *
22   * The GFX doorbell solution provides a mechanism for submission of workload
23   * to the graphics hardware by a ring3 application without the penalty of
24   * ring transition for each workload submission.
25   *
26   * In SR-IOV mode, the doorbells are treated as shared resource and PF must
27   * be able to provision exclusive range of IDs across VFs, which may want to
28   * use this feature.
29   */
30  
dbm_to_guc(struct xe_guc_db_mgr * dbm)31  static struct xe_guc *dbm_to_guc(struct xe_guc_db_mgr *dbm)
32  {
33  	return container_of(dbm, struct xe_guc, dbm);
34  }
35  
dbm_to_gt(struct xe_guc_db_mgr * dbm)36  static struct xe_gt *dbm_to_gt(struct xe_guc_db_mgr *dbm)
37  {
38  	return guc_to_gt(dbm_to_guc(dbm));
39  }
40  
dbm_to_xe(struct xe_guc_db_mgr * dbm)41  static struct xe_device *dbm_to_xe(struct xe_guc_db_mgr *dbm)
42  {
43  	return gt_to_xe(dbm_to_gt(dbm));
44  }
45  
46  #define dbm_assert(_dbm, _cond)		xe_gt_assert(dbm_to_gt(_dbm), _cond)
47  #define dbm_mutex(_dbm)			(&dbm_to_guc(_dbm)->submission_state.lock)
48  
49  static void dbm_print_locked(struct xe_guc_db_mgr *dbm, struct drm_printer *p, int indent);
50  
__fini_dbm(struct drm_device * drm,void * arg)51  static void __fini_dbm(struct drm_device *drm, void *arg)
52  {
53  	struct xe_guc_db_mgr *dbm = arg;
54  	unsigned int weight;
55  
56  	mutex_lock(dbm_mutex(dbm));
57  
58  	weight = bitmap_weight(dbm->bitmap, dbm->count);
59  	if (weight) {
60  		struct drm_printer p = xe_gt_info_printer(dbm_to_gt(dbm));
61  
62  		xe_gt_err(dbm_to_gt(dbm), "GuC doorbells manager unclean (%u/%u)\n",
63  			  weight, dbm->count);
64  		dbm_print_locked(dbm, &p, 1);
65  	}
66  
67  	bitmap_free(dbm->bitmap);
68  	dbm->bitmap = NULL;
69  	dbm->count = 0;
70  
71  	mutex_unlock(dbm_mutex(dbm));
72  }
73  
74  /**
75   * xe_guc_db_mgr_init() - Initialize GuC Doorbells Manager.
76   * @dbm: the &xe_guc_db_mgr to initialize
77   * @count: number of doorbells to manage
78   *
79   * The bare-metal or PF driver can pass ~0 as &count to indicate that all
80   * doorbells supported by the hardware are available for use.
81   *
82   * Only VF's drivers will have to provide explicit number of doorbells IDs
83   * that they can use.
84   *
85   * Return: 0 on success or a negative error code on failure.
86   */
xe_guc_db_mgr_init(struct xe_guc_db_mgr * dbm,unsigned int count)87  int xe_guc_db_mgr_init(struct xe_guc_db_mgr *dbm, unsigned int count)
88  {
89  	int ret;
90  
91  	if (count == ~0)
92  		count = GUC_NUM_DOORBELLS;
93  
94  	dbm_assert(dbm, !dbm->bitmap);
95  	dbm_assert(dbm, count <= GUC_NUM_DOORBELLS);
96  
97  	if (!count)
98  		goto done;
99  
100  	dbm->bitmap = bitmap_zalloc(count, GFP_KERNEL);
101  	if (!dbm->bitmap)
102  		return -ENOMEM;
103  	dbm->count = count;
104  
105  	ret = drmm_add_action_or_reset(&dbm_to_xe(dbm)->drm, __fini_dbm, dbm);
106  	if (ret)
107  		return ret;
108  done:
109  	xe_gt_dbg(dbm_to_gt(dbm), "using %u doorbell%s\n",
110  		  dbm->count, str_plural(dbm->count));
111  	return 0;
112  }
113  
dbm_reserve_chunk_locked(struct xe_guc_db_mgr * dbm,unsigned int count,unsigned int spare)114  static int dbm_reserve_chunk_locked(struct xe_guc_db_mgr *dbm,
115  				    unsigned int count, unsigned int spare)
116  {
117  	unsigned int used;
118  	int index;
119  
120  	dbm_assert(dbm, count);
121  	dbm_assert(dbm, count <= GUC_NUM_DOORBELLS);
122  	dbm_assert(dbm, dbm->count <= GUC_NUM_DOORBELLS);
123  	lockdep_assert_held(dbm_mutex(dbm));
124  
125  	if (!dbm->count)
126  		return -ENODATA;
127  
128  	if (spare) {
129  		used = bitmap_weight(dbm->bitmap, dbm->count);
130  		if (used + count + spare > dbm->count)
131  			return -EDQUOT;
132  	}
133  
134  	index = bitmap_find_next_zero_area(dbm->bitmap, dbm->count, 0, count, 0);
135  	if (index >= dbm->count)
136  		return -ENOSPC;
137  
138  	bitmap_set(dbm->bitmap, index, count);
139  
140  	return index;
141  }
142  
dbm_release_chunk_locked(struct xe_guc_db_mgr * dbm,unsigned int start,unsigned int count)143  static void dbm_release_chunk_locked(struct xe_guc_db_mgr *dbm,
144  				     unsigned int start, unsigned int count)
145  {
146  	dbm_assert(dbm, count);
147  	dbm_assert(dbm, count <= GUC_NUM_DOORBELLS);
148  	dbm_assert(dbm, dbm->count);
149  	dbm_assert(dbm, dbm->count <= GUC_NUM_DOORBELLS);
150  	lockdep_assert_held(dbm_mutex(dbm));
151  
152  	if (IS_ENABLED(CONFIG_DRM_XE_DEBUG)) {
153  		unsigned int n;
154  
155  		for (n = 0; n < count; n++)
156  			dbm_assert(dbm, test_bit(start + n, dbm->bitmap));
157  	}
158  	bitmap_clear(dbm->bitmap, start, count);
159  }
160  
161  /**
162   * xe_guc_db_mgr_reserve_id_locked() - Reserve a single GuC Doorbell ID.
163   * @dbm: the &xe_guc_db_mgr
164   *
165   * This function expects that submission lock is already taken.
166   *
167   * Return: ID of the allocated GuC doorbell or a negative error code on failure.
168   */
xe_guc_db_mgr_reserve_id_locked(struct xe_guc_db_mgr * dbm)169  int xe_guc_db_mgr_reserve_id_locked(struct xe_guc_db_mgr *dbm)
170  {
171  	return dbm_reserve_chunk_locked(dbm, 1, 0);
172  }
173  
174  /**
175   * xe_guc_db_mgr_release_id_locked() - Release a single GuC Doorbell ID.
176   * @dbm: the &xe_guc_db_mgr
177   * @id: the GuC Doorbell ID to release
178   *
179   * This function expects that submission lock is already taken.
180   */
xe_guc_db_mgr_release_id_locked(struct xe_guc_db_mgr * dbm,unsigned int id)181  void xe_guc_db_mgr_release_id_locked(struct xe_guc_db_mgr *dbm, unsigned int id)
182  {
183  	return dbm_release_chunk_locked(dbm, id, 1);
184  }
185  
186  /**
187   * xe_guc_db_mgr_reserve_range() - Reserve a range of GuC Doorbell IDs.
188   * @dbm: the &xe_guc_db_mgr
189   * @count: number of GuC doorbell IDs to reserve
190   * @spare: number of GuC doorbell IDs to keep available
191   *
192   * This function is dedicated for the for use by the PF which expects that
193   * allocated range for the VF will be contiguous and that there will be at
194   * least &spare IDs still available for the PF use after this reservation.
195   *
196   * Return: starting ID of the allocated GuC doorbell ID range or
197   *         a negative error code on failure.
198   */
xe_guc_db_mgr_reserve_range(struct xe_guc_db_mgr * dbm,unsigned int count,unsigned int spare)199  int xe_guc_db_mgr_reserve_range(struct xe_guc_db_mgr *dbm,
200  				unsigned int count, unsigned int spare)
201  {
202  	int ret;
203  
204  	mutex_lock(dbm_mutex(dbm));
205  	ret = dbm_reserve_chunk_locked(dbm, count, spare);
206  	mutex_unlock(dbm_mutex(dbm));
207  
208  	return ret;
209  }
210  
211  /**
212   * xe_guc_db_mgr_release_range() - Release a range of Doorbell IDs.
213   * @dbm: the &xe_guc_db_mgr
214   * @start: the starting ID of GuC doorbell ID range to release
215   * @count: number of GuC doorbell IDs to release
216   */
xe_guc_db_mgr_release_range(struct xe_guc_db_mgr * dbm,unsigned int start,unsigned int count)217  void xe_guc_db_mgr_release_range(struct xe_guc_db_mgr *dbm,
218  				 unsigned int start, unsigned int count)
219  {
220  	mutex_lock(dbm_mutex(dbm));
221  	dbm_release_chunk_locked(dbm, start, count);
222  	mutex_unlock(dbm_mutex(dbm));
223  }
224  
dbm_print_locked(struct xe_guc_db_mgr * dbm,struct drm_printer * p,int indent)225  static void dbm_print_locked(struct xe_guc_db_mgr *dbm, struct drm_printer *p, int indent)
226  {
227  	unsigned int rs, re;
228  	unsigned int total;
229  
230  	drm_printf_indent(p, indent, "count: %u\n", dbm->count);
231  	if (!dbm->bitmap)
232  		return;
233  
234  	total = 0;
235  	for_each_clear_bitrange(rs, re, dbm->bitmap, dbm->count) {
236  		drm_printf_indent(p, indent, "available range: %u..%u (%u)\n",
237  				  rs, re - 1, re - rs);
238  		total += re - rs;
239  	}
240  	drm_printf_indent(p, indent, "available total: %u\n", total);
241  
242  	total = 0;
243  	for_each_set_bitrange(rs, re, dbm->bitmap, dbm->count) {
244  		drm_printf_indent(p, indent, "reserved range: %u..%u (%u)\n",
245  				  rs, re - 1, re - rs);
246  		total += re - rs;
247  	}
248  	drm_printf_indent(p, indent, "reserved total: %u\n", total);
249  }
250  
251  /**
252   * xe_guc_db_mgr_print() - Print status of GuC Doorbells Manager.
253   * @dbm: the &xe_guc_db_mgr to print
254   * @p: the &drm_printer to print to
255   * @indent: tab indentation level
256   */
xe_guc_db_mgr_print(struct xe_guc_db_mgr * dbm,struct drm_printer * p,int indent)257  void xe_guc_db_mgr_print(struct xe_guc_db_mgr *dbm,
258  			 struct drm_printer *p, int indent)
259  {
260  	mutex_lock(dbm_mutex(dbm));
261  	dbm_print_locked(dbm, p, indent);
262  	mutex_unlock(dbm_mutex(dbm));
263  }
264  
265  #if IS_BUILTIN(CONFIG_DRM_XE_KUNIT_TEST)
266  #include "tests/xe_guc_db_mgr_test.c"
267  #endif
268