Showing posts with label Device drivers. Show all posts
Showing posts with label Device drivers. Show all posts

Saturday, 3 December 2016

Network device driver

Network device driver
•       Architecture



sk_buff structure
•       The struct sk_buff is the structure representing a network packet
•       Designed to easily support encapsulation/decapsulation of data through the protocol layers
•       In addition to the data itself, an sk_buff maintains
–      head, the start of the packet
–      data, the start of the packet payload
–      tail, the end of the packet payload
–      end, the end of the packet
–      len, the amount of data of the packet
•       These fields are updated when the packet goes through the protocol layers

Allocating a skb
•       Function dev_alloc_skb() allows to allocate an SKB
•       Can be called from an interrupt handler. Usually the case on reception.
•       On Ethernet, the size allocated is usually the length of the packet + 2, so that the IP header is word-aligned(the Ethernet header is 14 bytes)
•       skb = dev_alloc_skb(length + NET_IP_ALIGN);



Reserving space in a skb
•       Need to skip NET_IP_ALIGN bytes at the beginning of the SKB
•       Done with skb_reserve()
•       skb_reserve(skb, NET_IP_ALIGN);



Copy the received data
•       The packet payload must be copied from the DMA buffer to the SKB, using
•       static inline void skb_copy_to_linear_data(struct sk_buff *skb, const void *from, const unsigned int len);
•       static inline void skb_copy_to_linear_data_offset(struct sk_buff *skb, const int offset, const void *from, const unsigned int len);
                                skb_copy_to_linear_data(skb, dmabuffer, length);



Update pointers in skb
•       skb_put() is used to update the SKB pointers after copying the payload
•       skb_put(skb, length);



Struct net_device
•       This structure represents a single network interface
•       Allocation takes place with alloc_etherdev()
–      The size of private data must be passed as argument. The pointer to these private data can be read in
                net_device->priv
–      alloc_etherdev() is a specialization of alloc_netdev() for Ethernet interfaces
•       Registration with register_netdev()
•       Unregistration with unregister_netdev()
•       Liberation with free_netdev()

Struct net_device_ops
•       The methods of a network interface. The most important ones:
–      ndo_open(), called when the network interface is up'ed
–      ndo_close(), called when the network interface is down'ed
–      ndo_start_xmit(), to start the transmission of a packet
•       And others:
–      ndo_get_stats(), to get statistics
–      ndo_do_ioctl(), to implement device specific operations
–      ndo_set_rx_mode(), to select promiscuous, multicast, etc.
–      ndo_set_mac_address(), to set the MAC address
–      ndo_set_multicast_list(), to set multicast filters
•       Set the netdev_ops field in the struct net_device structure to point to the struct net_device_ops structure.

Utility functions
•       netif_start_queue()
–      Tells the kernel that the driver is ready to send packets
•       netif_stop_queue()
–      Tells the kernel to stop sending packets. Useful at driver cleanup of course, but also when all transmission buffers are full.
•       netif_queue_stopped()
–      Tells whether the queue is currently stopped or not
•       netif_wake_queue()
–      Wakeup a queue after a netif_stop_queue(). The kernel will resume sending packets

Transmission
•                       The driver implements the ndo_start_xmit() operation
•       The kernel calls this operation with a SKB as argument
•       The driver sets up DMA buffers and other hardware-dependent mechanisms and starts the transmission
–      Depending on the number of free DMA buffers available, the driver can also stop the queue with netif_stop_queue()
•       When the packet has been sent, an interrupt is raised. The driver is responsible for
–      Acknowledging the interrupt
–      Freeing the used DMA buffers
–      Freeing the SKB with dev_kfree_skb_irq()
–      If the queue was stopped, start it again
•       Returns NETDEV_TX_OK or NETDEV_TX_BUSY

Reception: original mode
•       Reception is notified by an interrupt. The interrupt handler should
–      Allocate an SKB with dev_alloc_skb()
–      Reserve the 2 bytes offset with skb_reserve()
–      Copy the packet data from the DMA buffers to the SKB
•       skb_copy_to_linear_data() or
•       skb_copy_to_linear_data_offset()
–      Update the SKB pointers with skb_put()
–      Update the skb->protocol field with eth_type_trans(skb,netdevice)
–      Give the SKB to the kernel network stack with netif_rx(skb)

Reception: NAPI mode
•       The original mode is nice and simple, but when the network traffic is high, the interrupt rate is high. The NAPI mode allows to switch to polled mode when the interrupt rate is too high.
•       In the network interface private structure, add a struct napi_struct
•       At driver initialization, register the NAPI poll operation:
–      netif_napi_add(dev, &bp->napi, macb_poll, 64);
–      dev is the network interface
–      &bp->napi is the struct napi_struct
–      macb_poll is the NAPI poll operation     
–      64 is the «weight» that represents the importance of the network interface. It limits the number of packets each interface can feed to the networking core in each polling cycle. If this quota is not met, the driver will return back to interrupt mode. Don't send this quota to a value greater than the number of packets the interface can store.
•       In the interrupt handler, when a packet has been received:
              if (napi_schedule_prep(&bp->napi)) {
              /* Disable reception interrupts */
              __napi_schedule(& bp->napi);
              }
–      The kernel will call our poll() operation regularly
•       The poll() operation has the following prototype
–      static int macb_poll(struct napi_struct *napi, int budget)
•       It must receive at most budget packets and push them to the network stack using netif_receive_skb().
•       If less than budget packets have been received, switch back to interrupt mode using napi_complete(& bp->napi) and re-enable interrupts
•       Must return the number of packets received

ethtool
•       ethtool is a userspace tool that allows to query low-level information from an Ethernet interface and to modify its configuration
•       On the kernel side, at the driver level, a struct ethtool_ops structure can be declared and connected to the struct net_device using the ethtool_ops field.
•       List of operations: get_settings(), set_settings(), get_drvinfo(), get_wol(), set_wol(), get_link(), get_eeprom(), set_eeprom(), get_tso(), set_tso(), get_flags(), set_flags(), etc.

statistics
•       The network driver is also responsible for keeping statistics up to date about the number of packets/bytes received/transmitted, the number of errors, of collisions, etc.
–      Collecting these information is left to the driver
•       To expose these information, the driver must implement a
–      get_stats() operation, with the following prototype
–      struct net_device_stats *foo_get_stats (struct net_device *dev);
•       The net_device_stats structure must be filled with the driver. It contains fields such as rx_packets, tx_packets, rx_bytes, tx_bytes, rx_errors, tx_errors, rx_dropped, tx_dropped, multicast, collisions, etc.

Quick references
•       Linux Device Drivers, chapter 17 (a little bit old)
•       Documentation/networking/netdevices.txt
•       Documentation/networking/phy.txt
•       include/linux/netdevice.h,
•       include/linux/ethtool.h, include/linux/phy.h,
•       include/linux/sk_buff.h
•       And of course, drivers/net/ for several examples of drivers
•       Driver code templates in the kernel sources:
•       drivers/usb/usbskeleton.c
•       drivers/net/isaskeleton.c
•       drivers/net/pciskeleton.c
•       drivers/pci/hotplug/pcihp_skeleton.c


Interrupt Handling Driver Development


•       Registering an interrupt handler
•       Defined in include/linux/interrupt.h
•       int request_irq(                                                Returns 0 if successful
unsigned int irq,                               Requested irq channel
irq_handler_t handler,                  Interrupt handler
unsigned long irq_flags,                Optional flags
const char * devname,                  Registered name
void *dev_id);                                   Pointer to some handler data, Cannot be NULL and must be unique for shared irqs!

•       void free_irq(unsigned int irq, void *dev_id);
•       dev_id cannot be NULL and must be unique for shared irqs. Otherwise, on a shared interrupt line, free_irq wouldn't know which handler to free
•       Main irq_flags bit values (can be combined, none is fine too)
•       IRQF_SHARED
•       The interrupt channel can be shared by several devices. Requires a hardware status register telling whether an IRQ was raised or not.
•       IRQF_SAMPLE_RANDOM
•       Use the IRQ arrival time to feed the kernel random number generator.

Interrupt handler constraints
•       No guarantee in which address space the system will be in when the interrupt occurs: can't transfer data to and from user space
•       Interrupt handler execution is managed by the CPU, not by the scheduler. Handlers can't run actions that may sleep, because there is nothing to resume their execution. In particular, need to allocate memory with GFP_ATOMIC.
•       Interrupt handlers are run with all interrupts disabled (since 2.6.36). Therefore, they have to complete their job quickly enough, to avoiding blocking interrupts for too long.

Information of installed handlers
/proc/interrupts                                                               PANDA Board (OMAP4 ARM)
                CPU0     CPU1
39:          4              0             GIC TWL6030-PIH
41:          0              0              GIC l3-dbg-irq
42:          0              0              GIC l3-app-irq
43:          0              0              GIC prcm
44:          20294 0                 GIC DMA
52:          0              0              GIC gpmc
53:          47590 0                 GIC SGX ISR
57:          6              0              GIC OMAP DISPC
69:          14           0              GIC gp timer
85:          0              0              GIC omapdss_dsi1
88:          300         0              GIC omap_i2c
89:          0              0              GIC omap_i2c
91:          6909 0 G               IC mmc1
93:          0              0              GIC omap_i2c
94:          0              0              GIC omap_i2c
102: 0    0              GIC serial idle

Interrupt handler prototype
•       irqreturn_t foo_interrupt (int irq, void *dev_id)
•       Arguments
–      irq, the IRQ number
–      dev_id, the opaque pointer passed at request_irq()
•       Return value
–      IRQ_HANDLED: recognized and handled interrupt
–      IRQ_NONE: not on a device managed by the module. Useful to share interrupt channels and/or report spurious interrupts to  the kernel.

Interrupt handler’s job
•       Acknowledge the interrupt to the device  (otherwise no more interrupts will be generated, or the interrupt will keep firing over and over again)
•       Read/write data from/to the device
•       Wake up any waiting process waiting for the completion of an operation, typically using wait queues
•       wake_up_interruptible(&module_queue);

Threaded interrupts
•       In 2.6.30, support for threaded interrupts has been added to the
•       Linux kernel
•       The interrupt handler is executed inside a thread.
•       Allows to block during the interrupt handler, which is often needed for I2C/SPI devices as the interrupt handler needs to communicate with them.
•       Allows to set a priority for the interrupt handler execution, which is useful for real-time usage of Linux
•       int request_threaded_irq(unsigned int irq, irq_handler_t handler, irq_handler_t thread_fn, unsigned long flags, const char *name, void *dev);
–      handler, “hard IRQ” handler
–      thread_fn, executed in a thread

Top half and bottom half processing
•       Splitting the execution of interrupt handlers in 2 parts
•       Top half
–      This is the real interrupt handler, which should complete as quickly as possible since all interrupts are disabled. If possible, take the data out of the device and schedule a bottom half to handle it.
•       Bottom half
•       Is the general Linux name for various mechanisms which allow to postpone the handling of interrupt-related work.
•       Implemented in Linux as softirqs, tasklets or workqueues.



SoftIRQs
•       Softirqs are a form of bottom half processing
•       The softirqs handlers are executed with all interrupts enabled, and a given softirq handler can run simultaneously on multiple CPUs
•       They are executed once all interrupt handlers have completed, before the kernel resumes scheduling processes, so sleeping is
•       not allowed.
•       The number of softirqs is fixed in the system, so softirqs are not directly used by drivers, but by complete kernel subsystems (network, etc.)
•       The list of softirqs is defined in include/linux/interrupt.h:
–       HI, TIMER, NET_TX, NET_RX, BLOCK, BLOCK_IOPOLL, TASKLET, SCHED, HRTIMER, RCU
•       The HI and TASKLET softirqs are used to execute tasklets

Tasklets
•       Tasklets are executed within the HI and TASKLET softirqs.
•       They are executed with all interrupts enabled, but a given tasklet is guaranteed to execute on a single CPU at a time.
•       A tasklet can be declared statically with the
–      DECLARE_TASKLET() macro or dynamically with the
–      tasklet_init() function.
•       A tasklet is simply implemented as a function.
•       Tasklets can easily be used by individual device drivers, as opposed to softirqs.
•       The interrupt handler can schedule the execution of a tasklet With
–      tasklet_schedule() to get it executed in the TASKLET softirq
–      tasklet_hi_schedule() to get it executed in the HI softirq (higher priority)

Work queues
•       Workqueues are a general mechanism for deferring work. It is not limited in usage to handling interrupts.
•       The function registered as workqueue is executed in a thread, which means :
–      All interrupts are enabled
–      Sleeping is allowed
•       A workqueue is registered with INIT_WORK and typically triggered with queue_work()
•       The complete API, in include/linux/workqueue.h provides many other possibilities (creating its own workqueue threads, etc.)

Examples
•       Softirq
•       Tasklet
•       Work queue

Summery
•       Device driver
–      When the device file is first opened, register an interrupt handler for the device's interrupt channel.
•       Interrupt handler
–      Called when an interrupt is raised.
•       Acknowledge the interrupt
•       If needed, schedule a tasklet taking care of handling data. Otherwise, wake up processes  waiting for the data.
•       Tasklet
–      Process the data
–      Wake up processes waiting for the data
–      Device driver    
–      When the device is no longer opened by any process, unregister the interrupt handler.