// SPDX-License-Identifier: GPL-2.0 #include #include #include #include #include #include #include #include #include struct hello_dev { struct device *dev; struct cdev cdev; unsigned int minor; }; #define HELLO_MAX_DEVICES 10 static struct class *hello_class; static dev_t hello_devt; static int hello_open(struct inode *node, struct file *f) { struct cdev *cd = node->i_cdev; struct hello_dev *hello = container_of(cd, struct hello_dev, cdev); dev_info(hello->dev, "%s\n", __func__); f->private_data = hello; return 0; } static int hello_release(struct inode *node, struct file *f) { struct hello_dev *hello = f->private_data; dev_info(hello->dev, "%s\n", __func__); f->private_data = NULL; return 0; } static ssize_t hello_read(struct file *f, char __user *u, size_t s, loff_t *l) { struct hello_dev *hello = f->private_data; char buf[10]; size_t len; dev_info(hello->dev, "%s: size=%zu offset=%llu\n", __func__, s, *l); if (*l >= 40) return 0; len = snprintf(buf, sizeof(buf), "hello:%d\n", hello->minor); if (s < len) len = s; if (copy_to_user(u, buf, len) != 0) return -EFAULT; *l += len; return len; } static ssize_t hello_write(struct file *f, const char __user *u, size_t s, loff_t *l) { struct hello_dev *hello = f->private_data; dev_info(hello->dev, "%s: size=%zu offset=%llu\n", __func__, s, *l); return s; } static const struct file_operations hello_fops = { .owner = THIS_MODULE, .open = hello_open, .release = hello_release, .read = hello_read, .write = hello_write, }; static int hello_probe(struct platform_device *pdev) { struct hello_dev *hello; struct device *dev; dev_t devt; int ret; hello = devm_kzalloc(&pdev->dev, sizeof(*hello), GFP_KERNEL); if (!hello) { dev_err(&pdev->dev, "devm_kzalloc failed\n"); return -ENOMEM; } #ifdef CONFIG_OF ret = of_property_read_u32(pdev->dev.of_node, "index", &hello->minor); if (ret < 0) { dev_err(&pdev->dev, "no index specified\n"); goto err_out; } #else hello->minor = (unsigned)pdev->id; #endif if (hello->minor >= HELLO_MAX_DEVICES) { dev_err(&pdev->dev, "invalid index: %u\n", hello->minor); ret = -EINVAL; goto err_out; } hello->dev = &pdev->dev; platform_set_drvdata(pdev, hello); devt = MKDEV(MAJOR(hello_devt), hello->minor); cdev_init(&hello->cdev, &hello_fops); hello->cdev.owner = THIS_MODULE; ret = cdev_add(&hello->cdev, devt, 1); if (ret != 0) { dev_err(&pdev->dev, "cdev_add failed\n"); goto err_drvdata; } dev = device_create(hello_class, &pdev->dev, devt, hello, "hello%d", hello->minor); if (IS_ERR(dev)) { dev_err(&pdev->dev, "device_create failed\n"); ret = PTR_ERR(dev); goto err_cdev; } dev_info(&pdev->dev, "HELLO! I am hello device %d!\n", hello->minor); return 0; err_cdev: cdev_del(&hello->cdev); err_drvdata: platform_set_drvdata(pdev, NULL); err_out: return ret; } static int hello_remove(struct platform_device *pdev) { struct hello_dev *hello = platform_get_drvdata(pdev); device_destroy(hello_class, MKDEV(MAJOR(hello_devt), hello->minor)); cdev_del(&hello->cdev); platform_set_drvdata(pdev, NULL); dev_info(&pdev->dev, "GOODBYE! I was hello device %d!\n", hello->minor); return 0; } static const struct of_device_id hello_match[] = { { .compatible = "virtual,hello", }, { /* end of table */ } }; static struct platform_driver hello_driver = { .driver = { .name = "hello", .of_match_table = hello_match, }, .probe = hello_probe, .remove = hello_remove, }; #ifndef CONFIG_OF static struct platform_device *pdevs[3]; #endif static int __init hello_init(void) { int ret; printk(KERN_INFO "%s\n", __func__); ret = alloc_chrdev_region(&hello_devt, 0, HELLO_MAX_DEVICES, "hello"); if (ret < 0) return ret; hello_class = class_create(THIS_MODULE, "hello"); if (IS_ERR(hello_class)) { printk(KERN_ERR "%s: failed to create class\n", __func__); ret = PTR_ERR(hello_class); goto err_region; } ret = platform_driver_register(&hello_driver); if (ret != 0) goto err_class; #ifndef CONFIG_OF pdevs[0] = platform_device_register_simple("hello", 1, NULL, 0); pdevs[1] = platform_device_register_simple("hello", 3, NULL, 0); pdevs[2] = platform_device_register_simple("hello", 5, NULL, 0); #endif return 0; err_class: class_destroy(hello_class); err_region: unregister_chrdev_region(hello_devt, HELLO_MAX_DEVICES); return ret; } static void __exit hello_exit(void) { printk(KERN_INFO "%s\n", __func__); #ifndef CONFIG_OF platform_device_unregister(pdevs[0]); platform_device_unregister(pdevs[1]); platform_device_unregister(pdevs[2]); #endif platform_driver_unregister(&hello_driver); class_destroy(hello_class); unregister_chrdev_region(hello_devt, HELLO_MAX_DEVICES); } module_init(hello_init); module_exit(hello_exit); MODULE_AUTHOR("John Ogness "); MODULE_DESCRIPTION("a great module for hello-ing!"); MODULE_LICENSE("GPL v2"); MODULE_VERSION("20190101");