The Firmware Hub
PC-compatible systems use a NOR flash chip called the Firmware Hub (FWH) to hold the BIOS. The FWH is not directly connected to the processor's address and data bus. Instead, it's interfaced via the Low Pin Count (LPC) bus, which is part of South Bridge chipsets. The connection diagram is shown in Figure 17.5.
Figure 17.5 The Firmware Hub on a PC-compatible system.
The MTD subsystem includes drivers to interface the processor with the FWH. FWHs are usually not compliant with the CFI specification. Instead, they conform to the JEDEC (Joint Electron Device Engineering Council) standard. To inform MTD about a yet unsupported JEDEC chip, add an entry to the jedec_table array in drivers/mtd/chips/jedec_probe.c with information such as the chip manufacturer ID and the command-set ID. Here is an example:
static const struct amd_flash_info jedec_table[] = { /* ... */ { .mfr_id = MANUFACTURER_ID, /* E.g.: MANUFACTURER_ST */ .dev_id = DEVICE_ID, /* E.g.: M50FW080 */ .name = "MYNAME", /* E.g.: "M50FW080" */ .uaddr = { [0] = MTD_UADDR_UNNECESSARY, }, .DevSize = SIZE_1MiB, /* E.g.: 1MB */ .CmdSet = CMDSET, /* Command-set to communicate with the flash chip e.g., P_ID_INTEL_EXT */ .NumEraseRegions = 1, /* One region */ .regions = { ERASEINFO (0x10000, 16),/* Sixteen 64K sectors */ } }, /* ... */ };
When you have your chip details imprinted in the jedec_table as shown here, MTD should recognize your flash, provided you have enabled the right kernel configuration options. The following configuration makes the kernel aware of an FWH that interfaces to the processor via an Intel ICH2 or ICH4 South Bridge chipset:
CONFIG_MTD=y Enable the MTD subsystem CONFIG_MTD_GEN_PROBE=y Common routines for chip probing CONFIG_MTD_JEDECPROBE=y JEDEC chip driver CONFIG_MTD_CFI_INTELEXT=y The command-set for communicating with the chip CONFIG_MTD_ICHXROM=y The map driver
CONFIG_MTD_JEDECPROBE enables the JEDEC MTD chip driver, and CONFIG_MTD_ICH2ROM adds the MTD map driver that maps the FWH to the processor's address space. In addition, you need to include the appropriate command-set implementation (for example, CONFIG_MTD_CFI_INTELEXT for Intel Extension commands).
After these modules have been loaded, you can talk to the FWH from user-space applications via device nodes exported by MTD. You can, for example, reprogram the BIOS from user space using a simple application, as shown in Listing 17.5. Be warned that incorrectly operating this program can corrupt the BIOS and render your system unbootable!
Listing 17.5 operates on the MTD char device associated with the FWH, which it assumes to be /dev/mtd/0. The program issues three MTD-specific ioctl commands:
- MEMUNLOCK to unlock the flash sectors prior to programming
- MEMERASE to erase flash sectors prior to rewriting
- MEMLOCK to relock the sectors after programming
Listing 17.5. Updating the BIOS
#include <linux/mtd/mtd.h> #include <stdio.h> #include <fcntl.h> #include <asm/ioctl.h> #include <signal.h> #include <sys/stat.h> #define BLOCK_SIZE 4096 #define NUM_SECTORS 16 #define SECTOR_SIZE 64*1024 int main(int argc, char *argv[]) { int fwh_fd, image_fd; int usect=0, lsect=0, ret; struct erase_info_user fwh_erase_info; char buffer[BLOCK_SIZE]; struct stat statb; /* Ignore SIGINTR(^C) and SIGSTOP (^Z), lest you end up with a corrupted flash and an unbootable system */ sigignore(SIGINT); sigignore(SIGTSTP); /* Open MTD char device */ fwh_fd = open("/dev/mtd/0", O_RDWR); if (fwh_fd < 0) exit(1); /* Open BIOS image */ image_fd = open("bios.img", O_RDONLY); if (image_fd < 0) exit(2); /* Sanity check */ fstat(image_fd, &statb); if (statb.st_size != SECTOR_SIZE*NUM_SECTORS) { printf("BIOS image looks bad, exiting.\n"); exit(3); } /* Unlock and erase all sectors */ while (usect < NUM_SECTORS) { printf("Unlocking & Erasing Sector[%d]\r", usect+1); fwh_erase_info.start = usect*SECTOR_SIZE; fwh_erase_info.length = SECTOR_SIZE; ret = ioctl(fwh_fd, MEMUNLOCK, &fwh_erase_info); if (ret != 0) goto bios_done; ret = ioctl(fwh_fd, MEMERASE, &fwh_erase_info); if (ret != 0) goto bios_done; usect++; } /* Read blocks from the BIOS image and dump it to the Firmware Hub */ while ((ret = read(image_fd, buffer, BLOCK_SIZE)) != 0) { if (ret < 0) goto bios_done; ret = write(fwh_fd, buffer, ret); if (ret <= 0) goto bios_done; } /* Verify by reading blocks from the BIOS flash and comparing with the image file */ /* ... */ bios_done: /* Lock back the unlocked sectors */ while (lsect < usect) { printf("Relocking Sector[%d]\r", lsect+1); fwh_erase_info.start = lsect*SECTOR_SIZE; fwh_erase_info.length = SECTOR_SIZE; ret = ioctl(fwh_fd, MEMLOCK, &fwh_erase_info); if (ret != 0) printf("Relock failed on sector %d!\n", lsect); lsect++; } close(image_fd); close(fwh_fd); }