2011-08-22

Writing a Linux device driver module for kernels 2.6 or later with udev

This is a short tutorial for a sample character device module aimed at Linux kernels 2.6 and later (including 3.x and 4.x) using udev.
I thought might as well produce my own tutorial, after finding that most of the ones floating around the net are either lacking in terms of features, or not as up to date as could be with regards to the latest kernel advancements.

Some of the features demonstrated with this sample are:
  • creation of a character device as a module
  • use of udev, for the device instantiation
  • use of a kernel FIFO, for data queuing
  • use of a mutex, to prevent concurrent access to the same device
  • use of sysfs, as a means to add data to the queue
  • detection of access mode on open, to ensure read-only access
  • provision of module options during insmod, to enable debug output
Not bad for a short sample, eh?

Of course, this example is just there to help get you started, and does not replace a proper guide to Linux driver development, such as the freely available and strongly recommended "Linux Device Drivers - Third Edition" from O'Reilly.


What's the deal with udev?

If you have some basic knowledge of device drivers on UNIX systems, you probably are already aware that devices are instantiated into the dev/ directory through the use of a (major, minor) pair of integers. UNIX veterans will no doubt recall how they used to call on mknod to create their devices, and some UNIX systems still require that. Well into the 2.6 kernels, Linux was also more or less using the statically allocated major/minor approach, through its devfs implementation. At one stage however, kernel developers got slightly annoyed about having to coordinate the allocation of major/minor numbers, not being able to keep /dev as clean and as abstracted as they liked and also feared that Linux would run out of numbers to assign. Because of that, they developed a new approach to device instantiation called udev, which led them to eventually ditch devfs altogether in kernel 2.6.18. For more information about udev vs devfs, you may want to read Greg Kroah-Hartman's The final word from December 2003.

How this matters to you as a driver writer is that a lot of existing tutorials you will find rely on devfs or static allocation of a major and minor number, which doesn't apply to recent kernels. Thus, you want a tutorial that demonstrates how to use udev, such as the one provided here.
For the record, this sample driver was tested with kernel 3.0.3 as well as 2.6.39.


Device description

The origin of this device driver sample mostly stems from wanting to craft a virtual device to simulate an HDMI-CEC enabled TV, in order to facilitate the development of libcec/cecd. Basically, with such a driver, a libcec application can connect to a virtual device that plays a pre-recorder set of data, which avoids the need to have an actual HDMI-CEC device plugged in or even a development platform that has an HDMI port.

Since the driver above is fairly straightforward to write, it is a good candidate to serve as a base for a sample. In order to turn it into something that is generic enough for a tutorial, we did remove anything that was HDMI-CEC related and turned the driver into one that can store sequences of character data and then repeat them in the order they were received, pretty much as a FIFO for text string. Given this feature description, it makes a lot of sense to call our little device 'parrot'.

For this 'parrot' device then, what we need first is a buffer to store data. The smart approach here, rather than reinvent the wheel is to use a Kernel FIFO, as this is a feature provided by the kernel and specifically aimed at device drivers. This will greatly simplify our design and of course, if you use this sample as a base for a driver that does actual hardware I/O, knowing how to use a Kernel FIFO will no doubt come handy.

Now, while we obviously are going to use read accesses to the actual device (cat /dev/parrot) to read FIFO messages, we are not going to use write accesses to the device to fill the FIFO. Instead we will use sysfs, and make our device read-only.
This may sound counter-intuitive, aside from wanting to demonstrate the use of sysfs, but the reason is, in a typical virtual repeater device scenario, you would use separate FIFOs for read and write, or a FIFO for read and a sink for write, since you don't want data written by your application to the device to interfere the pre-defined scenario you have for readout. Of course, if you want to add a device write endpoint to our sample device, it is a simple matter of copy/pasting the read procedures, and modify them for write access.

Finally, because this is a sample, we will add some debug output which we will toggle using a module parameter on insmod as it is a good example to demonstrate how driver load options can be handled.


Source Walkthrough

The open()/close() calls should be fairly straightforward. The open() call demonstrates how one can use the struct file f_flags attribute to find out whether the caller is trying to use the device for read-only, write-only or read/write access. As indicated, we forcibly prevent write access, and return an error if the device is open write-only or read/write. Then we use the non-blocking mutex_trylock() and mutex_unlock(), along with a global device mutex, to prevent more than one process from accessing the device at any one time. This way, we won't have to worry about concurrent read accesses to our Kernel FIFO.

The device_read() call uses the kfifo_to_user() to copy data from the Kernel FIFO into userspace. One thing you must be mindful of, when writing device drivers, is that kernelspace and userspace are segregated so you can't just pass a kernel buffer pointer to a user application and expect it to work. Data must be duplicated to or from userspace. Hence the _to_user call. Now, because we store multiple messages in a flat FIFO, and don't use specific message terminators such as NUL, we need a table to tell us the length of each message. This is what the parrot_msg_len[] (circular) table provides. For this to work, we also need 2 indexes into it: one to point to the length of the next message to be read (parrot_msg_idx_rd), and one to point to the next available length entry for a write operation (parrot_msg_idx_wr). We could also have used _enqueue and _dequeue for the suffixes, as this is what you will generally find in a driver source.

The one_shot and message_read variables are used to alleviate a problem you will face if you use cat to read from the device. The problem with cat is that it will continue to issue read requests, until the device indicates that there is no more data to be read (with a read size of zero). Thus, if left unchecked, cat will deplete the whole FIFO, rather than return a single message. To avoid that, and because we know that cat will issue an open() call before attempting to read the device, we use the boolean message_read to tell us if this is our first read request since open or not. If it isn't the first request, and the one_shot module parameter is enabled (which is the default), we just return zero for all subsequent read attempts. This way, each time you issue a cat, you will at most read one message only.

With our open(), close() and read() calls properly defined, we can set the file operations structure up, which we do in the static struct file_operations fops. For more on the file operations which you can use for character devices, please see Chapter 3 of the Linux Device Drivers guide.

All the above takes care of our basic driver workhorse. Yet we still haven't provided any means for the user to populate the FIFO. This is done through the next subroutine called sys_add_to_fifo().

The first thing we do there is check for overflows on either the FIFO or the message length table. Then, we use kfifo_in() to copy the data provided by the user to the sysfs endpoint (see below). Note that, in this case, the sysfs mechanisms have already copied the data provided by the user into kernelspace, therefore using kfifo_in() just works, and there is not need to call kfifo_from_user(), as we would have had to do if we were writing a regular driver write procedure.

The other sysfs endpoint we provide is a FIFO reset facility, implemented in sys_reset, which is very straightforward.

With our sysfs functions defined, we can use the DEVICE_ATTR() macros to define the structure which we'll have to pass to the system during the module initialization. The S_IWUSR parameter indicates that we want users to have write access to these endpoints.

Now, we come to the last part of our driver implementation, with the actual module initialization and destruction. In module_init() the first thing we do, and this is the part where we rely on udev, is to dynamically allocate a major, through register_chrdev(). This call takes 3 parameters: The first one is either a specific major number, or 0 if you want the system to allocate one for you (our case), the second is a string identifier (which can be different from the name you will use in /dev) and the last parameter is a pointer to a file operations structure.

Next, we have a choice of creating a device associated with a class or a bus. For a simple sample such as ours, and given that our device is not tied to any hardware or protocol, creating a bus would be an overkill, so we will go with a class. For laymen, the difference between using a bus and a class is that you will get your devices listed in /sys/devices/<classname> in sysfs, instead of /sys/devices/virtual, and you will also have them under <classname> in /dev (though it is possible to achieve the same if you add a "/" in your device name when calling device_create()).

With a class established, we can then call device_create() which performs the actual instantiation of our device. The description of the device_create() parameters can be found here. We use the MKDEV() macro to create a dev_t out of the major we previously obtained, using 0 as the minor as we only have one device. It is at this stage that a "parrot_device" gets created in /dev, using the parameter provided for the name.

The next calls to device_create_file() are used to create the two sysfs endpoints, fifo and reset, out of the structures previously defined with DEVICE_ATTR(). Because we use a class, this results in the /sys/devices/virtual/parrot/parrot_device/fifo and /sys/devices/virtual/parrot/parrot_device/reset endpoints being created.

Finally we end the module_init() call with the remainder of the device initialization (FIFO, mutex, indexes).

The remainder of the code should be fairly explicit so I won't comment on it.


Source:

parrot_driver.h:
/*
 * Linux 2.6 and later 'parrot' sample device driver
 *
 * Copyright (c) 2011-2015, Pete Batard <pete@akeo.ie>
 *
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

#define DEVICE_NAME "device"
#define CLASS_NAME "parrot"
#define PARROT_MSG_FIFO_SIZE 1024
#define PARROT_MSG_FIFO_MAX  128

#define AUTHOR "Pete Batard <pete@akeo.ie>"
#define DESCRIPTION "'parrot' sample device driver"
#define VERSION "1.2"

/* We'll use our own macros for printk */
#define dbg(format, arg...) do { if (debug) pr_info(CLASS_NAME ": %s: " format, __FUNCTION__, ## arg); } while (0)
#define err(format, arg...) pr_err(CLASS_NAME ": " format, ## arg)
#define info(format, arg...) pr_info(CLASS_NAME ": " format, ## arg)
#define warn(format, arg...) pr_warn(CLASS_NAME ": " format, ## arg)

parrot_driver.c:
/*
 * Linux 2.6 and later 'parrot' sample device driver
 *
 * Copyright (c) 2011-2015, Pete Batard <pete@akeo.ie>
 *
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/types.h>
#include <linux/mutex.h>
#include <linux/kfifo.h>
#include "parrot_driver.h"

/* Module information */
MODULE_AUTHOR(AUTHOR);
MODULE_DESCRIPTION(DESCRIPTION);
MODULE_VERSION(VERSION);
MODULE_LICENSE("GPL");

/* Device variables */
static struct class* parrot_class = NULL;
static struct device* parrot_device = NULL;
static int parrot_major;
/* Flag used with the one_shot mode */
static bool message_read;
/* A mutex will ensure that only one process accesses our device */
static DEFINE_MUTEX(parrot_device_mutex);
/* Use a Kernel FIFO for read operations */
static DECLARE_KFIFO(parrot_msg_fifo, char, PARROT_MSG_FIFO_SIZE);
/* This table keeps track of each message length in the FIFO */
static unsigned int parrot_msg_len[PARROT_MSG_FIFO_MAX];
/* Read and write index for the table above */
static int parrot_msg_idx_rd, parrot_msg_idx_wr;

/* Module parameters that can be provided on insmod */
static bool debug = false; /* print extra debug info */
module_param(debug, bool, S_IRUGO | S_IWUSR);
MODULE_PARM_DESC(debug, "enable debug info (default: false)");
static bool one_shot = true; /* only read a single message after open() */
module_param(one_shot, bool, S_IRUGO | S_IWUSR);
MODULE_PARM_DESC(one_shot, "disable the readout of multiple messages at once (default: true)");


static int parrot_device_open(struct inode* inode, struct file* filp)
{
 dbg("");

 /* Our sample device does not allow write access */
 if ( ((filp->f_flags & O_ACCMODE) == O_WRONLY)
   || ((filp->f_flags & O_ACCMODE) == O_RDWR) ) {
  warn("write access is prohibited\n");
  return -EACCES;
 }

 /* Ensure that only one process has access to our device at any one time
  * For more info on concurrent accesses, see http://lwn.net/images/pdf/LDD3/ch05.pdf */
 if (!mutex_trylock(&parrot_device_mutex)) {
  warn("another process is accessing the device\n");
  return -EBUSY;
 }

 message_read = false;
 return 0;
}

static int parrot_device_close(struct inode* inode, struct file* filp)
{
 dbg("");
 mutex_unlock(&parrot_device_mutex);
 return 0;
}

static ssize_t parrot_device_read(struct file* filp, char __user *buffer, size_t length, loff_t* offset)
{
 int retval;
 unsigned int copied;

 /* The default from 'cat' is to issue multiple reads until the FIFO is depleted
  * one_shot avoids that */
 if (one_shot && message_read) return 0;
 dbg("");

 if (kfifo_is_empty(&parrot_msg_fifo)) {
  dbg("no message in fifo\n");
  return 0;
 }

 retval = kfifo_to_user(&parrot_msg_fifo, buffer, parrot_msg_len[parrot_msg_idx_rd], &copied);
 /* Ignore short reads (but warn about them) */
 if (parrot_msg_len[parrot_msg_idx_rd] != copied) {
  warn("short read detected\n");
 }
 /* loop into the message length table */
 parrot_msg_idx_rd = (parrot_msg_idx_rd+1)%PARROT_MSG_FIFO_MAX;
 message_read = true;

 return retval ? retval : copied;
}

/* The file_operation scructure tells the kernel which device operations are handled.
 * For a list of available file operations, see http://lwn.net/images/pdf/LDD3/ch03.pdf */
static struct file_operations fops = {
 .read = parrot_device_read,
 .open = parrot_device_open,
 .release = parrot_device_close
};

/* Placing data into the read FIFO is done through sysfs */
static ssize_t sys_add_to_fifo(struct device* dev, struct device_attribute* attr, const char* buf, size_t count)
{
 unsigned int copied;

 dbg("");
 if (kfifo_avail(&parrot_msg_fifo) < count) {
  warn("not enough space left on fifo\n");
  return -ENOSPC;
 }
 if ((parrot_msg_idx_wr+1)%PARROT_MSG_FIFO_MAX == parrot_msg_idx_rd) {
  /* We've looped into our message length table */
  warn("message length table is full\n");
  return -ENOSPC;
 }

 /* The buffer is already in kernel space, so no need for ..._from_user() */
 copied = kfifo_in(&parrot_msg_fifo, buf, count);
 parrot_msg_len[parrot_msg_idx_wr] = copied;
 if (copied != count) {
  warn("short write detected\n");
 }
 parrot_msg_idx_wr = (parrot_msg_idx_wr+1)%PARROT_MSG_FIFO_MAX;

 return copied;
}

/* This sysfs entry resets the FIFO */
static ssize_t sys_reset(struct device* dev, struct device_attribute* attr, const char* buf, size_t count)
{
 dbg("");

 /* Ideally, we would have a mutex around the FIFO, to ensure that we don't reset while in use.
  * To keep this sample simple, and because this is a sysfs operation, we don't do that */
 kfifo_reset(&parrot_msg_fifo);
 parrot_msg_idx_rd = parrot_msg_idx_wr = 0;

 return count;
}

/* Declare the sysfs entries. The macros create instances of dev_attr_fifo and dev_attr_reset */
static DEVICE_ATTR(fifo, S_IWUSR, NULL, sys_add_to_fifo);
static DEVICE_ATTR(reset, S_IWUSR, NULL, sys_reset);

/* Module initialization and release */
static int __init parrot_module_init(void)
{
 int retval;
 dbg("");

 /* First, see if we can dynamically allocate a major for our device */
 parrot_major = register_chrdev(0, DEVICE_NAME, &fops);
 if (parrot_major < 0) {
  err("failed to register device: error %d\n", parrot_major);
  retval = parrot_major;
  goto failed_chrdevreg;
 }

 /* We can either tie our device to a bus (existing, or one that we create)
  * or use a "virtual" device class. For this example, we choose the latter */
 parrot_class = class_create(THIS_MODULE, CLASS_NAME);
 if (IS_ERR(parrot_class)) {
  err("failed to register device class '%s'\n", CLASS_NAME);
  retval = PTR_ERR(parrot_class);
  goto failed_classreg;
 }

 /* With a class, the easiest way to instantiate a device is to call device_create() */
 parrot_device = device_create(parrot_class, NULL, MKDEV(parrot_major, 0), NULL, CLASS_NAME "_" DEVICE_NAME);
 if (IS_ERR(parrot_device)) {
  err("failed to create device '%s_%s'\n", CLASS_NAME, DEVICE_NAME);
  retval = PTR_ERR(parrot_device);
  goto failed_devreg;
 }

 /* Now we can create the sysfs endpoints (don't care about errors).
  * dev_attr_fifo and dev_attr_reset come from the DEVICE_ATTR(...) earlier */
 retval = device_create_file(parrot_device, &dev_attr_fifo);
 if (retval < 0) {
  warn("failed to create write /sys endpoint - continuing without\n");
 }
 retval = device_create_file(parrot_device, &dev_attr_reset);
 if (retval < 0) {
  warn("failed to create reset /sys endpoint - continuing without\n");
 }

 mutex_init(&parrot_device_mutex);
 /* This device uses a Kernel FIFO for its read operation */
 INIT_KFIFO(parrot_msg_fifo);
 parrot_msg_idx_rd = parrot_msg_idx_wr = 0;

 return 0;

failed_devreg:
 class_destroy(parrot_class);
failed_classreg:
 unregister_chrdev(parrot_major, DEVICE_NAME);
failed_chrdevreg:
 return -1;
}

static void __exit parrot_module_exit(void)
{
 dbg("");
 device_remove_file(parrot_device, &dev_attr_fifo);
 device_remove_file(parrot_device, &dev_attr_reset);
 device_destroy(parrot_class, MKDEV(parrot_major, 0));
 class_destroy(parrot_class);
 unregister_chrdev(parrot_major, DEVICE_NAME);
}

/* Let the kernel know the calls for module init and exit */
module_init(parrot_module_init);
module_exit(parrot_module_exit);
The relevant Makefile is provided in the parrot source archive (link below).

Testing:
root@linux:/ext/src/parrot# make
make -C /usr/src/linux SUBDIRS=/mnt/hd/src/parrot modules
make[1]: Entering directory `/mnt/hd/src/linux-3.0.3'
  CC [M]  /mnt/hd/src/parrot/parrot_driver.o
  Building modules, stage 2.
  MODPOST 1 modules
  CC      /mnt/hd/src/parrot/parrot_driver.mod.o
  LD [M]  /mnt/hd/src/parrot/parrot_driver.ko
make[1]: Leaving directory `/mnt/hd/src/linux-3.0.3'
root@linux:/ext/src/parrot# insmod parrot_driver.ko debug=1
root@linux:/ext/src/parrot# lsmod
Module                  Size  Used by
parrot_driver           4393  0
root@linux:/ext/src/parrot# ls -lF /dev/parrot_device
crw------- 1 root root 251, 0 2011-08-22 22:04 /dev/parrot_device
root@linux:/ext/src/parrot# ls -lF /sys/devices/virtual/parrot/parrot_device/
total 0
-r--r--r-- 1 root root 4096 2011-08-22 22:05 dev
--w------- 1 root root 4096 2011-08-22 22:05 fifo
drwxr-xr-x 2 root root    0 2011-08-22 22:05 power/
--w------- 1 root root 4096 2011-08-22 22:05 reset
lrwxrwxrwx 1 root root    0 2011-08-22 22:04 subsystem -> ../../../../class/parrot/
-rw-r--r-- 1 root root 4096 2011-08-22 22:04 uevent
root@linux:/ext/src/parrot# echo "this should generate an error" > /dev/parrot_device
-bash: /dev/parrot_device: Permission denied
root@linux:/ext/src/parrot# echo "Yabba Dabba Doo" > /sys/devices/virtual/parrot/parrot_device/fifo
root@linux:/ext/src/parrot# echo "Yabba Dabba Daa" > /sys/devices/virtual/parrot/parrot_device/fifo
root@linux:/ext/src/parrot# cat /dev/parrot_device
Yabba Dabba Doo
root@linux:/ext/src/parrot# cat /dev/parrot_device
Yabba Dabba Daa
root@linux:/ext/src/parrot# echo "test" > /sys/devices/virtual/parrot/parrot_device/fifo
root@linux:/ext/src/parrot# echo "test" > /sys/devices/virtual/parrot/parrot_device/fifo
root@linux:/ext/src/parrot# echo 1 > /sys/devices/virtual/parrot/parrot_device/reset
root@linux:/ext/src/parrot# cat /dev/parrot_device
root@linux:/ext/src/parrot#

Links:

2011-08-12

Which controllers support USB 3.0 Debug?

As defined per section 7.6 of the xHCI specifications?

Unfortunately, none of the current ones from Renesas, VIA Labs or Fresco Logic do, and neither of these manufacturers are planning to add the capability on their existing controllers, through a firmware update for instance... Instead, they only have plans to provide it on their next generation controllers, which won't be available for some time (yes, I am aware that the new Renesas chips have been announced since 2011.03, but I have yet to see a PCI-E expansion card with one of those -- If you actually managed to get your hands on one, I'd like to hear from you!  
UPDATE 2011.08.29: Apparently those guys did... but as a world exclusive, since these cards won't be available till later on this year. Darn!).
UPDATE 2012.01.10: At long friggin' last, uPD720201 based PCIE cards, with debug support, are starting to become available, one such being the Buffalo IFC-PCIE4U3S. Only seems to be available in Asia and Australia at the moment however, but even then, orders may not be satisfied before February because of lack of availability...

So this means, if you have a NEC/Renesas uPD720200/uPD720200A, a VIA Labs VL800/801 or a Fresco Logic FL1000/FL1009 based USB 3.0 controller, you're out of luck with regards to USB 3.0 debug.
This doesn't bode too well for USB 3.0 as replacement for legacy RS232 ports...
Oh and the other disappointing part of the current crop of PCIE-USB3 controllers is they don't support USB-3.0 boot either. Quite the letdown...

Hint: If you want to display the Extended Capabilities of your controller, you can do so by modifying the xhci_setup_port_arrays() of drivers/usb/host/xhci-mem.c in () on a recent Linux kernel, as it already has some code to scan the extended caps. Using this method, I was able to find that, as of firmware 4015, the only Extended Capabilities provided by an uPD720200 USB 3.0 controller was "USB Legacy Support" (xHCI specs chpater 7.1) and "xHCI Supported Protocol" (7.2). The same applies for a Fresco Logic FL1000 based controller that I just got my hands on, though this one also sports vendor specific Extended Capabilities, with IDs 192 and 193.

Below are the answers from various manufacturers regarding their planned support of USB 3.0 debug. Being mostly interested in addon cards for existing systems, I haven't asked Intel or AMD.

NEC/Renesas:
"The uPD720200 does not support the debug port capability, and there is no plan to add it. But we have already added debug port support to our newer USB3 host controllers, uPD720201 and uPD720202."
VIA Labs:
"VL800/801 doesn't support debug port capability. As I know, the only one support is Renesas 3rd gen. host controller UPD720201_202, we plan to add this feature with VL805 that will MP next year."
Fresco Logic:
"FL1000/FL1009 didn't support USB debug extended capability.
We are doing it (for the next generation of controllers) and the schedule will be 2012/Q1."

2011-08-09

Enabling option ROM and flashing gPXE on an SMC 1211

If you're going to play with PCI option ROMs, you're likely to salvage a PCI Network Interface Card (NIC) with a flash ROM, or at least one that has a socket for it. And given their popularity in the late 90s/early 2ks, you have a fair chance getting your hands on with a RealTek RT8139 based one. One such NIC is is the SMC 1211 (or "Accton Technology Corporation SMC2-1211TX" as reported by lscpi -nn on Linux), which, in its basic configuration, comes with a DIP socket for 5V flash chips up to 128 KB in size. This is actually quite a desirable card to have as it is beautifully supported by flashrom, which has great support for the RT8139 chip, and no extra tweaking is needed to support the maximum flash size of 128 KB, as can be the case with other NICs. Add a W29C011A-15 or compatible, which can be easily obtained or salvaged from an old motherboard and you'll have more than enough space to play with an option ROM.

Only problem of course is that most SMC 1211s don't come with a flash chip by default so the option ROM is disabled and needs to be re-enabled. Luckily, Realtek does provide a tool called rset8139.exe to do just that. Of course, rather than blindly trusting the tool before we go and play with a custom option ROM, we may as well attempt to check that everything is in order so we are first going to flash a proper one, such as gPXE before running the rset8139 tool. Off we go then to etherboot/gPXE's awesome rom-o-matic, fill our options, including the 1113:1211 VID:PID of our SMC card and get our gPXE ROM back then.

First order of the day, since we're using a 128 KB flash chip and the gPXE ROM we got was smaller, is pad our ROM to our target size with:

cat gpxe-1.0.1-11131211.rom /dev/zero | dd bs=1k count=128 > gpxe_128k.rom

Then we flash with:

flashrom -p nicrealtek -w gpxe_128k.rom

So that takes care of having a proper option ROM. Of course, since we haven't enabled it on the card, you will find that no matter the options you select in your BIOS, the SMC option ROM is not executed and this is precisely why we need to run that rset8139 utility. Now, it is possible that Linux or Windows version of this utility exist, but it looks like the most common version is the DOS one, which, thanks to the oh-so-convenient Rufus (see this post), running from a DOS bootable USB stick is no problem at all.

One important thing to note is there exists multiple versions of the utility, ranging from 5.00 to 5.09, and that not all of these appear to detect the SMC 1211 card. Some regression has been introduced by Realtek, which leaves the most recent versions of rset8139 unable to change settings for the 1211. Therefore, the version I recommend using if you have an SMC card is v5.01, which can be picked here. If you don't have an SMC card, then you can try your luck with v5.0.9, which appears to be the most recent, and which is available here.

Once you have created your DOS bootable USB stick and copied rset8193.exe over, you should end up with something similar to the screenshot below (courtesy of desconexão.net), where you will be able to enable and set your ROM size:


After these settings are saved and you reboot, you should find that the gPXE option ROM payload is now executed by your BIOS, and with that you can get cracking on building a custom option ROM using your RT8139 based card.

2011-08-08

USB_DOS: The (once) easiest way to create a DOS bootable USB stick on Windows

(Updated 2011.12.14: this guide is now obsolete and has been superseded. If you want to create an MS-DOS or FreeDOS bootable USB Flash Drive, please visit the Rufus page).

If you ever need to run a DOS utility (eg. to flash a BIOS or a firmware), and want to painlessly create a bootable DOS USB key from Windows, this is for you.

Courtesy of FreeDOS and the HP Bootable USB utility (HPUSBFW), the following archive contains everything you need to create a fully functional DOS bootable USB stick. Just extract the file below (using 7-zip if needed) and follow the instructions:

USB_DOS v1.0

Now, for some additional information, in case this may be of interest to you:
  • The HPUSBFW executable has been modified from the original to request elevation on Vista and later. If you remove the manifest you should find that it matches the official HP one.
  • The FreeDOS command.com and kernel.sys files come from the current version of FreeDOS (1.0). command.com has been patched to prevent it from requesting date and time input from the user at boottime. If you unpack command.com with upx, you should find that the only difference with official is a set of 4 NOPs (0x90).
    Oh, and yes I tried to recompile the latest FreeCOM from SVN, in a FreeDOS VMWare image, but it's been quite a struggle (hint: don't waste your time with OpenWatcom, use Borland Turbo C++ v2.01 instead) and the resulting command.com freezes after a few commands when used bare with kernel.sys, as is the case with HPUSBFW.

2011-08-03

UBRX - L2 cache as instruction RAM (CAiR)

Word of advice: if you want to play with CPU caches, and especially an L2 cache, don't use a Slot 1 Pentium III as your test system: L2 is disabled by default there and, because it's really some fast RAM that was added on the board that also has the CPU chip, initializing it take a little more effort than simply flipping a switch... If you want to know all about how to initialize L2 cache on Slot 1 Pentiums, you may want to have a look at the old freebios code.

But now, with L2 sorted out and as a famous robot once said: "Things are starting to look up..."

Why is L2 cache so important for UBRX you ask? Good question.
You see, our goal is to run binary code that was uploaded by the user, on a system that is considered RAM-less, therefore all we have at our disposal are the caches. Now, CAR (Cache As RAM) has been in use by coreboot for some time, but the problem with this implementation is that it only uses L1 cache. However, if you know your CPU architecture 101, you are aware that there are two L1 caches ondie: one for data and another for instructions, with the instruction one being read-only. Thus, the CAR setup method from coreboot only provides access to the L1 data cache, not the instruction one, so we can't simply upload our code into L1-Data and expect it to run.

On the other hand, the L2 cache is a unified one, which means that it works for both data and instruction. Thus, if we manage to get our code onto L2, and have all the caches in WriteBack mode, we should be able to get the CPU to fetch instructions, which we uploaded, from L2, and we're good. This is called Cache As instruction RAM (CAiR). And with L2 caches being more than 256 KB in size, we could actually run a hefty and quite complex section of code, rather than be limited to the 16 or 32 KB of L1.

To achieve that, simply upload the code you want into L1-Data (which would have been initialized as CAR), then read or write a contiguous section of data, from a different address, that is larger than your L1 cache. As L1-Data gets replaced, your code gets pushed onto L2, where it is not accessible for execution by the CPU.
Neat!

So, how does it work in the UBRX console? Like this:
s/u/r/q> s
$60000010 a             # disable cache
$11e c $0 d $01043531 m # setup L2 for PIII Slot 1
$2ff c $0 d $c00 m      # fixed + var MTRRs
$268 c $06060606 d m    # C0000-C7FFF as WriteBack
! $10 a                 # flush and enable cache
$8000 c $c0000 <        # preload region to L2-Unified
# load our code in L1-Data
$c0000 d $f8ba68b0 z    # 'h'
$c0004 d $65b0ee03 z    # 'e'
$c0008 d $ee03f8ba z
$c000c d $f8ba6cb0 z    # 'l'
$c0010 d $6cb0ee03 z    # 'l'
$c0014 d $ee03f8ba z
$c0018 d $f8ba6fb0 z    # 'o'
$c001c d $0db0ee03 z    # CR
$c0020 d $ee03f8ba z
$c0024 d $f8ba0ab0 z    # LF
$c0028 d $ffcbee03 z
$8000 c $c0030 >        # flush L1-Data onto L2-Unified

$c0800 b $c0000         # stack at C8000, code at C0000
.
s/u/r/q> r
hello
s/u/r/q>
Now, by flashing less than 4KB of your BIOS bootblock, you are able to run ANY code you want using the UBRX the recovery console, even if you don't have any RAM installed, with the added benefit that your code can be as large as your L2 cache. Neat!

All of this and more in UBRX v0.4.

2011-07-15

Creating a bootable UEFI DUET USB stick

Now that one of my patches has made it into the UEFI/EDK2 SVN repository, I'm going to provide a quite guide on the easiest way to create a bootable UEFI USB stick for legacy platforms, on Windows.
In case you are not familiar with EFI/UEFI, it is very much possible to run EFI even on legacy (i.e. non EFI) hardware, through a process called DUET, which provides a complete EFI emulation layer using the underlying BIOS. This is great for testing, without having to modify your hardware in any way.

It is all provided by the DuetPkg of the EDK2. However, the main issue with EDK2 is that it may be a difficult to get working, unless you used the same development tools as the EDK2 developers, which, on Windows would default to Visual Studio 2008, which is not free.

Isn't there simple way to use freely available tools on Windows, to easily compile a DUET USB bootstick?
Glad you asked. There is now, and here is the complete process that goes with it:
  • Download and install the Windows Server 2003 WinDDK, v3790.1830, using this direct link (230 MB ISO download). This is of course not the latest DDK but unless you want to waste time in extra configuration, you might as well go with the supported version. If I find the time to fix EDK2 for the latest, I may send another patch to the EDK2 team, but for now, this will have to do.
  • (Optional) If you plan to use ASL to compile ACPI, which isn't needed for DUET, create a C:\ASL directory, download the Microsoft ASL Assembler/Compiler archive from here, open the .exe with 7-zip. Then open the .msi from the .exe (Open Inside), find the file _C6C6461BF87C49D66033009609D53FA5, extract it to your C:\ASL directory and rename it asl.exe (count on Microsoft to provide an installer... for a mere 55 KB exe). Also note that the ASL compiler is for 32 bit only. If you're running 64 bit, tough luck.
  • Mount or burn the ISO and install the WinDDK, to the default C:\WINDDK\3790.1830 directory.
  • If needed, download and install TortoiseSVN, or any other SVN client.
  • Fetch the latest EDK2 from SVN, using https://edk2.svn.sourceforge.net/svnroot/edk2/trunk/edk2 as the SVN repository.
  • Open a DDK console ("Development Kits" → "Win2k3 Free x64 Build Environment" or "Win2k3 Free x86 Build Environment") and navigate to your edk2 direcvtory
  • Run the command:
    edksetup.bat
    This will initialize your environment by creating the required files in the Conf\ directory. You can safely ignore the warning about cygwin.
  • Open the file Conf\target.txt and edit the TOOL_CHAIN_TAG line to have:
    TOOL_CHAIN_TAG = DDK3790xASL
    You also may want to change MAX_CONCURRENT_THREAD_NUMBER and make sure TARGET_ARCH is properly set to either IA32 or X64 according to your needs, as it avoids having to provide extra arch parameter when invoking the commands below).
  • In the console, run (32 bit):
    build -p DuetPkg\DuetPkgIA32.dsc
    or (64 bit)
    build -p DuetPkg\DuetPkgX64.dsc
    It may take a while but it should complete successfully.
  • cd to DuetPkg\ and run:
    PostBuild.bat
  • Plug in your USB key and check its drive letter. In the following I will assume it is F:, and run:
    CreateBootDisk.bat usb F: FAT16
  • Unplug and replug the key as requested, and run:
    CreateBootDisk.bat usb F: FAT16 step2
You should now have a bootable UEFI USB key. Plug that into a PC, boot it, and you will be welcomed into the wonderful world of EFI.
Note that if you rebuild any of the packages, you only have to perform the last step to update the key.

2011-07-11

Introducing UBRX - an Universal BIOS Recovery Console for x86 PCs

Following up on the previous BIOS generation endeavour, as well as wanting to demonstrate to any naysayer that a universal recovery console (or panic room) in the BIOS bootblock of an x86 system is not something from the realm of fantasy, it is my pleasure to introduce UBRX, the Universal Bootblock Recovery console for X86 systems.

In its current version (0.1), it is mostly a Proof of Concept aimed at the coreboot team, since the crafting of a 'panic room' type bootblock has been on their mind for some time, and my understanding is that their approach would be to make such a bootblock platform specific (the motherboard would first need to be supported by coreboot), whereas I believe that there is a way to be a lot more generic to produce a 'panic room' feature that works on all platforms, even the ones that coreboot doesn't support yet. The current bootblock is less than 2 KB, which ought to leave plenty of room for a Y-modem, CAR, and a full console implementation.

If your aim is to develop a real BIOS from scratch, and you have a PC that is more recent than the year 2000 (most PCs from the late nineties should work too) UBRX might also be of interest to you, since it should provide you with a serial console. In short, UBRX can also be used as a base for BIOS development, regardless of the machine you have.

What UBRX implements then is a safe method for the detection of a PnP Super I/O chip, as well as the detection and initialisation of a 16550 compatible UART there, to then provide on-demand access to a serial console. The emphasis here has to be with the safety of the detection being performed, as it is intended to be executed at every boot, without resulting in advert consequences for non PnP Super I/O hardware residing in the same hardware space as the one we check, or any non UART function residing in the Super I/O. To find more about how we achieve detection safety, I suggest you check the "Detection Primer" section of the UBRX README.

Now, of course, as with "unlimited" broadband, the "universal" applicability of UBRX comes with some understanding that fairness needs to be applied to the claim. As such, non PnP Super I/Os, PnP Super I/Os that require uncommon initialization (I'm looking at you ITE), and platforms using CPUs other than Intel or AMD are not currently supported. Also, due to the lack of datasheets from nVidia, it is very likely that UBRX may not work with motherboards sporting an nVidia chipset. However, if you have an Intel motherboard with an ICH# chipset, or an AMD motherboard with an SB##0 SouthBridge (which probably covers more than 90% of PCs from the last few years), UBRX is expected to work.

Finally, as UBRX is only a Proof of Concept for now, the console is limited to a serial repeater, so there's not much you can do with it. Especially it it missing CAR (Cache As RAM) initialization and Y-modem functionality, to be able to transfer and run bare metal executables to do interesting things, such as flashing the remainder of the BIOS, initializing the full range of RAM according to the hardware, or loading a debugger. Therefore, if you choose to test and flash UBRX, remember that you must have means to reflash your BIOS using an external program, as your PC will become unusable.

Please head to the github project for more info. A source tar archive of the source can also be downloaded here.

2011-07-07

git remote repository

If you're like me, you probably have a small Linux server somewhere, begging to be used as a remote central git repository, be it only for the purpose of backing up your development projects on a second machine. A Linux based wireless router or plug computer is perfect for such a job, and, as with any remote VCS, can be invaluable with helping to recover from mishaps on your development rig. And of course, if you're doing development and testing on different computers at once, being able to push/pull from a central server makes your life a lot easier.

Git daemon

By default, git includes a daemon utility to serve clients using the git:// protocol, so you don't even have to install anything extra. The one thing that may matter to you is that the default git daemon does not provide any authentication mechanism, so anybody on your network will be able to push/pull files onto a project. For a SOHO, this isn't a big deal, but in a corporate environment, you may want to look into something a bit more secure.

Our first order of the day then is to decide the directory we want to serve git project from on the server and start the git daemon. In this example, I will use /usr/src on the server. The command to run git daemon then is:
git daemon --reuseaddr --base-path=/usr/src --export-all --verbose --enable=receive-pack --detach --syslog
The --export-all allows pull/clone to be issued by remote clients, regardless of whether a git-daemon-export-ok file exists in the git directory. The --enable=receive-pack is required if you want to be able to push from a client, as it is disabled by default and you will get the error: 'receive-pack': service not enabled for './.git'. The rest of the options should be fairly explicit (if not, see the git daemon page. As you can see, these settings are as permissive as can be, which is probably what you want if you are starting with using git as a server.

Creating the server repository

With the daemon running (check your syslog or messages to confirm), we can now create the directory we want to be used as the origin on our server. Let's call it myproject. Thus:
root@git-server:~# cd /usr/src/
root@git-server:/usr/src# mkdir myproject
root@git-server:/usr/src# cd myproject/
root@git-server:/usr/src/myproject# git init
Initialized empty Git repository in /usr/src/myproject/.git/
Note that the git people historically recommend creating project directory with a .git extension, but there doesn't seem to be much point to it.

Indiana Git and the Intuitiveness of Doom

At this stage, if you have existing files for your project, you have the choice of transferring them to the server and git add + git commit them, or first clone the empty directory on your client and then add the files there. With the latter being the most likely situation, this is what I will demonstrate, therefore (the following was done on Linux - if you are using Msys-git on Windows, see below):
user@git-client:~$ git clone git://git-server/myproject
Cloning into myproject...
warning: You appear to have cloned an empty repository.
user@git-client:~$ cd myproject/
# in real life you would copy, add and commit existing project files there
user@git-client:~/myproject$ touch README
user@git-client:~/myproject$ git add README
user@git-client:~/myproject$ git commit -m "added README"
[master (root-commit) e3cb915] added README
 0 files changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 README
So far so good, but then:
user@git-client:~/myproject$ git push
No refs in common and none specified; doing nothing.
Perhaps you should specify a branch such as 'master'.
Everything up-to-date
OK, googling around shows you need to add origin master to that first push, so:
user@git-client:~/myproject$ git push origin master
Counting objects: 3, done.
Writing objects: 100% (3/3), 208 bytes, done.
Total 3 (delta 0), reused 0 (delta 0)
remote: error: refusing to update checked out branch: refs/heads/master
remote: error: By default, updating the current branch in a non-bare repository
remote: error: is denied, because it will make the index and work tree inconsistent
remote: error: with what you pushed, and will require 'git reset --hard' to match
remote: error: the work tree to HEAD.
remote: error:
remote: error: You can set 'receive.denyCurrentBranch' configuration variable to
remote: error: 'ignore' or 'warn' in the remote repository to allow pushing into
remote: error: its current branch; however, this is not recommended unless you
remote: error: arranged to update its work tree to match what you pushed in some
remote: error: other way.
remote: error:
remote: error: To squelch this message and still keep the default behaviour, set
remote: error: 'receive.denyCurrentBranch' configuration variable to 'refuse'.
To git://git-server/myproject
 ! [remote rejected] master -> master (branch is currently checked out)
error: failed to push some refs to 'git://git-server/myproject'
Now that's even worse! What the heck?

Long story short, git has a design limitation that prevents it to update both the working directory structure (the user visible files such as 'README', .c/.h, etc.) and the index (the hashed nodes, that contain the same information plus details of the various changes the files went trough) at the same time, on the server. Yes, if they really wanted to, the git developers could easily work around that limitation by providing an option to automatically force a sync on the remote working dir from the index, when index changes have been pushed, but they have decided that, since there exists individual cases where such an option would cause harm, they might as well prevent everybody from doing so altogether, regardless of whether people might be fully aware of what they are doing and accept the consequences.
So this means that, as long as git sees the server git directory being checked out with the 'master' branch, which is the case by default even on an empty directory, as well as the client git repo also using the 'master' branch, which is also the default, it considers that the server repository has precedence (i.e. that there might exist uncommitted changes in the server repo, that have higher priority than any committed client ones), and therefore, will reject remote requests to update the index, such as a push.

To work around that then, you need to make sure that the server doesn't have the 'master' branch checked out locally, when you are issuing a git push from the client. Easiest way to do that is to check into a different branch, away from 'master', on the server, so if we just checkout onto a 'dummy' branch that we create (-b option), git should be happy again. Let's try that:
root@git-server:/usr/src/myproject# git checkout -b dummy
fatal: You are on a branch yet to be born
Right... And people ask me why I'm still not entirely convinced that git is the best invention since sliced bread. Yes, I'll be the first to say that it is usually miles better than the competition, and when it works, it's just brilliant, but quite frankly, there is such thing as intuitive, and as far as intuitive goes, there is still a lot of room for improvement with git.
As the error indicates, the problem this time is that our repo is bare. It doesn't even have a HEAD. Now, because we don't have all day to figure out each of git's numerous intricacies, we just take matters into our own hands and hook into the git index directly with:
root@git-server:/usr/src/myproject# git symbolic-ref HEAD refs/heads/dummy
This should ensure that git no longer sees us as potentially modifying the (still non-existing) 'master' branch on the server and stop bugging us.

Then, if you go back to the client:
user@git-client:~/myproject~ git push origin master
Counting objects: 3, done.
Writing objects: 100% (3/3), 208 bytes, done.
Total 3 (delta 0), reused 0 (delta 0)
To git://git-server/myproject
 * [new branch]      master -> master
At last, success!! From there on you should be able to use a naked git push on the client and roll.
If you later want to edit/modify the repo content on the server (the server working dir will remain empty even after a push, because the working directory is no longer following the 'master' HEAD), you can do so by switching back to the 'master' branch with git checkout master. Then an up to date version of what you have pushed should be in your server working dir. However, remember that, if you are planning to remotely push some more, you need to use either git checkout [-b] dummy (if you don't mind having a dummy branch created) or git symbolic-ref HEAD refs/heads/dummy (if you don't want a branch) on the server when you are done, to prevent git from rejecting the operation. If you use the latter, be mindful that you will get a warning: remote HEAD refers to nonexistent ref, unable to checkout error (yes git, if it results in a failure, it is an error, not a warning), so you may have to go back to your server and set symbolic-ref to 'master' before cloning. And if you use the former, make sure that's you're working on the 'master' branch and not 'dummy' after cloning.

Yay, now we have a working git server, and more importantly, we know how to work around the various counter-intuitive git limitations to create repositories on it... Of course, that is, as long as you don't use msys-git on Windows 7...

What's wrong with git daemon and msys-git?

OK, so we are sorted out on Linux. How about we try the same thing on Windows 7 with msys-git?
user@WINDOWS ~
$ git clone git://git-server/myproject
Cloning into myproject...
remote: Counting objects: 8, done.
remote: Compressing objects: 100% (4/4), done.
remote: Total 8 (delta 0), reused 0 (delta 0)
Receiving objects: 100% (8/8), done.

user@WINDOWS ~
$ cd myproject/

user@WINDOWS ~/myproject (master)
$ echo "test" > README

user@WINDOWS ~/myproject (master)
$ git add README
warning: LF will be replaced by CRLF in README.
The file will have its original line endings in your working directory.

user@WINDOWS ~/myproject (master)
$ git commit -m "edited README"
[master warning: LF will be replaced by CRLF in README.
The file will have its original line endings in your working directory.
a0f27c3] edited README
warning: LF will be replaced by CRLF in README.
The file will have its original line endings in your working directory.
 1 files changed, 1 insertions(+), 1 deletions(-)
So far, so good. But then, if you issue git push the command hangs forever:
user@WINDOWS ~/myproject (master)
$ git push
Counting objects: 5, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3)
On the server, the log doesn't seem to indicate that much is amiss:
Jul  7 16:22:23 git-server git-daemon[7324]: Connection from 1.2.3.4:51238
Jul  7 16:22:23 git-server git-daemon[7324]: Extended attributes (13 bytes) exist <host=git-server>
Jul  7 16:22:23 git-server git-daemon[7324]: Request receive-pack for '/myproject'
Congratulations! You've just encountered msys-git bug #457 for which no patch exists yet. If you want a workaround, you can either switch to using ssh (but then you need to sort out authentication, which, for a single-user internal-only VCS operation is a bit of an overkill) or share your repository with samba, and then add the following in the [remote "origin"] section from .git/config:
pushurl = file:////git-server/src/myproject
As can be inferred the above simply forces git to use Samba rather than the git protocol for push operations. With this workaround in place, you are truly set to use a remote git server.

ADDON: The following script might be used as a very useful first commit for the server repository, as it helps toggle between the ability to commit files on the server and allowing client commits:
#!/bin/sh
git branch | grep -q \*
if [ $? -eq 0 ]; then
  git symbolic-ref HEAD refs/heads/dummy
  echo "Switched to fake branch 'dummy'"
else
  git symbolic-ref HEAD refs/heads/master
  echo "Switched to branch 'master'"
fi

2011-07-03

Windows driver utilities and other good stuff

Courtesy of Xiaofan.
If you test driver installations quite often under Windows 7, then PnPUtil from Microsoft can be quite useful.

The RAPR GUI driver store management utility can also help easily identify the inf associated with a driver.

Note that PnPUtil has a bug under Vista that output redirection does not work and it affects RAPR as well. Travis, of libusbK, and libusbDotNet fame has a console version of of driver store management utility located here. The download includes the source as well.
Those who are interested can probably enhance it and create a GUI version for it. Do not use it under XP though.

2011-07-01

com0com signed drivers

If you are using VMware to play with virtual serial consoles on Windows, and especially if you want to have access to that console right from the start, a very nice tool to have is com0com, which is a null-modem emulator that creates a pair of connected virtual COM ports.
VMware does of course offer the possibility to use a named pipe, but you can only connect to it after the image has started running, which may result in bootup messages being truncated. And if you use the VMware option of directing serial to a file, then you can no longer interact.

Com0com solves this problem nicely by creating a pair of virtual COM ports, that are always available, and that you can use with VMware on one end, and putty or HyperTerminal on the other.

The only problem however is that, as of version 2.2.2.0, the current com0com drivers are unsigned. However, since I had a driver signing certificate begging to be used (before it expired), I have now made a signed version of the drivers available, so that you can easily install com0com without having to change the boot properties of your Vista or Windows 7 installation.

The signed 2.2.2.0 com0com drivers can be obtained here. They are compatible with any Windows 10, Windows 8.x, Windows Vista or Windows XP system.


Important Note: It is not because the signing certificate for the com0com driver above is now expired that this driver is invalid and/or should not be used. Timestamping was used at the time of the signature, therefore the driver package above is exactly as good as one using a non expired certificate. If signed driver packages ceased to be valid after the certificate expired, this would close the door for developers using a code signing certificate that they renew on a yearly basis, as it would require their users to upgrade their driver of applications every single year, which is of course nonsense. In short, regardless of what the expiration date on the certificate is, this driver package is no less usable or less safe that one with a non expired certificate.

Note that I stripped the GUI installer, so only the commandline one is available. Even with commandline, the installation is very easy however. All that you need to do is:
  1. Download and extract the files (using 7-zip if needed)
  2. Open an elevated command prompt in the i386\ (32 bit) or x64\ (64 bit) directory
  3. Run the command:
    setupc
  4. Enter the install command:
    install - -
  5. Accept the prompts.
Once the driver is installed, two connected COM ports will be created: CNCA0 and CNCB0, which you can use, one with VMware, and the other with putty.

Now you don't have to miss any bootup messages and you can use an interactive COM port that persists regardless of the power status of the virtual machine. Enjoy!

2011-06-08

Crafting a BIOS from scratch

Introduction

Ideally, there would have been an entry between this one and the last, where I'd give you some pointers on how to disassemble the VMware BIOS we extracted using IDA Pro. However, one of the points of this series is to explore ways of improving/revisiting the too often ignored x86 bootloader recovery process (a.k.a. 'panic room'), that really ought to be part of any system boot operations. As such, we might as well jump straight into the fun by creating a VMware BIOS from scratch.

Your first question might be "Why would anyone want to craft their own BIOS from scratch (apart for an academical exercise)?". Well, in an ideal world, chipmakers such as intel and AMD would follow the example of the few SoCs manufacturers who got it right and provide both an UART and a small serial boot recovery ROM, ondie, to relegate the prospect of a non-functional system because of bad/uninitialized flash, to the footnotes of history. Alas, with CPUs well into their 4th decade of existence, that still hasn't happened. Therefore, to compensate for this missing feature, we'll have to implement such a feature ourselves, in the flash ROM, and that means writing a BIOS bootblock from scratch. And if you know how to write a BIOS bootblock, then you know how to write a complete BIOS. As to why one would actually want to replace a fully functional BIOS on actual hardware, just wait till you purchase a 3TB (or larger) HDD, or create a >2TB RAID array, on an not so old machine, with the intent of booting Windows from it...

Of course, crafting a fully featured BIOS, that can actually boot an OS, is something better left to large projects such as coreboot with a payload of either SeaBIOS or Tianocore (UEFI), so we're not going to do that here. Instead, our aim will be to produce a very simple BIOS, that just does serial I/O, and that can be used as a development base for more interesting endeavours, such as 'panic room' type flash recovery, or regular BIOS instrumentation, to help with the development of an UEFI 'BIOS' for legacy platforms. Trying things out with a virtual machine, before jumping onto actual hardware, seems like the smart thing to do.


Know thy enemy, a.k.a. "Which SuperIO?"

Hardware wise, the only subsystem we want to access then is the SuperIO chip, since it provides the (virtual) UART we are after. We're not going to bother about PCI, RAM, Video, or even Cache as RAM (CAR) access: just plain good old serial debug will do. And while I'm not going to go as far as saying that implementing the items listed above would be trivial, the fact is, as long as you have serial debug, at least you don't have to shoot in the dark, and that's big help.

In the case of VMware, all you need to know is that the SuperIO is a (virtual) National Semiconductor PC97338 (datasheet here). Unfortunately neither coreboot's superiotool or lm-sensors's sensors-detect seem to detect it at the moment, which is quite unfortunate (Didn't someone mention they were porting coreboot/LinuxBIOS to VMware some time ago? What happened?), as a lot of time was wasted on the SuperIO errand.

And maybe I missed a step somewhere, but from what I can see, the VMware virtual chip is not set to run in PnP mode. Thus, what I'm going to expose in the code below with regards to accessing the serial port is very specific to the VMware PC97338 non-PnP implementation, and may not translate so well to SuperIO chips that run in PnP mode. Oh well... Also, if you disassemble the VMware BIOS, you'll see some mucking around with a SuperIO chip located at port 0x398 early in the bootblock, with 0x398 being one of the possible bases for the PC97338... Except the VMware SuperIO base is indeed at the 0x2e location, so all that early stuff is a wild goose chase. Thanks a lot guys!

Therefore, just to reiterate, all you need to know is that the VMware SuperIO is a PC97338, at port 0x2e and running in non PnP mode. With that you can run along, and get going implementing early serial in your own BIOS.


Toolchain considerations and software constraints

With the hardware in check, and before we start writing anything, it might help to have a look at our other requirements.

First of all, as far as the development toolchain is concerned, and even more so as what follows is aimed at being usable by the largest number of people, we will use a GNU toolchain all the way. That means, as soon as you have a gcc setup on your platform that can produce x86 code, you should be good to go. And for the record, I have verified that the files I'm presenting below can produce a BIOS ROM on Windows, with either MinGW32, MinGW-w64 or cygwin, as well as Linux x86 or x64, with regular gcc. OSX (with a proper gcc toolchain) as well as cross compilers on other UNIX architectures are expected to work too. So if you don't have gcc setup on your system, go get it now!

Then comes our choice of language. The coreboot and other projects seem to be quite adamant about developing as little as possible in assembly, but I don't see it that way for the two following reasons:
  1. We have no stack after reset but unless we plan on doing non 'panic room' type things, we actually don't have much use for one in the first place. From experience (with Realtek SoCs) I can tell you that if your 'panic room' needs any form of memory to be initialized to be able to run, and that applies to Cache as RAM, you're not doing it right.
  2. RAM space is infinite. BIOS bootcode blocks aren't. If there's one space you want to optimize it's that 4K or 8K BIOS recovery bootblock that you'll keep and never re-flash at the end of your BIOS. Flash manufacturers are providing features to help with flash recovery - make use of them dammit!!
Therefore, assembly it is.

Now, the one caveat is that the GNU assembler seems to be the only tool still around defaulting to the AT&T syntax which, while arguably more sensible than the intel one, nobody else, and especially not IDA, uses. Instead the intel syntax prevails. While this could have been an annoyance, any recent versions of GNU as also supports the Intel syntax, which can be be switched on in your code with .intel_syntax noprefix. Now that's better!

Finally, we know we'll have to follow the following constraints:
  1. First instruction must be located at address 4GB-0x10 (or FFFF:FFF0 if you prefer), and the whole BIOS must reside at the very end of the 32 bit address space. This is an x86 CPU initialization requirement
  2. The processor starts in real address mode on reset. Another x86 reset constraint. Now, some people choose to switch to protected mode as soon as they can (so that they can use C), but we have optimization in mind, so we'll keep real address mode all the way.
  3. The BIOS ROM size must be 512 KB. This time it's a VMware requirement.
  4. We are also supposed to be careful about far jumps in our code, as another x86 boottime constraint. But that won't be an issue for a bootblock section of a few KB, which we plan to locate at the end of the BIOS anyway.


Producing a BIOS ROM

Now we jump into the gory details at last.

Since the reset vector is located at the end of the BIOS, we need to have at least two sections in our sourcecode: one that contains the bulk of our code, which I'll call main and which I'll arbitrarily set to start at 4 KB before the end of the ROM, and another, starting at FFFF:FFF0 and going to the end of the ROM, which I'll call reset and whose only purpose will be to jump into our entrypoint in the main section.

Below is an example of how one can establish these two sections in the assembly source, as well as the associated GNU ld script that ensures they will be located at the right destination address in the ROM. Because our BIOS is short, I'll use a single bios.S source for the code, and I'll call the ld script bios.ld. Hence bios.S:
.section main
init:   <insert useful code here>
        ...

.section reset
        jmp init
        .align 16
NB: the .align 16 at the end is there to ensure that the reset section is exactly 16 bytes. This way, we're sure that our reset section will occupy [FFFF:FFF0 - FFFF:FFFF] and we won't have to do extra padding.

bios.ld:
MEMORY { ROM (rx) : org = 4096M - 512K, len = 512K }
SECTIONS { 
        .main 4096M - 4K    : { *(main) }
        .reset 4096M - 0x10 : { *(reset) }
        }
As you can see above, the ld script simply sets the ROM to be 512 KB in size, located at the end of the 4 GB (=4096M, since GNU ld doesn't know the G suffix yet) and, as indicated, we placed our bootcode segment (main) to start at the last 4 KB block of ROM.
The script should sort out our addresses as we want them then, and once ld has churned through it and produced a new object file (which I'll call bios.out), we should be able to use objcopy with option -j to extract the various binary payloads of interest to us.

Now, the problem is that objcopy -j will only extract the payload data. We could of course use a trick like.align 4K-0x10 at the end of our main section, but that would mean we'd then have to edit our bootcode size in two separate files when we update it. The smarter approach is to use the --gap-fill option of objcopy, to conveniently fill any gap between sections main and reset.

Another problem we face is that the above script only produces the binary data starting with the 4K at the end of the ROM, since the first section we extract (main) starts there. So at most objcopy will create 4 KB of data, far from the 512 KB we actually need. The solution: create a dummy section in our source, which I'll call begin and which I'll also use to put a BIOS ID string, and tell ld either explicitly, or better simply with a >ROM directive (so that we don't have to fill in the ROM size a 3rd time in the script) where it should reside.

After that, if we extract the begin, main and reset sections in order, with the --gap-fill option, we should have a 512 KB binary file with everything mapped where it should be. Neat!


Caveats

Before I present the actual code, a quick summary caveats & gotchas which might be of interest to you if you use this code as a base, and clarifying why everything in our sources isn't exactly as simple as what's exposed above:
  • Gotcha #1: Linux will bother you with a missing .igot.plt section. This looks like a known bug. As a workaround, we added a dummy section for it.
  • Gotcha #2: This is a minor annoyance, but GNU ld doesn't handle constants in the MEMORY section (it's a bug). So the ROM size has to be specified twice in the ld script, and we couldn't use two nice constants at the top, as anybody would think of doing.
  • Gotcha #3: objcopy can only extract sections that have the ALLOC attribute. This attribute is properly set on Windows as soon as you define a section, but not on Linux, where you have to add the flag explicitly (eg: .section main, "ax" for 'ALLOC' and 'CODE'). Note that you always can check how the attributes of your sections are set with objdump -x bios.out
  • Gotcha #4: Using a jmp init in the reset section may result in a target address that is offset by 2 on some platforms (this seems to be a binutils bug). Thus we have to handcraft it.
  • Gotcha #5 (this is getting better and better): On Windows, when using MinGW32 or cygwin (but not MinGW-w64), if you don't define an entrypoint in the linker script, your antivirus may erroneously identify bios.out as containing a Trojan and delete it. "Holy mother of false positives, Batman!" So we need to add an ENTRY(init) statement at the top of our section list.
  • Gotcha #6: DON'T waste your time trying to use XCode on OSX. It is riddled with problems. Use a proper GNU suite instead.

Sourcecode

bios.S:
/********************************************************************************/
/*                         VMware BIOS ROM example                              */
/*       Copyright (c) 2011 Pete Batard (pete@akeo.ie) -  Public Domain         */
/********************************************************************************/


/********************************************************************************/
/* GNU Assembler Settings:                                                      */
/********************************************************************************/
.intel_syntax noprefix  /* Use Intel assembler syntax (same as IDA Pro)         */
.code16                 /* After reset, the x86 CPU is in real / 16 bit mode    */
/********************************************************************************/


/********************************************************************************/
/* Macros:                                                                      */
/********************************************************************************/
/* This macro allows stackless subroutine calls                                 */
.macro  ROM_CALL addr
        mov  sp, offset 1f      /* Use a local label as we don't know the size  */
        jmp  \addr              /* of the jmp instruction (can be 2 or 3 bytes) */
1:      /* see http://sourceware.org/binutils/docs-2.21/as/Symbol-Names.html    */
.endm


/********************************************************************************/
/* Constants:                                                                   */
/********************************************************************************/
/* The VMware platform uses an emulated NS PC97338 as SuperIO                   */
SUPERIO_BASE  = 0x2e    /* Do NOT believe what you see in the BIOS bootblock:   */
                        /* the VMware SuperIO base is 0x2e and not 0x398.       */
PC97338_FER   = 0x00    /* PC97338 Function Enable Register                     */
PC97338_FAR   = 0x01    /* PC97338 Function Address Register                    */
PC97338_PTR   = 0x02    /* PC97338 Power and Test Register                      */

/* 16650 UART setup */
COM_BASE      = 0x3f8   /* Our default COM1 base, after SuperIO init            */
COM_RB        = 0x00    /* Receive Buffer (R)                                   */
COM_TB        = 0x00    /* Transmit Buffer (W)                                  */
COM_BRD_LO    = 0x00    /* Baud Rate Divisor LSB (when bit 7 of LCR is set)     */
COM_BRD_HI    = 0x01    /* Daud Rate Divisor MSB (when bit 7 of LCR is set)     */
COM_IER       = 0x01    /* Interrupt Enable Register                            */
COM_FCR       = 0x02    /* 16650 FIFO Control Register (W)                      */
COM_LCR       = 0x03    /* Line Control Register                                */
COM_MCR       = 0x04    /* Modem Control Registrer                              */
COM_LSR       = 0x05    /* Line Status Register                                 */
/********************************************************************************/


/********************************************************************************/
/* begin : Dummy section marking the very start of the BIOS.                    */
/* This allows the .rom binary to be filled to the right size with objcopy.     */
/********************************************************************************/
.section begin, "a"             /* The 'ALLOC' flag is needed for objcopy       */
        .ascii "VMBIOS v1.00"   /* Dummy ID string                              */
        .align 16
/********************************************************************************/


/********************************************************************************/
/* main:                                                                        */
/* This section will be relocated according to the bios.ld script.              */
/********************************************************************************/
/* 'init' doesn't have to be at the beginning, so you can move it around, as    */
/* long as remains reachable, with a short jump, from the .reset section.       */
.section main, "ax"
.globl init             /* init must be declared global for the linker and must */
init:                   /* point to the first instruction of your code section  */
        cli             /* NOTE: This sample BIOS runs with interrupts disabled */
        cld             /* String direction lookup: forward                     */
        mov  ax, cs     /* A real BIOS would keep a copy of ax, dx as well as   */
        mov  ds, ax     /* initialize fs, gs and possibly a GDT for protected   */
        mov  ss, ax     /* mode. We don't do any of this here.                  */

init_superio:
        mov  dx, SUPERIO_BASE   /* The PC97338 datasheet says we are supposed   */
        in   al, dx             /* to read this port twice on startup, but the  */
        in   al, dx             /* VMware virtual chip doesn't seem to care...  */

        /* Feed the SuperIO configuration values from a data section            */
        mov  si, offset superio_conf    /* Don't forget the 'offset' here!      */
        mov  cx, (serial_conf - superio_conf)/2
write_superio_conf:
        mov  ax, [si]
        ROM_CALL superio_out
        add  si, 0x02
        loop write_superio_conf

init_serial:            /* Init serial port                                     */
        mov  si, offset serial_conf
        mov  cx, (hello_string - serial_conf)/2
write_serial_conf:
        mov  ax, [si]
        ROM_CALL serial_out
        add  si, 0x02
        loop write_serial_conf

print_hello:            /* Print a string                                       */
        mov  si, offset hello_string
        ROM_CALL print_string

serial_repeater:        /* End the BIOS with a simple serial repeater           */
        ROM_CALL readchar
        ROM_CALL putchar
        jmp serial_repeater

/********************************************************************************/
/* Subroutines:                                                                 */
/********************************************************************************/
superio_out:            /* AL (IN): Register index,  AH (IN): Data to write     */
        mov  dx, SUPERIO_BASE
        out  dx, al
        inc  dx
        xchg al, ah
        out  dx, al
        jmp  sp


serial_out:             /* AL (IN): COM Register index, AH (IN): Data to Write  */
        mov  dx, COM_BASE
        add  dl, al     /* Unless something is wrong, we won't overflow to DH   */
        mov  al, ah
        out  dx, al
        jmp  sp


putchar:                /* AL (IN): character to print                          */
        mov  dx, COM_BASE + COM_LSR
        mov  ah, al
tx_wait:
        in   al, dx
        and  al, 0x20   /* Check that transmit register is empty                */
        jz   tx_wait
        mov  dx, COM_BASE + COM_TB
        mov  al, ah
        out  dx, al
        jmp  sp


readchar:               /* AL (OUT): character read from serial                 */
        mov  dx, COM_BASE + COM_LSR
rx_wait:
        in   al, dx
        and  al, 0x01
        jz   rx_wait
        mov  dx, COM_BASE + COM_RB
        in   al, dx
        jmp  sp


print_string:           /* SI (IN): offset to NUL terminated string             */
        lodsb
        or   al, al
        jnz  write_char
        jmp  sp
write_char:
        shl  esp, 0x10  /* We're calling a sub from a sub => preserve SP        */
        ROM_CALL putchar
        shr  esp, 0x10  /* Restore SP                                           */
        jmp  print_string


/********************************************************************************/
/* Data:                                                                        */
/********************************************************************************/
superio_conf:
/* http://www.datasheetcatalog.org/datasheet/nationalsemiconductor/PC97338.pdf  */
        .byte PC97338_FER, 0x0f         /* Enable COM, PAR and FDC              */
        .byte PC97338_FAR, 0x10         /* LPT=378, COM1=3F8, COM2=2F8          */
        .byte PC97338_PTR, 0x00         /* Make sure COM1 test mode is cleared  */
serial_conf:    /* See http://www.versalogic.com/kb/KB.asp?KBID=1395            */
        .byte COM_MCR,     0x00         /* RTS/DTS off, disable loopback        */
        .byte COM_FCR,     0x07         /* Enable & reset FIFOs. DMA mode 0.    */
        .byte COM_LCR,     0x80         /* Set DLAB (access baudrate registers) */
        .byte COM_BRD_LO,  0x01         /* Baud Rate 115200 = 0x0001            */
        .byte COM_BRD_HI,  0x00
        .byte COM_LCR,     0x03         /* Unset DLAB. Set 8N1 mode             */
hello_string:
        .string "\r\nHello BIOS world!\r\n" /* .string adds a NUL terminator    */
/********************************************************************************/


/********************************************************************************/
/* reset: this section must reside at 0xfffffff0, and be exactly 16 bytes       */
/********************************************************************************/
.section reset, "ax"
        /* Issue a manual jmp to work around a binutils bug.                    */
        /* See coreboot's src/cpu/x86/16bit/reset16.inc                         */
        .byte  0xe9
        .int   init - ( . + 2 )
        .align 16, 0xff /* fills section to end of ROM (with 0xFF)              */
/********************************************************************************/

bios.ld:
OUTPUT_ARCH(i8086)                      /* i386 for 32 bit, i8086 for 16 bit       */

/* Set the variable below to the address you want the "main" section, from bios.S, */
/* to be located. The BIOS should be located at the area just below 4GB (4096 MB). */
main_address = 4096M - 4K;              /* Use the last 4K block                   */

/* Set the BIOS size below (both locations) according to your target flash size    */
MEMORY {
        ROM (rx) : org = 4096M - 512K, len = 512K
}

/* You shouldn't have to modify anything below this                                */
SECTIONS {
        ENTRY(init)                     /* To avoid antivirus false positives      */
        /* Sanity check on the init entrypoint                                     */
        _assert = ASSERT(init >= 4096M - 64K, 
                "'init' entrypoint too low - it needs to reside in the last 64K.");
        .begin : {      /* NB: ld section labels MUST be 6 letters or less         */
                *(begin)
        } >ROM          /* Places this first section at the beginning of the ROM   */
        /* the --gap-fill option of objcopy will be used to fill the gap to .main  */
        .main main_address : {
                *(main)
        }
        .reset 4096M - 0x10 : {         /* First instruction executed after reset  */
                *(reset)
        }
        .igot 0 : {                     /* Required on Linux                       */
                *(.igot.plt)
        }
}

Makefile (IMPORTANT: if you copy/paste, you will have to restore the tabs at the beginning of each line that start with a space):
ASM       = gcc
CC        = gcc
LD        = ld
OBJDUMP   = objdump
OBJCOPY   = objcopy

CFLAGS    = -m32 -nostartfiles

OBJECTS   = bios.o
TARGET    = bios
MEMLAYOUT = xMemLayout.map

.PHONY: all clean

all: $(TARGET).rom

clean:
 @-rm -f -v *.o $(TARGET).out $(MEMLAYOUT)

%.o: %.c Makefile
 @echo "[CC]  $@"
 @$(CC) -c -o $*.o $(CFLAGS) $<

%.o: %.S Makefile
 @echo "[AS]  $<"
 @$(ASM) -c -o $*.o $(CFLAGS) $<

# Produce a disassembly dump of the main section, for verification purposes
dis: $(TARGET).out
 @echo "[DIS] $<"
 @$(OBJCOPY) -O binary -j .main --set-section-flags .main=alloc,load,readonly,code $< main.bin
 @$(OBJDUMP) -D -bbinary -mi8086 -Mintel main.bin | less
 @-rm -f main.bin

$(TARGET).out: $(OBJECTS) $(TARGET).ld
 @echo "[LD]  $@"
 @$(LD) $(LDFLAGS) -T$(TARGET).ld -o $@ $(OBJECTS) -Map $(MEMLAYOUT)

$(TARGET).rom: $(TARGET).out
 @echo "[ROM] $@"
 @# Note: -j only works for sections that have the 'ALLOC' flag set
 @$(OBJCOPY) -O binary -j .begin -j .main -j .reset --gap-fill=0x0ff $< $@

Compiling and testing
  • Copy the Makefile, bios.S and bios.ld from above, or extract the files from the archive below to a directory
  • run make. You should end up with a 512 KB bios.rom file
  • Copy bios.rom to your target VMware image directory and manually edit your .vmx file to have the line:
    bios440.filename = "bios.rom"
  • Edit your virtual machine settings to make sure it has a serial port.
    The preferred method to access the serial console is to use a null modem emulator, such as com0com, a signed version of which I made available in the next post.
    Otherwise, you can use either use an actual host COM port (you'll need a null modem cable to another serial port), output to file (which is the easiest way to confirm that the BIOS works, as you will see some output there, but you won't be able to test the repeater) or a named pipe (with the end of pipe set for an application such as putty - the problem with using a pipe however being that you can only connect to it after the VM is started, so you will likely miss the initial serial output).
    The Serial port needs to be set to 115200 bauds, 8N1.
  • Run the machine. You should see an "hello world" message printed out, and, provided your serial configuration allows input, anything you type should be echoed back on your terminal

Goodies
  • vmbios-1.1.tgz: an archive containing all the files above, as well as the generated BIOS.
  • Note that you can issue 'make dis' to get a disassembly output of the main section if needed.
  • Note that for reference, a memory map called xMemLayout.map is also produced during the build.
  • BIOS Disassembly Ninjutsu Uncovered, by Darmawan Salihun (PDF): If you're going to do start with BIOS modding, this should be your reference. Or see the author's page.

Happy BIOS hacking!

2011-06-05

Extracting and using a modified VMWare Player BIOS or UEFI firmware

(updated 2016.05.17 for VMWare UEFI firmware extraction)

One day or another, you may want to play with BIOS/UEFI firmware modification. But before jumping into physical motherboard flash alteration, where the consequences of a mishap can be difficult to salvage, experimenting with the BIOS/UEFI in a virtual environment sounds like a sound first step. You have of course the opportunity to do so using bochs, however the ubiquitous nature and ease of use of the (freely available) VMware Player can turn the latter in a much better candidate for the job if you don't really care about the extra features that bochs brings in. This post details how one can extract the BIOS/UEFI firmwares used by the VMWare Player, and how to setup the player to use a modified one.
  1. Download and install VMware Player. The current version of VMware Player is 12, and the download can be obtained from this page (freely, but after e-mail registration). Binaries are available for Windows and Linux, in both 32 and 64 bit versions.
  2. Extract the BIOS from the VMware executables by following the instructions below:

    • Windows: Download and install 7-zip (which any reasonable Windows user should have installed anyway). Then navigate to your VMware Player installation directory and locate the vmware-vmx.exe application (notice the -vmx here). It should reside in the same directory for 32 bit, or in the x64\ directory for 64 bit. Open it in 7-Zip and go to the .rsrc\BINRES\ directory.
      For the BIOS (internally called bios440, as it emulates an intel 440BX chipset), you need to look for a file exactly 524 288 bytes (512 KB) in size. On current versions, there should be only one, called '6006'. This is the BIOS file we are after, so just extract it to a directory of your choice.
      For the UEFI firmware, you should look for a file that is 2 097 152 bytes (2 MB) in size. Because VMWare can emulate both 32 and 64-bit platforms, you will find that there exists 2 of these. At the time of writing this article, just know that '6020' is the IA32 UEFI firmware and '6021' is the X64 UEFI firmware.
    • Linux: From a terminal, navigate to the directory vmware-vmx binary resides (default is /usr/lib/vmware/bin. Issue the following set of commands (copied from the Arch Linux VMware page):
      $ objcopy /usr/lib/vmware/bin/vmware-vmx -O binary -j bios440 --set-section-flags bios440=a bios440.rom.Z
      $ perl -e 'use Compress::Zlib; my $v; read STDIN, $v, '$(stat -c%s "./bios440.rom.Z")'; $v = uncompress($v); print $v;' < bios440.rom.Z > bios440.rom
      Remember that you can always use objdump -h to find the various sections before using objcopy, in case the BIOS is no longer called bios440.rom
  3. Edit the BIOS or UEFI firmware as you see fit. Note that the BIOS VMware uses is of type Phoenix.
    If you are on Windows, you can download a full version of Phoenix Bios Editor Pro v2.1.0.0 from Intel under the name BIOS Logo Change Utility (or simply search for "Phoenix" on the Intel Download Center). If you install this tool on Vista or later, you will have to run Phoenix Bios Editor Pro as Administrator, and possibly in a 32 bit environment, for it to work.
  4. Once your BIOS/UEFI file is modified according to your needs, locate the Virtual Machine you plan to test your firmware with and copy your modified file into its directory. Then, edit the .vmx file to add one of these lines, according to the type of platform you want to run.
    • For BIOS, you only need to add:
      bios440.filename = "bios440.rom"
    • For 32-bit UEFI, you will need to have something like:
      firmware = "efi"
      efi32.filename = "efi32.rom"
    • For 64-bit UEFI, you will need:
      firmware = "efi"
      efi32.filename = "efi64.rom"
  5. Run the image, and it should use your modified firmware. If you have simply modded the original BIOS using Phoenix Bios Editor, a good way to confirm that the VM is using your custom BIOS is to change the 'Quiet Boot Logo' section, which contains the VMware logos you see during BIOS execution.
Also note that, as pointed out on this page, if you are usingVMWare Fusion, you may actually be able to extract the various embedded binaries using the -e switch (the page linked also provides a list of all the binaries you can extract). Sadly however, this switch doesn't apply for VMWare Player on Windows...

2011-05-05

Installing the drivers for an HP LaserJet 6P on Windows

From the time where printer manufacturers were still producing near indestructible models. Plus, 5 years on, and I'm still using the toner cartridge that came with the printer. Add a USB → IEEE1284 adapter, plug into a NAS or plug computer, and enjoy your network printer.

Now, I did spend some time getting cups going, but I found it a bit flaky, so I reverted to direct samba access to the raw device. But that means the need for a driver on Windows, and Windows 7 considers that the HP printing dinosaurs are too old to be supported by default. On the other hand, that probably means you'll be able to find a cheap one on ebay.

Now, for those of us who know better and still want to do our Windows printing through a trusted HP LaserJet, the procedure to obtain the drivers is as follows (should work for the whole range of old HP printers, and not just the 6P):

  1. Open IE (won't work in Firefox or Chrome)
  2. Go to http://catalog.update.microsoft.com
  3. Search your model, e.g. "LaserJet 6P". This should list a set of drivers for Windows 7, Windows Server 2008, etc. that are also of course compatible with Windows 10. Gotta wonder what the "search for drivers online" feature of Windows is really used for.
  4. Select the most relevant driver, e.g. "Microsoft driver update for HP LaserJet 6P"
  5. Click "View Basket"
  6. Click "Download"
  7. You'll get something like X86-all-XXXX.cab or AMD64-all-XXXX.cab file that you need to save. Be mindful that if you try to use the AMD64 package on a 32 bit version of Windows, or X86 on 64 bit, it will not work, so make sure you get the right one.
  8. Open the .cab file you just saved using 7-Zip, and extract all the files into some directory.
  9. Install your printer and select "Have Disk" when prompted for a driver. Then point to the directory where you extracted the files.
  10. Enjoy another many years use of your LaserJet printer. In all fairness, the early LaserJet printers are so indestructible that you'll probably die long before your printer does.

2011-04-08

Quick and dirty script to compile binutils with all targets

Yeah, yeah, I know you're not supposed to compile binutils in situ and whatnot, but I need a fully functional objdump/objcopy for testing stuff and I don't have all day.

The following script, when ran from the binutils-x.yz directory will compile binutils with all the targets. Worked 8 years ago, works today. Who'd want more?
#!/bin/sh

ROOT=`pwd`

for i in libiberty intl bfd opcodes binutils
do
cd $ROOT/$i
./configure --enable-targets=all --disable-nls
make -j4
done
You'll get a bunch of warnings about configure: WARNING: unrecognized options: --enable-targets, --disable-nls for some of the subprojects, but you can ignore them. Once you're done, you should have a nice objdump executable in the binutils/ directory, which, when poked with option -i should report something like this:
/usr/src/binutils-2.23.51-1/binutils/objdump: supported targets: pe-i386 a.out.adobe a.out-zero-big a.out-mips-little epoc-pe-arm-big epoc-pe-arm-little epoc-pei-arm-big epoc-pei-arm-little pe-arm-wince-big pe-arm-wince-little pei-arm-wince-big pei-arm-wince-little coff-arm-big coff-arm-little a.out-arm-netbsd pe-arm-big pe-arm-little pei-arm-big pei-arm-little b.out.big b.out.little elf32-avr elf32-bfin elf32-bfinfdpic elf32-big elf32-bigarc elf32-bigarm elf32-bigarm-symbian elf32-bigarm-vxworks elf32-bigmips elf32-bigmips-vxworks elf32-bigmoxie elf32-bignios2 elf32-cr16 elf32-cr16c elf32-cris elf32-crx elf32-d10v elf32-d30v elf32-dlx elf32-epiphany elf32-fr30 elf32-frv elf32-frvfdpic elf32-h8300 elf32-hppa-linux elf32-hppa-netbsd elf32-hppa elf32-i370 elf32-i386-freebsd elf32-i386-nacl elf32-i386-sol2 elf32-i386-vxworks elf32-i386 elf32-i860-little elf32-i860 elf32-i960 elf32-ip2k elf32-iq2000 elf32-lm32 elf32-little elf32-littlearc elf32-littlearm elf32-littlearm-symbian elf32-littlearm-vxworks elf32-littlemips elf32-littlemips-vxworks elf32-littlemoxie elf32-littlenios2 elf32-m32c elf32-m32r elf32-m32rle elf32-m32r-linux elf32-m32rle-linux elf32-m68hc11 elf32-m68hc12 elf32-m68k elf32-m88k elf32-mcore-big elf32-mcore-little elf32-mep elf32-metag elf32-microblaze elf32-mn10200 elf32-mn10300 elf32-mt elf32-msp430 elf32-openrisc elf32-or32 elf32-pj elf32-pjl elf32-powerpc elf32-powerpc-vxworks elf32-powerpcle elf32-powerpc-freebsd elf32-rl78 elf32-rx-be elf32-rx-be-ns elf32-rx-le elf32-s390 elf32-sh elf32-shbig-fdpic elf32-shbig-linux elf32-sh-fdpic elf32-shl elf32-shl-symbian elf32-sh-linux elf32-shl-nbsd elf32-shl-vxworks elf32-sh-nbsd elf32-sh-vxworks elf32-sparc elf32-sparc-sol2 elf32-sparc-vxworks elf32-spu elf32-tic6x-be elf32-tic6x-le elf32-tilegx-be elf32-tilegx-le elf32-tilepro elf32-tradbigmips elf32-tradlittlemips elf32-tradbigmips-freebsd elf32-tradlittlemips-freebsd elf32-us-cris elf32-v850 elf32-v850-rh850 elf32-vax elf32-xc16x elf32-xgate elf32-xstormy16 elf32-xtensa-be elf32-xtensa-le pe-powerpc pei-powerpc pe-powerpcle pei-powerpcle a.out-cris ecoff-bigmips ecoff-biglittlemips ecoff-littlemips coff-go32 coff-go32-exe coff-h8300 coff-h8500 a.out-hp300hpux a.out-i386 a.out-i386-bsd coff-i386 a.out-i386-freebsd a.out-i386-lynx coff-i386-lynx msdos a.out-i386-netbsd i386os9k pei-i386 coff-i860 coff-Intel-big coff-Intel-little ieee coff-m68k coff-m68k-un a.out-m68k-netbsd coff-m68k-sysv coff-m88kbcs a.out-m88k-mach3 a.out-m88k-openbsd mach-o-be mach-o-le mach-o-fat mach-o-i386 pe-mcore-big pe-mcore-little pei-mcore-big pei-mcore-little pe-mips pei-mips a.out-newsos3 nlm32-i386 nlm32-powerpc nlm32-sparc coff-or32-big a.out-pc532-mach a.out-ns32k-netbsd a.out-pdp11 pef pef-xlib ppcboot aixcoff-rs6000 coff-sh-small coff-sh coff-shl-small coff-shl pe-shl pei-shl som coff-sparc a.out-sparc-little a.out-sparc-linux a.out-sparc-lynx coff-sparc-lynx a.out-sparc-netbsd a.out-sunos-big sym a.out-tic30 coff-tic30 coff0-beh-c54x coff0-c54x coff1-beh-c54x coff1-c54x coff2-beh-c54x coff2-c54x coff-tic80 a.out-vax-bsd a.out-vax-netbsd a.out-vax1k-netbsd versados vms-libtxt coff-w65 coff-we32k coff-z80 coff-z8k elf32-am33lin srec symbolsrec verilog tekhex binary ihex
/usr/src/binutils-2.23.51-1/binutils/objdump: supported architectures: aarch64 alpha alpha:ev4 alpha:ev5 alpha:ev6 arc arc5 base arc6 arc7 arc8 arm armv2 armv2a armv3 armv3m armv4 armv4t armv5 armv5t armv5te xscale ep9312 iwmmxt iwmmxt2 avr avr:1 avr:2 avr:25 avr:3 avr:31 avr:35 avr:4 avr:5 avr:51 avr:6 avr:101 avr:102 avr:103 avr:104 avr:105 avr:106 avr:107 bfin cr16 cr16c cris crisv32 cris:common_v10_v32 crx d10v d10v:ts2 d10v:ts3 d30v dlx epiphany32 epiphany16 fr30 frv tomcat simple fr550 fr500 fr450 fr400 fr300 h8300 h8300h h8300s h8300hn h8300sn h8300sx h8300sxn h8500 hppa1.1 hppa2.0w hppa2.0 hppa1.0 i370:common i370:360 i370:370 i386 i386:x86-64 i386:x64-32 i8086 i386:intel i386:x86-64:intel i386:x64-32:intel i860 i960:core i960:ka_sa i960:kb_sb i960:mc i960:xa i960:ca i960:jx i960:hx ia64-elf64 ia64-elf32 ip2022ext ip2022 iq2000 iq10 k1om k1om:intel l1om l1om:intel lm32 m16c m32c m32r m32rx m32r2 m68hc11 m68hc12 m68hc12 m9s12x m9s12xg m68k m68k:68000m68k:68008 m68k:68010 m68k:68020 m68k:68030 m68k:68040 m68k:68060 m68k:cpu32 m68k:fido m68k:isa-a:nodiv m68k:isa-a m68k:isa-a:mac m68k:isa-a:emac m68k:isa-aplus m68k:isa-aplus:mac m68k:isa-aplus:emac m68k:isa-b:nousp m68k:isa-b:nousp:mac m68k:isa-b:nousp:emac m68k:isa-b m68k:isa-b:mac m68k:isa-b:emac m68k:isa-b:float m68k:isa-b:float:mac m68k:isa-b:float:emac m68k:isa-c m68k:isa-c:mac m68k:isa-c:emac m68k:isa-c:nodiv m68k:isa-c:nodiv:mac m68k:isa-c:nodiv:emac m68k:5200 m68k:5206e m68k:5307 m68k:5407 m68k:528x m68k:521x m68k:5249 m68k:547x m68k:548x m68k:cfv4e m88k:88100 MCore mep h1 c5 metag MicroBlaze mips mips:3000 mips:3900 mips:4000 mips:4010 mips:4100 mips:4111 mips:4120 mips:4300 mips:4400 mips:4600 mips:4650 mips:5000 mips:5400 mips:5500 mips:5900 mips:6000 mips:7000 mips:8000 mips:9000 mips:10000 mips:12000 mips:14000 mips:16000 mips:16 mips:mips5 mips:isa32 mips:isa32r2 mips:isa64 mips:isa64r2 mips:sb1 mips:loongson_2e mips:loongson_2f mips:loongson_3a mips:octeon mips:octeon+ mips:octeon2 mips:xlr mips:micromips mmix mn10200 mn10300 am33 am33-2 moxie msp:14 msp:11 msp:110 msp:12 msp:13 msp:14msp:15 msp:16 msp:21 msp:31 msp:32 msp:33 msp:41 msp:42 msp:43 msp:44 ms1 ms1-003 ms2 nios2 ns32k:32032 ns32k:32532 openrisc or32 pdp11 powerpc:common powerpc:common64 powerpc:603 powerpc:EC603e powerpc:604 powerpc:403 powerpc:601 powerpc:620 powerpc:630 powerpc:a35 powerpc:rs64ii powerpc:rs64iii powerpc:7400 powerpc:e500 powerpc:e500mc powerpc:e500mc64 powerpc:MPC8XX powerpc:750 powerpc:titan powerpc:vle powerpc:e5500 powerpc:e6500 rs6000:6000 rs6000:rs1 rs6000:rsc rs6000:rs2 rl78 rx rx s390:31-bit s390:64-bit score7 score3 sh sh2 sh2e sh-dsp sh3 sh3-nommu sh3-dsp sh3e sh4 sh4a sh4al-dsp sh4-nofpu sh4-nommu-nofpu sh4a-nofpu sh2a sh2a-nofpu sh2a-nofpu-or-sh4-nommu-nofpu sh2a-nofpu-or-sh3-nommu sh2a-or-sh4 sh2a-or-sh3e sh5 sparc sparc:sparclet sparc:sparclite sparc:v8plus sparc:v8plusa sparc:sparclite_le sparc:v9 sparc:v9a sparc:v8plusb sparc:v9b spu:256K tms320c30 tms320c4x tms320c3x tms320c54x tic6x tic80 tilegx tilegx32 tilepro v850 (using old gcc ABI) v850e3v5 (using old gcc ABI) v850e2v4 (using old gcc ABI) v850e2v3 (using old gcc ABI) v850e2 (using old gcc ABI) v850e1 (using old gcc ABI) v850e (using old gcc ABI) v850-rh850 v850e3v5 v850e2v4 v850e2v3 v850e2 v850e1 v850e vax w65 we32k:32000 xstormy16 xtensa xc16x xc16xl xc16xs xgate z80-any z80-strict z80 z80-full r800 z8001 z8002