1 | // SPDX-License-Identifier: GPL-2.0 |
2 | |
3 | /* |
4 | * Copyright 2022 HabanaLabs, Ltd. |
5 | * All Rights Reserved. |
6 | * |
7 | */ |
8 | |
9 | #include <linux/debugfs.h> |
10 | #include <linux/device.h> |
11 | #include <linux/idr.h> |
12 | |
13 | #include <drm/drm_accel.h> |
14 | #include <drm/drm_debugfs.h> |
15 | #include <drm/drm_drv.h> |
16 | #include <drm/drm_file.h> |
17 | #include <drm/drm_ioctl.h> |
18 | #include <drm/drm_print.h> |
19 | |
20 | static DEFINE_SPINLOCK(accel_minor_lock); |
21 | static struct idr accel_minors_idr; |
22 | |
23 | static struct dentry *accel_debugfs_root; |
24 | |
25 | static struct device_type accel_sysfs_device_minor = { |
26 | .name = "accel_minor" |
27 | }; |
28 | |
29 | static char *accel_devnode(const struct device *dev, umode_t *mode) |
30 | { |
31 | return kasprintf(GFP_KERNEL, fmt: "accel/%s" , dev_name(dev)); |
32 | } |
33 | |
34 | static const struct class accel_class = { |
35 | .name = "accel" , |
36 | .devnode = accel_devnode, |
37 | }; |
38 | |
39 | static int accel_sysfs_init(void) |
40 | { |
41 | return class_register(class: &accel_class); |
42 | } |
43 | |
44 | static void accel_sysfs_destroy(void) |
45 | { |
46 | class_unregister(class: &accel_class); |
47 | } |
48 | |
49 | static int accel_name_info(struct seq_file *m, void *data) |
50 | { |
51 | struct drm_info_node *node = (struct drm_info_node *) m->private; |
52 | struct drm_minor *minor = node->minor; |
53 | struct drm_device *dev = minor->dev; |
54 | struct drm_master *master; |
55 | |
56 | mutex_lock(&dev->master_mutex); |
57 | master = dev->master; |
58 | seq_printf(m, fmt: "%s" , dev->driver->name); |
59 | if (dev->dev) |
60 | seq_printf(m, fmt: " dev=%s" , dev_name(dev: dev->dev)); |
61 | if (master && master->unique) |
62 | seq_printf(m, fmt: " master=%s" , master->unique); |
63 | if (dev->unique) |
64 | seq_printf(m, fmt: " unique=%s" , dev->unique); |
65 | seq_puts(m, s: "\n" ); |
66 | mutex_unlock(lock: &dev->master_mutex); |
67 | |
68 | return 0; |
69 | } |
70 | |
71 | static const struct drm_info_list accel_debugfs_list[] = { |
72 | {"name" , accel_name_info, 0} |
73 | }; |
74 | #define ACCEL_DEBUGFS_ENTRIES ARRAY_SIZE(accel_debugfs_list) |
75 | |
76 | /** |
77 | * accel_debugfs_init() - Initialize debugfs for device |
78 | * @dev: Pointer to the device instance. |
79 | * |
80 | * This function creates a root directory for the device in debugfs. |
81 | */ |
82 | void accel_debugfs_init(struct drm_device *dev) |
83 | { |
84 | drm_debugfs_dev_init(dev, root: accel_debugfs_root); |
85 | } |
86 | |
87 | /** |
88 | * accel_debugfs_register() - Register debugfs for device |
89 | * @dev: Pointer to the device instance. |
90 | * |
91 | * Creates common files for accelerators. |
92 | */ |
93 | void accel_debugfs_register(struct drm_device *dev) |
94 | { |
95 | struct drm_minor *minor = dev->accel; |
96 | |
97 | minor->debugfs_root = dev->debugfs_root; |
98 | |
99 | drm_debugfs_create_files(files: accel_debugfs_list, ACCEL_DEBUGFS_ENTRIES, |
100 | root: dev->debugfs_root, minor); |
101 | } |
102 | |
103 | /** |
104 | * accel_set_device_instance_params() - Set some device parameters for accel device |
105 | * @kdev: Pointer to the device instance. |
106 | * @index: The minor's index |
107 | * |
108 | * This function creates the dev_t of the device using the accel major and |
109 | * the device's minor number. In addition, it sets the class and type of the |
110 | * device instance to the accel sysfs class and device type, respectively. |
111 | */ |
112 | void accel_set_device_instance_params(struct device *kdev, int index) |
113 | { |
114 | kdev->devt = MKDEV(ACCEL_MAJOR, index); |
115 | kdev->class = &accel_class; |
116 | kdev->type = &accel_sysfs_device_minor; |
117 | } |
118 | |
119 | /** |
120 | * accel_minor_alloc() - Allocates a new accel minor |
121 | * |
122 | * This function access the accel minors idr and allocates from it |
123 | * a new id to represent a new accel minor |
124 | * |
125 | * Return: A new id on success or error code in case idr_alloc failed |
126 | */ |
127 | int accel_minor_alloc(void) |
128 | { |
129 | unsigned long flags; |
130 | int r; |
131 | |
132 | spin_lock_irqsave(&accel_minor_lock, flags); |
133 | r = idr_alloc(&accel_minors_idr, NULL, start: 0, ACCEL_MAX_MINORS, GFP_NOWAIT); |
134 | spin_unlock_irqrestore(lock: &accel_minor_lock, flags); |
135 | |
136 | return r; |
137 | } |
138 | |
139 | /** |
140 | * accel_minor_remove() - Remove an accel minor |
141 | * @index: The minor id to remove. |
142 | * |
143 | * This function access the accel minors idr and removes from |
144 | * it the member with the id that is passed to this function. |
145 | */ |
146 | void accel_minor_remove(int index) |
147 | { |
148 | unsigned long flags; |
149 | |
150 | spin_lock_irqsave(&accel_minor_lock, flags); |
151 | idr_remove(&accel_minors_idr, id: index); |
152 | spin_unlock_irqrestore(lock: &accel_minor_lock, flags); |
153 | } |
154 | |
155 | /** |
156 | * accel_minor_replace() - Replace minor pointer in accel minors idr. |
157 | * @minor: Pointer to the new minor. |
158 | * @index: The minor id to replace. |
159 | * |
160 | * This function access the accel minors idr structure and replaces the pointer |
161 | * that is associated with an existing id. Because the minor pointer can be |
162 | * NULL, we need to explicitly pass the index. |
163 | * |
164 | * Return: 0 for success, negative value for error |
165 | */ |
166 | void accel_minor_replace(struct drm_minor *minor, int index) |
167 | { |
168 | unsigned long flags; |
169 | |
170 | spin_lock_irqsave(&accel_minor_lock, flags); |
171 | idr_replace(&accel_minors_idr, minor, id: index); |
172 | spin_unlock_irqrestore(lock: &accel_minor_lock, flags); |
173 | } |
174 | |
175 | /* |
176 | * Looks up the given minor-ID and returns the respective DRM-minor object. The |
177 | * refence-count of the underlying device is increased so you must release this |
178 | * object with accel_minor_release(). |
179 | * |
180 | * The object can be only a drm_minor that represents an accel device. |
181 | * |
182 | * As long as you hold this minor, it is guaranteed that the object and the |
183 | * minor->dev pointer will stay valid! However, the device may get unplugged and |
184 | * unregistered while you hold the minor. |
185 | */ |
186 | static struct drm_minor *accel_minor_acquire(unsigned int minor_id) |
187 | { |
188 | struct drm_minor *minor; |
189 | unsigned long flags; |
190 | |
191 | spin_lock_irqsave(&accel_minor_lock, flags); |
192 | minor = idr_find(&accel_minors_idr, id: minor_id); |
193 | if (minor) |
194 | drm_dev_get(dev: minor->dev); |
195 | spin_unlock_irqrestore(lock: &accel_minor_lock, flags); |
196 | |
197 | if (!minor) { |
198 | return ERR_PTR(error: -ENODEV); |
199 | } else if (drm_dev_is_unplugged(dev: minor->dev)) { |
200 | drm_dev_put(dev: minor->dev); |
201 | return ERR_PTR(error: -ENODEV); |
202 | } |
203 | |
204 | return minor; |
205 | } |
206 | |
207 | static void accel_minor_release(struct drm_minor *minor) |
208 | { |
209 | drm_dev_put(dev: minor->dev); |
210 | } |
211 | |
212 | /** |
213 | * accel_open - open method for ACCEL file |
214 | * @inode: device inode |
215 | * @filp: file pointer. |
216 | * |
217 | * This function must be used by drivers as their &file_operations.open method. |
218 | * It looks up the correct ACCEL device and instantiates all the per-file |
219 | * resources for it. It also calls the &drm_driver.open driver callback. |
220 | * |
221 | * Return: 0 on success or negative errno value on failure. |
222 | */ |
223 | int accel_open(struct inode *inode, struct file *filp) |
224 | { |
225 | struct drm_device *dev; |
226 | struct drm_minor *minor; |
227 | int retcode; |
228 | |
229 | minor = accel_minor_acquire(minor_id: iminor(inode)); |
230 | if (IS_ERR(ptr: minor)) |
231 | return PTR_ERR(ptr: minor); |
232 | |
233 | dev = minor->dev; |
234 | |
235 | atomic_fetch_inc(v: &dev->open_count); |
236 | |
237 | /* share address_space across all char-devs of a single device */ |
238 | filp->f_mapping = dev->anon_inode->i_mapping; |
239 | |
240 | retcode = drm_open_helper(filp, minor); |
241 | if (retcode) |
242 | goto err_undo; |
243 | |
244 | return 0; |
245 | |
246 | err_undo: |
247 | atomic_dec(v: &dev->open_count); |
248 | accel_minor_release(minor); |
249 | return retcode; |
250 | } |
251 | EXPORT_SYMBOL_GPL(accel_open); |
252 | |
253 | static int accel_stub_open(struct inode *inode, struct file *filp) |
254 | { |
255 | const struct file_operations *new_fops; |
256 | struct drm_minor *minor; |
257 | int err; |
258 | |
259 | minor = accel_minor_acquire(minor_id: iminor(inode)); |
260 | if (IS_ERR(ptr: minor)) |
261 | return PTR_ERR(ptr: minor); |
262 | |
263 | new_fops = fops_get(minor->dev->driver->fops); |
264 | if (!new_fops) { |
265 | err = -ENODEV; |
266 | goto out; |
267 | } |
268 | |
269 | replace_fops(filp, new_fops); |
270 | if (filp->f_op->open) |
271 | err = filp->f_op->open(inode, filp); |
272 | else |
273 | err = 0; |
274 | |
275 | out: |
276 | accel_minor_release(minor); |
277 | |
278 | return err; |
279 | } |
280 | |
281 | static const struct file_operations accel_stub_fops = { |
282 | .owner = THIS_MODULE, |
283 | .open = accel_stub_open, |
284 | .llseek = noop_llseek, |
285 | }; |
286 | |
287 | void accel_core_exit(void) |
288 | { |
289 | unregister_chrdev(ACCEL_MAJOR, name: "accel" ); |
290 | debugfs_remove(dentry: accel_debugfs_root); |
291 | accel_sysfs_destroy(); |
292 | idr_destroy(&accel_minors_idr); |
293 | } |
294 | |
295 | int __init accel_core_init(void) |
296 | { |
297 | int ret; |
298 | |
299 | idr_init(idr: &accel_minors_idr); |
300 | |
301 | ret = accel_sysfs_init(); |
302 | if (ret < 0) { |
303 | DRM_ERROR("Cannot create ACCEL class: %d\n" , ret); |
304 | goto error; |
305 | } |
306 | |
307 | accel_debugfs_root = debugfs_create_dir(name: "accel" , NULL); |
308 | |
309 | ret = register_chrdev(ACCEL_MAJOR, name: "accel" , fops: &accel_stub_fops); |
310 | if (ret < 0) |
311 | DRM_ERROR("Cannot register ACCEL major: %d\n" , ret); |
312 | |
313 | error: |
314 | /* |
315 | * Any cleanup due to errors will be done in drm_core_exit() that |
316 | * will call accel_core_exit() |
317 | */ |
318 | return ret; |
319 | } |
320 | |