Simple Linux Device Driver
March 31, 2016, 4:39 p.m.
This driver :
1) Creates a character device called kbdozgur
2) Handles an interrupt(keyboard) , buffers it
- send buffered data when kbdozgur opened for read
- prints into dmesg when you put a message in kbdozgur
#include <linux/init.h> // Macros used to mark up functions e.g. __init __exit
#include <linux/module.h> // Core header for loading LKMs into the kernel
#include <linux/device.h> // Header to support the kernel Driver Model
#include <linux/kernel.h> // Contains types, macros, functions for the kernel
#include <linux/fs.h> // Header for the Linux file system support
#include <asm/uaccess.h> // Required for the copy to user function
#include <linux/interrupt.h>
#include <asm/io.h>
#define DEVICE_NAME "kbdozgur" ///< The device will appear at /dev/kbdozgur using this value
#define CLASS_NAME "kbdozgur" ///< The device class -- this is a character device driver
MODULE_AUTHOR("Mehmet Ozgur Bayhan");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Kill all the white men!");
MODULE_VERSION("0.2");
#define BUFFER_SIZE 200
static unsigned char messageFromInterrupt[BUFFER_SIZE]; //buffer that holds interrupt values
static short bufferCounter = 0; //buffer counter for loop
static int majorNumber; ///< Stores the device number -- determined automatically
static char messageFromUser[BUFFER_SIZE] = { 0 }; ///< Memory for the string that is passed from userspace
//static short size_of_message; ///< Used to remember the size of the string stored
static int numberOpens = 0; ///< Counts the number of times the device is opened
static struct class* kbdozgurcharClass = NULL; ///< The device-driver class struct pointer
static struct device* kbdozgurcharDevice = NULL; ///< The device-driver device struct pointer
// The prototype functions for the character driver -- must come before the struct definition
static int dev_open(struct inode *, struct file *);
static int dev_release(struct inode *, struct file *);
static ssize_t dev_read(struct file *, char *, size_t, loff_t *);
static ssize_t dev_write(struct file *, const char *, size_t, loff_t *);
static struct file_operations fops = { .open = dev_open, .read = dev_read, .write = dev_write, .release = dev_release, };
irq_handler_t irq_handler(int irq, void *dev_id, struct pt_regs *regs) {
static unsigned char scancode;
//Read keyboard status
scancode = inb(0x60);
if (scancode == 0x01) {
printk(KERN_INFO "MOB: Inputs are > %s\n", messageFromInterrupt);
bufferCounter = 0;
memset(&messageFromInterrupt[0], 0, sizeof(messageFromInterrupt));
}
else if (scancode == 0x1E) {
messageFromInterrupt[bufferCounter] = 'a';
bufferCounter++;
}
else if (scancode == 0x1F) {
messageFromInterrupt[bufferCounter] = 's';
bufferCounter++;
}
else if (scancode == 0x20) {
messageFromInterrupt[bufferCounter] = 'd';
bufferCounter++;
}
else if (scancode == 0x21) {
messageFromInterrupt[bufferCounter] = 'f';
bufferCounter++;
}
else if (scancode == 0x22) {
messageFromInterrupt[bufferCounter] = 'g';
bufferCounter++;
}
else if (scancode == 0x23) {
messageFromInterrupt[bufferCounter] = 'h';
bufferCounter++;
}
else if (scancode == 0x24) {
messageFromInterrupt[bufferCounter] = 'j';
bufferCounter++;
}
if (bufferCounter >= BUFFER_SIZE) {
bufferCounter = 0;
memset(&messageFromInterrupt[0], 0, sizeof(messageFromInterrupt));
}
return (irq_handler_t) IRQ_HANDLED;
}
static int init_mod(void) {
int result;
/*
*****************************
* Create Character device
*****************************
*/
// Try to dynamically allocate a major number for the device
majorNumber = register_chrdev(0, DEVICE_NAME, &fops);
if (majorNumber < 0) {
printk(KERN_ALERT "MOB: kbdozgurcharClass failed to register a major number\n");
return majorNumber;
}
printk(KERN_INFO "MOB: registered correctly with major number %d\n", majorNumber);
// Register the device class
kbdozgurcharClass = class_create(THIS_MODULE, CLASS_NAME);
if (IS_ERR(kbdozgurcharClass)) { // Check for error and clean up if there is
unregister_chrdev(majorNumber, DEVICE_NAME);
printk(KERN_ALERT "MOB: Failed to register device class\n");
return PTR_ERR(kbdozgurcharClass); // Correct way to return an error on a pointer
}
printk(KERN_INFO "MOB: device class registered correctly\n");
// Register the device driver
kbdozgurcharDevice = device_create(kbdozgurcharClass, NULL, MKDEV(majorNumber, 0), NULL, DEVICE_NAME);
if (IS_ERR(kbdozgurcharDevice)) { // Clean up if there is an error
class_destroy(kbdozgurcharClass); // Repeated code but the alternative is goto statements
unregister_chrdev(majorNumber, DEVICE_NAME);
printk(KERN_ALERT "MOB: Failed to create the device\n");
return PTR_ERR(kbdozgurcharDevice);
}
printk(KERN_INFO "MOB: device class created correctly\n"); // Made it! device was initialized
/*
*****************************
* Bind interrupt
*****************************
*/
// Request IRQ 1, the keyboard IRQ, to go to our irq_handler SA_SHIRQ means we're willing to have othe handlers on this IRQ. SA_INTERRUPT can be used to make the handler into a fast interrupt.
result = request_irq(1, (irq_handler_t) irq_handler, IRQF_SHARED, "kbdozgur", (void *) (irq_handler));
if (result) printk(KERN_INFO "MOB: can't get shared interrupt for keyboard\n");
printk(KERN_INFO "MOB: kbdozgur loaded.\n");
return result;
}
static void exit_mod(void) {
/*
* ****************************
* Destroy Character Device
* ****************************
*/
device_unregister(kbdozgurcharDevice);
device_destroy(kbdozgurcharClass, MKDEV(majorNumber, 0)); // remove the device
class_unregister(kbdozgurcharClass); // unregister the device class
class_destroy(kbdozgurcharClass); // remove the device class
unregister_chrdev(majorNumber, DEVICE_NAME); // unregister the major number
printk(KERN_INFO "MOB: Goodbye from the LKM!\n");
/*
* ****************************
* Free IRQ bind
* ****************************
*/
free_irq(1, (void *) (irq_handler));
printk(KERN_INFO "MOB: kbdozgur unloaded.\n");
}
// Default open function for device
static int dev_open(struct inode *inodep, struct file *filep) {
numberOpens++;
printk(KERN_INFO "MOB: Device has been opened %d time(s)\n", numberOpens);
return 0;
}
/** @brief This function is called whenever device is being read from user space i.e. data is
* being sent from the device to the user. In this case is uses the copy_to_user() function to
* send the buffer string to the user and captures any errors.
* KERNEL SPACE > USER SPACE
* @param filep A pointer to a file object (defined in linux/fs.h)
* @param buffer The pointer to the buffer to which this function writes the data
* @param len The length of the buffer
* @param offset The offset if required
*/
static ssize_t dev_read(struct file *filep, char *buffer, size_t len, loff_t *offset) {
size_t size_requested;
if (len >= bufferCounter) size_requested = bufferCounter;
else size_requested = len;
// if (copy_to_user(buffer, messageFromInterrupt, size_requested)) {
// bufferCounter = bufferCounter - size_requested;
// memset(&messageFromInterrupt[0], 0, sizeof(messageFromInterrupt));
// return -EFAULT;
// }
// else return size_requested;
copy_to_user(buffer, messageFromInterrupt, size_requested);
bufferCounter = bufferCounter - size_requested;
memset(&messageFromInterrupt[0], 0, sizeof(messageFromInterrupt));
return size_requested;
}
/** @brief This function is called whenever the device is being written to from user space i.e.
* data is sent to the device from the user. The data is copied to the messageFromUser[] array in this
* LKM using the sprintf() function along with the length of the string.
* USER SPACE > KERNEL SPACE
* @param filep A pointer to a file object
* @param buffer The buffer to that contains the string to write to the device
* @param len The length of the array of data that is being passed in the const char buffer
* @param offset The offset if required
*/
static ssize_t dev_write(struct file *filep, const char *buffer, size_t len, loff_t *offset) {
short size_of_message;
copy_from_user(messageFromUser, buffer, len);
size_of_message = strlen(messageFromUser); // store the length of the stored message
printk(KERN_INFO "MOB: Received %d characters from the user >> %s", len, messageFromUser);
return len;
}
/** @brief The device release function that is called whenever the device is closed/released by
* the userspace program
* @param inodep A pointer to an inode object (defined in linux/fs.h)
* @param filep A pointer to a file object (defined in linux/fs.h)
*/
static int dev_release(struct inode *inodep, struct file *filep) {
printk(KERN_INFO "MOB: Device successfully closed\n");
return 0;
}
module_init(init_mod);
module_exit(exit_mod);