Intel 8254x

From OSDev Wiki
Jump to: navigation, search

This page is under construction! This page or section is a work in progress and may thus be incomplete. Its content may be changed in the near future.

The Intel 8254x series is comprised of: 82546GB/EB, 82545GM/EM, 82544GC/EI, 82541(PI/GI/EI), 82541ER, 82547GI/EI, and 82540EP/EM Gigabit Ethernet Controllers.

Intel 82540EM-based card

Contents

Overview

Intel 8254x-based cards come in 32-/64-bit, 33/66 MHz PCI and PCI-X flavors. The Intel 82547GI(EI) connects to the motherboard via a Communications Streaming Architecture (CSA) port instead of a PCI/PCI-X bus. The 82541xx and 82540EP/EM controllers do not support the PCI-X bus.

They are all high-performance, Gigabit-capable controllers and range from 1 to 4 ethernet/fiber ports per controller.

The Intel 8254x series heavily utilizes task offloading. Each controller has an "offloading engine" for tasks such as TCP/UDP/IP checksum calculations, packet filtering, and packet segmentation.

  • Jumbo packets are supported.
  • Wake on LAN (WoL) is supported.
  • A four wire serial EEPROM interface as well as a generic EEPROM "read" interface is implemented within the configuration registers.
  • D0 and D3 power states are supported through ACPI.

Programming

Detection

Section 5.2 in the 8254x Software Developer's Manual lists the Vendor and Device ID's of the various device in the 8254x series. These are used to detect devices on the PCI bus by looking in the PCI Configuration Space registers.

The device will also fill in the PCI Base Address Registers (BAR). BAR0 will either be a 64-bit or 32-bit MMIO address (checked by testing bits 2:1 to see if it's 00b (32-bit) or 10b (64-bit)) that points to the device's base register space. BAR0 should always be used to interface with the device via MMIO as the BAR number never changes in different devices in the series.

There is also a BAR that will contain an I/O base address, this can be detected by looking at each BAR and testing bit 1. Documentation states this will be in either BAR2 or BAR4, but emulators may move it.

When using MMIO, reading/writing to/from registers is very straight-forward.

*(uint32_t *)(ioaddr + reg) = val; // writes "val" to an MMIO address
val = *(uint32_t *)(ioaddr + reg); // reads "val" from an MMIO address

When using IO, reading/writing to/from registers is a little more complicated as the IO address space for the 8254x is only 8 bytes wide. The register at offset 0x00 is the "IOADDR" window. The register at offset 0x04 is the "IODATA" window. IOADDR holds the IO address that the IODATA window operates on. So, basic operation is to set the IOADDR window and then the desired action using the IODATA window.

outl(ioaddr + 0x00, reg);  // set the IOADDR window
outl(ioaddr + 0x04, val);  // write the value to the IOADDR window which will end up in the register in IOADDR
inl(ioaddr + 0x04);        // read back the value

Initialization

The 8254x will be on an undefined state and as such it needs to be reset. The first thing that should be done is enabling bus mastering, memory and IO accesses from the PCI command register.

Then the NIC should be reset by setting CTRL.RST (0x4000000) bit in the CTRL (0x00000) register of the card.

EEPROM Reading

There are a few variants of the card with many differences, most notably the method to access the EEPROM and the Flash memory of the card. Here we will only describe methods applicable to cards that use the EEPROM method.

After that the EEPROM must be enabled in order to be able to read the MAC address of the NIC, this is done by setting the EECD.SK (0x01), EECD.CS (0x02) and EECD.DI (0x04) bits of the EECD (0x00010) register. This will allow software to perform reads to the EEPROM.

Before reading the EEPROM has a "lock-unlock" mechanism to prevent software-hardware collisions when reading from the EEPROM.

To lock the EEPROM the EECD.REQ (0x40) bit must be set in the EECD register. Then wait until the EECD.GNT (0x80) bit becomes set. Unlocking only requires to clear EECD.REQ.

To finally read the EEPROM first the kernel should AND the address to 12 (Applicable only to 82541x or 82547GI/EI cards) or 8 bits; then bit shift the desired address to 2 (Applicable only to 82541x or 82547GI/EI cards) or by 4. The kernel must OR it with the EECD.START (0x01) bit. Then finally write it to the EERD (0x00014) register.

The kernel should wait until the EEPROM read operation is finished by checking until EECD.DONE becomes clear. Then the kernel must read the EERD register, shift it to the right by 16 bits and truncate it to 16-bits.

After that the EERD.START bit must be cleared.

static uint16_t i8254x_read_eeprom(uint8_t addr) {
    uint32_t tmp;
    uint16_t data;
 
    if((le32_to_cpu(mmio_read_dword(dev_info.mmio.addr, I8254X_EECD)) & I8254X_EECD_EE_PRES) == 0) {
        kpanic("EEPROM present bit is not set for i8254x\n");
    }
 
    /* Tell the EEPROM to start reading */
    if(dev_info.version == I82547GI_EI
    || dev_info.version == I82541EI_A0
    || dev_info.version == I82541EI_B0
    || dev_info.version == I82541ER_C0
    || dev_info.version == I82541GI_B1
    || dev_info.version == I82541PI_C0) {
        /* Specification says that only 82541x devices and the
         * 82547GI/EI do 2-bit shift */
        tmp = ((uint32_t)addr & 0xfff) << 2;
    } else {
        tmp = ((uint32_t)addr & 0xff) << 8;
    }
    tmp |= I8254X_EERD_START;
    mmio_write_dword(dev_info.mmio.addr, I8254X_EERD, cpu_to_le32(tmp));
 
    /* Wait until the read is finished - then the DONE bit is cleared */
    timeout((le32_to_cpu(mmio_read_dword(dev_info.mmio.addr, I8254X_EERD)) & I8254X_EERD_DONE) == 0, 100);
 
    /* Obtain the data */
    data = (uint16_t)(le32_to_cpu(mmio_read_dword(dev_info.mmio.addr, I8254X_EERD)) >> 16);
 
    /* Tell EEPROM to stop reading */
    tmp = le32_to_cpu(mmio_read_dword(dev_info.mmio.addr, I8254X_EERD));
    tmp &= ~(uint32_t)I8254X_EERD_START;
    mmio_write_dword(dev_info.mmio.addr, I8254X_EERD, cpu_to_le32(tmp));
    return data;
}

When all data is finally read the kernel should unlock the EEPROM to let hardware access it.

Obtaining the MAC address

Obtaining the MAC is quite trivial and only requires reading the first 3-words of the EEPROM.

uint8_t dev_info.mac_addr[6];
 
/* Assumes a little-endian architecture */
i8254x_lock_eeprom();
*((uint16_t *)&dev_info.mac_addr[0]) = i8254x_read_eeprom(0x00);
*((uint16_t *)&dev_info.mac_addr[2]) = i8254x_read_eeprom(0x01);
*((uint16_t *)&dev_info.mac_addr[4]) = i8254x_read_eeprom(0x02);
i8254x_unlock_eeprom();

Emulation

  • VirtualBox (3.1 is all I can personally confirm) supports rather dodgy implementations of an Intel PRO/1000 MT Server (82545EM), Intel PRO/1000 MT Desktop (82540EM), and Intel PRO/1000 T Server (82543GC).
    • Bugs:
      • The EERD register is unimplemented (you *must* use the 4-wire access method if you want to read from the EEPROM). [01000101 - I had a patch committed to fix this. It will soon be mainstream]
  • VMWare Virtual Server 2 emulates/virtualizes an 82545EM-based card rather well.
  • QEMU (since 0.10.0) supports an 82540EM-based card and it seems to work OK. It is the default network card since 0.11.0.
    • Bugs:
      • QEMU does not properly handle the software reset operation (CTRL.RST) in builds prior to June 2009.
      • QEMU (version 4.2.1 tested) doesn't seem to support flash memory, instead shifting the IO Register Base Address up.
  • IIRC (needs confirmation) Microsoft's Hyper-V supports an 8254x-series card.

Documentation

Example driver

Personal tools
Namespaces
Variants
Actions
Navigation
About
Toolbox