Changes between Initial Version and Version 1 of NewDriver


Ignore:
Timestamp:
Oct 7, 2009, 10:58:08 PM (15 years ago)
Author:
Nicolas Pouillon
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • NewDriver

    v1 v1  
     1
     2Adding a driver may be quite easy if the driver class already exists. If not, [NewDriverClass create a new driver class] and come back here.
     3
     4We'll go through this guide assuming we are adding a new character device, we'll call it `pmtt`.
     5
     6This device is an imaginary one with:
     7 * a transmit register `TXR`,
     8 * a receive register `RXR`,
     9 * a status register `STR`, telling whether we can receive,
     10 * an interrupt mask register `ITR`, masking send / receive interrupts.
     11
     12= Creating a new directory =
     13
     14We'll go to the correct directory in the source tree and create the directory for the device:
     15
     16{{{
     17$ cd mutekh/device/driver/char
     18$ mkdir pmtt
     19$ cd pmtt
     20}}}
     21
     22= Creating the files =
     23
     24Now we can create the files. There are usually 4 files for a device:
     25 * a `.config` for the BuildSystem,
     26 * a `.h` for declaring the public functions,
     27 * a `.c` implementing the actual driver,
     28 * a `-private.h` containing the private definitions for the driver.
     29
     30== The `.config` file ==
     31
     32Let's start with the `.config` file. It will contain:
     33{{{
     34%config CONFIG_DRIVER_CHAR_PMTT
     35desc Enable PMTT imaginary device
     36default undefined
     37provide CONFIG_DRIVER_TTY
     38%config end
     39}}}
     40
     41Here we declare this driver implements a TTY, i.e. a device with a send/receive interface.
     42
     43== The `.h` file ==
     44
     45Now we may define the external interface of our device.
     46
     47Interface function prototypes are defined in two headers:
     48 * `hexo/device.h` defines the class-agnostic functions: init, cleanup and irq,
     49 * `device/`''class''`.h` defines the class-specific functions. for character devices, there is only a `request` function to implement.
     50
     51In 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`.
     52{{{
     53#ifndef DRIVER_TTY_PMTT_H
     54#define DRIVER_TTY_PMTT_H
     55
     56#include <device/char.h>
     57#include <hexo/device.h>
     58
     59/* The device constructor & destructor */
     60DEV_INIT(pmtt_init);
     61DEV_CLEANUP(pmtt_cleanup);
     62
     63/* The request handler */
     64DEVCHAR_REQUEST(pmtt_request);
     65
     66/* The IRQ handler */
     67DEV_IRQ(pmtt_irq);
     68
     69#endif
     70}}}
     71
     72== The private header ==
     73
     74Here we can define the internal constants, like the header offsets and the register meanings.
     75
     76All 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.
     77
     78{{{
     79#ifndef DRIVER_TTY_PMTT_PRIVATE_H
     80#define DRIVER_TTY_PMTT_PRIVATE_H
     81
     82/* Register offsets */
     83#define PMTT_TXR 0
     84#define PMTT_RXR 1
     85#define PMTT_STR 2
     86#define PMTT_ITR 3
     87
     88/* Status register bits */
     89#define PMTT_STR_RXREADY 1
     90#define PMTT_STR_TXREADY 2
     91
     92struct pmtt_state_s
     93{
     94   struct dev_char_rq_s *current_request;
     95};
     96
     97#endif
     98}}}
     99
     100== The implementation file ==
     101
     102The 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.
     103
     104First we have common header and declarations:
     105
     106{{{
     107#include "pmtt.h"
     108#include "pmtt-private.h"
     109
     110#include <device/icu.h>
     111#include <hexo/types.h>
     112#include <hexo/device.h>
     113#include <device/driver.h>
     114#include <hexo/iospace.h>
     115#include <hexo/alloc.h>
     116#include <hexo/interrupt.h>
     117}}}
     118
     119Let's begin with the device constructor. Here we have to:
     120 * Fill the device structure with a static and read-only pointer-to-function table,
     121 * Allocate internal state for the driver and put it in the `drv_pv` field of the device,
     122 * Register to the interrupt controller unit,
     123 * Initialize the peritheral.
     124
     125The function table is:
     126{{{
     127static const struct driver_s pmtt_drv =
     128{
     129    .class = device_class_char,
     130    .f_init = pmtt_init,
     131    .f_cleanup = pmtt_cleanup,
     132    .f_irq = pmtt_irq,
     133    .f.chr = {
     134        .f_request = pmtt_request,
     135    }
     136};
     137}}}
     138
     139When a device is created, the calling code must fill the device structure `icudev` and `irq` fields.
     140The driver is responsible for registering to the ICU.
     141
     142{{{
     143DEV_INIT(pmtt_init)
     144{
     145   struct pmtt_state_s *state = mem_alloc(sizeof(struct pmtt_state_s), MEM_SCOPE_SYS);
     146
     147   if ( state == NULL )
     148     return ENOMEM;
     149
     150   state->current_request = NULL;
     151
     152   dev->drv = &pmtt_drv;
     153
     154   dev->drv_pv = state;
     155
     156   DEV_ICU_BIND(dev->icudev, dev, dev->irq, pmtt_irq);
     157
     158   return 0;
     159}
     160}}}
     161
     162The destructor is quite straightforward:
     163
     164{{{
     165DEV_CLEANUP(pmtt_cleanup)
     166{
     167   struct pmtt_context_s *pv = dev->drv_pv;
     168
     169   DEV_ICU_UNBIND(dev->icudev, dev, dev->irq);
     170
     171   mem_free(pv);
     172}
     173}}}
     174
     175For 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.
     176
     177For 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.
     178
     179{{{
     180DEVCHAR_REQUEST(pmtt_request)
     181{
     182   struct pmtt_context_s *pv = dev->drv_pv;
     183
     184   if (rq->size == 0) {
     185      rq->error = 0;
     186      rq->callback(dev, rq, 0);
     187   }
     188
     189   if (pv->current_request != NULL) {
     190      rq->error = EBUSY;
     191      rq->callback(dev, rq, 0);
     192   }
     193
     194   pv->current_request = rq;
     195
     196   switch (rq->type)
     197   {
     198   case DEV_CHAR_READ:
     199      // Enable RX irq
     200      cpu_mem_write_32(dev->addr[0] + PMTT_ITR, PMTT_RXREADY);
     201      break;
     202
     203   case DEV_CHAR_WRITE:
     204      // Enable TX irq
     205      cpu_mem_write_32(dev->addr[0] + PMTT_ITR, PMTT_TXREADY);
     206      break;
     207    }
     208}
     209}}}
     210
     211Then we can define the IRQ handler, where all the device logic takes place:
     212
     213{{{
     214DEV_IRQ(pmtt_irq)
     215{
     216   struct pmtt_context_s *pv = dev->drv_pv;
     217
     218   struct dev_char_rq_s *rq = pv->current_request;
     219
     220   size_t len = 0;
     221
     222   switch (rq->type)
     223   {
     224   case DEV_CHAR_READ:
     225      while ( rq->size && (cpu_mem_read_32(dev->addr[0] + PMTT_STR) & PMTT_RXREADY) ) {
     226         *(rq->data) = cpu_mem_read_32(dev->addr[0] + PMTT_RXR);
     227         rq->size--;
     228         rq->data++;
     229         len++;
     230      }
     231      break;
     232
     233   case DEV_CHAR_WRITE:
     234      while ( rq->size && (cpu_mem_read_32(dev->addr[0] + PMTT_STR) & PMTT_TXREADY) ) {
     235         cpu_mem_read_32(dev->addr[0] + PMTT_TXR, *(rq->data));
     236         rq->size--;
     237         rq->data++;
     238         len++;
     239      }
     240      break;
     241   }
     242
     243   // At last, we can consider the request as finished if the callback says so,
     244   // or if the size is 0.
     245   if ( rq->callback(dev, rq, size) || rq->size == 0 ) {
     246      pv->current_request = NULL;
     247      cpu_mem_write_32(dev->addr[0] + PMTT_ITR, 0);
     248   }
     249
     250   // Tell the ICU we processed the IRQ.
     251   return 1;
     252}
     253}}}
     254
     255= Possible improvements =
     256
     257As an exercise to the reader, this driver could be improved to:
     258 * buffer the incoming characters even if no request is pending,
     259 * handle concurrent requests,
     260 * correctly lock the device to avoid race-conditions when multiple processors access the same device.