|
| 1 | +/* |
| 2 | + * static_key.c |
| 3 | + */ |
| 4 | + |
| 5 | +#include <linux/atomic.h> |
| 6 | +#include <linux/device.h> |
| 7 | +#include <linux/fs.h> |
| 8 | +#include <linux/kernel.h> /* for sprintf() */ |
| 9 | +#include <linux/module.h> |
| 10 | +#include <linux/printk.h> |
| 11 | +#include <linux/types.h> |
| 12 | +#include <linux/uaccess.h> /* for get_user and put_user */ |
| 13 | + |
| 14 | +#include <asm/errno.h> |
| 15 | + |
| 16 | +static int device_open(struct inode *inode, struct file *file); |
| 17 | +static int device_release(struct inode *inode, struct file *file); |
| 18 | +static ssize_t device_read(struct file *file, char __user *buf, size_t count, |
| 19 | + loff_t *ppos); |
| 20 | +static ssize_t device_write(struct file *file, const char __user *buf, |
| 21 | + size_t count, loff_t *ppos); |
| 22 | + |
| 23 | +#define SUCCESS 0 |
| 24 | +#define DEVICE_NAME "key_state" |
| 25 | +#define BUF_LEN 10 |
| 26 | + |
| 27 | +static int major; |
| 28 | + |
| 29 | +enum { |
| 30 | + CDEV_NOT_USED = 0, |
| 31 | + CDEV_EXCLUSIVE_OPEN = 1, |
| 32 | +}; |
| 33 | + |
| 34 | +static atomic_t already_open = ATOMIC_INIT(CDEV_NOT_USED); |
| 35 | + |
| 36 | +static char msg[BUF_LEN + 1]; |
| 37 | + |
| 38 | +static struct class *cls; |
| 39 | + |
| 40 | +static DEFINE_STATIC_KEY_FALSE(fkey); |
| 41 | + |
| 42 | +static struct file_operations chardev_fops = { |
| 43 | + .owner = THIS_MODULE, |
| 44 | + .open = device_open, |
| 45 | + .release = device_release, |
| 46 | + .read = device_read, |
| 47 | + .write = device_write, |
| 48 | +}; |
| 49 | + |
| 50 | +static int __init chardev_init(void) |
| 51 | +{ |
| 52 | + major = register_chrdev(0, DEVICE_NAME, &chardev_fops); |
| 53 | + if (major < 0) { |
| 54 | + pr_alert("Registering char device failed with %d\n", major); |
| 55 | + return major; |
| 56 | + } |
| 57 | + |
| 58 | + pr_info("I was assigned major number %d\n", major); |
| 59 | + |
| 60 | + cls = class_create(THIS_MODULE, DEVICE_NAME); |
| 61 | + |
| 62 | + device_create(cls, NULL, MKDEV(major, 0), NULL, DEVICE_NAME); |
| 63 | + |
| 64 | + pr_info("Device created on /dev/%s\n", DEVICE_NAME); |
| 65 | + |
| 66 | + return SUCCESS; |
| 67 | +} |
| 68 | + |
| 69 | +static void __exit chardev_exit(void) |
| 70 | +{ |
| 71 | + device_destroy(cls, MKDEV(major, 0)); |
| 72 | + class_destroy(cls); |
| 73 | + |
| 74 | + /* Unregister the device */ |
| 75 | + unregister_chrdev(major, DEVICE_NAME); |
| 76 | +} |
| 77 | + |
| 78 | +/* Methods */ |
| 79 | + |
| 80 | +/** |
| 81 | + * Called when a process tried to open the device file, like |
| 82 | + * cat /dev/key_state |
| 83 | + */ |
| 84 | +static int device_open(struct inode *inode, struct file *file) |
| 85 | +{ |
| 86 | + if (atomic_cmpxchg(&already_open, CDEV_NOT_USED, CDEV_EXCLUSIVE_OPEN)) |
| 87 | + return -EBUSY; |
| 88 | + |
| 89 | + sprintf(msg, static_key_enabled(&fkey) ? "enabled\n" : "disabled\n"); |
| 90 | + |
| 91 | + pr_info("fastpath 1\n"); |
| 92 | + if (static_branch_unlikely(&fkey)) |
| 93 | + pr_alert("do unlikely thing\n"); |
| 94 | + pr_info("fastpath 2\n"); |
| 95 | + |
| 96 | + try_module_get(THIS_MODULE); |
| 97 | + |
| 98 | + return SUCCESS; |
| 99 | +} |
| 100 | + |
| 101 | +/** |
| 102 | + * Called when a process closes the device file |
| 103 | + */ |
| 104 | +static int device_release(struct inode *inode, struct file *file) |
| 105 | +{ |
| 106 | + /* We are now ready for our next caller. */ |
| 107 | + atomic_set(&already_open, CDEV_NOT_USED); |
| 108 | + |
| 109 | + /** |
| 110 | + * Decrement the usage count, or else once you opened the file, you will |
| 111 | + * never get rid of the module. |
| 112 | + */ |
| 113 | + module_put(THIS_MODULE); |
| 114 | + |
| 115 | + return SUCCESS; |
| 116 | +} |
| 117 | + |
| 118 | +/** |
| 119 | + * Called when a process, which already opened the dev file, attempts to |
| 120 | + * read from it. |
| 121 | + */ |
| 122 | +static ssize_t device_read(struct file *filp, /* see include/linux/fs.h */ |
| 123 | + char __user *buffer, /* buffer to fill with data */ |
| 124 | + size_t length, /* length of the buffer */ |
| 125 | + loff_t *offset) |
| 126 | +{ |
| 127 | + /* Number of the bytes actually written to the buffer */ |
| 128 | + int bytes_read = 0; |
| 129 | + const char *msg_ptr = msg; |
| 130 | + |
| 131 | + if (!*(msg_ptr + *offset)) { /* We are at the end of the message */ |
| 132 | + *offset = 0; /* reset the offset */ |
| 133 | + return 0; /* signify end of file */ |
| 134 | + } |
| 135 | + |
| 136 | + msg_ptr += *offset; |
| 137 | + |
| 138 | + /* Actually put the date into the buffer */ |
| 139 | + while (length && *msg_ptr) { |
| 140 | + /** |
| 141 | + * The buffer is in the user data segment, not the kernel |
| 142 | + * segment so "*" assignment won't work. We have to use |
| 143 | + * put_user which copies data from the kernel data segment to |
| 144 | + * the user data segment. |
| 145 | + */ |
| 146 | + put_user(*(msg_ptr++), buffer++); |
| 147 | + length--; |
| 148 | + bytes_read++; |
| 149 | + } |
| 150 | + |
| 151 | + *offset += bytes_read; |
| 152 | + |
| 153 | + /* Most read functions return the number of bytes put into the buffer. */ |
| 154 | + return bytes_read; |
| 155 | +} |
| 156 | + |
| 157 | +/* Called when a process writes to dev file; echo "enable" > /dev/key_state */ |
| 158 | +static ssize_t device_write(struct file *filp, const char __user *buffer, |
| 159 | + size_t length, loff_t *offset) |
| 160 | +{ |
| 161 | + char command[10]; |
| 162 | + |
| 163 | + if (length > 10) { |
| 164 | + pr_err("command exceeded 10 char\n"); |
| 165 | + return -EINVAL; |
| 166 | + } |
| 167 | + |
| 168 | + if (copy_from_user(command, buffer, length)) |
| 169 | + return -EFAULT; |
| 170 | + |
| 171 | + if (strncmp(command, "enable", strlen("enable")) == 0) |
| 172 | + static_branch_enable(&fkey); |
| 173 | + else if (strncmp(command, "disable", strlen("disable")) == 0) |
| 174 | + static_branch_disable(&fkey); |
| 175 | + else { |
| 176 | + pr_err("Invalid command: %s\n", command); |
| 177 | + return -EINVAL; |
| 178 | + } |
| 179 | + |
| 180 | + /* Again, return the number of input characters used. */ |
| 181 | + return length; |
| 182 | +} |
| 183 | + |
| 184 | +module_init(chardev_init); |
| 185 | +module_exit(chardev_exit); |
| 186 | + |
| 187 | +MODULE_LICENSE("GPL"); |
0 commit comments