| Linux Kernel & Device Driver Programming |
Please do not be confused by two slightly different uses of "memory mapping" in Linux.
First, there is the mmap system call, which can be used to "map" a portion (or all) of a file onto a portion of a processes virtual memory space. The mapping is implemented using the page-swapping machinery provided to support virtual memory.
Second, there is memory-mapped I/O, in which some registers and/or local memory of an I/O (controller) device are mapped to physical memory addresses. That is, when a reference is made to a particular physical memory address, that reference is not routed to a normal (RAM) memory device; instead, it is routed to the appropriate I/O device.
These two concepts sometimes are used in combination, since when a program running on the CPU wants to access I/O device memory (that is, to do memory-mapped I/O), the paged virtual memory system needs to map some virtual address to the physical address, which is in turn mapped to the I/O address.
(From a VIA Technologies web page, at http://www.via.com.)
Observe the various buses, the devices connected to each of them, and the roles of the Bridges in connecting the busses.
The ioread8, ioread16, etc. replace older-style readb, readw, etc., which are now deprecated.
Note: The links below are to copies of the module files that I have hand-converted to HTML. It may also be helpful to use the cross-referenced version of the example short.
default base I/O port number (standard PC parallel port)
short_queue wait queue declaration
short_incr_bp (It is not clear to me, at the moment, why the comment says this is "atomic", when it is done in two steps separated by a barrier. It is also unclear why the barrier call is needed in this case, unless to prevent merging with code preceding the call.)
short_open - remaps fops, for just one node
short_release - does nothing
short_modes - different types of short devices
The port offset frombase and the mode are encoded in the minor device number.
kmalloc() is used here with allocation "priority" GFP_KERNEL, which means the process calling do_short_read may block
The
insb call reads a string of length count, byte-wise, from
port address to the buffer designated by ptr.
The rmb() call guarantees any reads appearing before the barrier
are completed prior to the execution of any subsequent read.
The inb call reads a single byte from port address.
The ioread8 call also reads a single byte from port address, but this form works for memory-mapped I/O.
The inb_p call also reads a single byte from port address, but will pause (only if necessary) to make sure the device has time to respond. ("_p" is for "pause")
The copy_to_user function is used to copy the data to the return location in user space.
The kfree function is used to free up the kernel memory buffer.
Why is do_short_read() is introduced, rather than the code being put directly into short_read?
The outb_p call writes a single byte from
port address. It will pause (only if necessary) to make sure the device
has time to respond. ("_p" is for "pause")
The wmb() call guarantees any writes appearing before the barrier
are completed prior to the execution of any subsequent write.
The outsb call writes a string of length count, byte-wise, from
port address to the buffer designated by ptr.
The outb call writes a single byte to port address.
The iowrite8 call also writes a single byte to port address, but this form works for memory-mapped I/O.
checking that the I/O port range is not in use, and reserving it.
If the I/O is memory-mapped, we instead check and then reserve the I/O memory range. In addition, we have to remap it.
register_chrdev is used to register the device
Note that dynamic assignment of the major device number is used here, if the default initial value of zero for major is not overridden by a module parameter value.
We will use this example again later, when we look at interrupt handling.
e.g.,
0000-001f : dma1 0020-003f : pic1 0040-005f : timer 0060-006f : keyboard 0070-007f : rtc 0080-008f : dma page reg 00a0-00bf : pic2 00c0-00df : dma2 00f0-00ff : fpu 0170-0177 : ide1 01f0-01f7 : ide0 02f8-02ff : serial(auto) 0376-0376 : ide1 03c0-03df : vga+ 03f6-03f6 : ide0 03f8-03ff : serial(auto) 0cf8-0cff : PCI conf1 1000-103f : 3Com Corporation 3c905 100BaseTX [Boomerang] 1000-103f : 00:0c.0 1050-1053 : Advanced Micro Devices [AMD] AMD-760 MP [IGD4-2P] System Controller 2000-2fff : PCI Bus #01 2000-20ff : ATI Technologies Inc 3D Rage Pro AGP 1X/2X f000-f00f : Advanced Micro Devices [AMD] AMD-766 [ViperPlus] IDE f000-f007 : ide0 f008-f00f : ide1
int check_region(unsigned long start, unsinged long len); struct resource *request_region(unsigned long start, unsigned long len, char *name); void release_region(unsigned long start, unsigned long len);
Is it obvious why two independent drivers cannot safely share the same port? Is it also obvious that two different threads of control in the same driver cannot (in general) safely share access to the same port?
Discuss in class how this could cause serious problems where the protocol for communicating with a device requires a sequence of steps to be performed in a particular order with no other operations on the device interleaved, e.g., with the video frame grabber used for the projects in this course in 2003-2004.
In kernel version 2.6.11 linux/ioport.h the I/O port registration calls are actually macros:
#define request_region(start,n,name) __request_region(&ioport_resource, (start), (n), (name)) extern struct resource * __request_region(struct resource *, unsigned long start, unsigned long n, const char *name); #define check_region(start,n) __check_region(&ioport_resource, (start), (n)) extern int __check_region(struct resource *, unsigned long, unsigned long); #define release_region(start,n) __release_region(&ioport_resource, (start), (n)) extern void __release_region(struct resource *, unsigned long, unsigned long);
See example of call to request_region in short.c.
Also see example in skull_init.c:
#include <linux/ioport.h>
#include <linux/errno.h>
static int skull_detect (unsigned int port; unsigned int range)
{
int err;
if ((err = check_region (port, range)) = 0) return err; /* busy */
if (skull_probe_hw (port, range) != 0) return -ENODEV; /* not found */
request_region (port, range, "skull"); /* "can't fail" */
return 0;
}
And in skull_clean.c:
static void skull_release (unsigned int port, unsigned int range)
{
release_region (port, range);
}
00000000-0009f7ff : System RAM 0009f800-0009ffff : reserved 000a0000-000bffff : Video RAM area 000c0000-000c7fff : Video ROM 000dc000-000dcfff : Advanced Micro Devices [AMD] AMD-766 [ViperPlus] USB 000dc000-000dcfff : usb-ohci 000e0000-000effff : Extension ROM 000f0000-000fffff : System ROM 00100000-3ffeffff : System RAM 00100000-0026b019 : Kernel code 0026b01a-0037b9c3 : Kernel data 3fff0000-3ffffbff : ACPI Tables 3ffffc00-3fffffff : ACPI Non-volatile Storage f4001000-f4001fff : Advanced Micro Devices [AMD] AMD-760 MP [IGD4-2P] System Controller f4100000-f41fffff : PCI Bus #01 f4100000-f4100fff : ATI Technologies Inc 3D Rage Pro AGP 1X/2X f5000000-f5ffffff : PCI Bus #01 f5000000-f5ffffff : ATI Technologies Inc 3D Rage Pro AGP 1X/2X f8000000-fbffffff : Advanced Micro Devices [AMD] AMD-760 MP [IGD4-2P] System Controller fec00000-fec0ffff : reserved fee00000-fee00fff : reserved fff80000-ffffffff : reserved
int check_mem_region (unsigned long start, unsigned long len); int request_mem_region (unsigned long start, unsigend long len, char * name); int release_mem_region (unsgined long start, unsigned long len);
See example of correct usage with request_mem_region in short.c, and later call to release_mem_region.
Example of incorrect (old-style) usage:
if (check_mem_region (mem_add,mem_size)) {
printk ("drivername: memory already in use\n"); return -EBUSY;
}
request_mem_region (mem_addr, mem_size, "drivername");
Do you see the problem?
declared in linux/ioport.h:
struct resource {
const char *name;
unsigned long start, end;
unsigned long flags;
struct resource *parent, *sibling, *child;
}
See also
© 2003, 2004, 2005
T. P. Baker ($Id: ch9.html,v 1.1 2010/06/07 14:29:15 baker Exp baker $)