Writing code to access the hardware under Linux is quite a bit more difficult since (in most cases) a separate device driver must be coded and installed into the kernel. The protection mechanisms that prevent harm to the system by misbehaving user processes stymie the diagnostic developer. This article explains the porting 16-bit MS-DOS Diagnostics source code developed using Visual C++ 1.52 to the GNU C++ compiler and Linux OS environment.
A user process running with root privilege can access I/O ports and memory, but can't disable or handle interrupts. PCI configuration space access isn't safe either because consecutive writes to the configuration address and data ports are required. Tight control of time delays isn't possible either since the user process can be put to sleep at any time. See Linux Device Drivers section "Doing it in User Space" in [RUB] Chapter 2 on page 36.
The GCC (GNU C Compiler) that comes with the Linux Distribution handles inline assembly code and both the CPUID and RDTSC instructions will execute correctly in a user context. GCC also offers 64-bit signed and unsigned integer math with the long long types. These capabilities cover the last bullet above. A single Linux module will handle all of the others except the interrupt handlers.
Modules may also dynamically register nodes in the /proc filesystem. The most common use of a /proc node is to deliver a buffer of data to the reader.
The number of modules required can be cut down considerably by creating a single module to provide general-purpose access to the kernel for each of the resource classes. The Linux Wormhole driver module provides the following services:
ioctl (int fd, int request, char *argp);The fd parameter is the file descriptor for the module obtained by a previous open(2) call.
The request parameter identifies the service required of the module. A symbol for example WORM_IOP_R is defined for each request. Each request also has an associated structure type. The argp parameter points to the structure provided by the caller. Data is passed to and from the module through this structure. The kernel calls copy_from_user and get_user_ret are used by the module to get data from the user. The kernel call put_user_ret is used to write data back to user space. See asm/uaccess.h.
The Wormhole module source code uses the macros inb, outb, inw, outw, inl, outl provided by asm/io.h to implement the I/O port access. See the section "Using I/O Ports" in [RUB] Chapter 8 on page 164.
The first is based on the 18.2 Hz (54.94ms) DOS system clock. Linux modules can provide delays in increments of the system timer interrupt, which is currently 10ms. The Wormhole ioctl request WORM_DELAY_MS takes the number of milliseconds to delay as the argument. The driver determines the smallest system timer value that will occur after the delay has expired, sets a timeout, and sleeps. The driver will wake up and return to the user process when the timeout occurs.
The second DOS diagnostics code delay function performs microsecond resolution delays based on the time it takes to write to a non-decoded ISA bus port. This is somewhere in the neighborhood of 700-1000ns. Linux offers the kernel function udelay defined in <linux/delay.h>. See the section "Short Delays" in [RUB] Chapter 6 on page 137. This function bases the delay time on a software loop calibrated at boot time. Experiments show the delay time to be accurate with the best accuracy for delays below 100us. This function is only suitable for small delays (up to around 1ms) since it busy waits, preventing other tasks from running. The Wormhole ioctl request WORM_DELAY_US passes the number of microseconds to delay to the kernel function udelay.
In Linux the processor is running in protected mode and the memory management unit is enabled. The desired physical memory location must be mapped via the page tables and its virtual address must be known. Linux offers the kernel function vremap, which will create the virtual to physical mapping for a block of memory. The physical address must be above the top of DRAM memory. Kernel function ioremap can be used to map in memory-mapped devices and PCI memory. The Wormhole requests WORM_PCIMEM_R and WORM_PCIMEM_W will map a page, perform one 32-bit or 8-bit read or write access then unmap the page. See the section "High PCI Memory" in [RUB] Chapter 8 on page 175.
The Wormhole requests WORM_BIOSMEM_R and WORM_BIOSMEM_W access the System BIOS area below 1M. They use kernel macros readb, readl, writeb, and writel to perform the memory access. See the section "ISA Memory Below 1M" in [RUB] Chapter 8 on page 171.
The Wormhole driver performance is limited by the context switch overhead of the ioctl call. If thousands of operations are required the total time will be significantly longer than the time consumed by a dedicated module.
The Wormhole driver only does one access per call. If several accesses must be done atomically, with no intervening task switches, the Wormhole driver is unsuitable.
The Wormhole driver and Linux user process cannot offer real time response. In the diagnostics environment this problem can be limited by running with no users logged in. Otherwise a dedicated module is required.
The Wormhole driver does not control access to the kernel resources. It is the responsibility of the caller to not break anything in the kernel or change the state of device registers of devices for which are modules are running.