162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci 362306a36Sopenharmony_ci/* 462306a36Sopenharmony_ci * Copyright 2022 HabanaLabs, Ltd. 562306a36Sopenharmony_ci * All Rights Reserved. 662306a36Sopenharmony_ci * 762306a36Sopenharmony_ci */ 862306a36Sopenharmony_ci 962306a36Sopenharmony_ci#include <linux/debugfs.h> 1062306a36Sopenharmony_ci#include <linux/device.h> 1162306a36Sopenharmony_ci#include <linux/idr.h> 1262306a36Sopenharmony_ci 1362306a36Sopenharmony_ci#include <drm/drm_accel.h> 1462306a36Sopenharmony_ci#include <drm/drm_debugfs.h> 1562306a36Sopenharmony_ci#include <drm/drm_drv.h> 1662306a36Sopenharmony_ci#include <drm/drm_file.h> 1762306a36Sopenharmony_ci#include <drm/drm_ioctl.h> 1862306a36Sopenharmony_ci#include <drm/drm_print.h> 1962306a36Sopenharmony_ci 2062306a36Sopenharmony_cistatic DEFINE_SPINLOCK(accel_minor_lock); 2162306a36Sopenharmony_cistatic struct idr accel_minors_idr; 2262306a36Sopenharmony_ci 2362306a36Sopenharmony_cistatic struct dentry *accel_debugfs_root; 2462306a36Sopenharmony_cistatic struct class *accel_class; 2562306a36Sopenharmony_ci 2662306a36Sopenharmony_cistatic struct device_type accel_sysfs_device_minor = { 2762306a36Sopenharmony_ci .name = "accel_minor" 2862306a36Sopenharmony_ci}; 2962306a36Sopenharmony_ci 3062306a36Sopenharmony_cistatic char *accel_devnode(const struct device *dev, umode_t *mode) 3162306a36Sopenharmony_ci{ 3262306a36Sopenharmony_ci return kasprintf(GFP_KERNEL, "accel/%s", dev_name(dev)); 3362306a36Sopenharmony_ci} 3462306a36Sopenharmony_ci 3562306a36Sopenharmony_cistatic int accel_sysfs_init(void) 3662306a36Sopenharmony_ci{ 3762306a36Sopenharmony_ci accel_class = class_create("accel"); 3862306a36Sopenharmony_ci if (IS_ERR(accel_class)) 3962306a36Sopenharmony_ci return PTR_ERR(accel_class); 4062306a36Sopenharmony_ci 4162306a36Sopenharmony_ci accel_class->devnode = accel_devnode; 4262306a36Sopenharmony_ci 4362306a36Sopenharmony_ci return 0; 4462306a36Sopenharmony_ci} 4562306a36Sopenharmony_ci 4662306a36Sopenharmony_cistatic void accel_sysfs_destroy(void) 4762306a36Sopenharmony_ci{ 4862306a36Sopenharmony_ci if (IS_ERR_OR_NULL(accel_class)) 4962306a36Sopenharmony_ci return; 5062306a36Sopenharmony_ci class_destroy(accel_class); 5162306a36Sopenharmony_ci accel_class = NULL; 5262306a36Sopenharmony_ci} 5362306a36Sopenharmony_ci 5462306a36Sopenharmony_cistatic int accel_name_info(struct seq_file *m, void *data) 5562306a36Sopenharmony_ci{ 5662306a36Sopenharmony_ci struct drm_info_node *node = (struct drm_info_node *) m->private; 5762306a36Sopenharmony_ci struct drm_minor *minor = node->minor; 5862306a36Sopenharmony_ci struct drm_device *dev = minor->dev; 5962306a36Sopenharmony_ci struct drm_master *master; 6062306a36Sopenharmony_ci 6162306a36Sopenharmony_ci mutex_lock(&dev->master_mutex); 6262306a36Sopenharmony_ci master = dev->master; 6362306a36Sopenharmony_ci seq_printf(m, "%s", dev->driver->name); 6462306a36Sopenharmony_ci if (dev->dev) 6562306a36Sopenharmony_ci seq_printf(m, " dev=%s", dev_name(dev->dev)); 6662306a36Sopenharmony_ci if (master && master->unique) 6762306a36Sopenharmony_ci seq_printf(m, " master=%s", master->unique); 6862306a36Sopenharmony_ci if (dev->unique) 6962306a36Sopenharmony_ci seq_printf(m, " unique=%s", dev->unique); 7062306a36Sopenharmony_ci seq_puts(m, "\n"); 7162306a36Sopenharmony_ci mutex_unlock(&dev->master_mutex); 7262306a36Sopenharmony_ci 7362306a36Sopenharmony_ci return 0; 7462306a36Sopenharmony_ci} 7562306a36Sopenharmony_ci 7662306a36Sopenharmony_cistatic const struct drm_info_list accel_debugfs_list[] = { 7762306a36Sopenharmony_ci {"name", accel_name_info, 0} 7862306a36Sopenharmony_ci}; 7962306a36Sopenharmony_ci#define ACCEL_DEBUGFS_ENTRIES ARRAY_SIZE(accel_debugfs_list) 8062306a36Sopenharmony_ci 8162306a36Sopenharmony_ci/** 8262306a36Sopenharmony_ci * accel_debugfs_init() - Initialize debugfs for accel minor 8362306a36Sopenharmony_ci * @minor: Pointer to the drm_minor instance. 8462306a36Sopenharmony_ci * @minor_id: The minor's id 8562306a36Sopenharmony_ci * 8662306a36Sopenharmony_ci * This function initializes the drm minor's debugfs members and creates 8762306a36Sopenharmony_ci * a root directory for the minor in debugfs. It also creates common files 8862306a36Sopenharmony_ci * for accelerators and calls the driver's debugfs init callback. 8962306a36Sopenharmony_ci */ 9062306a36Sopenharmony_civoid accel_debugfs_init(struct drm_minor *minor, int minor_id) 9162306a36Sopenharmony_ci{ 9262306a36Sopenharmony_ci struct drm_device *dev = minor->dev; 9362306a36Sopenharmony_ci char name[64]; 9462306a36Sopenharmony_ci 9562306a36Sopenharmony_ci INIT_LIST_HEAD(&minor->debugfs_list); 9662306a36Sopenharmony_ci mutex_init(&minor->debugfs_lock); 9762306a36Sopenharmony_ci sprintf(name, "%d", minor_id); 9862306a36Sopenharmony_ci minor->debugfs_root = debugfs_create_dir(name, accel_debugfs_root); 9962306a36Sopenharmony_ci 10062306a36Sopenharmony_ci drm_debugfs_create_files(accel_debugfs_list, ACCEL_DEBUGFS_ENTRIES, 10162306a36Sopenharmony_ci minor->debugfs_root, minor); 10262306a36Sopenharmony_ci 10362306a36Sopenharmony_ci if (dev->driver->debugfs_init) 10462306a36Sopenharmony_ci dev->driver->debugfs_init(minor); 10562306a36Sopenharmony_ci} 10662306a36Sopenharmony_ci 10762306a36Sopenharmony_ci/** 10862306a36Sopenharmony_ci * accel_set_device_instance_params() - Set some device parameters for accel device 10962306a36Sopenharmony_ci * @kdev: Pointer to the device instance. 11062306a36Sopenharmony_ci * @index: The minor's index 11162306a36Sopenharmony_ci * 11262306a36Sopenharmony_ci * This function creates the dev_t of the device using the accel major and 11362306a36Sopenharmony_ci * the device's minor number. In addition, it sets the class and type of the 11462306a36Sopenharmony_ci * device instance to the accel sysfs class and device type, respectively. 11562306a36Sopenharmony_ci */ 11662306a36Sopenharmony_civoid accel_set_device_instance_params(struct device *kdev, int index) 11762306a36Sopenharmony_ci{ 11862306a36Sopenharmony_ci kdev->devt = MKDEV(ACCEL_MAJOR, index); 11962306a36Sopenharmony_ci kdev->class = accel_class; 12062306a36Sopenharmony_ci kdev->type = &accel_sysfs_device_minor; 12162306a36Sopenharmony_ci} 12262306a36Sopenharmony_ci 12362306a36Sopenharmony_ci/** 12462306a36Sopenharmony_ci * accel_minor_alloc() - Allocates a new accel minor 12562306a36Sopenharmony_ci * 12662306a36Sopenharmony_ci * This function access the accel minors idr and allocates from it 12762306a36Sopenharmony_ci * a new id to represent a new accel minor 12862306a36Sopenharmony_ci * 12962306a36Sopenharmony_ci * Return: A new id on success or error code in case idr_alloc failed 13062306a36Sopenharmony_ci */ 13162306a36Sopenharmony_ciint accel_minor_alloc(void) 13262306a36Sopenharmony_ci{ 13362306a36Sopenharmony_ci unsigned long flags; 13462306a36Sopenharmony_ci int r; 13562306a36Sopenharmony_ci 13662306a36Sopenharmony_ci spin_lock_irqsave(&accel_minor_lock, flags); 13762306a36Sopenharmony_ci r = idr_alloc(&accel_minors_idr, NULL, 0, ACCEL_MAX_MINORS, GFP_NOWAIT); 13862306a36Sopenharmony_ci spin_unlock_irqrestore(&accel_minor_lock, flags); 13962306a36Sopenharmony_ci 14062306a36Sopenharmony_ci return r; 14162306a36Sopenharmony_ci} 14262306a36Sopenharmony_ci 14362306a36Sopenharmony_ci/** 14462306a36Sopenharmony_ci * accel_minor_remove() - Remove an accel minor 14562306a36Sopenharmony_ci * @index: The minor id to remove. 14662306a36Sopenharmony_ci * 14762306a36Sopenharmony_ci * This function access the accel minors idr and removes from 14862306a36Sopenharmony_ci * it the member with the id that is passed to this function. 14962306a36Sopenharmony_ci */ 15062306a36Sopenharmony_civoid accel_minor_remove(int index) 15162306a36Sopenharmony_ci{ 15262306a36Sopenharmony_ci unsigned long flags; 15362306a36Sopenharmony_ci 15462306a36Sopenharmony_ci spin_lock_irqsave(&accel_minor_lock, flags); 15562306a36Sopenharmony_ci idr_remove(&accel_minors_idr, index); 15662306a36Sopenharmony_ci spin_unlock_irqrestore(&accel_minor_lock, flags); 15762306a36Sopenharmony_ci} 15862306a36Sopenharmony_ci 15962306a36Sopenharmony_ci/** 16062306a36Sopenharmony_ci * accel_minor_replace() - Replace minor pointer in accel minors idr. 16162306a36Sopenharmony_ci * @minor: Pointer to the new minor. 16262306a36Sopenharmony_ci * @index: The minor id to replace. 16362306a36Sopenharmony_ci * 16462306a36Sopenharmony_ci * This function access the accel minors idr structure and replaces the pointer 16562306a36Sopenharmony_ci * that is associated with an existing id. Because the minor pointer can be 16662306a36Sopenharmony_ci * NULL, we need to explicitly pass the index. 16762306a36Sopenharmony_ci * 16862306a36Sopenharmony_ci * Return: 0 for success, negative value for error 16962306a36Sopenharmony_ci */ 17062306a36Sopenharmony_civoid accel_minor_replace(struct drm_minor *minor, int index) 17162306a36Sopenharmony_ci{ 17262306a36Sopenharmony_ci unsigned long flags; 17362306a36Sopenharmony_ci 17462306a36Sopenharmony_ci spin_lock_irqsave(&accel_minor_lock, flags); 17562306a36Sopenharmony_ci idr_replace(&accel_minors_idr, minor, index); 17662306a36Sopenharmony_ci spin_unlock_irqrestore(&accel_minor_lock, flags); 17762306a36Sopenharmony_ci} 17862306a36Sopenharmony_ci 17962306a36Sopenharmony_ci/* 18062306a36Sopenharmony_ci * Looks up the given minor-ID and returns the respective DRM-minor object. The 18162306a36Sopenharmony_ci * refence-count of the underlying device is increased so you must release this 18262306a36Sopenharmony_ci * object with accel_minor_release(). 18362306a36Sopenharmony_ci * 18462306a36Sopenharmony_ci * The object can be only a drm_minor that represents an accel device. 18562306a36Sopenharmony_ci * 18662306a36Sopenharmony_ci * As long as you hold this minor, it is guaranteed that the object and the 18762306a36Sopenharmony_ci * minor->dev pointer will stay valid! However, the device may get unplugged and 18862306a36Sopenharmony_ci * unregistered while you hold the minor. 18962306a36Sopenharmony_ci */ 19062306a36Sopenharmony_cistatic struct drm_minor *accel_minor_acquire(unsigned int minor_id) 19162306a36Sopenharmony_ci{ 19262306a36Sopenharmony_ci struct drm_minor *minor; 19362306a36Sopenharmony_ci unsigned long flags; 19462306a36Sopenharmony_ci 19562306a36Sopenharmony_ci spin_lock_irqsave(&accel_minor_lock, flags); 19662306a36Sopenharmony_ci minor = idr_find(&accel_minors_idr, minor_id); 19762306a36Sopenharmony_ci if (minor) 19862306a36Sopenharmony_ci drm_dev_get(minor->dev); 19962306a36Sopenharmony_ci spin_unlock_irqrestore(&accel_minor_lock, flags); 20062306a36Sopenharmony_ci 20162306a36Sopenharmony_ci if (!minor) { 20262306a36Sopenharmony_ci return ERR_PTR(-ENODEV); 20362306a36Sopenharmony_ci } else if (drm_dev_is_unplugged(minor->dev)) { 20462306a36Sopenharmony_ci drm_dev_put(minor->dev); 20562306a36Sopenharmony_ci return ERR_PTR(-ENODEV); 20662306a36Sopenharmony_ci } 20762306a36Sopenharmony_ci 20862306a36Sopenharmony_ci return minor; 20962306a36Sopenharmony_ci} 21062306a36Sopenharmony_ci 21162306a36Sopenharmony_cistatic void accel_minor_release(struct drm_minor *minor) 21262306a36Sopenharmony_ci{ 21362306a36Sopenharmony_ci drm_dev_put(minor->dev); 21462306a36Sopenharmony_ci} 21562306a36Sopenharmony_ci 21662306a36Sopenharmony_ci/** 21762306a36Sopenharmony_ci * accel_open - open method for ACCEL file 21862306a36Sopenharmony_ci * @inode: device inode 21962306a36Sopenharmony_ci * @filp: file pointer. 22062306a36Sopenharmony_ci * 22162306a36Sopenharmony_ci * This function must be used by drivers as their &file_operations.open method. 22262306a36Sopenharmony_ci * It looks up the correct ACCEL device and instantiates all the per-file 22362306a36Sopenharmony_ci * resources for it. It also calls the &drm_driver.open driver callback. 22462306a36Sopenharmony_ci * 22562306a36Sopenharmony_ci * Return: 0 on success or negative errno value on failure. 22662306a36Sopenharmony_ci */ 22762306a36Sopenharmony_ciint accel_open(struct inode *inode, struct file *filp) 22862306a36Sopenharmony_ci{ 22962306a36Sopenharmony_ci struct drm_device *dev; 23062306a36Sopenharmony_ci struct drm_minor *minor; 23162306a36Sopenharmony_ci int retcode; 23262306a36Sopenharmony_ci 23362306a36Sopenharmony_ci minor = accel_minor_acquire(iminor(inode)); 23462306a36Sopenharmony_ci if (IS_ERR(minor)) 23562306a36Sopenharmony_ci return PTR_ERR(minor); 23662306a36Sopenharmony_ci 23762306a36Sopenharmony_ci dev = minor->dev; 23862306a36Sopenharmony_ci 23962306a36Sopenharmony_ci atomic_fetch_inc(&dev->open_count); 24062306a36Sopenharmony_ci 24162306a36Sopenharmony_ci /* share address_space across all char-devs of a single device */ 24262306a36Sopenharmony_ci filp->f_mapping = dev->anon_inode->i_mapping; 24362306a36Sopenharmony_ci 24462306a36Sopenharmony_ci retcode = drm_open_helper(filp, minor); 24562306a36Sopenharmony_ci if (retcode) 24662306a36Sopenharmony_ci goto err_undo; 24762306a36Sopenharmony_ci 24862306a36Sopenharmony_ci return 0; 24962306a36Sopenharmony_ci 25062306a36Sopenharmony_cierr_undo: 25162306a36Sopenharmony_ci atomic_dec(&dev->open_count); 25262306a36Sopenharmony_ci accel_minor_release(minor); 25362306a36Sopenharmony_ci return retcode; 25462306a36Sopenharmony_ci} 25562306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(accel_open); 25662306a36Sopenharmony_ci 25762306a36Sopenharmony_cistatic int accel_stub_open(struct inode *inode, struct file *filp) 25862306a36Sopenharmony_ci{ 25962306a36Sopenharmony_ci const struct file_operations *new_fops; 26062306a36Sopenharmony_ci struct drm_minor *minor; 26162306a36Sopenharmony_ci int err; 26262306a36Sopenharmony_ci 26362306a36Sopenharmony_ci minor = accel_minor_acquire(iminor(inode)); 26462306a36Sopenharmony_ci if (IS_ERR(minor)) 26562306a36Sopenharmony_ci return PTR_ERR(minor); 26662306a36Sopenharmony_ci 26762306a36Sopenharmony_ci new_fops = fops_get(minor->dev->driver->fops); 26862306a36Sopenharmony_ci if (!new_fops) { 26962306a36Sopenharmony_ci err = -ENODEV; 27062306a36Sopenharmony_ci goto out; 27162306a36Sopenharmony_ci } 27262306a36Sopenharmony_ci 27362306a36Sopenharmony_ci replace_fops(filp, new_fops); 27462306a36Sopenharmony_ci if (filp->f_op->open) 27562306a36Sopenharmony_ci err = filp->f_op->open(inode, filp); 27662306a36Sopenharmony_ci else 27762306a36Sopenharmony_ci err = 0; 27862306a36Sopenharmony_ci 27962306a36Sopenharmony_ciout: 28062306a36Sopenharmony_ci accel_minor_release(minor); 28162306a36Sopenharmony_ci 28262306a36Sopenharmony_ci return err; 28362306a36Sopenharmony_ci} 28462306a36Sopenharmony_ci 28562306a36Sopenharmony_cistatic const struct file_operations accel_stub_fops = { 28662306a36Sopenharmony_ci .owner = THIS_MODULE, 28762306a36Sopenharmony_ci .open = accel_stub_open, 28862306a36Sopenharmony_ci .llseek = noop_llseek, 28962306a36Sopenharmony_ci}; 29062306a36Sopenharmony_ci 29162306a36Sopenharmony_civoid accel_core_exit(void) 29262306a36Sopenharmony_ci{ 29362306a36Sopenharmony_ci unregister_chrdev(ACCEL_MAJOR, "accel"); 29462306a36Sopenharmony_ci debugfs_remove(accel_debugfs_root); 29562306a36Sopenharmony_ci accel_sysfs_destroy(); 29662306a36Sopenharmony_ci idr_destroy(&accel_minors_idr); 29762306a36Sopenharmony_ci} 29862306a36Sopenharmony_ci 29962306a36Sopenharmony_ciint __init accel_core_init(void) 30062306a36Sopenharmony_ci{ 30162306a36Sopenharmony_ci int ret; 30262306a36Sopenharmony_ci 30362306a36Sopenharmony_ci idr_init(&accel_minors_idr); 30462306a36Sopenharmony_ci 30562306a36Sopenharmony_ci ret = accel_sysfs_init(); 30662306a36Sopenharmony_ci if (ret < 0) { 30762306a36Sopenharmony_ci DRM_ERROR("Cannot create ACCEL class: %d\n", ret); 30862306a36Sopenharmony_ci goto error; 30962306a36Sopenharmony_ci } 31062306a36Sopenharmony_ci 31162306a36Sopenharmony_ci accel_debugfs_root = debugfs_create_dir("accel", NULL); 31262306a36Sopenharmony_ci 31362306a36Sopenharmony_ci ret = register_chrdev(ACCEL_MAJOR, "accel", &accel_stub_fops); 31462306a36Sopenharmony_ci if (ret < 0) 31562306a36Sopenharmony_ci DRM_ERROR("Cannot register ACCEL major: %d\n", ret); 31662306a36Sopenharmony_ci 31762306a36Sopenharmony_cierror: 31862306a36Sopenharmony_ci /* 31962306a36Sopenharmony_ci * Any cleanup due to errors will be done in drm_core_exit() that 32062306a36Sopenharmony_ci * will call accel_core_exit() 32162306a36Sopenharmony_ci */ 32262306a36Sopenharmony_ci return ret; 32362306a36Sopenharmony_ci} 324