#include #include #include #include #include #include #include #include #include #include #include #include #include #define DEVICE_NAME "cashd" #define CLASS_NAME "cashd_class" #define MINOR_BASE 0 #define MINOR_COUNT 2 #define BUFFER_SIZE 4 #define GPIO1_CTL 0xFD6D0B50 #define GPIO1_STATUS 0xFD6D0940 #define GPIO0_CTL 0xFD6D0B60 #define GPIO0_STATUS 0xFD6D0950 struct cashd_device { unsigned char *buffer; size_t buffer_size; wait_queue_head_t read_wait; struct mutex lock; struct cdev cdev; int dev_major; int dev_minor; unsigned int in_status; unsigned int status_changed; int last_status; int first_read; struct delayed_work delay_work1; struct delayed_work delay_work2; void __iomem *gpio_ctl_reg_base; void __iomem *gpio_status_reg_base; int count; // debug code }; static int cashd_major = 0; static struct class *cashd_class = NULL; static struct cashd_device *cashd_devices[MINOR_COUNT]; static unsigned int minors[MINOR_COUNT] = {10, 11}; static void gpio_ctl_delay_work_func(struct work_struct *work) { unsigned int value = 0; struct delayed_work *dwork = to_delayed_work(work); struct cashd_device *dev = container_of(dwork, struct cashd_device, delay_work1); value = readl(dev->gpio_ctl_reg_base); value &= 0xfffffffe; writel(value, dev->gpio_ctl_reg_base); } static void gpio_status_delay_work_func(struct work_struct *work) { unsigned int value = 0; struct delayed_work *dwork = to_delayed_work(work); struct cashd_device *dev = container_of(dwork, struct cashd_device, delay_work2); value = readl(dev->gpio_status_reg_base); value = value & 0x2; if(value != dev->last_status) { dev->last_status = value; dev->status_changed = 1; wake_up_interruptible(&dev->read_wait); } // debug code // dev->count++; // if(dev->count >= 50) // { // dev->count = 0; // dev->status_changed = 1; // printk("%s %s %d\n", __FILE__, __func__, __LINE__); // wake_up_interruptible(&dev->read_wait); // } schedule_delayed_work(&dev->delay_work2, msecs_to_jiffies(100)); } static int cashd_open(struct inode *inode, struct file *filp) { struct cashd_device *dev = NULL; unsigned int minor = iminor(inode); int i = 0; for(i = 0; i < MINOR_COUNT; i++) { if(minor == minors[i]) { dev = cashd_devices[i]; break; } } if (!dev) { pr_err("cashd: Device not initialized for minor %d\n", minor); return -ENODEV; } filp->private_data = dev; dev->first_read = 0; pr_info("cashd: Device /dev/cashd%d opened\n", minor); return 0; } static int cashd_release(struct inode *inode, struct file *filp) { unsigned int minor = iminor(inode); pr_info("cashd: Device /dev/cashd%d closed\n", minor); return 0; } static ssize_t cashd_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos) { struct cashd_device *dev = filp->private_data; ssize_t bytes_read = 0; int ret = 0; int nonblock = filp->f_flags & O_NONBLOCK; if (!dev) return -EINVAL; if (!buf || count == 0) return -EINVAL; if(dev->first_read == 0) { dev->first_read = 1; } else if (wait_event_interruptible(dev->read_wait, dev->status_changed)) { // 被信号中断 printk(KERN_INFO "gpio_monitor: wait interrupted by signal\n"); return -ERESTARTSYS; } dev->in_status = readl(dev->gpio_status_reg_base); if(dev->in_status & 0x2) { ret = copy_to_user(buf, "o", 1); if(ret != 0) { return -EFAULT; } } else { ret = copy_to_user(buf, "c", 1); if(ret != 0) { return -EFAULT; } } dev->status_changed = 0; bytes_read = 1; return bytes_read; } static ssize_t cashd_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos) { struct cashd_device *dev = filp->private_data; int ret; unsigned int value = 0; if (!dev) return -EINVAL; if(count > BUFFER_SIZE) { count = BUFFER_SIZE; } ret = copy_from_user(dev->buffer, buf, count); if(ret != 0) { return -EFAULT; } if(dev->buffer[0] == 'o' || dev->buffer[0] == 'O') { // printk("%s %s %d minor=%d\n", __FILE__, __func__, __LINE__, dev->dev_minor); value = readl(dev->gpio_ctl_reg_base); value |= 0x1; writel(value, dev->gpio_ctl_reg_base); schedule_delayed_work(&dev->delay_work1, msecs_to_jiffies(300)); } else { printk(KERN_INFO "cashd: Invalid input\n"); } return count; } static unsigned int cashd_poll(struct file *filp, poll_table *wait) { struct cashd_device *dev = filp->private_data; unsigned int mask = 0; if (!dev) return POLLERR; poll_wait(filp, &dev->read_wait, wait); mutex_lock(&dev->lock); if(dev->status_changed) mask |= POLLIN | POLLRDNORM; mutex_unlock(&dev->lock); return mask; } static const struct file_operations cashd_fops = { .owner = THIS_MODULE, .open = cashd_open, .release = cashd_release, .read = cashd_read, .write = cashd_write, .poll = cashd_poll, }; static int __init cashd_init_device(struct cashd_device *dev, int minor) { int ret; dev->buffer = kmalloc(BUFFER_SIZE, GFP_KERNEL); if (!dev->buffer) { pr_err("cashd: Failed to allocate buffer for minor %d\n", minor); return -ENOMEM; } memset(dev->buffer, 0, BUFFER_SIZE); dev->buffer_size = BUFFER_SIZE; dev->dev_major = cashd_major; dev->dev_minor = minor; dev->last_status = -1; init_waitqueue_head(&dev->read_wait); mutex_init(&dev->lock); INIT_DELAYED_WORK(&dev->delay_work1, gpio_ctl_delay_work_func); INIT_DELAYED_WORK(&dev->delay_work2, gpio_status_delay_work_func); cdev_init(&dev->cdev, &cashd_fops); dev->cdev.owner = THIS_MODULE; ret = cdev_add(&dev->cdev, MKDEV(cashd_major, minor), 1); if (ret) { pr_err("cashd: Failed to add cdev for minor %d\n", minor); kfree(dev->buffer); return ret; } return 0; } static char *my_devnode(struct device *dev, umode_t *mode) { if (mode) { *mode = 0666; } return NULL; } int cashd_init(void) { int ret; int i; struct device *device; cashd_major = 56; cashd_class = class_create(THIS_MODULE, CLASS_NAME); if (IS_ERR(cashd_class)) { ret = PTR_ERR(cashd_class); pr_err("cashd: Failed to create class\n"); goto fail_class; } cashd_class->devnode = my_devnode; for (i = 0; i < MINOR_COUNT; i++) { cashd_devices[i] = kzalloc(sizeof(struct cashd_device), GFP_KERNEL); if (!cashd_devices[i]) { pr_err("cashd: Failed to allocate device for minor %d\n", i); ret = -ENOMEM; goto fail_devices; } ret = cashd_init_device(cashd_devices[i], minors[i]); if (ret) { pr_err("cashd: Failed to init device for minor %d\n", i); kfree(cashd_devices[i]); cashd_devices[i] = NULL; goto fail_devices; } device = device_create(cashd_class, NULL, MKDEV(cashd_major, minors[i]), NULL, "cashd%d", i); if (IS_ERR(device)) { ret = PTR_ERR(device); pr_err("cashd: Failed to create device for minor %d\n", i); cdev_del(&cashd_devices[i]->cdev); kfree(cashd_devices[i]); cashd_devices[i] = NULL; goto fail_devices; } if(i == 0) { cashd_devices[i]->gpio_ctl_reg_base = ioremap(GPIO0_CTL, 4); cashd_devices[i]->gpio_status_reg_base = ioremap(GPIO0_STATUS, 4); } else if(i == 1) { cashd_devices[i]->gpio_ctl_reg_base = ioremap(GPIO1_CTL, 4); cashd_devices[i]->gpio_status_reg_base = ioremap(GPIO1_STATUS, 4); } schedule_delayed_work(&cashd_devices[i]->delay_work2, msecs_to_jiffies(100)); pr_info("cashd: Created /dev/cashd%d\n", i); } pr_info("cashd: Driver initialized successfully\n"); return 0; fail_devices: for (i = 0; i < MINOR_COUNT; i++) { if (cashd_devices[i]) { device_destroy(cashd_class, MKDEV(cashd_major, minors[i])); cdev_del(&cashd_devices[i]->cdev); if (cashd_devices[i]->buffer) kfree(cashd_devices[i]->buffer); kfree(cashd_devices[i]); } } class_destroy(cashd_class); fail_class: unregister_chrdev_region(MKDEV(cashd_major, MINOR_BASE), MINOR_COUNT); return ret; } void cashd_exit(void) { int i; pr_info("cashd: Cleaning up driver\n"); for (i = 0; i < MINOR_COUNT; i++) { if (cashd_devices[i]) { cancel_delayed_work_sync(&cashd_devices[i]->delay_work1); cancel_delayed_work_sync(&cashd_devices[i]->delay_work2); device_destroy(cashd_class, MKDEV(cashd_major, minors[i])); cdev_del(&cashd_devices[i]->cdev); if (cashd_devices[i]->buffer) kfree(cashd_devices[i]->buffer); iounmap(cashd_devices[i]->gpio_ctl_reg_base); iounmap(cashd_devices[i]->gpio_status_reg_base); kfree(cashd_devices[i]); pr_info("cashd: Removed /dev/cashd%d\n", i); } } if (cashd_class) class_destroy(cashd_class); unregister_chrdev_region(MKDEV(cashd_major, MINOR_BASE), MINOR_COUNT); pr_info("cashd: Driver cleaned up\n"); }