FreeBSD Architecture Handbook
Prev | Chapter 9 Writing FreeBSD Device Drivers | Next

# 9.4 Character Devices

A character device driver is one that transfers data directly to and from a user process. This is the most common type of device driver and there are plenty of simple examples in the source tree.

This simple example pseudo-device remembers whatever values you write to it and can then supply them back to you when you read from it. Two versions are shown, one for FreeBSD 4.X and one for FreeBSD 5.X.

Example 9-1. Example of a Sample Echo Pseudo-Device Driver for FreeBSD 4.X

    /*
     * Simple `echo' pseudo-device KLD
     *
     * Murray Stokely
     */

    #define MIN(a,b) (((a) < (b)) ? (a) : (b))

    #include <sys/types.h>
    #include <sys/module.h>
    #include <sys/systm.h>  /* uprintf */
    #include <sys/errno.h>
    #include <sys/param.h>  /* defines used in kernel.h */
    #include <sys/kernel.h> /* types used in module initialization */
    #include <sys/conf.h>   /* cdevsw struct */
    #include <sys/uio.h>    /* uio struct */
    #include <sys/malloc.h>

    #define BUFFERSIZE 256

    /* Function prototypes */
    d_open_t	echo_open;
    d_close_t	echo_close;
    d_read_t	echo_read;
    d_write_t	echo_write;

    /* Character device entry points */
    static struct cdevsw echo_cdevsw = {
    	echo_open,
    	echo_close,
    	echo_read,
    	echo_write,
    	noioctl,
    	nopoll,
    	nommap,
    	nostrategy,
    	"echo",
    	33,              /* reserved for lkms - /usr/src/sys/conf/majors */
    	nodump,
    	nopsize,
    	D_TTY,
    	-1
    };

    typedef struct s_echo {
    	char msg[BUFFERSIZE];
    	int len;
    } t_echo;

    /* vars */
    static dev_t sdev;
    static int count;
    static t_echo *echomsg;

    MALLOC_DECLARE(M_ECHOBUF);
    MALLOC_DEFINE(M_ECHOBUF, "echobuffer", "buffer for echo module");

    /*
     * This function is called by the kld[un]load(2) system calls to
     * determine what actions to take when a module is loaded or unloaded.
     */

    static int
    echo_loader(struct module *m, int what, void *arg)
    {
    	int err = 0;

    	switch (what) {
    	case MOD_LOAD:                /* kldload */
    		sdev = make_dev(&echo_cdevsw,
    		    0,
    		    UID_ROOT,
    		    GID_WHEEL,
    		    0600,
    		    "echo");
    		/* kmalloc memory for use by this driver */
    		MALLOC(echomsg, t_echo *, sizeof(t_echo), M_ECHOBUF, M_WAITOK);
    		printf("Echo device loaded.\n");
    		break;
    	case MOD_UNLOAD:
    		destroy_dev(sdev);
    		FREE(echomsg,M_ECHOBUF);
    		printf("Echo device unloaded.\n");
    		break;
    	default:
    		err = EOPNOTSUPP;
    		break;
    	}
    	return(err);
    }

    int
    echo_open(dev_t dev, int oflags, int devtype, struct proc *p)
    {
    	int err = 0;

    	uprintf("Opened device \"echo\" successfully.\n");
    	return(err);
    }

    int
    echo_close(dev_t dev, int fflag, int devtype, struct proc *p)
    {
    	uprintf("Closing device \"echo.\"\n");
    	return(0);
    }

    /*
     * The read function just takes the buf that was saved via
     * echo_write() and returns it to userland for accessing.
     * uio(9)
     */

    int
    echo_read(dev_t dev, struct uio *uio, int ioflag)
    {
    	int err = 0;
    	int amt;

    	/*
    	 * How big is this read operation?  Either as big as the user wants,
    	 * or as big as the remaining data
    	 */
    	amt = MIN(uio->uio_resid, (echomsg->len - uio->uio_offset > 0) ?
    	    echomsg->len - uio->uio_offset : 0);
    	if ((err = uiomove(echomsg->msg + uio->uio_offset,amt,uio)) != 0) {
    		uprintf("uiomove failed!\n");
    	}
    	return(err);
    }

    /*
     * echo_write takes in a character string and saves it
     * to buf for later accessing.
     */

    int
    echo_write(dev_t dev, struct uio *uio, int ioflag)
    {
    	int err = 0;

    	/* Copy the string in from user memory to kernel memory */
    	err = copyin(uio->uio_iov->iov_base, echomsg->msg,
    	    MIN(uio->uio_iov->iov_len, BUFFERSIZE - 1));

    	/* Now we need to null terminate, then record the length */
    	*(echomsg->msg + MIN(uio->uio_iov->iov_len, BUFFERSIZE - 1)) = 0;
    	echomsg->len = MIN(uio->uio_iov->iov_len, BUFFERSIZE);

    	if (err != 0) {
    		uprintf("Write failed: bad address!\n");
    	}
    	count++;
    	return(err);
    }

    DEV_MODULE(echo,echo_loader,NULL);

Example 9-2. Example of a Sample Echo Pseudo-Device Driver for FreeBSD 5.X

    /*
     * Simple `echo' pseudo-device KLD
     *
     * Murray Stokely
     *
     * Converted to 5.X by Søren (Xride) Straarup
     */

    #include <sys/types.h>
    #include <sys/module.h>
    #include <sys/systm.h>  /* uprintf */
    #include <sys/errno.h>
    #include <sys/param.h>  /* defines used in kernel.h */
    #include <sys/kernel.h> /* types used in module initialization */
    #include <sys/conf.h>   /* cdevsw struct */
    #include <sys/uio.h>    /* uio struct */
    #include <sys/malloc.h>

    #define BUFFERSIZE 256

    /* Function prototypes */
    static d_open_t      echo_open;
    static d_close_t     echo_close;
    static d_read_t      echo_read;
    static d_write_t     echo_write;

    /* Character device entry points */
    static struct cdevsw echo_cdevsw = {
    	.d_version = D_VERSION,
    	.d_open = echo_open,
    	.d_close = echo_close,
    	.d_read = echo_read,
    	.d_write = echo_write,
    	.d_name = "echo",
    };

    typedef struct s_echo {
    	char msg[BUFFERSIZE];
    	int len;
    } t_echo;

    /* vars */
    static struct cdev *echo_dev;
    static int count;
    static t_echo *echomsg;

    MALLOC_DECLARE(M_ECHOBUF);
    MALLOC_DEFINE(M_ECHOBUF, "echobuffer", "buffer for echo module");

    /*
     * This function is called by the kld[un]load(2) system calls to
     * determine what actions to take when a module is loaded or unloaded.
     */

    static int
    echo_loader(struct module *m, int what, void *arg)
    {
    	int err = 0;

    	switch (what) {
    	case MOD_LOAD:                /* kldload */
    		echo_dev = make_dev(&echo_cdevsw,
    		    0,
    		    UID_ROOT,
    		    GID_WHEEL,
    		    0600,
    		    "echo");
    		/* kmalloc memory for use by this driver */
    		echomsg = malloc(sizeof(t_echo), M_ECHOBUF, M_WAITOK);
    		printf("Echo device loaded.\n");
    		break;
    	case MOD_UNLOAD:
    		destroy_dev(echo_dev);
    		free(echomsg, M_ECHOBUF);
    		printf("Echo device unloaded.\n");
    		break;
    	default:
    		err = EOPNOTSUPP;
    		break;
    	}
    	return(err);
    }

    static int
    echo_open(struct cdev *dev, int oflags, int devtype, struct thread *p)
    {
    	int err = 0;

    	uprintf("Opened device \"echo\" successfully.\n");
    	return(err);
    }

    static int
    echo_close(struct cdev *dev, int fflag, int devtype, struct thread *p)
    {
    	uprintf("Closing device \"echo.\"\n");
    	return(0);
    }

    /*
     * The read function just takes the buf that was saved via
     * echo_write() and returns it to userland for accessing.
     * uio(9)
     */

    static int
    echo_read(struct cdev *dev, struct uio *uio, int ioflag)
    {
    	int err = 0;
    	int amt;

    	/*
    	 * How big is this read operation?  Either as big as the user wants,
    	 * or as big as the remaining data
    	 */
    	amt = MIN(uio->uio_resid, (echomsg->len - uio->uio_offset > 0) ?
    	     echomsg->len - uio->uio_offset : 0);
    	if ((err = uiomove(echomsg->msg + uio->uio_offset, amt, uio)) != 0) {
    		uprintf("uiomove failed!\n");
    	}
    	return(err);
    }

    /*
     * echo_write takes in a character string and saves it
     * to buf for later accessing.
     */

    static int
    echo_write(struct cdev *dev, struct uio *uio, int ioflag)
    {
    	int err = 0;

    	/* Copy the string in from user memory to kernel memory */
    	err = copyin(uio->uio_iov->iov_base, echomsg->msg,
    	    MIN(uio->uio_iov->iov_len, BUFFERSIZE - 1));

    	/* Now we need to null terminate, then record the length */
    	*(echomsg->msg + MIN(uio->uio_iov->iov_len, BUFFERSIZE - 1)) = 0;
    	echomsg->len = MIN(uio->uio_iov->iov_len, BUFFERSIZE);

    	if (err != 0) {
    		uprintf("Write failed: bad address!\n");
    	}
    	count++;
    	return(err);
    }

    DEV_MODULE(echo,echo_loader,NULL);

To install this driver on FreeBSD 4.X you will first need to make a node on your filesystem with a command such as:

    # mknod /dev/echo c 33 0

With this driver loaded you should now be able to type something like:

    # echo -n "Test Data" > /dev/echo
    # cat /dev/echo
    Test Data

Real hardware devices are described in the next chapter.

Additional Resources

- Dynamic Kernel Linker (KLD) Facility Programming Tutorial - Daemonnews October 2000

- How to Write Kernel Drivers with NEWBUS - Daemonnews July 2000

Prev | Home | Next
Accessing a device driver | Up | Block Devices (Are Gone)