From de48c8d9f5b571fea7fb2dfbdfc0b757f0aba9e5 Mon Sep 17 00:00:00 2001 From: Pete Batard Date: Sat, 7 Nov 2015 17:03:09 +0000 Subject: [PATCH] Add original files --- Makefile | 32 +++++++ README.md | 5 +- parrot_driver.c | 241 ++++++++++++++++++++++++++++++++++++++++++++++++ parrot_driver.h | 34 +++++++ 4 files changed, 310 insertions(+), 2 deletions(-) create mode 100644 Makefile create mode 100644 parrot_driver.c create mode 100644 parrot_driver.h diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..b199997 --- /dev/null +++ b/Makefile @@ -0,0 +1,32 @@ +TARGET = parrot_driver + +ifneq ($(KERNELRELEASE),) +# call from kernel build system + +obj-m := $(TARGET).o + +else + +KERNELDIR ?= /usr/src/linux +PWD := $(shell pwd) + +default: + $(MAKE) -C $(KERNELDIR) SUBDIRS=$(PWD) modules + +endif + +clean: + rm -rf *.o *.ko *~ core .depend *.mod.c .*.cmd .tmp_versions .*.o.d + +depend .depend dep: + $(CC) $(CFLAGS) -M *.c > .depend + +ins: default rem + insmod $(TARGET).ko debug=1 + +rem: + @if [ -n "`lsmod | grep -s $(TARGET)`" ]; then rmmod $(TARGET); echo "rmmod $(TARGET)"; fi + +ifeq (.depend,$(wildcard .depend)) +include .depend +endif diff --git a/README.md b/README.md index 835a0a9..66deca5 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,3 @@ -# parrot -Linux parrot device driver sample +# Parrot - A Linux device driver sample + +See http://pete.akeo.ie/2011/08/writing-linux-device-driver-for-kernels.html diff --git a/parrot_driver.c b/parrot_driver.c new file mode 100644 index 0000000..78f3f49 --- /dev/null +++ b/parrot_driver.c @@ -0,0 +1,241 @@ +/* + * Linux 2.6 and 3.0 'parrot' sample device driver + * + * Copyright (c) 2011, Pete Batard + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "parrot_driver.h" + +/* Module information */ +MODULE_AUTHOR(AUTHOR); +MODULE_DESCRIPTION(DESCRIPTION); +MODULE_VERSION(VERSION); +MODULE_LICENSE("GPL"); + +/* Device variables */ +static struct class* parrot_class = NULL; +static struct device* parrot_device = NULL; +static int parrot_major; +/* Flag used with the one_shot mode */ +static bool message_read; +/* A mutex will ensure that only one process accesses our device */ +static DEFINE_MUTEX(parrot_device_mutex); +/* Use a Kernel FIFO for read operations */ +static DECLARE_KFIFO(parrot_msg_fifo, char, PARROT_MSG_FIFO_SIZE); +/* This table keeps track of each message length in the FIFO */ +static unsigned int parrot_msg_len[PARROT_MSG_FIFO_MAX]; +/* Read and write index for the table above */ +static int parrot_msg_idx_rd, parrot_msg_idx_wr; + +/* Module parameters that can be provided on insmod */ +static bool debug = false; /* print extra debug info */ +module_param(debug, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(debug, "enable debug info (default: false)"); +static bool one_shot = true; /* only read a single message after open() */ +module_param(one_shot, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(debug, "disable the readout of multiple messages at once (default: true)"); + + +static int parrot_device_open(struct inode* inode, struct file* filp) +{ + dbg(""); + + /* Our sample device does not allow write access */ + if ( ((filp->f_flags & O_ACCMODE) == O_WRONLY) + || ((filp->f_flags & O_ACCMODE) == O_RDWR) ) { + warn("write access is prohibited\n"); + return -EACCES; + } + + /* Ensure that only one process has access to our device at any one time + * For more info on concurrent accesses, see http://lwn.net/images/pdf/LDD3/ch05.pdf */ + if (!mutex_trylock(&parrot_device_mutex)) { + warn("another process is accessing the device\n"); + return -EBUSY; + } + + message_read = false; + return 0; +} + +static int parrot_device_close(struct inode* inode, struct file* filp) +{ + dbg(""); + mutex_unlock(&parrot_device_mutex); + return 0; +} + +static ssize_t parrot_device_read(struct file* filp, char __user *buffer, size_t length, loff_t* offset) +{ + int retval; + unsigned int copied; + + /* The default from 'cat' is to issue multiple reads until the FIFO is depleted + * one_shot avoids that */ + if (one_shot && message_read) return 0; + dbg(""); + + if (kfifo_is_empty(&parrot_msg_fifo)) { + dbg("no message in fifo\n"); + return 0; + } + + retval = kfifo_to_user(&parrot_msg_fifo, buffer, parrot_msg_len[parrot_msg_idx_rd], &copied); + /* Ignore short reads (but warn about them) */ + if (parrot_msg_len[parrot_msg_idx_rd] != copied) { + warn("short read detected\n"); + } + /* loop into the message length table */ + parrot_msg_idx_rd = (parrot_msg_idx_rd+1)%PARROT_MSG_FIFO_MAX; + message_read = true; + + return retval ? retval : copied; +} + +/* The file_operation scructure tells the kernel which device operations are handled. + * For a list of available file operations, see http://lwn.net/images/pdf/LDD3/ch03.pdf */ +static struct file_operations fops = { + .read = parrot_device_read, + .open = parrot_device_open, + .release = parrot_device_close +}; + +/* Placing data into the read FIFO is done through sysfs */ +static ssize_t sys_add_to_fifo(struct device* dev, struct device_attribute* attr, const char* buf, size_t count) +{ + unsigned int copied; + + dbg(""); + if (kfifo_avail(&parrot_msg_fifo) < count) { + warn("not enough space left on fifo\n"); + return -ENOSPC; + } + if ((parrot_msg_idx_wr+1)%PARROT_MSG_FIFO_MAX == parrot_msg_idx_rd) { + /* We've looped into our message length table */ + warn("message length table is full\n"); + return -ENOSPC; + } + + /* The buffer is already in kernel space, so no need for ..._from_user() */ + copied = kfifo_in(&parrot_msg_fifo, buf, count); + parrot_msg_len[parrot_msg_idx_wr] = copied; + if (copied != count) { + warn("short write detected\n"); + } + parrot_msg_idx_wr = (parrot_msg_idx_wr+1)%PARROT_MSG_FIFO_MAX; + + return copied; +} + +/* This sysfs entry resets the FIFO */ +static ssize_t sys_reset(struct device* dev, struct device_attribute* attr, const char* buf, size_t count) +{ + dbg(""); + + /* Ideally, we would have a mutex around the FIFO, to ensure that we don't reset while in use. + * To keep this sample simple, and because this is a sysfs operation, we don't do that */ + kfifo_reset(&parrot_msg_fifo); + parrot_msg_idx_rd = parrot_msg_idx_wr = 0; + + return count; +} + +/* Declare the sysfs entries. The macros create instances of dev_attr_fifo and dev_attr_reset */ +static DEVICE_ATTR(fifo, S_IWUSR, NULL, sys_add_to_fifo); +static DEVICE_ATTR(reset, S_IWUSR, NULL, sys_reset); + +/* Module initialization and release */ +static int __init parrot_module_init(void) +{ + int retval; + dbg(""); + + /* First, see if we can dynamically allocate a major for our device */ + parrot_major = register_chrdev(0, DEVICE_NAME, &fops); + if (parrot_major < 0) { + err("failed to register device: error %d\n", parrot_major); + retval = parrot_major; + goto failed_chrdevreg; + } + + /* We can either tie our device to a bus (existing, or one that we create) + * or use a "virtual" device class. For this example, we choose the latter */ + parrot_class = class_create(THIS_MODULE, CLASS_NAME); + if (IS_ERR(parrot_class)) { + err("failed to register device class '%s'\n", CLASS_NAME); + retval = PTR_ERR(parrot_class); + goto failed_classreg; + } + + /* With a class, the easiest way to instantiate a device is to call device_create() */ + parrot_device = device_create(parrot_class, NULL, MKDEV(parrot_major, 0), NULL, CLASS_NAME "_" DEVICE_NAME); + if (IS_ERR(parrot_device)) { + err("failed to create device '%s_%s'\n", CLASS_NAME, DEVICE_NAME); + retval = PTR_ERR(parrot_device); + goto failed_devreg; + } + + /* Now we can create the sysfs endpoints (don't care about errors). + * dev_attr_fifo and dev_attr_reset come from the DEVICE_ATTR(...) earlier */ + retval = device_create_file(parrot_device, &dev_attr_fifo); + if (retval < 0) { + warn("failed to create write /sys endpoint - continuing without\n"); + } + retval = device_create_file(parrot_device, &dev_attr_reset); + if (retval < 0) { + warn("failed to create reset /sys endpoint - continuing without\n"); + } + + mutex_init(&parrot_device_mutex); + /* This device uses a Kernel FIFO for its read operation */ + INIT_KFIFO(parrot_msg_fifo); + parrot_msg_idx_rd = parrot_msg_idx_wr = 0; + + return 0; + +failed_devreg: + class_unregister(parrot_class); + class_destroy(parrot_class); +failed_classreg: + unregister_chrdev(parrot_major, DEVICE_NAME); +failed_chrdevreg: + return -1; +} + +static void __exit parrot_module_exit(void) +{ + dbg(""); + device_remove_file(parrot_device, &dev_attr_fifo); + device_remove_file(parrot_device, &dev_attr_reset); + device_destroy(parrot_class, MKDEV(parrot_major, 0)); + class_unregister(parrot_class); + class_destroy(parrot_class); + unregister_chrdev(parrot_major, DEVICE_NAME); +} + +/* Let the kernel know the calls for module init and exit */ +module_init(parrot_module_init); +module_exit(parrot_module_exit); diff --git a/parrot_driver.h b/parrot_driver.h new file mode 100644 index 0000000..1e6e810 --- /dev/null +++ b/parrot_driver.h @@ -0,0 +1,34 @@ +/* + * Linux 2.6 and 3.0 'parrot' sample device driver + * + * Copyright (c) 2011, Pete Batard + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#define DEVICE_NAME "device" +#define CLASS_NAME "parrot" +#define PARROT_MSG_FIFO_SIZE 1024 +#define PARROT_MSG_FIFO_MAX 128 + +#define AUTHOR "Pete Batard " +#define DESCRIPTION "'parrot' sample device driver" +#define VERSION "0.3" + +/* We'll use our own macros for printk */ +#define dbg(format, arg...) do { if (debug) pr_info(CLASS_NAME ": %s: " format , __FUNCTION__ , ## arg); } while (0) +#define err(format, arg...) pr_err(CLASS_NAME ": " format, ## arg) +#define info(format, arg...) pr_info(CLASS_NAME ": " format, ## arg) +#define warn(format, arg...) pr_warn(CLASS_NAME ": " format, ## arg) -- 2.44.0