1. my_tty_driver.c
#include <linux/module.h>
#include <linux/tty.h>
#include <linux/tty_flip.h>
#include "echodev.h"
#define MYDEV_MAX_NDEV 16 /* maximum number of devices supported */
static unsigned ndev = 1; /* number of devices to create */
static unsigned major = 0; /* save major number assigned to driver */
module_param(ndev, uint, 0); /* allow configuration of # devices */
module_param(major, uint, 0); /* allow manual specification of major # */
struct mydev_struct {
struct echodev *e;
int open_count;
};
struct mydev_struct *mydev_table[MYDEV_MAX_NDEV];
static void
mydev_rx(void *priv, const unsigned char *data, int len)
{
struct tty_struct *tty = priv;
int rc;
printk("received %d bytes from device\n", len);
}
static int
mydev_open(struct tty_struct * tty, struct file * filp)
{
struct mydev_struct *dev = mydev_table[tty->index];
if (dev == NULL) {
if (!(dev = kmalloc(sizeof *dev, GFP_KERNEL)))
return -ENOMEM;
if (!(dev->e = echodev_init(tty, mydev_rx))) {
kfree(dev);
return -ENOMEM;
}
mydev_table[tty->index] = dev;
}
++dev->open_count;
tty->driver_data = dev;
return 0;
}
static void
mydev_close(struct tty_struct *tty, struct file * filp)
{
struct mydev_struct *dev = tty->driver_data;
if (dev) {
--dev->open_count;
if (dev->open_count <= 0) {
echodev_destroy(dev->e);
kfree(dev);
mydev_table[tty->index] = NULL;
}
}
}
static int
mydev_write(struct tty_struct *tty, const unsigned char *buf, int count)
{
struct mydev_struct *dev = tty->driver_data;
printk("attempting to send %d bytes to device\n", count);
return echodev_write(dev->e, buf, count);
}
static int
mydev_writeroom(struct tty_struct *tty)
{
struct mydev_struct *dev = tty->driver_data;
int room = echodev_free(dev->e);
printk("mydev_writeroom returns %d\n", room);
return room;
}
static struct tty_operations mydev_ops = {
.open = mydev_open,
.close = mydev_close,
.write = mydev_write,
.write_room = mydev_writeroom,
};
static struct tty_driver *mydev_driver;
static int __init
mymod_init (void)
{
int rc;
/* Ensure ndev isn't too big */
if (ndev > MYDEV_MAX_NDEV) {
printk("Capping ndev to %d\n", MYDEV_MAX_NDEV);
ndev = MYDEV_MAX_NDEV;
}
if (!(mydev_driver = alloc_tty_driver(ndev)))
return -ENOMEM;
mydev_driver->owner = THIS_MODULE;
mydev_driver->driver_name = "mydev_tty";
mydev_driver->name = "mydevtty";
mydev_driver->major = major;
mydev_driver->type = TTY_DRIVER_TYPE_SERIAL;
mydev_driver->subtype = SERIAL_TYPE_NORMAL;
mydev_driver->flags = TTY_DRIVER_REAL_RAW;
mydev_driver->init_termios = tty_std_termios;
mydev_driver->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL;
tty_set_operations(mydev_driver, &mydev_ops);
if ((rc = tty_register_driver(mydev_driver))) {
printk("tty_register_driver() returned %d\n", rc);
put_tty_driver(mydev_driver);
return rc;
}
/* Initialize hardware */
printk("Hello from mydev!\n");
return 0;
}
static void __exit
mymod_exit(void)
{
tty_unregister_driver(mydev_driver);
put_tty_driver(mydev_driver);
printk("Goodbye from mydev.\n");
}
module_init(mymod_init);
module_exit(mymod_exit);
MODULE_AUTHOR("Me");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Character driver skeleton.");
2. Makefile
obj-m := mydev.o
mydev-objs := mymod.o echodev.o
# With this simplified Makefile, type 'make' to build your module
# Type 'make install' to build and install the module to the target file system
KERNELDIR=$(shell ls -d ~/linux-2.6.[0-9]*)
TARGETDIR=/targetfs
PWD := $(shell pwd)
default:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
install:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules modules_install INSTALL_MOD_PATH=$(TARGETDIR)
3. echodev.c
#include <linux/slab.h>
#include "echodev.h"
static void
__echodev_work_fn(struct work_struct *work)
{
struct echodev *e = container_of(work, struct echodev, work);
int nbytes;
if ((nbytes = atomic_read(&e->nbuf))) {
if (e->r + nbytes > ECHODEV_BUFSIZE) {
int i = ECHODEV_BUFSIZE - e->r;
e->rx_fn(e->priv, e->buffer + e->r, i);
e->r = nbytes - i;
e->rx_fn(e->priv, e->buffer, e->r);
} else {
e->rx_fn(e->priv, e->buffer + e->r, nbytes);
e->r += nbytes;
}
atomic_sub(nbytes, &e->nbuf);
}
}
struct echodev*
echodev_init(void *priv, void (*rx_fn)(void*, const unsigned char*, int))
{
struct echodev *e = kzalloc(sizeof *e, GFP_KERNEL);
if(!e)
return NULL;
e->priv = priv;
e->rx_fn = rx_fn;
INIT_WORK(&e->work, __echodev_work_fn);
return e;
}
void
echodev_destroy(struct echodev *e)
{
cancel_work_sync(&e->work);
kfree(e);
}
int
echodev_write (struct echodev *e, const unsigned char * buf, int nbytes)
{
int nfree = ECHODEV_BUFSIZE - atomic_read(&e->nbuf);
if (nbytes > nfree)
nbytes = nfree;
if (e->w + nbytes > ECHODEV_BUFSIZE) {
int i = ECHODEV_BUFSIZE - e->w;
memcpy(e->buffer + e->w, buf, i);
e->w = nbytes - i;
memcpy(e->buffer, buf + i, e->w);
} else {
memcpy(e->buffer + e->w, buf, nbytes);
e->w += nbytes;
}
atomic_add(nbytes, &e->nbuf);
schedule_work(&e->work);
return nbytes;
}
int
echodev_free(struct echodev *e)
{
return ECHODEV_BUFSIZE - atomic_read(&e->nbuf);
}
4. echodev.h
#ifndef __ECHODEV_H__
#define __ECHODEV_H__
#include <linux/workqueue.h>
#include <asm/atomic.h>
#define ECHODEV_BUFSIZE 32
struct echodev {
char buffer[ECHODEV_BUFSIZE];
atomic_t nbuf;
void (*rx_fn)(void *, const unsigned char *, int);
int r, w;
struct work_struct work;
void *priv;
};
struct echodev *echodev_init (void *, void (*)(void*, const unsigned char*, int));
void echodev_destroy (struct echodev *);
int echodev_write(struct echodev *, const unsigned char *, int);
int echodev_free(struct echodev *);
#endif