O'Reilly logo

Linux Device Drivers, Second Edition by Alessandro Rubini, Jonathan Corbet

Stay ahead with the world's most comprehensive technology and business learning platform.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, tutorials, and more.

Start Free Trial

No credit card required

Backward Compatibility

As with other parts of the kernel, both memory mapping and DMA have seen a number of changes over the years. This section describes the things a driver writer must take into account in order to write portable code.

Changes to Memory Management

The 2.3 development series saw major changes in the way memory management worked. The 2.2 kernel was quite limited in the amount of memory it could use, especially on 32-bit processors. With 2.4, those limits have been lifted; Linux is now able to manage all the memory that the processor is able to address. Some things have had to change to make all this possible; overall, however, the scale of the changes at the API level is surprisingly small.

As we have seen, the 2.4 kernel makes extensive use of pointers to struct page to refer to specific pages in memory. This structure has been present in Linux for a long time, but it was not previously used to refer to the pages themselves; instead, the kernel used logical addresses.

Thus, for example, pte_page returned an unsigned long value instead of struct page *. The virt_to_page macro did not exist at all; if you needed to find a struct page entry you had to go directly to the memory map to get it. The macro MAP_NR would turn a logical address into an index in mem_map; thus, the current virt_to_page macro could be defined (and, in sysdep.h in the sample code, is defined) as follows:

#ifdef MAP_NR
#define virt_to_page(page) (mem_map + MAP_NR(page))
#endif

The MAP_NR macro went away when virt_to_page was introduced. The get_page macro also didn’t exist prior to 2.4, so sysdep.h defines it as follows:

#ifndef get_page
#  define get_page(p) atomic_inc(&(p)->count)
#endif

struct page has also changed with time; in particular, the virtual field is present in Linux 2.4 only.

The page_table_lock was introduced in 2.3.10. Earlier code would obtain the “big kernel lock” (by calling lock_kernel and unlock_kernel) before traversing page tables.

The vm_area_struct structure saw a number of changes in the 2.3 development series, and more in 2.1. These included the following:

  • The vm_pgoff field was called vm_offset in 2.2 and before. It was an offset in bytes, not pages.

  • The vm_private_data field did not exist in Linux 2.2, so drivers had no way of storing their own information in the VMA. A number of them did so anyway, using the vm_pte field, but it would be safer to obtain the minor device number from vm_file and use it to retrieve the needed information.

  • The 2.4 kernel initializes the vm_file pointer before calling the mmap method. In 2.2, drivers had to assign that value themselves, using the file structure passed in as an argument.

  • The vm_file pointer did not exist at all in 2.0 kernels; instead, there was a vm_inode pointer pointing to the inode structure. This field needed to be assigned by the driver; it was also necessary to increment inode->i_count in the mmap method.

  • The VM_RESERVED flag was added in kernel 2.4.0-test10.

There have also been changes to the the various vm_ops methods stored in the VMA:

  • 2.2 and earlier kernels had a method called advise, which was never actually used by the kernel. There was also a swapin method, which was used to bring in memory from backing store; it was not generally of interest to driver writers.

  • The nopage and wppage methods returned unsigned long (i.e., a logical address) in 2.2, rather than struct page *.

  • The NOPAGE_SIGBUS and NOPAGE_OOM return codes for nopage did not exist. nopage simply returned 0 to indicate a problem and send a bus signal to the affected process.

Because nopage used to return unsigned long, its job was to return the logical address of the page of interest, rather than its mem_map entry.

There was, of course, no high-memory support in older kernels. All memory had logical addresses, and the kmap and kunmap functions did not exist.

In the 2.0 kernel, the init_mm structure was not exported to modules. Thus, a module that wished to access init_mm had to dig through the task table to find it (as part of the init process). When running on a 2.0 kernel, scullp finds init_mm with this bit of code:

static struct mm_struct *init_mm_ptr;
#define init_mm (*init_mm_ptr) /* to avoid ifdefs later */

static void retrieve_init_mm_ptr(void)
{
    struct task_struct *p;

    for (p = current ; (p = p->next_task) != current ; )
        if (p->pid == 0)
            break;

    init_mm_ptr = p->mm;
}

The 2.0 kernel also lacked the distinction between logical and physical addresses, so the __va and __pa macros did not exist. There was no need for them at that time.

Another thing the 2.0 kernel did not have was maintenance of the module’s usage count in the presence of memory-mapped areas. Drivers that implement mmap under 2.0 need to provide open and close VMA operations to adjust the usage count themselves. The sample source modules that implement mmap provide these operations.

Finally, the 2.0 version of the driver mmap method, like most others, had a struct inode argument; the method’s prototype was

int (*mmap)(struct inode *inode, struct file *filp, 
            struct vm_area_struct *vma);

Changes to DMA

The PCI DMA interface as described earlier did not exist prior to kernel 2.3.41. Before then, DMA was handled in a more direct—and system-dependent—way. Buffers were “mapped” by calling virt_to_bus, and there was no general interface for handling bus-mapping registers.

For those who need to write portable PCI drivers, sysdep.h in the sample code includes a simple implementation of the 2.4 DMA interface that may be used on older kernels.

The ISA interface, on the other hand, is almost unchanged since Linux 2.0. ISA is an old architecture, after all, and there have not been a whole lot of changes to keep up with. The only addition was the DMA spinlock in 2.2; prior to that kernel, there was no need to protect against conflicting access to the DMA controller. Versions of these functions have been defined in sysdep.h; they disable and restore interrupts, but perform no other function.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, interactive tutorials, and more.

Start Free Trial

No credit card required