Adding a driver may be quite easy if the driver class already exists. If not, [NewDriverClass create a new driver class] and come back here. We'll go through this guide assuming we are adding a new character device, we'll call it `pmtt`. This device is an imaginary one with: * a transmit register `TXR`, * a receive register `RXR`, * a status register `STR`, telling whether we can receive, * an interrupt mask register `ITR`, masking send / receive interrupts. = Creating a new directory = We'll go to the correct directory in the source tree and create the directory for the device: {{{ $ cd mutekh/device/driver/char $ mkdir pmtt $ cd pmtt }}} = Creating the files = Now we can create the files. There are usually 4 files for a device: * a `.config` for the BuildSystem, * a `.h` for declaring the public functions, * a `.c` implementing the actual driver, * a `-private.h` containing the private definitions for the driver. == The `.config` file == Let's start with the `.config` file. It will contain: {{{ %config CONFIG_DRIVER_CHAR_PMTT desc Enable PMTT imaginary device default undefined provide CONFIG_DRIVER_TTY %config end }}} Here we declare this driver implements a TTY, i.e. a device with a send/receive interface. == The `.h` file == Now we may define the external interface of our device. Interface function prototypes are defined in two headers: * `device/device.h` defines the class-agnostic functions: init, cleanup and irq, * `device/`''class''`.h` defines the class-specific functions. for character devices, there is only a `request` function to implement. In order to enforce the correct prototype, even in case of evolution, it is mandatory to use the proper macros `DEV_INIT`, `DEV_CLEANUP`, `DEV_IRQ` and `DEVCHAR_REQUEST`. {{{ #ifndef DRIVER_TTY_PMTT_H #define DRIVER_TTY_PMTT_H #include #include /* The device constructor & destructor */ DEV_INIT(pmtt_init); DEV_CLEANUP(pmtt_cleanup); /* The request handler */ DEVCHAR_REQUEST(pmtt_request); /* The IRQ handler */ DEV_IRQ(pmtt_irq); #endif }}} == The private header == Here we can define the internal constants, like the header offsets and the register meanings. All the internally-used structures may also be defined here. Driver instances may have an internal state, we'll define a `struct pmtt_state_s` containing the current pending request. {{{ #ifndef DRIVER_TTY_PMTT_PRIVATE_H #define DRIVER_TTY_PMTT_PRIVATE_H /* Register offsets */ #define PMTT_TXR 0 #define PMTT_RXR 4 #define PMTT_STR 8 #define PMTT_ITR 12 /* Status register bits */ #define PMTT_STR_RXREADY 1 #define PMTT_STR_TXREADY 2 struct pmtt_state_s { struct dev_char_rq_s *current_request; }; #endif }}} == The implementation file == The drivers should be non-blocking and return as soon as possible. A callback (defined in the request structure) has to be called, either from the IRQ handler or the request function, telling the upper layer the about status of the request. Moreover, this driver relies on the ICU driver class in order to receive interrupts. First we have common header and declarations: {{{ #include "pmtt.h" #include "pmtt-private.h" #include #include #include #include #include #include #include }}} Let's begin with the device constructor. Here we have to: * Fill the device structure with a static and read-only pointer-to-function table, * Allocate internal state for the driver and put it in the `drv_pv` field of the device, * Register to the interrupt controller unit, * Initialize the peritheral. The function table is: {{{ static const struct driver_s pmtt_drv = { .class = device_class_char, .f_init = pmtt_init, .f_cleanup = pmtt_cleanup, .f_irq = pmtt_irq, .f.chr = { .f_request = pmtt_request, } }; }}} When a device is created, the calling code must fill the device structure `addr[]`, `icudev` and `irq` fields. The `addr[]` field may be used anytime, we'll use it later. The driver is responsible for registering to the ICU. {{{ DEV_INIT(pmtt_init) { struct pmtt_state_s *state = mem_alloc(sizeof(struct pmtt_state_s), MEM_SCOPE_SYS); if ( state == NULL ) return ENOMEM; state->current_request = NULL; dev->drv = &pmtt_drv; dev->drv_pv = state; DEV_ICU_BIND(dev->icudev, dev, dev->irq, pmtt_irq); return 0; } }}} The destructor is quite straightforward: {{{ DEV_CLEANUP(pmtt_cleanup) { struct pmtt_context_s *pv = dev->drv_pv; DEV_ICU_UNBIND(dev->icudev, dev, dev->irq); mem_free(pv); } }}} For character devices, the upper layer may decide to continue with the request or not, thus the callback has to be called any time the status changes. See [source:trunk/mutekh/drivers/include/device/char.h] for complete reference. For the sake of simplicity, this driver won't be able to handle concurrent requests. Therefore we'll return directly from handler if there is already a pending request. {{{ DEVCHAR_REQUEST(pmtt_request) { struct pmtt_context_s *pv = dev->drv_pv; if (rq->size == 0) { rq->error = 0; rq->callback(dev, rq, 0); } if (pv->current_request != NULL) { rq->error = EBUSY; rq->callback(dev, rq, 0); } pv->current_request = rq; switch (rq->type) { case DEV_CHAR_READ: // Enable RX irq cpu_mem_write_32(dev->addr[0] + PMTT_ITR, PMTT_RXREADY); break; case DEV_CHAR_WRITE: // Enable TX irq cpu_mem_write_32(dev->addr[0] + PMTT_ITR, PMTT_TXREADY); break; } } }}} Then we can define the IRQ handler, where all the device logic takes place: {{{ DEV_IRQ(pmtt_irq) { struct pmtt_context_s *pv = dev->drv_pv; struct dev_char_rq_s *rq = pv->current_request; size_t len = 0; switch (rq->type) { case DEV_CHAR_READ: while ( rq->size && (cpu_mem_read_32(dev->addr[0] + PMTT_STR) & PMTT_RXREADY) ) { *(rq->data) = cpu_mem_read_32(dev->addr[0] + PMTT_RXR); rq->size--; rq->data++; len++; } break; case DEV_CHAR_WRITE: while ( rq->size && (cpu_mem_read_32(dev->addr[0] + PMTT_STR) & PMTT_TXREADY) ) { cpu_mem_read_32(dev->addr[0] + PMTT_TXR, *(rq->data)); rq->size--; rq->data++; len++; } break; } // At last, we can consider the request as finished if the callback says so, // or if the size is 0. if ( rq->callback(dev, rq, size) || rq->size == 0 ) { pv->current_request = NULL; cpu_mem_write_32(dev->addr[0] + PMTT_ITR, 0); } // Tell the ICU we processed the IRQ. return 1; } }}} = Referencing the directory in the upper level directory = There is a Makefile in `mutekh/device/driver/char`, we have to edit it and add an reference to the `pmtt` subdirectory if the driver is enabled. We can add the following lines: {{{ ifeq ($(CONFIG_DRIVER_CHAR_PMTT), defined) subdirs += pmtt endif }}} = Possible improvements = As an exercise to the reader, this driver could be improved to: * buffer the incoming characters even if no request is pending, * handle concurrent requests, * correctly handle the endianness of the memory-mapped registers, * correctly lock the device to avoid race-conditions when multiple processors access the same device, * handle memory mapping of registers on MMU-enabled platforms.