/*
* scull.c -- the bare scull char module
*
* Copyright (C) 2001 Alessandro Rubini and Jonathan Corbet
* Copyright (C) 2001 O'Reilly & Associates
*
* The source code in this file can be freely used, adapted,
* and redistributed in source or binary form, so long as an
* acknowledgment appears in derived source files. The citation
* should list that the code comes from the book "Linux Device
* Drivers" by Alessandro Rubini and Jonathan Corbet, published
* by O'Reilly & Associates. No warranty is attached;
* we cannot take responsibility for errors or fitness for use.
*
*/
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/init.h>
#include <linux/kernel.h> /* printk() */
#include <linux/slab.h> /* kmalloc() */
#include <linux/fs.h> /* everything... */
#include <linux/errno.h> /* error codes */
#include <linux/types.h> /* size_t */
#include <linux/cdev.h>
#include <linux/uaccess.h> /* copy_*_user */
#include "scull.h" /* local definitions */
#include <linux/mutex.h>
int queue_init(void);
/*
* Our parameters which can be set at load time.
*/
static int scull_major = SCULL_MAJOR;
static int scull_minor = 0;
static int scull_fifo_elemsz = SCULL_FIFO_ELEMSZ_DEFAULT; /* ELEMSZ */
static int scull_fifo_size = SCULL_FIFO_SIZE_DEFAULT; /* N */
/* FIFO Queue Implementation Variables */
static char* fifo_queue;
static char* read_head; /* start */
static char* write_head; /* end */
/* explicitly defined in */
static struct semaphore empty_slots; /* starts at N / scull_fifo_size */
static struct semaphore full_slots; /* starts at 0 */
/* Define a mutex from the linux kernel API. */
DEFINE_MUTEX(mux);
/* Busy waits for a bit then sleeps */
module_param(scull_major, int, S_IRUGO);
module_param(scull_minor, int, S_IRUGO);
module_param(scull_fifo_size, int, S_IRUGO);
module_param(scull_fifo_elemsz, int, S_IRUGO);
MODULE_AUTHOR("tshabbic");
MODULE_LICENSE("Dual BSD/GPL");
static struct cdev scull_cdev; /* Char device structure */
/*
* Open and close
*/
static int scull_open(struct inode *inode, struct file *filp)
{
printk(KERN_INFO "scull open\n");
return 0; /* success */
}
static int scull_release(struct inode *inode, struct file *filp)
{
printk(KERN_INFO "scull close\n");
return 0;
}
static ssize_t scull_read(struct file *filp, char __user *buf,
size_t count, loff_t *f_pos)
{
/* Size of one slot: length header followed by ELEMSZ bytes of data */
const size_t stride = sizeof(size_t) + (size_t)scull_fifo_elemsz;
/* Total size of the flat array, also used to detect the wrap around point */
const size_t total = (size_t)scull_fifo_size * stride;
size_t stored_len; /* length recorded in the slot's header */
size_t n; /* number of data bytes we will hand to the user */
/* Sleep until at least one slot is full. Interruptible so Ctrl-c works.
* Nothing is held yet, so nothing needs releasing on failure. */
if (down_interruptible(&full_slots)) {
return -ERESTARTSYS;
}
/* Take the mutex protecting read_head and the slot contents.
* If interrupted here we already consumed a "full" count without
* reading anything, so give it back for another consumer. */
if (mutex_lock_interruptible(&mux)) {
up(&full_slots);
return -ERESTARTSYS;
}
/* Pull the stored length out of the slot header first */
memcpy(&stored_len, read_head, sizeof(size_t));
/* Defensive clamp: a corrupt header must never make us read past
* the element's data area. */
if (stored_len > (size_t)scull_fifo_elemsz) {
stored_len = (size_t)scull_fifo_elemsz;
}
n = (count < stored_len) ? count : stored_len;
/* Copy the data (just past the header) out to user space. */
if (copy_to_user(buf, read_head + sizeof(size_t), n)) {
mutex_unlock(&mux);
up(&full_slots);
return -EFAULT;
}
read_head += stride;
if (read_head >=fifo_queue + total) {
read_head =fifo_queue;
}
mutex_unlock(&mux);
up(&empty_slots);
return (ssize_t)n;
}
static ssize_t scull_write(struct file *filp, const char __user *buf,
size_t count, loff_t *f_pos)
{
/* Size of one slot: length header followed by ELEMSZ bytes of data */
const size_t stride = sizeof(size_t) + (size_t)scull_fifo_elemsz;
/* Total size of the flat array, also used to detect the wrap around point */
const size_t total = (size_t)scull_fifo_size * stride;
/* Messages longer than ELEMSZ are truncated, not rejected */
size_t len_to_copy = (count < (size_t)scull_fifo_elemsz) ? count : (size_t)scull_fifo_elemsz;
/* Sleep until a slot is free. Interruptible so Ctrl-C works as per the assignment spec.
* Nothing is held yet, so nothing needs releasing on failure. */
if (down_interruptible(&empty_slots)) {
return -ERESTARTSYS;
}
/* Take the mutex protecting write_head and the slot contents.
* If interrupted here we already consumed a slot, so give it back. */
if (mutex_lock_interruptible(&mux)) {
up(&empty_slots);
return -ERESTARTSYS;
}
/* Copy the message data just past the length header of the slot.
* On failure, release everything we hold and leave write_head alone
* so the slot stays free. */
if (copy_from_user(write_head + sizeof(size_t), buf, len_to_copy)) {
mutex_unlock(&mux);
up(&empty_slots);
return -EFAULT;
}
/* Store the actual message length at the front of the slot.
* memcpy avoids alignment assumptions about the header. */
memcpy(write_head, &len_to_copy, sizeof(size_t));
/* Advance by one slot and wrap back to the array base if necessary */
write_head += stride;
if (write_head >=fifo_queue + total) {
write_head =fifo_queue;
}
mutex_unlock(&mux);
/* wake a consumer if one is waiting */
up(&full_slots);
return (ssize_t)len_to_copy; /* data bytes only, header excluded */
}
/*
* The ioctl() implementation
*/
static long scull_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
int err = 0;
int retval = 0;
/*
* extract the type and number bitfields, and don't decode
* wrong cmds: return ENOTTY (inappropriate ioctl) before access_ok()
*/
if (_IOC_TYPE(cmd) != SCULL_IOC_MAGIC) return -ENOTTY;
if (_IOC_NR(cmd) > SCULL_IOC_MAXNR) return -ENOTTY;
err = !access_ok((void __user *)arg, _IOC_SIZE(cmd));
if (err) return -EFAULT;
switch(cmd) {
case SCULL_IOCGETELEMSZ:
return scull_fifo_elemsz;
default: /* redundant, as cmd was checked against MAXNR */
return -ENOTTY;
}
return retval;
}
struct file_operations scull_fops = {
.owner = THIS_MODULE,
.unlocked_ioctl = scull_ioctl,
.open = scull_open,
.release = scull_release,
.read = scull_read,
.write = scull_write,
};
int queue_init(void)
{
int queue_length = (scull_fifo_size * (sizeof(size_t) + scull_fifo_elemsz));
fifo_queue = kmalloc(queue_length, GFP_KERNEL);
if (!fifo_queue)
{
printk(KERN_ERR "scull: kmalloc fail upon trying to create the fifo queue!\n");
return -1;
}
/* initialize the semaphores declared above */
sema_init(&empty_slots, scull_fifo_size); /* our produces are ready */
sema_init(&full_slots, 0);
read_head = fifo_queue;
write_head = fifo_queue;
return 0;
}
/*
* The cleanup function is used to handle initialization failures as well.
* Thefore, it must be careful to work correctly even if some of the items
* have not been initialized
*/
void scull_cleanup_module(void)
{
dev_t devno = MKDEV(scull_major, scull_minor);
/* avoid an eventual kernel panic */
if (mutex_is_locked(&mux) == 1) {
mutex_unlock(&mux);
}
/* free our queue */
kfree(fifo_queue);
/* Get rid of the char dev entry */
cdev_del(&scull_cdev);
/* cleanup_module is never called if registering failed */
unregister_chrdev_region(devno, 1);
}
int scull_init_module(void)
{
int result;
dev_t dev = 0;
/*
* Get a range of minor numbers to work with, asking for a dynamic
* major unless directed otherwise at load time.
*/
if (scull_major) {
dev = MKDEV(scull_major, scull_minor);
result = register_chrdev_region(dev, 1, "scull");
} else {
result = alloc_chrdev_region(&dev, scull_minor, 1, "scull");
scull_major = MAJOR(dev);
}
if (result < 0) {
printk(KERN_WARNING "scull: can't get major %d\n", scull_major);
return result;
}
cdev_init(&scull_cdev, &scull_fops);
scull_cdev.owner = THIS_MODULE;
result = cdev_add (&scull_cdev, dev, 1);
/* Fail gracefully if need be */
if (result) {
printk(KERN_NOTICE "Error %d adding scull character device", result);
goto fail;
}
if (queue_init() < 0) {
goto fail;
}
printk(KERN_INFO "scull: FIFO SIZE=%u, ELEMSZ=%u\n", scull_fifo_size, scull_fifo_elemsz);
return 0; /* succeed */
fail:
scull_cleanup_module();
return result;
}
module_init(scull_init_module);
module_exit(scull_cleanup_module);