File chrdev.h
Go to the documentation of this file
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef __AKOZLINS_DMABUF_HCHRDEV_H__
#define __AKOZLINS_DMABUF_HCHRDEV_H__
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include "module.h"
struct chrdev_device {
struct cdev cdev;
struct device* device;
void* private_data;
};
struct chrdev {
char* name;
struct class* class;
dev_t dev;
int count;
struct chrdev_device devices[];
};
static void chrdev_device_del(struct chrdev_device* chrdev_device) {
if (IS_ERR_OR_NULL(chrdev_device))
return;
M_INFO("\n");
if (!IS_ERR_OR_NULL(chrdev_device->device)) {
M_INFO("device_destroy(minor = %d)\n", MINOR(chrdev_device->cdev.dev));
device_destroy(chrdev_device->device->class, chrdev_device->cdev.dev);
chrdev_device->device = NULL;
}
if (chrdev_device->cdev.count != 0) {
M_INFO("cdev_del(minor = %d)\n", MINOR(chrdev_device->cdev.dev));
cdev_del(&chrdev_device->cdev);
chrdev_device->cdev.count = 0;
}
chrdev_device->private_data = NULL;
}
static void chrdev_free(struct chrdev* chrdev) {
if (IS_ERR_OR_NULL(chrdev))
return;
M_INFO("\n");
for (int i = 0; i < chrdev->count; i++) {
chrdev_device_del(&chrdev->devices[i]);
}
if (chrdev->count != 0) {
M_INFO("unregister_chrdev_region(count = %d)\n", chrdev->count);
unregister_chrdev_region(chrdev->dev, chrdev->count);
chrdev->count = 0;
}
if (!IS_ERR_OR_NULL(chrdev->class)) {
M_INFO("class_destroy(name = '%s')\n", chrdev->class->name);
class_destroy(chrdev->class);
chrdev->class = NULL;
}
if (chrdev->name != NULL)
kfree(chrdev->name);
kfree(chrdev);
}
static struct chrdev_device* chrdev_device_add(struct chrdev* chrdev, int minor,
const struct file_operations* fops,
struct device* parent, void* private_data) {
int error;
struct chrdev_device* chrdev_device;
if (IS_ERR_OR_NULL(chrdev))
return ERR_PTR(-EFAULT);
if (!(0 <= minor && minor < chrdev->count))
return ERR_PTR(-EINVAL);
M_INFO("minor = %d\n", minor);
chrdev_device = &chrdev->devices[minor];
cdev_init(&chrdev_device->cdev, fops);
chrdev_device->cdev.owner = THIS_MODULE;
chrdev_device->cdev.dev = MKDEV(MAJOR(chrdev->dev), MINOR(chrdev->dev) + minor);
chrdev_device->private_data = private_data;
M_INFO("cdev_add(minor = %d)\n", minor);
error = cdev_add(&chrdev_device->cdev, chrdev_device->cdev.dev, 1);
if (error) {
M_ERR("cdev_add(minor = %d): error = %d\n", minor, error);
// cdev_init calls kobject_init which must be cleaned up with kobject_put
// (see __register_chrdev)
kobject_put(&chrdev_device->cdev.kobj);
chrdev_device->cdev.count = 0; // mark as not initialized
goto err_out;
}
chrdev_device->device = device_create(chrdev->class, parent, chrdev_device->cdev.dev, NULL,
"%s%d", chrdev->name, minor);
if (IS_ERR_OR_NULL(chrdev_device->device)) {
error = PTR_ERR(chrdev_device->device);
chrdev_device->device = NULL;
M_ERR("device_create(minor = %d): error = %d\n", minor, error);
goto err_out;
}
return chrdev_device;
err_out:
chrdev_device_del(chrdev_device);
return ERR_PTR(error);
}
static struct chrdev* chrdev_alloc(const char* name, int count) {
int error;
struct chrdev* chrdev;
if (name == NULL || count <= 0)
return ERR_PTR(-EINVAL);
M_INFO("name = '%s', count = %d\n", name, count);
chrdev = kzalloc(sizeof(*chrdev) + count * sizeof(chrdev->devices[0]), GFP_KERNEL);
if (IS_ERR_OR_NULL(chrdev)) {
error = PTR_ERR(chrdev);
if (error == 0)
error = -ENOMEM;
chrdev = NULL;
M_ERR("kzalloc: error = %d\n", error);
goto err_out;
}
chrdev->name = kstrdup(name, GFP_KERNEL);
if (IS_ERR_OR_NULL(chrdev->name)) {
error = PTR_ERR(chrdev->name);
if (error == 0)
error = -ENOMEM;
chrdev->name = NULL;
M_ERR("kstrdup: error = %d\n", error);
goto err_out;
}
// create /sys/class/${name}
chrdev->class = class_create(THIS_MODULE, chrdev->name);
if (IS_ERR_OR_NULL(chrdev->class)) {
error = PTR_ERR(chrdev->class);
chrdev->class = NULL;
M_ERR("class_create(name = '%s'): error = %d\n", name, error);
goto err_out;
}
// add entry to /proc/devices
error = alloc_chrdev_region(&chrdev->dev, 0, count, chrdev->name);
if (error) {
M_ERR("alloc_chrdev_region(count = %d, name = '%s'): error = %d\n", count, name, error);
goto err_out;
}
chrdev->count = count;
return chrdev;
err_out:
chrdev_free(chrdev);
return ERR_PTR(error);
}
#endif // __AKOZLINS_DMABUF_HCHRDEV_H__