1

User Image 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);

 

 


1