====== gPXE Driver API Documentation ======
A gPXE network driver may incorporate elements of the following:
* [[#gpxe_pci_device_driver_api|gPXE PCI Device Driver API]]
* [[#gpxe_network_driver_api|gPXE Network Driver API]]
* [[#non-volatile_storage_api|Non-Volatile Storage API]]
Note the [[:dev:netdriverapi|previous driver model]] of Etherboot is deprecated.
Existing Etherboot PCI drivers are temporarily supported via the compatibility layer in src/drivers/net/legacy.c
Drivers currently conforming to the gPXE Network Driver API are:
* 3c90x
* ath5k
* atl1e
* b44
* e1000
* etherfabric
* mtnic
* natsemi
* phantom
* pnic
* r8169
* rtl8139
* rtl818x
* sis190
* sky2
===== gPXE PCI Device Driver API =====
A PCI driver provides its API routines to gPXE via a ''struct pci_driver''. For example, in natsemi.c:
struct pci_driver natsemi_driver __pci_driver = {
.ids = natsemi_nics,
.id_count = (sizeof (natsemi_nics) / sizeof (natsemi_nics[0])),
.probe = natsemi_probe,
.remove = natsemi_remove,
};
The ''.ids'' and ''.id_count'' members list the vendor & device IDs of supported devices.
The functions natsemi_probe & natsemi_remove are driver implementations of the required PCI device driver API functions:
* ''[[#probe|static int probe ( struct pci_device* , const struct pci_device_id* )]]''
* ''[[#remove|static void remove ( struct pci_device* )]]''
==== probe ====
''static int probe ( struct pci_device* , const struct pci_device_id* )''\\
This function is called [[:soc:2008:mdeck:notes:initialization|first]] to initialize the card. Here a typical network driver will:
- Allocate a ''struct net_device'' with associated private data using ''alloc_etherdev()''.
- Associate the driver functions with the ''net_device'' via ''netdev_init()''.
- Associate the ''net_device'' with the ''pci_device'' via ''pci_set_drvdata()''.
- Initialize private data.
- Ensure busmastering is enabled and check pci latency with ''adjust_pci_device()''.
- Reset the device.
- Initialize [[#EEPROM|EEPROM]].
- Read the MAC address from EEPROM.
- Check the link state and report ''netdev_link_up()'' if connected. Many drivers don't yet handle the link state and simply assume the link is up.
- Name the device and add it to the list of network devices via ''register_netdev()''.
- Possibly setup a non-volatile stored options block with ''nvo_init()'' & ''register_nvo()''.
==== remove ====
''static void remove ( struct pci_device* )''\\
This function is called last to remove the device. A typical driver will:
- Call ''unregister_nvo()'' for any registered non-volatile stored options.
- Call ''iounmap()'' for any addresses previously mapped with ''ioremap()''.
- Call ''unregister_netdev()'' for the device previously registered with ''register_netdev()''
- Reset the device.
- Dissociate driver functions from ''net_device'' via ''netdev_nullify()''.
- Decrement reference count of ''net_device'' with ''netdev_put()''.
===== gPXE Network Driver API =====
A network driver in gPXE provides its API routines to the system via a ''struct net_device_operations'' during the initial ''probe()'' call
(see [[:soc:2008:mdeck:notes:initialization|initialization of a network driver]]). For example, in natsemi.c:
static struct net_device_operations natsemi_operations = {
.open = natsemi_open,
.close = natsemi_close,
.transmit = natsemi_transmit,
.poll = natsemi_poll,
.irq = natsemi_irq,
};
Here, natsemi_open/close/etc are driver implementations of the required network driver API functions:
* ''[[#close|static void close ( struct net_device* )]]''
* ''[[#open|static int open ( struct net_device* )]]''
* ''[[#transmit|static int transmit ( struct net_device*, struct io_buffer* )]]''
* ''[[#poll|static void poll ( struct net_device* )]]''
* ''[[#irq|static void irq ( struct net_device*, int enable )]]''
==== close ====
''static void close ( struct net_device* )''\\
This function is called if the device is open in ''autoboot()'' during [[:soc:2008:mdeck:notes:initialization|initialization]], after a failed or successful attempt to boot the network device. In this routine, a typical driver might:
- Acknowledge interrupts.
- Disable irq, receives.
- Reset the device.
- Free any used resources (e.g. rx/tx rings, dma buffers, etc).
==== open ====
''static int open ( struct net_device* )''\\
This function is first called in ''netboot()'' when attempting a device boot during [[:soc:2008:mdeck:notes:initialization|initialization]], after ''close()'' of any previous device attempt is called. A driver would:
- Program MAC address to device.
- Setup TX & RX rings.
- Perform other configuration (e.g. filters, bursts, interrupts).
- Enable RX and TX.
==== transmit ====
''static int transmit ( struct net_device*, struct io_buffer* )''\\
A data transmission is actuated with this routine. A typical driver might:
- Check for tx overflow.
- Save buffer pointer for later tx completion reference.
- Pad & align packet if necessary.
- Add packet to transmit ring.
- Possibly ensure transmit is on.
==== poll ====
''static void poll ( struct net_device* )''\\
This function is called periodically by the network stack to process tx completions and rx packets. A typical driver would:
- Acknowledge interrupts.
- Check hardware and feed tx completions to ''netdev_tx_complete()'' or ''netdev_tx_complete_err()''.
- Add good received packets to receive queue with ''netdev_rx()'', or report corrupted packets to ''netdev_rx_err()''
- Check link state occasionally, and report changes with ''netdev_link_up()'' or ''netdev_link_down()''
==== irq ====
''static void irq ( struct net_device*, int enable )''\\
In this function, a typical driver will:
- Enable interrupts if the int parameter is non-zero
Note the //force interrupt// behavior from Etherboot is deprecated.
===== Non-Volatile Storage API =====
The nvs API may be used to access non-volatile storage that conforms to a number of supported SPI variants.
* To initialize nvs support:
- The read_bit() and write_bit() function pointers are stored in a ''struct spi_bit_basher''.
- The mode & endianness are also initialized.
- ''init_spi_bit_basher()'' is called.
- A ''struct spi_device'' is initialized (EEPROM-model dependent).
- Bus is copied: ''spidev.bus = &spibb.bus''
- If non-volatile options will be utilized:
- Call ''nvo_init(&nvob, &spidev.nvs, nvof, ..)'' where ''nvof'' is an array of ''struct nvo_fragment''s that specify the usable regions. This will also initialize a settings block.
- Else, if non-volatile options will //not// be used:
- Initialize a ''struct nvo_block'' with ''nvob.nvs = &spidev.nvs''
- Assign usable regions via ''nvob.fragments = nvof'' where ''nvof'' is an array of ''struct nvo_fragment''s that specify the usable regions.
* Use ''nvs_read()'' to perform a serial read @ a specific address.
* Use ''nvs_write()'' to perform a serial write @ a specific address.