114 lines
		
	
	
		
			3.1 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			114 lines
		
	
	
		
			3.1 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| // SPDX-License-Identifier: GPL-2.0
 | |
| /*
 | |
|  * Copyright (c) 2024, NVIDIA CORPORATION & AFFILIATES
 | |
|  */
 | |
| 
 | |
| #include <linux/auxiliary_bus.h>
 | |
| #include <linux/slab.h>
 | |
| 
 | |
| #define AUXILIARY_MAX_IRQ_NAME 11
 | |
| 
 | |
| struct auxiliary_irq_info {
 | |
| 	struct device_attribute sysfs_attr;
 | |
| 	char name[AUXILIARY_MAX_IRQ_NAME];
 | |
| };
 | |
| 
 | |
| static struct attribute *auxiliary_irq_attrs[] = {
 | |
| 	NULL
 | |
| };
 | |
| 
 | |
| static const struct attribute_group auxiliary_irqs_group = {
 | |
| 	.name = "irqs",
 | |
| 	.attrs = auxiliary_irq_attrs,
 | |
| };
 | |
| 
 | |
| static int auxiliary_irq_dir_prepare(struct auxiliary_device *auxdev)
 | |
| {
 | |
| 	int ret = 0;
 | |
| 
 | |
| 	guard(mutex)(&auxdev->sysfs.lock);
 | |
| 	if (auxdev->sysfs.irq_dir_exists)
 | |
| 		return 0;
 | |
| 
 | |
| 	ret = devm_device_add_group(&auxdev->dev, &auxiliary_irqs_group);
 | |
| 	if (ret)
 | |
| 		return ret;
 | |
| 
 | |
| 	auxdev->sysfs.irq_dir_exists = true;
 | |
| 	xa_init(&auxdev->sysfs.irqs);
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * auxiliary_device_sysfs_irq_add - add a sysfs entry for the given IRQ
 | |
|  * @auxdev: auxiliary bus device to add the sysfs entry.
 | |
|  * @irq: The associated interrupt number.
 | |
|  *
 | |
|  * This function should be called after auxiliary device have successfully
 | |
|  * received the irq.
 | |
|  * The driver is responsible to add a unique irq for the auxiliary device. The
 | |
|  * driver can invoke this function from multiple thread context safely for
 | |
|  * unique irqs of the auxiliary devices. The driver must not invoke this API
 | |
|  * multiple times if the irq is already added previously.
 | |
|  *
 | |
|  * Return: zero on success or an error code on failure.
 | |
|  */
 | |
| int auxiliary_device_sysfs_irq_add(struct auxiliary_device *auxdev, int irq)
 | |
| {
 | |
| 	struct auxiliary_irq_info *info __free(kfree) = NULL;
 | |
| 	struct device *dev = &auxdev->dev;
 | |
| 	int ret;
 | |
| 
 | |
| 	ret = auxiliary_irq_dir_prepare(auxdev);
 | |
| 	if (ret)
 | |
| 		return ret;
 | |
| 
 | |
| 	info = kzalloc(sizeof(*info), GFP_KERNEL);
 | |
| 	if (!info)
 | |
| 		return -ENOMEM;
 | |
| 
 | |
| 	sysfs_attr_init(&info->sysfs_attr.attr);
 | |
| 	snprintf(info->name, AUXILIARY_MAX_IRQ_NAME, "%d", irq);
 | |
| 
 | |
| 	ret = xa_insert(&auxdev->sysfs.irqs, irq, info, GFP_KERNEL);
 | |
| 	if (ret)
 | |
| 		return ret;
 | |
| 
 | |
| 	info->sysfs_attr.attr.name = info->name;
 | |
| 	ret = sysfs_add_file_to_group(&dev->kobj, &info->sysfs_attr.attr,
 | |
| 				      auxiliary_irqs_group.name);
 | |
| 	if (ret)
 | |
| 		goto sysfs_add_err;
 | |
| 
 | |
| 	xa_store(&auxdev->sysfs.irqs, irq, no_free_ptr(info), GFP_KERNEL);
 | |
| 	return 0;
 | |
| 
 | |
| sysfs_add_err:
 | |
| 	xa_erase(&auxdev->sysfs.irqs, irq);
 | |
| 	return ret;
 | |
| }
 | |
| EXPORT_SYMBOL_GPL(auxiliary_device_sysfs_irq_add);
 | |
| 
 | |
| /**
 | |
|  * auxiliary_device_sysfs_irq_remove - remove a sysfs entry for the given IRQ
 | |
|  * @auxdev: auxiliary bus device to add the sysfs entry.
 | |
|  * @irq: the IRQ to remove.
 | |
|  *
 | |
|  * This function should be called to remove an IRQ sysfs entry.
 | |
|  * The driver must invoke this API when IRQ is released by the device.
 | |
|  */
 | |
| void auxiliary_device_sysfs_irq_remove(struct auxiliary_device *auxdev, int irq)
 | |
| {
 | |
| 	struct auxiliary_irq_info *info __free(kfree) = xa_load(&auxdev->sysfs.irqs, irq);
 | |
| 	struct device *dev = &auxdev->dev;
 | |
| 
 | |
| 	if (!info) {
 | |
| 		dev_err(&auxdev->dev, "IRQ %d doesn't exist\n", irq);
 | |
| 		return;
 | |
| 	}
 | |
| 	sysfs_remove_file_from_group(&dev->kobj, &info->sysfs_attr.attr,
 | |
| 				     auxiliary_irqs_group.name);
 | |
| 	xa_erase(&auxdev->sysfs.irqs, irq);
 | |
| }
 | |
| EXPORT_SYMBOL_GPL(auxiliary_device_sysfs_irq_remove);
 |