-
Notifications
You must be signed in to change notification settings - Fork 17
Example device driver
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] (Device-Drivers)
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.
In the create function you should:
- Create a new device struct
- Initialize the hardware
- Attach function your drivers' function pointers to the picoTCP device
- Register the device in picoTCP
- Return a pointer to the 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;
}
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);
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;
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;
}
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;
}
The previous function initialized the driver, and you are almost ready to go. Two more functions must be implemented:
- Sending packets
- Receiving 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;
}
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:
- Copy the frame from the device driver's buffers into a newly allocated frame in picoTCP
- 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;
}
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.
Getting Started
- Setting up the environment
- Testing
- Configuring and compiling
- Running picoTCP on Linux - Deprecated (see setting up)
- Running picoTCP on Windows
Porting
- Build process explained
- Porting the build to another compiler or IDE
- Porting picoTCP to your favorite embedded target
- Porting picoTCP to your favorite Operating System
- Example device driver
Development