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