A network driver ‘framework’ We construct a ‘skeleton’ module showing just the essential...
-
date post
21-Dec-2015 -
Category
Documents
-
view
218 -
download
2
Transcript of A network driver ‘framework’ We construct a ‘skeleton’ module showing just the essential...
A network driver ‘framework’
We construct a ‘skeleton’ module showing just the essential pieces of a Linux network device driver
Overview
hardware
device driver module
Linux operating system
Kernel
networking subsystem
application program
standard runtime libraries
user space kernel space
Source-code layout
#include <linux/module.h>#include <linux/etherdevice.h>…typedef struct { /* driver’s private data */ } MY_DRIVERDATA;
char modname[ ] = “netframe”;struct net_device *netdev;
netframe.c
my_init my_exit The mandatory module- administration functions
my_open()
my_stop()
my_hard_start_xmit()
my_isr()
my_get_info()
The network driver’s “payload” functions
module_init()
• This function will execute when the driver is installed in the kernel (‘/sbin/insmod’)
• Its role is to allocate and partially initialize a ‘struct net_device’ object for our network interface controller (i.e., hardware device), then “register” that object with the kernel
• For ethernet NICs there exists a kernel helper-function that drivers can utilize
The ‘key’ statements…
typedef struct { /* the driver’s private data */ } MY_DRIVERDATA;
struct net_device *netdev; static int __init my_init( void ) {
netdev = alloc_etherdev( sizeof( MY_DRIVERDATA ) );if ( !netdev ) return –ENOMEM;
netdev->open = my_open;netdev->stop = my_stop;netdev->hard_start_xmit = my_hard_start_xmit;
return register_netdev( netdev );}
module_exit()
• This function will execute when the driver is removed from the kernel (‘/sbin/rmmod’)
• Its role is to “unregister” the ‘net_device’ structure and free the memory that was allocated during the module’s initialization
The ‘key’ statements…
struct net_device *netdev; static void __exit my_exit( void ) {
unregister_netdev( netdev );
free_netdev( netdev ); }
open()
• The kernel will call this function when the system administrator “configures” the NIC (e.g., with the ‘/sbin/ifconfig’ command) to assign an IP-address to the interface and and bring it UP
• Thus the role of ‘open()’ would be to reset the hardware to a known working state and initiate packet-queueing by the kernel
The ‘key’ statements…
int my_open( struct net_device *dev ) {
/* initialize any remaining ‘private’ data */
/* prepare the hardware for operation */
/* install an Interrupt Service Routine */
/* enable the NIC to generate interrupts */
netif_start_queue( netdev );return 0; //SUCCESS
}
stop()
• The kernel will call this function when the NIC is brought DOWN (i.e., to turn off its transmission and reception of packets)
• This could occur because of a command (such as ‘/sbin/ifconfig’) executed by the System Administrator, or because a user is removing the driver-module from the kernel (with the ‘/sbin/rmmod’ command)
The ‘key’ statements…
int my_stop( struct net_device *dev ) {
netif_stop_queue( netdev );
/* kill any previously scheduled ‘tasklets’ (or other deferred work) */
/* turn off the NIC’s transmit and receive engines */
/* disable the NIC’s ability to generate interrupts */
/* delete the NIC’s Interrupt Service Routine */
return 0; //SUCCESS }
hard_start_xmit()
• The kernel will call this function whenever it has data that it wants the NIC to transmit
• The kernel will supply the address for a socket-buffer (‘struct sk_buff’) that holds the packet-data that is to be transmitted
• So this function’s duties are: to initiate transmission, update relevant statistics, and then release that ‘sk_buff’ structure
The ‘key’ statements…
int my_hard_start_xmit( struct sk_buff *skb, struct net_device *dev ) {
/* code goes here to initiate transmission by the hardware */
dev->trans_start = jiffies;dev->stats.tx_packets += 1;dev->stats.tx_bytes += skb->len;
dev_kfree_skb( skb );return 0; //SUCCESS
}
What about reception?
• The NIC hardware receives data-packets asynchronously – not at a time of its own choosing – and we don’t want our system to be ‘stalled’ doing ‘busy-waiting’
• Thus an interrupt handler is normally used to detect and arrange for received packets to be validated and dispatched to upper layers in the kernel’s network subsystem
Simulating an interrupt
• Our network device-driver ‘framework’ was only designed for demonstration purposes; it does not work with any actual hardware
• But we can use a ‘software interrupt’ that will trigger the execution of our ISR
• To implement this scheme, we’ll need to employ an otherwise unused IRQ-number, along with its associated ‘Interrupt-ID’
Advanced Programmable Interrupt Controller
Multi-CORE CPU
CPU0
CPU1 I/O
APICLOCALAPIC
LOCALAPIC
●●●
IRQ0IRQ1IRQ2IRQ3
IRQ23
The I/O APIC component is programmable – its 24 inputs can be assigned to interrupt ID-numbers in the range 0x20..0xFF (lower numbers are reserved by Intel for the CPU’s exception-vectors)
The I/O-APIC’s 24 Redirection Table registers determine these assignments
• The I/O APIC in our classroom machines supports 24 Interrupt-Request input-lines
• Its 24 programmable registers determine how interrupt-signals get routed to CPUs
Two-dozen IRQs
Redirection-table
Redirection Table Entry
reserved
reserved interrupt
vectorID
L/P
STATUS
H/L
RIRR
E/L
MASK
extended destination
63 56 55 48 32
destination
31 16 15 14 13 12 11 10 9 8 7 0
delivery mode
000 = Fixed001 = Lowest Priority010 = SMI 011 = (reserved)100 = NMI101 = INIT110 = (reserved)111 = ExtINT
Trigger-Mode (1=Edge-triggered, 0=Level-triggered)
Remote IRR (for Level-Triggered only) 0 = Reset when EOI received from Local-APIC 1 = Set when Local-APICs accept Level-Interrupt sent by IO-APIC
Interrupt Input-pin Polarity (1=Active-High, 0=Active-Low)
Destination-Mode (1=Logical, 0=Physical)Delivery-Status (1=Pending, 0=Idle)
Our ‘ioapic.c’ module
• Last semester we created a module that will show us which IRQ-numbers are not currently being used by our system, and the Interrupt-IDs those IRQ-signals were assigned to by Linux during ‘startup’
Timeout for an in-class demonstration
my_isr()
• We created a “dummy” Interrupt Service Routine for our ‘netframe.c’ demo-module
#define IRQ 4 // temporarily unused (normally for serial-UART #define intID 0x49 // our I/O-APIC has assigned this ID to to IRQ 4
irqreturn_t my_isr( int irq, void *my_netdev_addr ) {
struct net_device *dev = (struct net_device *)my_netdev_addr;MY_DRIVERDATA *priv = dev->priv;
// we do processing of the received packet in our “bottom half”tasklet_schedule( &priv->my_rxtasklet );return IRQ_HANDLED;
}
Installing and removing an ISR
if ( request_irq( IRQ, my_isr, IRQF_SHARED, dev->name, dev ) < 0 )return –EBUSY;
IRQ’s signal-number
entry-point for interrupt-handleroption-flags name for display
ISR data-argument
free_irq( IRQ, dev );
This statement would go in the driver’s ‘open()’ function…
…and this statement would go in the driver’s ‘stop()’ function
Here ‘dev’ is the address of the interface’s ‘struct net_device’ object
Processing a received packet
• When the NIC notifies our driver that it has received a new ethernet-packet, our driver must allocate a socket-buffer structure for the received data, initialize the ‘sk_buff’ with that data and supporting parameters, then pass that socket-buffer upward to the kernel’s network subsystem for delivery to the appropriate application-program that is listening for it
The ‘key’ statements… void my_rxhandler( unsigned long data ) {
struct net_device *dev = (struct net_device *)data;struct sk_buff *skb;int rxbytes = 60; // just an artificial value here
skb = dev_alloc_skb( rxbytes + 2 );
skb->dev = dev;skb->protocol = eth_type_trans( skb, dev );skb->ip_summed = CHECKSUM_NONE;
dev->stats.rx_packets += 1;dev->stats.rx_bytes += rxbytes;
netif_rx( skb ); }
Triggering the interrupt…
• We allow a user to trigger execution of our interrupt-handler (for testing purposes), by reading from a pseudo-file that our driver creates during module-initialization, whose ‘get_info()’ function includes execution of a software-interrupt instruction: ‘int $0x49’
• This inline assembly language instruction is produced via the GNU ‘asm’ construct
Using the ‘asm-construct’
#define intID 0x49
asm(“ int %0 “ : : “i” (intID) );
assembly language opcode
parameter indicator
parameter type (“i” = immediate data)
parameter-value (symbolic)statement keyword
This example shows how a symbolic constant’s value, defined in the high-level C programming language using a ‘#define’ preprocessor directive, is able to be referenced by an “inline” assembly language statement within a C code-module
Testing our ‘framework’
• You can download, compile, and install our ‘netframe.c’ network driver module
• It doesn’t do anything with real hardware, but it does illustrate essential interactions of a network device driver with the Linux operating system’s networking subsystem
In-class exercise #1
• Use the ‘/sbin/ifconfig’ command to assign an IP-address to the ‘struct net_device’ object that our framework-module creates
• You can discover the interface’s name by using our earlier ‘netdevs.c’ module
• You should use a ‘private’ IP-address
• EXAMPLE (for station ‘hrn23501’): $ sudo /sbin/ifconfig eth1 192.168.86.1 up
In-class exercise #2
• Use ‘ifconfig’ to confirm the IP-address, the IRQ, and the interface’s status:
$ /sbin/ifconfig eth1
• Use ‘ifconfig’ to examine the interface’s statistics (packets transmitted/received)
In-class exercise #3
• Use the ‘cat’ command to simulate an interrupt from your device’s interface
• Verify that your interrupt-handler did get executed, by looking at the statistics, and by displaying the output of a pseudo-file Linux creates (named ‘/proc/interrupts’)
$ cat /proc/interrupts