In order to implement the requirements of the specification, the first task is to decide what the device driver's internal data structures will look like. What is required is that the device driver should be capable of storing a number of short text messages which are in transit between two processes. The number of messages the driver is required to hold is supposed to be infinite (infinite on a computer usually means 'until you run out') but in practice it would probably only hold a few messages until they could be read. This means that you really want to use a variable number of dynamically allocated buffers which can be built up into a FIFO queue which is best implemented as a linked list. This will require a kernel mechanism for the dynamic allocation and release of blocks of memory.
Note that the standard library malloc() and free() functions cannot be used because the standard library is not available from within the kernel. As you will see, however, there is a kernel equivalent to malloc() which can be used instead.
The structure from which the linked list of messages will be built has the following layout:
struct tdd_buf { int buf_size; char buffer[MAX_BUF]; struct tdd_buf *link; }
where buffer[] is the array that holds one of the short messages, buf_size says how many characters in the buffer[] array are in use, and link is the linked list pointer to the next tdd_buf. The symbolic constant MAX_BUF can be set to whatever matches your idea of the maximum length of a 'short' message. My default is 120 characters.
Notice that the prefix used in the tiny device driver to make its identifiers unique will be tdd_, though many of the identifiers are declared to be static so that they will not be visible outside the source file for the driver anyway.
Bearing this in mind, the tiny device driver header information is as follows:
/* 1 */ *define KERNEL #include <linux/kernel.h> #include <linux/sched.h> #include <linux/tty.h> #include <linux/signal.h> #include <linux/ermo.h> #include <linux/malloc.h> #include <asm/io.h> #include <asm/segment.h> #include <asm/system.h> #include <asm/irq.h> #include "tdd.h" /* 2 */ static int tdd_trace; static int write_busy; static int read_busy; static struct tdd_buf *qhead; static struct tdd_buf *qtail; /* 3 */ static int tdd_read(struct mode *, struct file *, char *, int); static int tdd_write(struct mode *, struct file *, char *, mt); static int tdd_ioctl(struct mode *, struct file *, unsigned int, unsigned long); static int tdd_open(struct mode *, struct file *); static void tdd_release(struct mode *, struct file *); extern void console_print(char *); struct file_operations tdd_fops = { NULL, tdd_read, tdd_write, NULL, NULL, tdd_ioctl, NULL, tdd_open, tdd_release, NULL, NULL, NULL, NULL };
The following list refers to the numbered comments in the header information:
Over and above this header code, there is also a header file called <tdd.h> which contains the #def ines and structure declaration required by the device driver and also by user code wishing to use this driver:
#ifdef KERNEL /* If we're in kernel code */ #define TRACE_TXT(text) \ if (tdd_trace) \ { \ console_print(text); \ console_print("\n"); \ } \ #define TRACE_CHR(chr) \ { if (tdd_trace) \ console_print(chr); \ } #define TDD_WRITE 0 /* /dev/tddw minor device number */ #define TDD_READ 1 /* /dev/tddr minor device number */ #endif #define FALSE 0 #define TRUE 1 #define MAX_BUF 120 /* Size of struct tdd~buf buffer */ #define TDD_TRON (('M'<<8)|0x01) /* Trace on cmd for ioctlO */ #define TDD_TROFF (('M'<<8)|0x02) /* Trace off cmd for ioctlO */ struct tdd_buf { int buf_size; char buffer [MAX_BUF]; struct tdd_buf *link; };
The only things here which require some explanation are the macro definitions for TRACE_TXT() and TRACE_CHR(). These are provided to give a very simple trace facility for debugging purposes. At various strategic locations throughout the device driver code, calls to these macros are inserted so that 'got here' type messages can be displayed on the machine's console terminal. Trace messages are displayed on the console screen using the kernel's internal function: console_print(). The display of trace messages is controlled by the state of a flag variable whose value can be set or reset via an appropriate ioctl() call. The two commands to ioctl() dealt with by the driver are TDD_TRON to turn tracing on and TDD_TROFF to turn it off again. Neither of these ioctl() commands takes any other parameters.