Skip to content

Example device driver

Maxime Vincent edited this page Jan 28, 2016 · 8 revisions

Intro

We will be showing how to write a very simple Ethernet device driver.

Before you start, please read the page on the architecture of device drivers first:

[Device Drivers] (Device-Drivers)

External interface

A device driver exposes at minimum one API call to the user: The create function.

The amount of parameters may vary, depending on the kind of driver you are writing.

struct pico_device *pico_eth_create(char *name, uint8_t *mac);

A destroy function is optional, as explained in the [Device Drivers] (Device-Drivers) architecture page.

Device initialization

In the create function you should:

  1. Create a new device struct
  2. Initialize the hardware
  3. Attach function your drivers' function pointers to the picoTCP device
  4. Register the device in picoTCP
  5. Return a pointer to the device struct

1. Create a new device struct

picoTCP provides a pico_device struct, that should be allocated, and registered. It can be extended (wrapped in your own struct) if needed.

struct pico_device* eth_dev = PICO_ZALLOC(sizeof(struct pico_device));
if(!eth_dev) {
	return NULL;
}

2. Initialize the hardware

This is device-specific. It could be a call to some HAL or BSP provided by the board manufacturer. E.g.:

BSP_ethernet_init(ETH_BASE, ETH_OPTION_1 | ETH_OPTION_2);
BSP_ethernet_set_mac(ETH_BASE, mac);
BSP_ethernet_enable(ETH_BASE);

3. Attach function pointers

A device driver needs at least a function for sending, and a function for receiving packets. In this case we use the polling option for receiving packet. This means we must implement a send and poll function (implementation below) and attach them to the right function pointers:

eth_dev->send = driver_eth_send;
eth_dev->poll = driver_eth_poll;

4. Register the device in picoTCP

Once initialization went fine, you should register the new device in picoTCP.

if( 0 != pico_device_init((struct pico_device *)eth_dev, name, mac)) {
    dbg ("Device init failed.\n");
    PICO_FREE(eth_dev);
    return NULL;
}

5. Return a pointer to the device struct

If initialization fails, return a NULL pointer; otherwise, return a pointer to the newly allocated device struct.

return eth_dev;

###The complete pico_eth_create function

struct pico_device *pico_eth_create(const char *name, const uint8_t *mac)
{
    /* Create device struct */
    struct pico_device* eth_dev = PICO_ZALLOC(sizeof(struct pico_device));
    if(!eth_dev) {
        return NULL;
    }

    /* Initialize hardware */
    BSP_ethernet_init(ETH_BASE, ETH_OPTION_1 | ETH_OPTION_2);
    BSP_ethernet_set_mac(ETH_BASE, mac);
    BSP_ethernet_enable(ETH_BASE);

    /* Attach function pointers */
    eth_dev->send = driver_eth_send;
    eth_dev->poll = driver_eth_poll;

    /* Register the device in picoTCP */
    if( 0 != pico_device_init((struct pico_device *)dev_eth, name, mac)) {
        dbg("Device init failed.\n");
        PICO_FREE(stellaris);
        return NULL;
    }

    /* Return a pointer to the device struct */ 
    return (struct pico_device *)dev_eth;
}

Sending and receiving

The previous function initialized the driver, and you are almost ready to go. Two more functions must be implemented:

  • Sending packets
  • Receiving packets

Sending packets

For sending, there is only one possibility: The stack calls your eth_dev->send function, whenever a packet must be put on the network. You should take a copy of this packet, because the stack frees it immediately after the send() function returns.

static int pico_eth_send(struct pico_device *dev, void *buf, int len)
{
    int retval = BSP_ethernet_send_packet(ETH_BASE, buf, len);
    /* send function must return amount of bytes put on the network - no negative values! */
    if(retval < 0)
        return 0;
    return retval;
}

Receiving packets

For receiving, there are two possibilities: Polling and asynchronous using an interrupt. (See [Device Drivers] (Device-Drivers) architecture page). Polling is the simplest case, which we demonstrate here.

Then, there are more possibilities:

  1. Copy the frame from the device driver's buffers into a newly allocated frame in picoTCP
  2. Do not copy the frame, but rather pass a reference to it; free it later, when the stack does not need it anymore. Technique called "zerocopy".

We demonstrate option #1 here.

The stack will poll every tick for new packets using eth_dev->poll.

static int pico_eth_poll(struct pico_device *dev, int loop_score)
{
    uint8_t *buf = NULL;
    uint32_t len = 0;

    while (loop_score > 0);
        if (!BSP_ethernet_packet_available(ETH_BASE)) {
            break;
        }
        len = BSP_ethernet_packet_get(ETH_BASE, &buf);
        if (len == 0) {
            break;
        }
        pico_stack_recv(dev, buf, len); /* this will copy the frame into the stack */
        loop_score--;
    }

    /* return (original_loop_score - amount_of_packets_received) */
    return loop_score;
}

That's it

This is all you need to implement a basic device driver.

For more possibilities and more information about the specifics, see the [Device Drivers] (Device-Drivers) architecture page.

Clone this wiki locally