Skip to content

Example device driver

GreaterFire edited this page Nov 5, 2018 · 8 revisions

Intro

We will be showing how to write a very simple Ethernet device driver. To keep things simple, we suppose you have access to a bunch of "board support package" or "hardware abstraction layer" that will do the actual interactions with the hardware. This way you can focus on the driver logic needed for picoTCP.

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

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 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(eth_dev, name, mac)) {
        dbg("Device init failed.\n");
        PICO_FREE(eth_dev);
        return NULL;
    }

    /* Return a pointer to the device struct */ 
    return eth_dev;
}

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 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 architecture page.

Clone this wiki locally