/* * static_key.c */ #include #include #include #include /* for sprintf() */ #include #include #include #include /* for get_user and put_user */ #include static int device_open(struct inode *inode, struct file *file); static int device_release(struct inode *inode, struct file *file); static ssize_t device_read(struct file *file, char __user *buf, size_t count, loff_t *ppos); static ssize_t device_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos); #define SUCCESS 0 #define DEVICE_NAME "key_state" #define BUF_LEN 10 static int major; enum { CDEV_NOT_USED = 0, CDEV_EXCLUSIVE_OPEN = 1, }; static atomic_t already_open = ATOMIC_INIT(CDEV_NOT_USED); static char msg[BUF_LEN + 1]; static struct class *cls; static DEFINE_STATIC_KEY_FALSE(fkey); static struct file_operations chardev_fops = { .owner = THIS_MODULE, .open = device_open, .release = device_release, .read = device_read, .write = device_write, }; static int __init chardev_init(void) { major = register_chrdev(0, DEVICE_NAME, &chardev_fops); if (major < 0) { pr_alert("Registering char device failed with %d\n", major); return major; } pr_info("I was assigned major number %d\n", major); cls = class_create(THIS_MODULE, DEVICE_NAME); device_create(cls, NULL, MKDEV(major, 0), NULL, DEVICE_NAME); pr_info("Device created on /dev/%s\n", DEVICE_NAME); return SUCCESS; } static void __exit chardev_exit(void) { device_destroy(cls, MKDEV(major, 0)); class_destroy(cls); /* Unregister the device */ unregister_chrdev(major, DEVICE_NAME); } /* Methods */ /** * Called when a process tried to open the device file, like * cat /dev/key_state */ static int device_open(struct inode *inode, struct file *file) { if (atomic_cmpxchg(&already_open, CDEV_NOT_USED, CDEV_EXCLUSIVE_OPEN)) return -EBUSY; sprintf(msg, static_key_enabled(&fkey) ? "enabled\n" : "disabled\n"); pr_info("fastpath 1\n"); if (static_branch_unlikely(&fkey)) pr_alert("do unlikely thing\n"); pr_info("fastpath 2\n"); try_module_get(THIS_MODULE); return SUCCESS; } /** * Called when a process closes the device file */ static int device_release(struct inode *inode, struct file *file) { /* We are now ready for our next caller. */ atomic_set(&already_open, CDEV_NOT_USED); /** * Decrement the usage count, or else once you opened the file, you will * never get rid of the module. */ module_put(THIS_MODULE); return SUCCESS; } /** * Called when a process, which already opened the dev file, attempts to * read from it. */ static ssize_t device_read(struct file *filp, /* see include/linux/fs.h */ char __user *buffer, /* buffer to fill with data */ size_t length, /* length of the buffer */ loff_t *offset) { /* Number of the bytes actually written to the buffer */ int bytes_read = 0; const char *msg_ptr = msg; if (!*(msg_ptr + *offset)) { /* We are at the end of the message */ *offset = 0; /* reset the offset */ return 0; /* signify end of file */ } msg_ptr += *offset; /* Actually put the date into the buffer */ while (length && *msg_ptr) { /** * The buffer is in the user data segment, not the kernel * segment so "*" assignment won't work. We have to use * put_user which copies data from the kernel data segment to * the user data segment. */ put_user(*(msg_ptr++), buffer++); length--; bytes_read++; } *offset += bytes_read; /* Most read functions return the number of bytes put into the buffer. */ return bytes_read; } /* Called when a process writes to dev file; echo "enable" > /dev/key_state */ static ssize_t device_write(struct file *filp, const char __user *buffer, size_t length, loff_t *offset) { char command[10]; if (length > 10) { pr_err("command exceeded 10 char\n"); return -EINVAL; } if (copy_from_user(command, buffer, length)) return -EFAULT; if (strncmp(command, "enable", strlen("enable")) == 0) static_branch_enable(&fkey); else if (strncmp(command, "disable", strlen("disable")) == 0) static_branch_disable(&fkey); else { pr_err("Invalid command: %s\n", command); return -EINVAL; } /* Again, return the number of input characters used. */ return length; } module_init(chardev_init); module_exit(chardev_exit); MODULE_LICENSE("GPL");