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.
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 calledvm_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 thevm_pte
field, but it would be safer to obtain the minor device number fromvm_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 thefile
structure passed in as an argument.The
vm_file
pointer did not exist at all in 2.0 kernels; instead, there was avm_inode
pointer pointing to theinode
structure. This field needed to be assigned by the driver; it was also necessary to incrementinode->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 thanstruct page *
.The
NOPAGE_SIGBUS
andNOPAGE_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);
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.
Get Linux Device Drivers, Second Edition now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.