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