Today I want to chat about what it takes to support a new Ethernet chip for download and KITL debugging on an x86 PC-based platform. We'll start by talking about how Ethernet drivers are represented in the x86 KITL structure, then we'll walk through (in a detailed, step-by-step fashion) adding a new driver to your bootloader and OS image. This article is valid for both CE5.0 and CE6.0.
This articles assumes you have some knowledge of what KITL (Kernel Independent Transport Layer) is, as well as basic understanding of what a bootloader does.
First, let's take a look at the architecture of x86 KITL. At the lowest level for our discussion, there is the driver code that supports our NIC (network interface card). This code implements the functions in the OAL_KITL_ETH_DRIVER structure, as described in platform\common\src\inc\oal_kitl.h. Things like GetFrame, SendFrame, Init, and InitDMABuffer will all be implemented here. There are working samples of this code in platform\common\src\common\ethdrv.
Because x86 supports a PCI bus, there may be multiple supported chips; the CEPC platform, for example, supports the RTL8139 chip as well as NE2000 compatible chips, among others. Each NIC we support will have a separate driver library that implements these functions. The OAL_KITL_ETH_DRIVER structure that represents our NIC driver plugs into a larger structure to allow us to support multiple drivers. This larger structure lists all of the drivers the platform supports. It is found in platform\common\src\x86\common\kitl\kitldrv_x86.c (1), and its type is SUPPORTED_NIC.
If we look more closely at the SUPPORTED_NIC structure we'll see several fields that distinguish one NIC driver from another. We can see the code in platform\common\src\x86\inc\x86kitl.h:
// Ethernet debug controller vendor and PCI information.
typedef struct _SUPPORTED_NIC // NIC vendor ID
USHORT wVenId; // PCI Vendor ID
USHORT wDevId; // PCI Device ID
DWORD dwUpperMAC; // 1st 3 bytes of mac address
UCHAR Type; // adapter type
UCHAR szAbbrev; // Vendor name abbreviation
const OAL_KITL_ETH_DRIVER *pDriver; // corresponding driver
} SUPPORTED_NIC, *PSUPPORTED_NIC;
The comments are pretty self-explanatory. The only one that's a little ambiguous is the UCHAR Type, which is just a CE-specific type that you can pick from the list in public\common\oak\inc\halether.h - EDBG_ADAPTER_NE2000, for example. The wVenId and Type fields in the SUPPORTED_NIC structure will identify the NIC to the bootloader and KITL as supported.
How it Fits Together
The SUPPORTED_NIC structure is compiled into oal_kitl_x86.lib, which our bootloader and KITL will link with. The bootloader and KITL will also link with static libraries for each NIC driver, such as rtl8139dbg.lib, ne2kdbg.lib. You can see examples in the SOURCES files in platform\CEPC\src\bootloader\eboot and platform\CEPC\src\kitl (2).
With all of this in place, we'll run the bootloader at device boot time and it will enumerate the devices on the PCI bus. It will read the PCI Config space and find network-class devices that have a PCI Vendor ID that matches one in the SUPPORTED_NIC list. If it can't find such a device, it will check the Adapter Type in the SUPPORTED_NIC list and match it against the default adapter type that's compiled into the bootloader. Once it finds a match, it will use the driver to download the OS image and then begin execution at the OS level.
Once downloaded, the OS will perform some basic initialization. Then it will go through the same matching process to discover a supported network card for KITL. If it finds a match, it will use that driver and NIC for the KITL connection.
Now that we understand how it all works, let's consider what is needed to add our own driver to this structure. We'll use CE6.0 as a basis since the architecture is the same in 5.0 and 6.0; see the footnotes for filenames / paths that have changed slightly from CE5.0.
Our overall approach will be to first create a driver library that supports the new NIC. Then we will create an x86 KITL library that includes this driver in the list.
We can use one the existing drivers from platform\common\src\common\ethdrv as a baseline for creating our driver. We’ll copy this driver into our BSP and then modify it there.
Step 1) Copy the platform\common\src\common\ethdrv\rtl8139\... to a directory in your own platform (for example platform\MyBSP\src\kitl\ethdrv\MyNIC).
Add your new directory to the DIRS file in the parent directory so it gets compiled when we build. In this new directory, change the filename of rtl8139.c to MyNIC.c. Open the SOURCES file and change the TARGETNAME to bsp_ethdrv_mynic, and change the SOURCES target to MyNIC.c.
Step 2) Replace the copied driver functions with functions that support your NIC.
This is the hardware-specific code that initializes your NIC, allows it to send and receive frames, etc. Note that the function names will change but the signatures should match the OAL_KITL_ETH_DRIVER structure. Once we have the code written, we’ll create a header with some prototypes so the functions can be referenced by the bootloader and KITL.
Step 3) In your platform\MyBSP\src\inc directory, make a header file, MyNIC.h that prototypes the MyNIC.c functions.
You can copy the RTL8139 prototypes from platform\common\src\inc\oal_ethdrv.h, and then modify them to match your MyNIC.c function names.
The next hurdle we need to get over is the fact that our driver list is compiled and linked in an "off-limits" directory. We can't modify the platform\common implementation for a number of reasons - if there is ever a QFE in this code, our modifications would conflict with it. Secondly, if we add our driver to the common list, the driver will be expected by all x86 BSPs that use the common library. So, we need to take the common list and make it our own. The easiest way to do this is to simply clone it.
Step 4) Copy platform\common\src\x86\kitl\... to a directory in your own platform (for example platform\MyBSP\src\kitl\x86kitllib).
Add your new directory to the DIRS file in the parent directory so it gets compiled when we build. Now that we have a copy of the supported NIC code, we need to add our driver to the list. We can also remove drivers that we don't want to support, thus reducing the size of our bootloader and OS. Before we can add our driver, though, we need to define an OAL_KITL_ETH_DRIVER structure that describes it.
Step 5) In your new kitldrv_x86.c, define an OAL_KITL_ETH_DRIVER structure for your NIC using the prototypes from your MyNIC.h file.
Step 6) Edit the SUPPORTED_NIC structure in kitldrv_x86.c, adding our driver to the list and removing any unwanted drivers.
If you don't know the PCI Vendor ID or Device ID of your NIC, you can boot your BSP without the driver in the list and examine the serial debug messages from the bootloader.
Now our NIC is in the supported list. We still need to spruce up our bootloader and KITL SOURCES files so that we're linking with our custom library as opposed to the original in platform\common.
Step 7) Edit the SOURCES file in .\base and .\baseboot (3) subdirectories of our cloned directory. Change the TARGETNAMEs from oal_kitl_x86.lib[s] to different names, such as mybsp_kitl_x86.lib and mybsp_kitl_x86_boot.lib.
Step 8) Edit the SOURCES files in your bootloader directory and kitl (4), and add the mybsp_kitl_x86_boot and mybsp_kitl_x86 libraries, respectively, to the TARGETLIBS for each SOURCES file. Note that the location of these libraries is going to be in $(_TARGETPLATROOT)\lib instead of $(_PLATCOMMONLIB).
Now we’ve pointed our bootloader and KITL implementations to link with our new driver list. The last step is to add the driver library that supports our NIC.
Step 9) Edit the SOURCES files in your bootloader directory and kitl (4), and add bsp_ethdrv_myNIC.lib to the TARGETLIBS for each SOURCES file.
Now you can rebuild your BSP and the resultant bootloader and OS image will support your new NIC!
I think you might agree that this architecture isn't the most flexible; that's something Microsoft will look at for future versions of CE.
(1) [In CE5.0, the filename is just kitldrv.c,]
(2) [In CE5.0, the driver libraries are linked with the OAL and the kernel in platform\CEPC\src\kernel\kernkitl]
(3) [In CE5.0, there are no subdirectories, so you only need to change a single SOURCES file.]
(4) [In CE5.0, this would be the kernkitl directory]