LDD2代码阅读手记:skull.c 对ISA I/O的内存探测分析
时间:2006-07-13 来源:lanttor.guo
|
笔记原创: 兰特 |
/*
* skull.c -- sample typeless module.
*
* Copyright (C) 2001 Alessandro Rubini and Jonathan Corbet
* Copyright (C) 2001 O'Reilly & Associates
*
* The source code in this file can be freely used, adapted,
* and redistributed in source or binary form, so long as an
* acknowledgment appears in derived source files. The citation
* should list that the code comes from the book "Linux Device
* Drivers" by Alessandro Rubini and Jonathan Corbet, published
* by O'Reilly & Associates. No warranty is attached;
* we cannot take responsibility for errors or fitness for use.
*
* BUGS:
* -it only runs on intel platforms.
* -readb() should be used (see short.c): skull doesn't work with 2.1
*
*/
#ifndef __KERNEL__
# define __KERNEL__
#endif
#ifndef MODULE
# define MODULE
#endif
#ifndef EXPORT_SYMTAB
# define EXPORT_SYMTAB
#endif
#include <linux/config.h>
#include <linux/module.h>
#include <linux/kernel.h> /* printk */
#include <linux/ioport.h>
#include <linux/errno.h>
#include <asm/system.h> /* cli(), *_flags */
#include <linux/mm.h> /* vremap (2.0) */
#include <asm/io.h> /* ioremap */
#include "sysdep.h"
/* The region we look at. */ //这是1MB地址空间下的ISA I/O内存区域,它的内存
//范围在0XA0000到0X100000之间
#define ISA_REGION_BEGIN 0xA0000
#define ISA_REGION_END 0x100000
#define STEP 2048
/* have three symbols to export */
void skull_fn1(void){}
static void skull_fn2(void){}
int skull_variable;
#ifndef __USE_OLD_SYMTAB__ //导出符号,在这里可以看到,静态函数也可以导出
EXPORT_SYMBOL (skull_fn1);
EXPORT_SYMBOL (skull_fn2);
EXPORT_SYMBOL (skull_variable);
#endif
static int skull_register(void) /* and export them */ //为了保持2.0内核兼容;对于2.4内核而言,此函数为空
{
#ifdef __USE_OLD_SYMTAB__
static struct symbol_table skull_syms = {
#include <linux/symtab_begin.h>
X(skull_fn1),
X(skull_fn2),
X(skull_variable),
#include <linux/symtab_end.h>
};
register_symtab(&skull_syms);
#endif /* __USE_OLD_SYMTAB__ */
return 0;
}
//I/O端口:一些处理器入x86为外设保留了独立的地址空间,以便和内存区分开来。x86还为I/O端口的读和写提供了分离的连线,并且使用特殊的CPU
//指令访问端口。
//因为外设要与外设总线相匹配,而最流行的I/O总线是基于个人计算机模型的,所以即使原本没有独立的I/O端口地址空间的处理器,在访问外设时也
//要虚拟成读写I/O端口。
/* perform hardware autodetection */
int skull_probe_hw(unsigned int port, unsigned int range) //I/O端口对应的硬件方面检测,测试时可以return -1;也可以
//return 0,得出两种不同的结果。
{
/* do smart probing here */
return -1; /* not found :-) */
}
/* perform hardware initalizazion */
int skull_init_board(unsigned int port) //硬件初始化,这里为空
{
/* do smart initalization here */
return 0; /* done :-) */
}
/* detect the the device if the region is still free */
static int skull_detect(unsigned int port, unsigned int range) //skull使用的I/O端口的检测和分配
{
int err;
if ((err = check_region(port,range)) < 0) return err; /* busy */ //int check_region(unsigned long start, unsigned long len)
//此函数检测是否可以分配某个端口范围
if (skull_probe_hw(port,range) != 0) return -ENODEV; /* not found */ //I/O端口对应的硬件检测
request_region(port,range,"skull"); /* "Can't fail" */
//struct resource *request_region(unsigned long start,unsigned long len, char *name),此函数完成真正的端口范围
//分配,分配成功后返回一个非空指针
return 0;
}
/*
* port ranges: the device can reside between
* 0x280 and 0x300, in step of 0x10. It uses 0x10 ports.
*/ //skull使用的I/O端口范围
#define SKULL_PORT_FLOOR 0x280
#define SKULL_PORT_CEIL 0x300
#define SKULL_PORT_RANGE 0x010
/*
* the following function performs autodetection, unless a specific
* value was assigned by insmod to "skull_port_base"
*/
static int skull_port_base=0; /* 0 forces autodetection */
MODULE_PARM (skull_port_base, "i");
MODULE_PARM_DESC (skull_port_base, "Base I/O port for skull");
static int skull_find_hw(void) /* returns the # of devices */ //在给定的I/O端口范围(0x280-0x300)里进行autodetection,
//探测步长为0x010
{
/* base is either the load-time value or the first trial */
int base = skull_port_base ? skull_port_base
: SKULL_PORT_FLOOR;
int result = 0;
/* loop one time if value assigned, try them all if autodetecting */
do {
if (skull_detect(base, SKULL_PORT_RANGE) == 0) {
skull_init_board(base);
result++;
}
base += SKULL_PORT_RANGE; /* prepare for next trial */
}
while (skull_port_base == 0 && base < SKULL_PORT_CEIL);
return result;
}
//I/O内存:除了x86上普遍使用的I/O端口之外,和设备通信的另一种主要机制是通过使用内存映射的寄存器或设备内存。
//这两种都被称为I/O内存,因为寄存器和内存的差别对软件是透明的。
//根据计算机平台和使用总线的不同,I/O内存可能是、也可能不是通过页表访问的。如果访问是经由页表进行的,那么
//内核必须首先安排物理地址使其对设备驱动可见(这通常意味着进行任何I/O之前必须先调用ioremap)。如果访问无需
//页表(直接映射的I/O内存),那么I/O内存区域就很像I/O端口,可以使用适当形式的包装函数来读写它们。
//内存资源管理有助于内存探测,因为它可以识别已经由其他设备声明使用的内存区段。但是,资源管理器不能分辨哪些
//设备的驱动程序已经加载,或者一个给定的区域是否包含有你感兴趣的设备。虽然如此,在实际探测内存、检查地址内
//容时它仍然是必须的。你可能会遇到3种不同的情况:映射到目标地址上的是RAM,或者是ROM,或者该区域是空闲的。
int skull_init(void)
{
/*
* Print the isa region map, in blocks of 2K bytes.
* This is not the best code, as it prints too many lines,
* but it deserves to remain short to be included in the book.
* Note also that read() should be used instead of pointers.
*/
unsigned char oldval, newval; /* values read from memory */
unsigned long flags; /* used to hold system flags */
unsigned long add, i;
void *base;
/* Use ioremap to get a handle on our region */
base = ioremap(ISA_REGION_BEGIN, ISA_REGION_END - ISA_REGION_BEGIN);//void *ioremap(unsigned long phys_addr, unsigned long size)
//该函数用于给I/O内存区域分配虚拟地址,并不实际分配内存;
//ioremap建立新的页表,其返回值是一个特殊的虚拟地址,可以用
//来访问指定的物理内存区域。
base -= ISA_REGION_BEGIN; /* Do the offset once */ //虚拟地址与实际物理地址之间的偏移量。
/* probe all the memory hole in 2KB steps */
for (add = ISA_REGION_BEGIN; add < ISA_REGION_END; add += STEP) {
/*
* Check for an already allocated region.
*/
if (check_mem_region (add, 2048)) { // check_mem_region(unsigned long start, unsigned long len);
// 该函数检测给定的I/O物理内存区范围是否可以分配,如过不可以
// 则返回一个负的错误编码。
printk(KERN_INFO "%lx: Allocated\n", add);
continue;
}
/*
* Read and write the beginning of the region and see what happens.
*/
save_flags(flags); //save_flags()是一个宏,用于保存标志(什么标志?)。参数
//flags直接被传入,不带&操作符。save_flags和restore_flags
//必须在同一个函数内调用。
cli(); //使用cli()禁止中断。检测I/O内存的代码使用cli禁止了中断,因
//为这些内存段只能通过物理地写入数据随后重新读出的方法才能识
//别,而在测试过程中,真正的I/O内存中的内容可能会被中断处理
//程序修改。
oldval = readb (base + add); /* Read a byte */ //读一个字节,在地址base+add上
writeb (oldval^0xff, base + add); //写一个字节,值为oldval的异或,在地址bash+add上
mb(); //设置内存屏障(memory barrier),防止编译器优化重新排序读/写
//指令。
newval = readb (base + add); //读该字节的新值
writeb (oldval, base + add); //将旧值写回该字节
restore_flags(flags);
if ((oldval^newval) == 0xff) { /* we re-read our change: it's ram */
//重读到了我们所做的改动,所以此地址空间映射的是RAM
printk(KERN_INFO "%lx: RAM\n", add);
continue;
}
if ((oldval^newval) != 0) { /* random bits changed: it's empty */
//有随机的位数发生变化,它是空的
printk(KERN_INFO "%lx: empty\n", add);
continue;
}
/*
* Expansion rom (executed at boot time by the bios)
* has a signature where the first byt is 0x55, the second 0xaa,
* and the third byte indicates the size of such rom
*/
if ( (oldval == 0x55) && (readb (base + add + 1) == 0xaa)) {
int size = 512 * readb (base + add + 2);
printk(KERN_INFO "%lx: Expansion ROM, %i bytes\n",
add, size);
add += (size & ~2048) - 2048; /* skip it */ //跳过Expansion rom (不能理解此句)
continue;
}
/*
* If the tests above failed, we still don't know if it is ROM or
* empty. Since empty memory can appear as 0x00, 0xff, or the low
* address byte, we must probe multiple bytes: if at least one of
* them is different from these three values, then this is rom
* (though not boot rom).
*/
printk(KERN_INFO "%lx: ", add);
for (i=0; i<5; i++) { //选择一个“随机”的地址,测探五次,区别该空间是
//空的,还是映射为ROM。
unsigned long radd = add + 57*(i+1); /* a "random" value */
unsigned char val = readb (base + radd);
if (val && val != 0xFF && val != ((unsigned long) radd&0xFF))
break;
}
printk("%s\n", i==5 ? "empty" : "ROM");
}
/*
* Find you hardware
*/
skull_find_hw();
/*
* Always fail to load (or suceed).
*/
skull_register(); /* register your symbol table */
return 0;
}
module_init(skull_init);
skull_clean.c代码分析:
#ifndef __KERNEL__
# define __KERNEL__
#endif
#ifndef MODULE
# define MODULE
#endif
#include <linux/config.h>
#define __NO_VERSION__
#include <linux/module.h>
#include <linux/version.h>
#include <linux/ioport.h>
#include "sysdep.h"
void skull_release(unsigned int port, unsigned int range)
{
release_region(port,range);
}
void skull_cleanup(void)
{
/* should put real values here ... */
/* skull_release(0,0); */
int i; //如果在skull_init.c中的skull_probe_hw()函数里返回值为0,表明I/O端口
//0x280-0x300在skull.o模块中被使用,
//则下面的skull_release()函数用于清除I/O端口0x280-0x300
//的注册;否则下面的代码应该被注释掉,skull_cleanup()函数为空。
for (i = 0; i < 0x80; i+= 0x10)
{
skull_release(0x280+i,0x10);
}
}
module_exit(skull_cleanup);
兰特说明:
1.通过makefile编译生成skull.o文件,用insmod加载该模块,然后用dmesg | less观察printk输出;
2. 通过cat /proc/iomem观察I/O内存分配情况,与上面的printk输出进行对比验证:
00000000-0009ffff : System RAM
000a0000-000bffff : Video RAM area
000c0000-000c7fff : Video ROM
000d1000-000d3fff : Extension ROM
000f0000-000fffff : System ROM
00100000-3fe8abff : System RAM
00100000-002a4cc0 : Kernel code
002a4cc1-003b9847 : Kernel data
3fe8ac00-3fe8cbff : ACPI Non-volatile Storage
3fe8cc00-3fe8ebff : ACPI Tables
3fe8ec00-3fffffff : reserved
e8000000-efffffff : PCI Bus #01
e8000000-efffffff : PCI device 10de:014e (nVidia Corporation)
f0000000-f3ffffff : reserved
f7e00000-f7efffff : PCI Bus #04
f7ef0000-f7efffff : Broadcom Corporation NetXtreme BCM5751 Gigabit Ethernet
f7ef0000-f7efffff : tg3
f7f00000-f7ffffff : PCI Bus #02
f8000000-feafffff : PCI Bus #01
f8000000-fbffffff : PCI device 10de:014e (nVidia Corporation)
fd000000-fdffffff : PCI device 10de:014e (nVidia Corporation)
febfc000-febfffff : PCI device 8086:27d8 (Intel Corp.)
fec00000-fed003ff : reserved
fed20000-fed9ffff : reserved
fee00000-feefffff : reserved
ffa80800-ffa80bff : PCI device 8086:27cc (Intel Corp.)
ffb00000-ffffffff : reserved
需要注意的是,左侧一列的地址均是真正的物理地址,而非虚拟地址。
3. 通过cat /proc/ioports观察I/O端口分配情况:
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
0280-028f : skull
0290-029f : skull
02a0-02af : skull
02b0-02bf : skull
02c0-02cf : skull
02d0-02df : skull
02e0-02ef : skull
02f0-02ff : skull
0376-0376 : ide1
03c0-03df : vga+
03f6-03f6 : ide0
03f8-03ff : serial(auto)
0cf8-0cff : PCI conf1
ece0-ecff : PCI device 8086:27da (Intel Corp.)
ff20-ff3f : PCI device 8086:27cb (Intel Corp.)
ff20-ff3f : usb-uhci
ff40-ff5f : PCI device 8086:27ca (Intel Corp.)
ff40-ff5f : usb-uhci
ff60-ff7f : PCI device 8086:27c9 (Intel Corp.)
ff60-ff7f : usb-uhci
ff80-ff9f : PCI device 8086:27c8 (Intel Corp.)
ff80-ff9f : usb-uhci
ffa0-ffaf : PCI device 8086:27c0 (Intel Corp.)
可以发现,I/O端口范围0x280-0x300均分配给skull来使用,这是因为在skull_init.c
代码中将skull_probe_hw()函数的返回值改为了0。同样的左侧一列的地址均为真实的I/O地址。
相关阅读 更多 +