puppy居
puppy居士
posts - 41,comments - 27,trackbacks - 0
内存管理综述(2.6.21)

R.wen

在物理内存页方面,linux从2.6.11开始使用4级页表来管理。分别是pgd, pud, pmd和pte,但是i386只是

使用了其中的两级--pgd和pte,因为1024×1024×4K = 4G,已经满足了i386的要求。

物理页面的分配方面,linux使用buddy算法。在支持NUMA的系统上,它首先将内存分成几个不同的节点

NODE,然后每个节点再分成不同的区域(ZONES),目前有三个区域--zone_dma(16M),zone_nomal(16

~896M),zone_highmem(896~1024M)。高端内存应该是历史的产物,再64位的系统上,它是不需要的



为了避免内存碎片,再分配小额内存的时候,Linux使用slab分配器。它的原理大概如下,首先它从buddy

系统中请求一定的物理页面(slab),然后再将它细分成objects, 这些object是物理上连续的,所以它又

通过一个数组来管理,并且通过使用一个slab管理的描述符,将这个数组串成链表。这是一个数组链表数

据结构的典型应用。kmalloc也是通过slab来实现的,一般情况下,它可分配的最大内存是128K.


在物理页面的分配上,上面两种算法有一个共同的特点,他们都使用了per-CPU cache.在buddy算法上,

如果使用者只是请求一个页面,它就从这个cache中分配,否则才从buddy系统中分配。而对于slab分配器

,它总是从当前CPU的cache中去分配的,如果这个cache为空,它就会从slab中取出相应的objs将这个

cache填满。这样就可以在SMP的情况下避免了锁的争用,大大的提高了SMP系统的并行性。



在进程地址空间方面,它是用mm_struct来描述一个进程的内存空间的。而用vm_area_struct结构来管理

地址区域的。在物理页面的分配上,linux使用lazy computing做法,即它是到产生缺页中断的时候才真

正去分配物理页面的。最终将这个页面物理地址写入相应的PTE。


还有一个值得注意的问题是有关高端内存的。
当系统的物理内存大于896M时,高于这个值的内存不能被直接映射,而是要在使用的时候建立。进程在内

核态的1G空间的映射关系如下:

               gap                                                                KMAP_BASE            
|===========|======|=========================|====|==========|=============|
   mem_map     VMLLOC_START            VMLLOC_END gap       FIXADDR_START FIXADDR_TOP

其中mem_map这一段是物理内存的大小,紧接着是一个大小为8M的gap,它是为了不要越界访问物理内存而

存在的。接下去就是用于管理高端内存的区域了。这个区域的最小值是VMALLOC_RESERVE,为128M,所以

在1G的内核地址空间中,只能用1G-128M=896M的地址建立直接映射。这个128M是个经验值,如果这个值太

大了,则能用于直接映射的物理内存的地址空间就小了,这样在使用内存的时候才建立映射,会影响系统

的性能。另一方面,如果这个值太小了,则同时能用的高端内存的量就更少了,会造成内存的浪费。所以

说这是一个考虑时间与空间的平衡值。

其中,FIXADDR_START到FIXADDR_TOP这段空间非常小,使用于建立临时映射的。它的接口是kmap_atomic

()。 内核只为每个CPU保留几个页面的地址空间。
接着是KMAP_BASE开始的一段,大小是LAST_PKMAP×PAGE_SIZE=1024×4K=4M。这段空间是用于kmap()建立

永久映射的。

剩下的一段就是VMLLOC_START与VMLLOC_END之间的空间了。它是由vmalloc分配的内存地址空间,因为由

它分配的物理内存是在分配时临时建立的,而且是逐页分配,不是一次性分配多个连续页的。所以,由它

分配的空间在线性地址上是连续的,但在物理上却可能是不连续的。注意这个区间上的空间是由

vm_struct描述的,而不是进程区域的vm_area_struct.不是同一个结构。

有一个外在的表现就是,由vmalloc(), kmap(), kmap_atomic()分配的内存得到的线性地址都是在

VMALLOC_START之上的。
posted @ 2008-08-22 14:16 puppy 阅读(247) | 评论 (0)编辑 收藏

内核编译链接过程是依靠vmlinux.lds文件,以arm为例vmlinux.lds文件位于kernel/arch/arm/vmlinux.lds,

vmlinux-armv.lds的生成过程在kernel/arch/arm/Makefile中

ifeq ($(CONFIG_CPU_32),y)
PROCESSOR = armv
TEXTADDR = 0xC0008000
LDSCRIPT = arch/arm/vmlinux-armv.lds.in
endif

arch/arm/vmlinux.lds: $(LDSCRIPT) dummy
@sed 's/TEXTADDR/$(TEXTADDR)/;s/DATAADDR/$(DATAADDR)/' $(LDSCRIPT) >$@

查看arch/arm/vmlinux.lds 中

OUTPUT_ARCH(arm)
ENTRY(stext)
SECTIONS
{
. = 0xC0008000;
.init : { /* Init code and data */
_stext = .;
__init_begin = .;
*(.text.init)
__proc_info_begin = .;
*(.proc.info)
__proc_info_end = .;
__arch_info_begin = .;
*(.arch.info)
__arch_info_end = .;
__tagtable_begin = .;
*(.taglist)
__tagtable_end = .;
*(.data.init)
. = ALIGN(16);
__setup_start = .;
*(.setup.init)
__setup_end = .;
__initcall_start = .;
*(.initcall.init)
__initcall_end = .;
. = ALIGN(4096);
__init_end = .;
}

/DISCARD/ : { /* Exit code and data */
*(.text.exit)
*(.data.exit)
*(.exitcall.exit)
}

.text : { /* Real text segment */
_text = .; /* Text and read-only data */
*(.text)
*(.fixup)
*(.gnu.warning)
*(.rodata)
*(.rodata.*)
*(.glue_7)
*(.glue_7t)
*(.got) /* Global offset table */

_etext = .; /* End of text section */
}

.kstrtab : { *(.kstrtab) }

. = ALIGN(16);
__ex_table : { /* Exception table */
__start___ex_table = .;
*(__ex_table)
__stop___ex_table = .;
}

__ksymtab : { /* Kernel symbol table */
__start___ksymtab = .;
*(__ksymtab)
__stop___ksymtab = .;
}

. = ALIGN(8192);

.data : {
/*
* first, the init task union, aligned
* to an 8192 byte boundary.
*/
*(.init.task)

/*
* then the cacheline aligned data
*/
. = ALIGN(32);
*(.data.cacheline_aligned)

/*
* and the usual data section
*/
*(.data)
CONSTRUCTORS

_edata = .;
}

.bss : {
__bss_start = .; /* BSS */
*(.bss)
*(COMMON)
_end = . ;
}
/* Stabs debugging sections. */
.stab 0 : { *(.stab) }
.stabstr 0 : { *(.stabstr) }
.stab.excl 0 : { *(.stab.excl) }
.stab.exclstr 0 : { *(.stab.exclstr) }
.stab.index 0 : { *(.stab.index) }
.stab.indexstr 0 : { *(.stab.indexstr) }
.comment 0 : { *(.comment) }
}

arch/arm/Makefile中:

vmlinux: arch/arm/vmlinux.lds

arch/arm/vmlinux.lds: $(LDSCRIPT) dummy
@sed 's/TEXTADDR/$(TEXTADDR)/;s/DATAADDR/$(DATAADDR)/' $(LDSCRIPT) >$@


MAKEBOOT = $(MAKE) -C arch/$(ARCH)/boot

bzImage zImage zinstall Image bootpImage install: vmlinux
@$(MAKEBOOT) $@

但在kernel/arch/arm/boot/Makefile

ifeq ($(CONFIG_ARCH_S3C2410),y)
ZTEXTADDR = 0x30008000
ZRELADDR = 0x30008000
endif

zImage: $(CONFIGURE) compressed/vmlinux
$(OBJCOPY) -O binary -R .note -R .comment -S compressed/vmlinux $@

compressed/vmlinux: $(TOPDIR)/vmlinux dep
@$(MAKE) -C compressed vmlinux

在compressed目录下的Makefile中

ZLDFLAGS = -p -X -T vmlinux.lds

SEDFLAGS = s/TEXT_START/$(ZTEXTADDR)/;s/LOAD_ADDR/$(ZRELADDR)/;s/BSS_START/$(ZBSSADDR)/

all: vmlinux

vmlinux: $(HEAD) $(OBJS) piggy.o vmlinux.lds
$(LD) $(ZLDFLAGS) $(HEAD) $(OBJS) piggy.o $(LIBGCC) -o vmlinux


vmlinux.lds: vmlinux.lds.in Makefile $(TOPDIR)/arch/$(ARCH)/boot/Makefile $(TOPDIR)/.config
@sed "$(SEDFLAGS)" < vmlinux.lds.in > $@

vmlinux-armv.lds.in文件的内容:

OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
{
. = LOAD_ADDR;
_load_addr = .;

. = TEXT_START;
_text = .;

.text : {
_start = .;
*(.start)
*(.text)
*(.fixup)
*(.gnu.warning)
*(.rodata)
*(.rodata.*)
*(.glue_7)
*(.glue_7t)
input_data = .;
piggy.o
input_data_end = .;
. = ALIGN(4);
}

_etext = .;

_got_start = .;
.got : { *(.got) }
_got_end = .;
.got.plt : { *(.got.plt) }
.data : { *(.data) }
_edata = .;

. = BSS_START;
__bss_start = .;
.bss : { *(.bss) }
_end = .;

.stack (NOLOAD) : { *(.stack) }

.stab 0 : { *(.stab) }
.stabstr 0 : { *(.stabstr) }
.stab.excl 0 : { *(.stab.excl) }
.stab.exclstr 0 : { *(.stab.exclstr) }
.stab.index 0 : { *(.stab.index) }
.stab.indexstr 0 : { *(.stab.indexstr) }
.comment 0 : { *(.comment) }
}

vmlinux.lds内容为

OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
{
. = 0x30008000;
_load_addr = .;

. = 0;
_text = .;

.text : {
_start = .;
*(.start)
*(.text)
*(.fixup)
*(.gnu.warning)
*(.rodata)
*(.rodata.*)
*(.glue_7)
*(.glue_7t)
input_data = .;
piggy.o
input_data_end = .;
. = ALIGN(4);
}

_etext = .;

_got_start = .;
.got : { *(.got) }
_got_end = .;
.got.plt : { *(.got.plt) }
.data : { *(.data) }
_edata = .;

. = ALIGN(4);
__bss_start = .;
.bss : { *(.bss) }
_end = .;

.stack (NOLOAD) : { *(.stack) }

.stab 0 : { *(.stab) }
.stabstr 0 : { *(.stabstr) }
.stab.excl 0 : { *(.stab.excl) }
.stab.exclstr 0 : { *(.stab.exclstr) }
.stab.index 0 : { *(.stab.index) }
.stab.indexstr 0 : { *(.stab.indexstr) }
.comment 0 : { *(.comment) }
}

一般情况下都在生成vmlinux后,再对内核进行压缩成为zImage,压缩的目录是kernel/arch/arm/boot。

下载到flash中的是压缩后的zImage文件,zImage是由压缩后的vmlinux和解压缩程序组成,如下图所示:

|-----------------|\    |-----------------|
| | \ | |
| | \ | decompress code |
| vmlinux | \ |-----------------| zImage
| | \| |
| | | |
| | | |
| | | |
| | /|-----------------|
| | /
| | /
| | /
|-----------------|/

zImage链接脚本也叫做vmlinux.lds,位于kernel/arch/arm/boot/compressed。

是由同一目录下的vmlinux.lds.in文件生成的在kernel/arch/arm/boot/Makefile文件中定义了:

ifeq ($(CONFIG_ARCH_S3C2410),y)
ZTEXTADDR = 0x30008000
ZRELADDR = 0x30008000
endif

ZTEXTADDR就是解压缩代码的ram偏移地址,ZRELADDR是内核ram启动的偏移地址,这里看到指定ZTEXTADDR的地址为30008000,

以上就是我对内核启动地址的分析,总结一下内核启动地址的设置:

设置kernel/arch/arm/boot/Makefile文件中的

ifeq ($(CONFIG_ARCH_S3C2410),y)
ZTEXTADDR = 0x30008000
ZRELADDR = 0x30008000
endif
# We now have a PIC decompressor implementation. Decompressors running
# from RAM should not define ZTEXTADDR. Decompressors running directly
# from ROM or Flash must define ZTEXTADDR (preferably via the config)
#

查看2410的datasheet ,发现内存映射的基址是0x3000 0000 ,那么 0x30008000又是如何来的呢?

在内核文档kernel/Document/arm/Booting 文件中有:

Calling the kernel image

Existing boot loaders: MANDATORY
New boot loaders: MANDATORY

There are two options for calling the kernel zImage. If the zImage is stored in flash, and is linked correctly to be run from flash, then it is legal for the boot loader to call the zImage in flash directly.

The zImage may also be placed in system RAM (at any location) and called there. Note that the kernel uses 16K of RAM below the image to store page tables. The recommended placement is 32KiB into RAM.

看来在image下面用了32K(0x8000)的空间存放内核页表,0x30008000就是2410的内核在RAM中的启动地址,这个地址就是这么来的。

关于内核解压缩的过程分析

内核压缩和解压缩代码都在目录kernel/arch/arm/boot/compressed,编译完成后将产生vmlinux、head.o、misc.o、head-s3c2410.o、piggy.o这几个文件,head.o是内核的头部文件,负责初始设置;

misc.o将主要负责内核的解压工作,它在head.o之后;

head-s3c2410.o文件主要针对的初始化,将在链接时与head.o合并;

piggy.o是一个中间文件,其实是一个压缩的内核(kernel/vmlinux),只不过没有和初始化文件及解压文件链接而已;

vmlinux是没有(zImage是压缩过的内核)压缩过的内核,就是由piggy.o、head.o、misc.o、head-s3c2410.o组成的。

在BootLoader完成系统的引导以后并将Linux内核调入内存之后,调用bootLinux(),

这个函数将跳转到kernel的起始位置。

如果kernel没有压缩,就可以启动了。

如果kernel压缩过,则要进行解压,在压缩过的kernel头部有解压程序。

压缩过得kernel入口第一个文件源码位置在arch/arm/boot/compressed/head.S。

它将调用函数decompress_kernel(),这个函数在文件arch/arm/boot/compressed/misc.c中,

decompress_kernel()又调用proc_decomp_setup(),arch_decomp_setup()进行设置,

然后使用在打印出信息“Uncompressing Linux...”后,调用gunzip()。将内核放于指定的位置。

以下分析head.S文件:

(1)对于各种Arm CPU的DEBUG输出设定,通过定义宏来统一操作。

(2)设置kernel开始和结束地址,保存architecture ID。

(3)如果在ARM2以上的CPU中,用的是普通用户模式,则升到超级用户模式,然后关中断。

(4)分析LC0结构delta offset,判断是否需要重载内核地址(r0存入偏移量,判断r0是否为零)。

这里是否需要重载内核地址,我以为主要分析arch/arm/boot/Makefile、arch/arm/boot/compressed/Makefile

和arch/arm/boot/compressed/vmlinux.lds.in三个文件,主要看vmlinux.lds.in链接文件的主要段的位置,

LOAD_ADDR(_load_addr)=0x30008000,而对于TEXT_START(_text、_start)的位置只设为0,BSS_START(__bss_start)=ALIGN(4)。

对于这样的结果依赖于,对内核解压的运行方式,也就是说,内核解压前是在内存(RAM)中还是在FLASH上,

因为这里,我们的BOOTLOADER将压缩内核(zImage)移到了RAM的0x30008000位置,我们的压缩内核是在内存(RAM)从0x30008000地址开始顺序排列,

因此我们的r0获得的偏移量是载入地址(0x30008000)。

接下来的工作是要把内核镜像的相对地址转化为内存的物理地址,即重载内核地址。

(5)需要重载内核地址,将r0的偏移量加到BSS region和GOT table中。

(6)清空bss堆栈空间r2-r3。

(7)建立C程序运行需要的缓存,并赋于64K的栈空间。

(8)这时r2是缓存的结束地址,r4是kernel的最后执行地址,r5是kernel境象文件的开始地址。检查是否地址有冲突。

将r5等于r2,使decompress后的kernel地址就在64K的栈之后。

(9)调用文件misc.c的函数decompress_kernel(),解压内核于缓存结束的地方(r2地址之后)。此时各寄存器值有如下变化:

r0为解压后kernel的大小

r4为kernel执行时的地址

r5为解压后kernel的起始地址

r6为CPU类型值(processor ID)

r7为系统类型值(architecture ID)

(10)将reloc_start代码拷贝之kernel之后(r5+r0之后),首先清除缓存,而后执行reloc_start。

(11)reloc_start将r5开始的kernel重载于r4地址处。

(12)清除cache内容,关闭cache,将r7中architecture ID赋于r1,执行r4开始的kernel代码。

下面简单介绍一下解压缩过程,也就是函数decompress_kernel实现的功能:

解压缩代码位于kernel/lib/inflate.c,inflate.c是从gzip源程序中分离出来的。包含了一些对全局数据的直接引用。

在使用时需要直接嵌入到代码中。gzip压缩文件时总是在前32K字节的范围内寻找重复的字符串进行编码,

在解压时需要一个至少为32K字节的解压缓冲区,它定义为window[WSIZE]。inflate.c使用get_byte()读取输入文件,

它被定义成宏来提高效率。输入缓冲区指针必须定义为inptr,inflate.c中对之有减量操作。inflate.c调用flush_window()

来输出window缓冲区中的解压出的字节串,每次输出长度用outcnt变量表示。在flush_window()中,还必

须对输出字节串计算CRC并且刷新crc变量。在调用gunzip()开始解压之前,调用makecrc()初始化CRC计算表。

最后gunzip()返回0表示解压成功。

我们在内核启动的开始都会看到这样的输出:

Uncompressing Linux...done, booting the kernel.

这也是由decompress_kernel函数内部输出的,它调用了puts()输出字符串,

puts是在kernel/include/asm-arm/arch-s3c2410/uncompress.h中实现的。

执行完解压过程,再返回到head.S中,启动内核:

call_kernel:    bl  cache_clean_flush
bl cache_off
mov r0, #0
mov r1, r7 @ restore architecture number
mov pc, r4 @ call kernel

下面就开始真正的内核了。

posted @ 2008-08-20 14:23 puppy 阅读(386) | 评论 (0)编辑 收藏
pci设备的初始化
R.wen


这里讨论系统上电时的情况, 热插拔的情况应该差不多.

首先是从根总线开始, 然后就是扫描这个根总线上的每一条子BUS, 如下:

unsigned int pci_scan_child_bus(struct pci_bus *bus)
{
    unsigned int devfn, pass, max = bus->secondary;
    struct pci_dev *dev;

    pr_debug("PCI: Scanning bus %04x:%02x\n", pci_domain_nr(bus), bus->number);

    /* Go find them, Rover! */
    这里循环256(0x100)/8 = 32次的意思是, 每个总线可能有32个设备,
    而每个设备可能是多功能的, 且最多有8个功能.
    for (devfn = 0; devfn < 0x100; devfn += 8)
        pci_scan_slot(bus, devfn);

    //下面在递归扫描PCI桥,不是我们要关心的.
    /*
    * After performing arch-dependent fixup of the bus, look behind
    * all PCI-to-PCI bridges on this bus.
    */
    pr_debug("PCI: Fixups for bus %04x:%02x\n", pci_domain_nr(bus), bus->number);
    pcibios_fixup_bus(bus);
    for (pass=0; pass < 2; pass++)
        list_for_each_entry(dev, &bus->devices, bus_list) {
            if (dev->hdr_type == PCI_HEADER_TYPE_BRIDGE ||
                dev->hdr_type == PCI_HEADER_TYPE_CARDBUS)
                max = pci_scan_bridge(bus, dev, max, pass);
        }

    /*
    * We've scanned the bus and so we know all about what's on
    * the other side of any bridges that may be on this bus plus
    * any devices.
    *
    * Return how far we've got finding sub-buses.
    */
    pr_debug("PCI: Bus scan for %04x:%02x returning with max=%02x\n",
        pci_domain_nr(bus), bus->number, max);
    return max;
}



/**
* pci_scan_slot - scan a PCI slot on a bus for devices.
* @bus: PCI bus to scan
* @devfn: slot number to scan (must have zero function.)
*
* Scan a PCI slot on the specified PCI bus for devices, adding
* discovered devices to the @bus->devices list. New devices
* will have an empty dev->global_list head.
*/
int pci_scan_slot(struct pci_bus *bus, int devfn)
{
    int func, nr = 0;
    int scan_all_fns;

    //空函数
    scan_all_fns = pcibios_scan_all_fns(bus, devfn);

    //每个设备可能有8个功能
    for (func = 0; func < 8; func++, devfn++) {
        struct pci_dev *dev;

        //分配并初始化找到的设备
        dev = pci_scan_single_device(bus, devfn);
        if (dev) {
            nr++;

            /*
             * If this is a single function device,
             * don't scan past the first function.
             */
            if (!dev->multifunction) {
                if (func > 0) {
                    dev->multifunction = 1;
                } else {
                     break;
                }
            }
        } else {
            if (func == 0 && !scan_all_fns)
                break;
        }
    }
    return nr;
}



struct pci_dev *pci_scan_single_device(struct pci_bus *bus, int devfn)
{
    struct pci_dev *dev;

    //扫描设备, 如果存在, 分配一个结构,
    //并读取这个设备的寄存器信息将其初始化
    dev = pci_scan_device(bus, devfn);
    if (!dev)
        return NULL;

    //将设备加入的bus总线
    pci_device_add(dev, bus);

    return dev;
}



/*
* Read the config data for a PCI device, sanity-check it
* and fill in the dev structure...
*/

这个函数的功能上面的注释已经说明白了
static struct pci_dev * __devinit
pci_scan_device(struct pci_bus *bus, int devfn)
{
    struct pci_dev *dev;
    u32 l;
    u8 hdr_type;
    int delay = 1;

    //两个检查

    if (pci_bus_read_config_dword(bus, devfn, PCI_VENDOR_ID, &l))
        return NULL;

    /* some broken boards return 0 or ~0 if a slot is empty: */
    if (l == 0xffffffff || l == 0x00000000 ||
        l == 0x0000ffff || l == 0xffff0000)
        return NULL;



    /* Configuration request Retry Status */
    while (l == 0xffff0001) {
        msleep(delay);
        delay *= 2;
        if (pci_bus_read_config_dword(bus, devfn, PCI_VENDOR_ID, &l))
            return NULL;
        /* Card hasn't responded in 60 seconds? Must be stuck. */
        if (delay > 60 * 1000) {
            printk(KERN_WARNING "Device %04x:%02x:%02x.%d not "
                    "responding\n", pci_domain_nr(bus),
                    bus->number, PCI_SLOT(devfn),
                    PCI_FUNC(devfn));
            return NULL;
        }
    }


    //读PCI类型信息, PCI_HEADER_TYPE是在这个寄存器中的偏移值
    //可能的类型信息如下:

#define PCI_HEADER_TYPE        0x0e    /* 8 bits */
#define PCI_HEADER_TYPE_NORMAL        0
#define PCI_HEADER_TYPE_BRIDGE        1
#define PCI_HEADER_TYPE_CARDBUS    2

    if (pci_bus_read_config_byte(bus, devfn, PCI_HEADER_TYPE, &hdr_type))
        return NULL;

    //如果上面的成功了, 则为设备分配一个pci_dev结构,
    //我们在写驱动的时候看到, 在调用probe函数是, 参数传入的就是这里得到的结构.
    //这个结构在这个函数里面已经初始化好了.
    dev = alloc_pci_dev();
    if (!dev)
        return NULL;

    //初步初始化一些域
    dev->bus = bus;
    dev->sysdata = bus->sysdata;
    dev->dev.parent = bus->bridge;
    dev->dev.bus = &pci_bus_type;
    dev->devfn = devfn;
    dev->hdr_type = hdr_type & 0x7f;
    dev->multifunction = !!(hdr_type & 0x80);
    dev->vendor = l & 0xffff;
    dev->device = (l >> 16) & 0xffff;
    dev->cfg_size = pci_cfg_space_size(dev); // 用于PCI-X
    dev->error_state = pci_channel_io_normal;

    /* Assume 32-bit PCI; let 64-bit PCI cards (which are far rarer)
       set this higher, assuming the system even supports it. */
    dev->dma_mask = 0xffffffff; //32为DMA
    //进一步初始化, 数据来自于寄存器
    if (pci_setup_device(dev) < 0) {
        kfree(dev);
        return NULL;
    }

    return dev;
}

//分配一个结构并初始化两个队列
struct pci_dev *alloc_pci_dev(void)
{
    struct pci_dev *dev;

    dev = kzalloc(sizeof(struct pci_dev), GFP_KERNEL);
    if (!dev)
        return NULL;

    INIT_LIST_HEAD(&dev->global_list);
    INIT_LIST_HEAD(&dev->bus_list);

    pci_msi_init_pci_dev(dev);

    return dev;
}

struct bus_type pci_bus_type = {
    .name        = "pci",
    .match        = pci_bus_match,
    .uevent        = pci_uevent,
    .probe        = pci_device_probe,
    .remove        = pci_device_remove,
    .suspend    = pci_device_suspend,
    .suspend_late    = pci_device_suspend_late,
    .resume_early    = pci_device_resume_early,
    .resume        = pci_device_resume,
    .shutdown    = pci_device_shutdown,
    .dev_attrs    = pci_dev_attrs,
};




/**
* pci_setup_device - fill in class and map information of a device
* @dev: the device structure to fill
*
* Initialize the device structure with information about the device's
* vendor,class,memory and IO-space addresses,IRQ lines etc.
* Called at initialisation of the PCI subsystem and by CardBus services.
* Returns 0 on success and -1 if unknown type of device (not normal, bridge
* or CardBus).
*/

这个函数比较简单, 可以看注释,就不说了

static int pci_setup_device(struct pci_dev * dev)
{
    u32 class;

    sprintf(pci_name(dev), "%04x:%02x:%02x.%d", pci_domain_nr(dev->bus),
        dev->bus->number, PCI_SLOT(dev->devfn), PCI_FUNC(dev->devfn));

    pci_read_config_dword(dev, PCI_CLASS_REVISION, &class);
    class >>= 8;                    /* upper 3 bytes */
    dev->class = class;
    class >>= 8;

    pr_debug("PCI: Found %s [%04x/%04x] %06x %02x\n", pci_name(dev),
        dev->vendor, dev->device, class, dev->hdr_type);

    /* "Unknown power state" */
    dev->current_state = PCI_UNKNOWN;

    /* Early fixups, before probing the BARs */
    pci_fixup_device(pci_fixup_early, dev);
    class = dev->class >> 8;

    switch (dev->hdr_type) {            /* header type */
    case PCI_HEADER_TYPE_NORMAL:            /* standard header */
        if (class == PCI_CLASS_BRIDGE_PCI)
            goto bad;
        pci_read_irq(dev);
        pci_read_bases(dev, 6, PCI_ROM_ADDRESS);
        pci_read_config_word(dev, PCI_SUBSYSTEM_VENDOR_ID, &dev->subsystem_vendor);
        pci_read_config_word(dev, PCI_SUBSYSTEM_ID, &dev->subsystem_device);

        /*
        *    Do the ugly legacy mode stuff here rather than broken chip
        *    quirk code. Legacy mode ATA controllers have fixed
        *    addresses. These are not always echoed in BAR0-3, and
        *    BAR0-3 in a few cases contain junk!
        */
        if (class == PCI_CLASS_STORAGE_IDE) {
            u8 progif;
            pci_read_config_byte(dev, PCI_CLASS_PROG, &progif);
            if ((progif & 1) == 0) {
                dev->resource[0].start = 0x1F0;
                dev->resource[0].end = 0x1F7;
                dev->resource[0].flags = LEGACY_IO_RESOURCE;
                dev->resource[1].start = 0x3F6;
                dev->resource[1].end = 0x3F6;
                dev->resource[1].flags = LEGACY_IO_RESOURCE;
            }
            if ((progif & 4) == 0) {
                dev->resource[2].start = 0x170;
                dev->resource[2].end = 0x177;
                dev->resource[2].flags = LEGACY_IO_RESOURCE;
                dev->resource[3].start = 0x376;
                dev->resource[3].end = 0x376;
                dev->resource[3].flags = LEGACY_IO_RESOURCE;
            }
        }
        break;

    case PCI_HEADER_TYPE_BRIDGE:            /* bridge header */
        if (class != PCI_CLASS_BRIDGE_PCI)
            goto bad;
        /* The PCI-to-PCI bridge spec requires that subtractive
           decoding (i.e. transparent) bridge must have programming
           interface code of 0x01. */
        pci_read_irq(dev);
        dev->transparent = ((dev->class & 0xff) == 1);
        pci_read_bases(dev, 2, PCI_ROM_ADDRESS1);
        break;

    case PCI_HEADER_TYPE_CARDBUS:            /* CardBus bridge header */
        if (class != PCI_CLASS_BRIDGE_CARDBUS)
            goto bad;
        pci_read_irq(dev);
        pci_read_bases(dev, 1, 0);
        pci_read_config_word(dev, PCI_CB_SUBSYSTEM_VENDOR_ID, &dev->subsystem_vendor);
        pci_read_config_word(dev, PCI_CB_SUBSYSTEM_ID, &dev->subsystem_device);
        break;

    default:                    /* unknown header */
        printk(KERN_ERR "PCI: device %s has unknown header type %02x, ignoring.\n",
            pci_name(dev), dev->hdr_type);
        return -1;

    bad:
        printk(KERN_ERR "PCI: %s: class %x doesn't match header type %02x. Ignoring

class.\n",
               pci_name(dev), class, dev->hdr_type);
        dev->class = PCI_CLASS_NOT_DEFINED;
    }

    /* We found a fine healthy device, go go go... */
    return 0;
}


设备初始化完成后, 我们就回到了pci_scan_single_device()这个函数,
下一步就是将这个分配好的设备加入的bus队列中去了.

void pci_device_add(struct pci_dev *dev, struct pci_bus *bus)
{
    device_initialize(&dev->dev);
    dev->dev.release = pci_release_dev;
    pci_dev_get(dev);

    set_dev_node(&dev->dev, pcibus_to_node(bus));
    dev->dev.dma_mask = &dev->dma_mask;
    dev->dev.coherent_dma_mask = 0xffffffffull;

    /* Fix up broken headers */
    pci_fixup_device(pci_fixup_header, dev);

    /*
    * Add the device to our list of discovered devices
    * and the bus list for fixup functions, etc.
    */
    INIT_LIST_HEAD(&dev->global_list);
    down_write(&pci_bus_sem);
    list_add_tail(&dev->bus_list, &bus->devices); //加入到bus的device队列
    up_write(&pci_bus_sem);
}


/**
*    device_initialize - init device structure.
*    @dev:    device.
*
*    This prepares the device for use by other layers,
*    including adding it to the device hierarchy.
*    It is the first half of device_register(), if called by
*    that, though it can also be called separately, so one
*    may use @dev's fields (e.g. the refcount).
*/

void device_initialize(struct device *dev)
{
    //属于devices_subsys子系统
    kobj_set_kset_s(dev, devices_subsys);

    //以下是初始化一下队列, 如等待队列
    kobject_init(&dev->kobj);
    klist_init(&dev->klist_children, klist_children_get,
           klist_children_put);
    INIT_LIST_HEAD(&dev->dma_pools);
    INIT_LIST_HEAD(&dev->node);
    init_MUTEX(&dev->sem);
    spin_lock_init(&dev->devres_lock);
    INIT_LIST_HEAD(&dev->devres_head);
    device_init_wakeup(dev, 0);
    set_dev_node(dev, -1);
}
posted @ 2008-08-20 11:33 puppy 阅读(347) | 评论 (0)编辑 收藏
amba总线驱动
   R.wen


1).总线的注册

/*
* Primecells are part of the Advanced Microcontroller Bus Architecture,
* so we call the bus "amba".
*/
static struct bus_type amba_bustype = {
    .name        = "amba",
    .dev_attrs    = amba_dev_attrs,
    .match        = amba_match,
    .uevent        = amba_uevent,
    .suspend    = amba_suspend,
    .resume        = amba_resume,
};

static int __init amba_init(void)
{
    return bus_register(&amba_bustype);
}

完整的bus_type结构如下:
struct bus_type {
    const char        * name;

    struct subsystem    subsys;
    struct kset        drivers;
    struct kset        devices;
    struct klist        klist_devices;
    struct klist        klist_drivers;

    struct blocking_notifier_head bus_notifier;

    struct bus_attribute    * bus_attrs;
    struct device_attribute    * dev_attrs;
    struct driver_attribute    * drv_attrs;

    int        (*match)(struct device * dev, struct device_driver * drv);
    int        (*uevent)(struct device *dev, char **envp,
                int num_envp, char *buffer, int buffer_size);
    int        (*probe)(struct device * dev);
    int        (*remove)(struct device * dev);
    void        (*shutdown)(struct device * dev);

    int (*suspend)(struct device * dev, pm_message_t state);
    int (*suspend_late)(struct device * dev, pm_message_t state);
    int (*resume_early)(struct device * dev);
    int (*resume)(struct device * dev);
};

我们看到,amba bus的注册结构中少了一般都会有的probe函数,当这个函数存在时,通用bus系统会调用这个函数,而这个函数又会调用设备的probe函数,否则,通用bus系统就会直接调用设备的probe函数。

2).设备驱动的注册函数.

/**
*    amba_driver_register - register an AMBA device driver
*    @drv: amba device driver structure
*
*    Register an AMBA device driver with the Linux device model
*    core. If devices pre-exist, the drivers probe function will
*    be called.
*/
int amba_driver_register(struct amba_driver *drv)
{
    drv->drv.bus = &amba_bustype;

#define SETFN(fn)    if (drv->fn) drv->drv.fn = amba_##fn
    SETFN(probe);
    SETFN(remove);
    SETFN(shutdown);

    return driver_register(&drv->drv);
}


当一个设备想要注册进这个amba总线时,它就会调用以初始化好了的amba_driver结构调用amba_driver_register()这个驱动注册函数。而下一步这个函数就会调用通用的驱动注册函数driver_register。
这个函数对其他函数的调用路径如下:
driver_register -> bus_add_driver -> driver_attach -> __driver_attach(对每个设备) -> driver_probe_device -> drv->bus->match(dev, drv)(检查这个设备是否与新注册的驱动匹配) ->    really_probe -> dev->bus->probe(dev)(如果存在) (否则) -> drv->probe(dev)




而且我们还可以看到,传递给驱动probe的参数,是一个具体的设备。这个结构是设备注册的时候已经分配好的了。


3)设备的注册
当往系统插入一个新的设备时,他就会调用amba_device_register函数,这个过程跟驱动注册的过程差不多。

总线维护两个队列,一个用于设备,一个用于驱动。当一个新的设备加入是,这个设备结构会链进设备队列。而当一个驱动加入是,它就会加入驱动队列。而且,无论是设备还是驱动,当有插入操作是,它都会遍历另一个队列,寻找相应的驱动或设备,如果找到匹配的, 就会就行drv->device = dev, dev->driver=drv操作,这个某个设备就和某个驱动关联了起来。这就是说这个驱动就可以用了。


amba设备的结构:

#define AMBA_NR_IRQS    2
struct amba_device {
    struct device        dev;
    struct resource        res;
    u64            dma_mask;
    unsigned int        periphid;
    unsigned int        irq[AMBA_NR_IRQS];
};



/**
*    amba_device_register - register an AMBA device
*    @dev: AMBA device to register
*    @parent: parent memory resource
*
*    Setup the AMBA device, reading the cell ID if present.
*    Claim the resource, and register the AMBA device with
*    the Linux device manager.
*/
int amba_device_register(struct amba_device *dev, struct resource *parent)
{
    u32 pid, cid;
    void __iomem *tmp;
    int i, ret;

    dev->dev.release = amba_device_release;
    dev->dev.bus = &amba_bustype;
    dev->dev.dma_mask = &dev->dma_mask;
    dev->res.name = dev->dev.bus_id;

    if (!dev->dev.coherent_dma_mask && dev->dma_mask)
        dev_warn(&dev->dev, "coherent dma mask is unset\n");

    ##申请资源
    ret = request_resource(parent, &dev->res);
    if (ret)
        goto err_out;

    ##将物理地址映射到虚拟地址空间,是内核可见
    tmp = ioremap(dev->res.start, SZ_4K);
    if (!tmp) {
        ret = -ENOMEM;
        goto err_release;
    }

    for (pid = 0, i = 0; i < 4; i++)
        pid |= (readl(tmp + 0xfe0 + 4 * i) & 255) << (i * 8);
    for (cid = 0, i = 0; i < 4; i++)
        cid |= (readl(tmp + 0xff0 + 4 * i) & 255) << (i * 8);

    iounmap(tmp);

    if (cid == 0xb105f00d)
        dev->periphid = pid;

    if (!dev->periphid) {
        ret = -ENODEV;
        goto err_release;
    }

    ##注册设备,类似于驱动注册
    ret = device_register(&dev->dev);
    if (ret)
        goto err_release;

    if (dev->irq[0] != NO_IRQ)
        ret = device_create_file(&dev->dev, &dev_attr_irq0);
    if (ret == 0 && dev->irq[1] != NO_IRQ)
        ret = device_create_file(&dev->dev, &dev_attr_irq1);
    if (ret == 0)
        return ret;

    device_unregister(&dev->dev);

err_release:
    release_resource(&dev->res);
err_out:
    return ret;
}


对于设备,还会提供如下函数,用于申请一块可用的io内存
/**
*    amba_request_regions - request all mem regions associated with device
*    @dev: amba_device structure for device
*    @name: name, or NULL to use driver name
*/
int amba_request_regions(struct amba_device *dev, const char *name)
{
    int ret = 0;

    if (!name)
        name = dev->dev.driver->name;

    if (!request_mem_region(dev->res.start, SZ_4K, name))
        ret = -EBUSY;

    return ret;
}
posted @ 2008-08-20 11:26 puppy 阅读(525) | 评论 (0)编辑 收藏
SD/MMC卡, 看了SANDISK的MMC规范文档, 对着看了MMCI host的驱动. 看多了发觉了一个有趣的内核编程"规则",当某个子系统有很多不同的模块, 但是模块之间又有很多相似的功能时,这个子系统就会抽象出一组与特定功能无关的核心代码,供各个模块共用. 

如内核(2.6.22.5)里的MMC驱动,在driver里分成三个部分, 分别是block, core和host, 其中这个core就是核心部分且与硬件无关的代码,   当要操作硬件是, core才会通过在host里注册的钩子函数去调用特定的host相关的操作函数.

block是块设备驱动代码, 在看过了ram disk或硬盘等块设备的驱动后这个也是比较容易理解的. 一个块设备所做的主要的工作就是, 初始化一个IO请求队列, 然后当有上层代码(如read/write)IO请求时, 这些请求就会被加入这个队列中. 块设备就是负责从这个队列中去读取请求, 然后处理这些请求. 就是说块设备就是通过这个队列与上层代码相连的.

MMC块设备驱动实现比较有趣, 它是通过一个内核线程去实现, 当队列为空时, 这个线程就进入睡眠状态, 当有请求加入队列时, 请求处理函数就会唤醒这个线程, 让它去处理这个请求.

我们知道, io请求是以request为单位的, 就是说传递到块设备驱动的每个请求为一个request, 这个request中包含了IO需要的所有信息. MMC块设备得到这个request后, 主要的工作就是将这个请求信息转换为MMC主机控制器可以识别的信息, 这就是mmc_command结构. 块设备驱动构造好这个结构后, 就会调用core里的函数, 将请求交由其去处理, 这就是说block驱动是看不到硬件的, 它只和core代码打交道, 之后core才会通过特定host 控制器注册的钩子函数, 将IO请求写入硬件, 发出真正的IO操作.

usb子系统工作过程也是差不多的, 如U盘,   当上层有一个IO请求request时, 她先通过scsi子系统生成一个scsi命令,因为U盘是模拟scsi盘工作的, 然后这个scsi命令会进一步被转换成urb命令, 再通过USB接口发个U盘的控制器.



mmci控制器驱动(2.6.22.5) -- (1) 驱动注册

R.wen

mmci为ARM的sd/mmc主控制器的驱动. 并且这个控制器是挂接在ARM的amba总线之下的, 所以驱动的注册会用到amba总线的一些函数.

1).驱动的注册.

static struct amba_driver mmci_driver = {
    .drv        = {
        .name    = DRIVER_NAME,
    },
    .probe        = mmci_probe,
    .remove        = mmci_remove,
    .suspend    = mmci_suspend,
    .resume        = mmci_resume,
    .id_table    = mmci_ids,
};

static int __init mmci_init(void)
{
    return amba_driver_register(&mmci_driver);
}

这个注册函数可以在前面的介绍中可以看到, 它定义了一个amba_driver结构,然后通过amba_driver_register(&mmci_driver)往amba总线上注

册这个驱动.
并且,如我们之前所说的, 这个注册函数最终会调用驱动中的probe函数完成驱动的初始化操作. 这就是mmc_probe():

static int mmci_probe(struct amba_device *dev, void *id)
{
    struct mmc_platform_data *plat = dev->dev.platform_data;
    struct mmci_host *host;
    struct mmc_host *mmc;
    int ret;

    /* must have platform data */
    if (!plat) {
        ret = -EINVAL;
        goto out;
    }

    //申请一个内存区
    ret = amba_request_regions(dev, DRIVER_NAME);
    if (ret)
        goto out;

    分配一个mmc_host结构, 还有一个特殊的mmci_host结构.
    mmc = mmc_alloc_host(sizeof(struct mmci_host), &dev->dev);
    if (!mmc) {
        ret = -ENOMEM;
        goto rel_regions;
    }

    host = mmc_priv(mmc); 两个结构关联起来


    //以下这段代码获取时钟的频率

    //得到一个clk结构
    host->clk = clk_get(&dev->dev, "MCLK");
    if (IS_ERR(host->clk)) {
        ret = PTR_ERR(host->clk);
        host->clk = NULL;
        goto host_free;
    }

    ret = clk_enable(host->clk);
    if (ret)
        goto clk_free;

    host->plat = plat;
    host->mclk = clk_get_rate(host->clk); //频率
    host->mmc = mmc;

    //得到设备寄存器的MMU后的基址
    host->base = ioremap(dev->res.start, SZ_4K);
    if (!host->base) {
        ret = -ENOMEM;
        goto clk_disable;
    }


    //这是请求操作函数结构,最终的对MMC卡的操作都是通过它来完成的.
    mmc->ops = &mmci_ops;
    mmc->f_min = (host->mclk + 511) / 512;
    mmc->f_max = min(host->mclk, fmax);
    mmc->ocr_avail = plat->ocr_mask;
    mmc->caps = MMC_CAP_MULTIWRITE;

    /*
    * We can do SGIO
    */
    mmc->max_hw_segs = 16;
    mmc->max_phys_segs = NR_SG;

    /*
    * Since we only have a 16-bit data length register, we must
    * ensure that we don't exceed 2^16-1 bytes in a single request.
    */
    mmc->max_req_size = 65535;

    /*
    * Set the maximum segment size. Since we aren't doing DMA
    * (yet) we are only limited by the data length register.
    */
    mmc->max_seg_size = mmc->max_req_size;

    /*
    * Block size can be up to 2048 bytes, but must be a power of two.
    */
    mmc->max_blk_size = 2048;

    /*
    * No limit on the number of blocks transferred.
    */
    mmc->max_blk_count = mmc->max_req_size;

    spin_lock_init(&host->lock);

    //先关中断
    writel(0, host->base + MMCIMASK0);
    writel(0, host->base + MMCIMASK1);
    writel(0xfff, host->base + MMCICLEAR);


    //申请两个中断
    ret = request_irq(dev->irq[0], mmci_irq, IRQF_SHARED, DRIVER_NAME " (cmd)", host);
    if (ret)
        goto unmap;

    ret = request_irq(dev->irq[1], mmci_pio_irq, IRQF_SHARED, DRIVER_NAME " (pio)", host);
    if (ret)
        goto irq0_free;

    //再开中断
    writel(MCI_IRQENABLE, host->base + MMCIMASK0);

    //关联结构
    amba_set_drvdata(dev, mmc);
    mmc_add_host(mmc);

    printk(KERN_INFO "%s: MMCI rev %x cfg %02x at 0x%016llx irq %d,%d\n",
        mmc_hostname(mmc), amba_rev(dev), amba_config(dev),
        (unsigned long long)dev->res.start, dev->irq[0], dev->irq[1]);

    //定时扫描控制器
    init_timer(&host->timer);
    host->timer.data = (unsigned long)host;
    host->timer.function = mmci_check_status;
    host->timer.expires = jiffies + HZ;
    add_timer(&host->timer);

    return 0;

irq0_free:
    free_irq(dev->irq[0], host);
unmap:
    iounmap(host->base);
clk_disable:
    clk_disable(host->clk);
clk_free:
    clk_put(host->clk);
host_free:
    mmc_free_host(mmc);
rel_regions:
    amba_release_regions(dev);
out:
    return ret;
}


先来看这个函数的最后一部分:


    init_timer(&host->timer);
    host->timer.data = (unsigned long)host;
    host->timer.function = mmci_check_status;
    host->timer.expires = jiffies + HZ;
    add_timer(&host->timer);

这是建立一个定时器, 时间为1秒, 超时后,它就会执行mmci_check_status函数:

static void mmci_check_status(unsigned long data)
{
    struct mmci_host *host = (struct mmci_host *)data;
    unsigned int status;

    status = host->plat->status(mmc_dev(host->mmc));
    if (status ^ host->oldstat)
        mmc_detect_change(host->mmc, 0);

    host->oldstat = status;
    mod_timer(&host->timer, jiffies + HZ);
}

我们可以看到, 这个函数通过mod_timer(), 修改这个定时器, 使之1秒之后再次执行, 这个就是说, MMC控制器是通过轮询的方式,每隔1秒调

用mmci_check_status去查询这个设备的状态有没有改变. 状态的查询是通过 host->plat->status(mmc_dev(host->mmc)去完成的:

static unsigned int realview_mmc_status(struct device *dev)
{
    struct amba_device *adev = container_of(dev, struct amba_device, dev);
    u32 mask;

    if (adev->res.start == REALVIEW_MMCI0_BASE)
        mask = 1;
    else
        mask = 2;

    return readl(REALVIEW_SYSMCI) & mask;
}

struct mmc_platform_data realview_mmc0_plat_data = {
    .ocr_mask    = MMC_VDD_32_33|MMC_VDD_33_34,
    .status        = realview_mmc_status,
};


它是通过读取相应的状态寄存器位,然后跟原来的状态比较(status ^ host->oldstat), 如果发生了改变, 则调用mmc_detect_change去处理这

个变化.

/**
*    mmc_detect_change - process change of state on a MMC socket
*    @host: host which changed state.
*    @delay: optional delay to wait before detection (jiffies)
*
*    All we know is that card(s) have been inserted or removed
*    from the socket(s). We don't know which socket or cards.
*/
void mmc_detect_change(struct mmc_host *host, unsigned long delay)
{
#ifdef CONFIG_MMC_DEBUG
    unsigned long flags;
    spin_lock_irqsave(&host->lock, flags);
    BUG_ON(host->removed);
    spin_unlock_irqrestore(&host->lock, flags);
#endif

    mmc_schedule_delayed_work(&host->detect, delay);
}


显然,他是通过工作队列来做的. 这个host->detect是一个delayed_work结构. 代表一个任务. 这个任务是在probe中通过mmc_alloc_host已经

初始化了.mmc_alloc_host我们后面在看, 这里我们只要知道它有这个这个操作INIT_DELAYED_WORK(&host->detect, mmc_rescan), 就是说每当

发现状态有所改变, MMCI最终会执行这个mmc_rescan().

static void mmc_rescan(struct work_struct *work)
{
    struct mmc_host *host =
        container_of(work, struct mmc_host, detect.work);
    u32 ocr;
    int err;

    mmc_bus_get(host);

    if (host->bus_ops == NULL) { //检测SD卡
        /*
        * Only we can add a new handler, so it's safe to
        * release the lock here.
        */
        mmc_bus_put(host);

        mmc_claim_host(host);

        mmc_power_up(host);
        mmc_go_idle(host);

        mmc_send_if_cond(host, host->ocr_avail);

        err = mmc_send_app_op_cond(host, 0, &ocr);
        if (err == MMC_ERR_NONE) {
            if (mmc_attach_sd(host, ocr))
                mmc_power_off(host);

        } else { //检测MMC卡

            /*
            * If we fail to detect any SD cards then try
            * searching for MMC cards.
            */
            err = mmc_send_op_cond(host, 0, &ocr);
            if (err == MMC_ERR_NONE) { //如果没有出错
                if (mmc_attach_mmc(host, ocr))
                    mmc_power_off(host);
            } else {
                mmc_power_off(host);
                mmc_release_host(host);
            }
        }
    } else {
        if (host->bus_ops->detect && !host->bus_dead)
            host->bus_ops->detect(host);

        mmc_bus_put(host);
    }
}


看MMC卡的情况, 如果没出错, 则会调用mmc_attach_mmc,这个函数将会为一张MMC卡分配一个mmc_card结构, 初始化她并将她加入队列 host-

>cards中去.

**回头看mmci_probe:
她先通过以下方式申请一个mmc_host和mmci_host结构,

struct mmci_host *host;
struct mmc_host *mmc;
mmc = mmc_alloc_host(sizeof(struct mmci_host), &dev->dev);

这两个结构是一般和特殊的关系, mmc_host是通用的mmc host控制器的结构, 而特殊的控制器有好多种,这里是arm的MMCI, 还有at91的,

intel的等等. 可以想到, 都是MMC控制器, 每个特殊的控制器肯定有很多相同的属性, 所以这里就抽象出一个父结构,相当于面向对象里的父类

, 这里就是mmc_host了.


/**
*    mmc_alloc_host - initialise the per-host structure.
*    @extra: sizeof private data structure
*    @dev: pointer to host device model structure
*
*    Initialise the per-host structure.
*/
struct mmc_host *mmc_alloc_host(int extra, struct device *dev)
{
    struct mmc_host *host;

    host = mmc_alloc_host_sysfs(extra, dev);
    if (host) {
        spin_lock_init(&host->lock);
        init_waitqueue_head(&host->wq);
        INIT_DELAYED_WORK(&host->detect, mmc_rescan);

        /*
        * By default, hosts do not support SGIO or large requests.
        * They have to set these according to their abilities.
        */
        host->max_hw_segs = 1;
        host->max_phys_segs = 1;
        host->max_seg_size = PAGE_CACHE_SIZE;

        host->max_req_size = PAGE_CACHE_SIZE;
        host->max_blk_size = 512;
        host->max_blk_count = PAGE_CACHE_SIZE / 512;
    }

    return host;
}

我们看到,首先是分配一个空间的函数, 有个extra参数,就是用于为特殊设备分配额外空间的大小.
如果分配成功, 则初始化一个等待队列和一个工作队列, 而这个工作队列就是我们先前说提到的.
在接下来就是一个控制器的默认参数的设置了.

分配完空间后, 接着就是host = mmc_priv(mmc),

static inline void *mmc_priv(struct mmc_host *host)
{
    return (void *)host->private;
}

就是将他们关联起来, 这样通过通用的mmc_host,就可以找到特殊的mmci_host了.



**下一步就是时钟频率的设置, MMC卡的传输速率跟这个频率有直接的关系. 比如, 这个频率是20MHz, 而且MMC卡工作在4位模式, 则它的传输

速率就是20M * 4 = 80M/sec, 就是每秒10M多字节的速度.

    host->clk = clk_get(&dev->dev, "MCLK");
    if (IS_ERR(host->clk)) {
        ret = PTR_ERR(host->clk);
        host->clk = NULL;
        goto host_free;
    }

    ret = clk_enable(host->clk);
    if (ret)
        goto clk_free;

    host->mclk = clk_get_rate(host->clk);


先取出一个时钟结构, 这个是根据MCLK这个名字,然后取出这个相应的结构的. 这个是在系统初始化的时候设置好的.
如何通过clk_get_rate取出一个整型的速率值.


**接着就是

    struct mmc_platform_data {
        unsigned int ocr_mask;            /* available voltages */
        u32 (*translate_vdd)(struct device *, unsigned int);
        unsigned int (*status)(struct device *);
    };

    struct mmc_platform_data *plat = dev->dev.platform_data;
    host->plat = plat;

这个也是通过dev传过来的参数, 我们在上面已经看到了它的使用, 就是用来检测控制器状态的变化.


mmc控制器操作函数的设置,

    mmc->ops = &mmci_ops;

假设一个一个进程要读一个MMC卡上的一个文件, 它就要将这个请求(request)发给mmc块设备(mmc_block), mmc块设备最终调用这个结构中的

函数处理这个请求.

static const struct mmc_host_ops mmci_ops = {
    .request    = mmci_request,
    .set_ios    = mmci_set_ios,
};

mmci_request()函数就是直接操作控制器的的寄存器了.


** 还有一些SGIO的设置等等,和如下的一些值的初始化:

    mmc->f_min = (host->mclk + 511) / 512;
    mmc->f_max = min(host->mclk, fmax);
    mmc->ocr_avail = plat->ocr_mask;
    mmc->caps = MMC_CAP_MULTIWRITE;

**下一步就是中断的处理了, 做法很简单,就是--关中断, 设置中断处理例程, 开中断.
    writel(0, host->base + MMCIMASK0);
    writel(0, host->base + MMCIMASK1);
    writel(0xfff, host->base + MMCICLEAR);

    ## 两个IRQ
    ret = request_irq(dev->irq[0], mmci_irq, IRQF_SHARED, DRIVER_NAME " (cmd)", host);
    if (ret)
        goto unmap;

    ret = request_irq(dev->irq[1], mmci_pio_irq, IRQF_SHARED, DRIVER_NAME " (pio)", host);
    if (ret)
        goto irq0_free;

    writel(MCI_IRQENABLE, host->base + MMCIMASK0);


** 将MMC写会到dev的drvdata中, 关联起来
    amba_set_drvdata(dev, mmc);


** 最后一步就是我们一开始所说的定时器设置了.
posted @ 2008-08-20 11:24 puppy 阅读(1617) | 评论 (1)编辑 收藏

一、#define offsetof(TYPE, MEMBER) ((size_t) & ((TYPE *)0)->MEMBER )
1. ( (TYPE *)0 ) 将零转型为TYPE类型指针;
2. ((TYPE *)0)->MEMBER 访问结构中的数据成员;
3. &( ( (TYPE *)0 )->MEMBER )取出数据成员的地址;
4.(size_t)(&(((TYPE*)0)->MEMBER))结果转换类型;
巧妙之处在于将0转换成(TYPE*),结构以内存空间首地址0作为起始地址,则成员地址自然为偏移地址。
举例说明:
#include<stdio.h>
typedef struct _test
{
      char i;
      int j;
      char k;
}Test;
int main()
{
      Test *p = 0;
      printf("%p\n", &(p->k));
}
答案:00000008
自己分析:这里使用的是一个利用编译器技术的小技巧,即先求得结构成员变量在结构体中的相对于结构体的首地址的偏移地址,然后根据结构体的首地址为0,从而得出该偏移地址就是该结构体变量在该结构体中的偏移,即:该结构体成员变量距离结构体首的距离。在offsetof()中,这个member成员的地址实际上就是type数据结构中member成员相对于结构变量的偏移量。对于给定一个结构,offsetof(type,member)是一个常量,list_entry()正是利用这个不变的偏移量来求得链表数据项的变量地址。

二、container_of()
container_of() 来自\linux\kernel.h
内核中的注释:container_of - cast a member of a tructure out to the containing structure。
ptr: the pointer to the member.
type: the type of the container struct this is embedded in.
member:the name of the member within the truct.

#define container_of(ptr, type, member) ({ \
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetof(type,member) );})

自己分析:
1.(type *)0->member为设计一个type类型的结构体,起始地址为0,编译器将结构体的起始的地址加上此结构体成员变量的偏移得到此结构体成员变量的偏移地址,由于结构体起始地址为0,所以此结构体成员变量的偏移地址就等于其成员变量在结构体内的距离结构体开始部分的偏移量。即:&(type *)0->member就是取出其成员变量的偏移地址。而其等于其在结构体内的偏移量:即为:(size_t)(& ((type *)0)->member)经过size_t的强制类型转换后,其数值为结构体内的偏移量。该偏移量这里由offsetof()求出。
2.typeof( ( (type *)0)->member )为取出member成员的变量类型。用其定义__mptr指针.ptr为指向该成员变量的指针。__mptr为member数据类型的常量指针,其指向ptr所指向的变量处。
3.(char *)__mptr转换为字节型指针。(char *)__mptr - offsetof(type,member))用来求出结构体起始地址(为char *型指针),然后(type *)( (char *)__mptr -offsetof(type,member) )在(type *)作用下进行将字节型的结构体起始指针转换为type *型的结构体起始指针。
4.({ })这个扩展返回程序块中最后一个表达式的值。
      这就是从结构体某成员变量指针来求出该结构体的首指针。指针类型从结构体某成员变量类型转换为该结构体类型。

posted @ 2008-08-20 10:44 puppy 阅读(5282) | 评论 (7)编辑 收藏

 

GNU C是一个功能非常强大的跨平台C编译器,它对C语言提供了很多扩展,这些扩展对优化、目标代码布局、更安全的检查等方面提供了很强的支持。本文把支持GNU扩展的C语言称为GNU C。

    Linux内核代码使用了大量的GNU C扩展,以至于能够编译Linux内核的唯一编译器是GNU CC,以前甚至出现过编译 Linux内核要使用特殊的GNU CC版本的情况。本文是对Linux内核使用的GNU C扩展的一个汇总,希望当你读内核源码遇到不理解的语法和语义时,能从本文找到一个初步的解答,更详细的信息可以查看gcc.info。

语句表达式
------------------------------------------
GNU C把包含在括号中的复合语句看做是一个表达式,称为语句表达式,它可以出现在任何允许表达式的地方,你可以在语句表达式中使用循环、局部变量等,原本只能在复合语句中使用。例如:
    #define min_t(type, x, y) ({ type __x = x;\
                                 type __y = y;\
                                 __x < __y ? __x : __y;})

复合语句的最后一个语句应该是一个表达式,它的值将成为这个语句表达式的值。这里定义了一个安全的求最小值的宏,在标准C中,通常定义为:
    #define min(x,y) ((x) < (y) ? (x) : (y))

这个定义计算x和y分别两次(x和y中的小者被计算两次),当参数有副作用时,将产生不正确的结果。 使用语句表达式只计算参数一次,避免了可能的错误。语句表达式通常用于宏定义。


typeof
------------------------------------------
使用前一节定义的宏需要知道参数的类型,利用typeof可以定义更通用的宏,不必事先知道参数的类型,例如:
    141: #define min(x,y) ({ \
    142:                     const typeof(x) _x = (x); \
    143:                     const typeof(y) _y = (y); \
    144:                     (void) (&_x == &_y); \
    145:                     _x < _y ? _x : _y; })

这里typeof(x)表示x的值类型,第142行定义了一个与x类型相同的局部常量_x并初使化为x,注意第144行的作用是检查参数x和y的类型是否 相同(如果x和y的类型不同编译器将会发出warning,并不影响后面语句的执行)。typeof可以用在任何类型可以使用的地方,通常用于宏定义。



零长度数组
------------------------------------------
    GNU C允许使用零长度数组,在定义变长对象的头结构时,这个特性非常有用。例如:
    struct minix_dir_entry {
        __u16 inode;
        char name[0];
    };
结构的最后一个元素定义为零长度数组,它不占结构的空间。在标准C中则需要定义数组长度为1,分配时计算对象大小比较复杂。


可变参数宏
------------------------------------------
    在 GNU C中,宏可以接受可变数目的参数,就象函数一样,例如:
    #define KERN_DEBUG "<7>"
    #define pr_debug(fmt,arg...) printk(KERN_DEBUG fmt,##arg)
这里arg表示其余的参数,可以是零个或多个,这些参数以及参数之间的逗号构成arg的值,在宏扩展时替换arg,例如:
    pr_debug("%s:%d",filename,line)
扩展为
    printk("<7>" "%s:%d", filename, line)

使用##的原因是处理arg不匹配任何参数的情况。如果arg的值为空,GNU C预处理器在这种特殊情况下,丢弃##之前的逗号,这样
   pr_debug("success!\n")
扩展为
   printk("<7>" "success!\n")
注意最后没有逗号。


标号元素
------------------------------------------
标准C要求数组或结构变量的初使化值必须以固定的顺序出现, 在GNU C中,通过指定索引或结构域名,允许初始化值以任意顺序出现。指定数组索引的方法是在初始化值前写 '[INDEX] =',要指定一个范围使用 '[FIRST ... LAST] =' 的形式,例如:
    static unsigned long irq_affinity [NR_IRQS] = { [0 ... NR_IRQS-1] = ~0UL };
    将数组的所有元素初使化为 ~0UL,这可以看做是一种简写形式。

    要指定结构元素,在元素值前写 'FIELDNAME:',例如:
        struct file_operations ext2_file_operations = {
            llseek: generic_file_llseek,
            read: generic_file_read,
            write: generic_file_write,
            ioctl: ext2_ioctl,
            mmap: generic_file_mmap,
            open: generic_file_open,
            release: ext2_release_file,
            fsync: ext2_sync_file,
        };

将结构ext2_file_operations的元素llseek初始化为generic_file_llseek,元素read 初始化genenric_file_read,依次类推。我觉得这是GNU C 扩展中最好的特性之一,当结构的定义变化以至元素的偏移改变时,这种初始化方法仍然保证已知元素的正确性。对于未出现在初始化中的元素,其初值为 0。



case范围
------------------------------------------
GNU C 允许在一个 case 标号中指定一个连续范围的值,例如:
    case '0' ... '9': c -= '0'; break;
    case 'a' ... 'f': c -= 'a'-10; break;
    case 'A' ... 'F': c -= 'A'-10; break;

    case '0' ... '9':
    相当于
    case '0': case '1': case '2': case '3': case '4':
    case '5': case '6': case '7': case '8': case '9':




声明的特殊属性
------------------------------------------
GNU C允许声明函数、变量和类型的特殊属性,以便手工的代码优化和更仔细的代码检查。要指定一个声明的属性,在声明后写__attribute__ (( ATTRIBUTE )),其中ATTRIBUTE是属性说明,多个属性以逗号分隔。GNU C支持十几个属性,这里介绍最常用的:

* noreturn
属性noreturn用于函数,表示该函数从不返回。这可以让编译器生成稍微优化的代码,最重要的是可以消除不必要的警告信息比如未初使化的变量。例如:
    # define ATTRIB_NORET __attribute__((noreturn)) ....
    asmlinkage NORET_TYPE void do_exit(long error_code)   
    ATTRIB_NORET;

* format(ARCHETYPE, STRING-INDEX, FIRST-TO-CHECK)
属性format用于函数,表示该函数使用printf, scanf 或strftime风格的参数,使用这类函数最容易犯的错误是格式串与参数不匹配,指定format属性可以让编译器根据格式串检查参数类型。例如:
    asmlinkage int printk(const char * fmt, ...)
    __attribute__ ((format (printf, 1, 2)));
表示第一个参数是格式串,从第二个参数起根据格式串检查参数。

* unused
属性unused用于函数和变量,表示该函数或变量可能不使用,这个属性可以避免编译器产生警告信息。

* section ("section-name")
属性section用于函数和变量,通常编译器将函数放在.text节,变量放在.data或 .bss 节,使用section属性,可以让编译器将函数或变量放在指定的节中。例如:
    #define __init __attribute__ ((__section__ (".text.init")))
    #define __exit __attribute__ ((unused, __section__(".text.exit")))
    #define __initdata __attribute__ ((__section__ (".data.init")))
    #define __exitdata __attribute__ ((unused, __section__ (".data.exit")))
    #define __initsetup __attribute__ ((unused,__section__ (".setup.init")))
    #define __init_call __attribute__ ((unused,__section__ (".initcall.init")))
    #define __exit_call __attribute__ ((unused,__section__ (".exitcall.exit")))
    连接器可以把相同节的代码或数据安排在一起,Linux内核很喜欢使用这种技术,例如系统的初始化代码被安排在单独的一个节,在初始化结束后就可以释放这部分内存。

* aligned (ALIGNMENT)
属性aligned用于变量、结构或联合类型,指定变量、结构域、结构或联合的对齐量,以字节为单位,例如:
        struct i387_fxsave_struct {
            unsigned short cwd;
            unsigned short swd;
            unsigned short twd;
            unsigned short fop;
        } __attribute__ ((aligned (16)));
    表示该结构类型的变量以16字节对齐。通常编译器会选择合适的对齐量,显示指定对齐通常是由于体系限制、优化等原因。

* packed
属性packed用于变量和类型,用于变量或结构域时表示使用最小可能的对齐,用于枚举、结构或联合类型时表示该类型使用最小的内存。例如:
        struct Xgt_desc_struct {
            unsigned short size;
            unsigned long address __attribute__((packed));
        };
域address将紧接着size分配。属性packed的用途大多是定义硬件相关的结构,使元素之间没有因对齐而造成的空洞。


当前函数名
------------------------------------------
    GNU CC预定义了两个标志符保存当前函数的名字,__FUNCTION__保存函数在源码中的名字;
__PRETTY_FUNCTION__保存带语言特色的名字。在C函数中,这两个名字是相同的,在C++函数中,__PRETTY_FUNCTION__包括函数返回类型等额外信息,Linux内核只使用了__FUNCTION__。
        void ext2_update_dynamic_rev(struct super_block *sb) {
            struct ext2_super_block *es = EXT2_SB(sb)->s_es;
            if (le32_to_cpu(es->s_rev_level) > EXT2_GOOD_OLD_REV)
            return;
            ext2_warning(sb, __FUNCTION__,
            "updating to rev %d because of new feature flag, "
            "running e2fsck is recommended",
            EXT2_DYNAMIC_REV);
    这里__FUNCTION__将被替换为字符串 "ext2_update_dynamic_rev"。虽然__FUNCTION__看起来类似于标准 C中的__FILE__,但实际上__FUNCTION__是被编译器替换的,不象__FILE__ 被预处理器替换。



内建函数
------------------------------------------
GNU C提供了大量的内建函数,其中很多是标准C库函数的内建版本,例如memcpy,它们与对应的C库函数功能相同,本文不讨论这类函数,其他内建函数的名字通常以__builtin开始。
* __builtin_return_address (LEVEL)
内建函数__builtin_return_address返回当前函数或其调用者的返回地址,参数LEVEL指定在栈上搜索框架的个数,0表示当前函数的返回地址,1表示当前函数的调用者的返回地址,依此类推。例如:
        printk(    KERN_ERR "schedule_timeout: wrong timeout "
                "value %lx from %p\n", timeout,
                __builtin_return_address(0));

* __builtin_constant_p(EXP)
内建函数__builtin_constant_p用于判断一个值是否为编译时常数,如果参数EXP的值是常数,函数返回 1,否则返回 0。例如:
        #define test_bit(nr,addr) \
            (__builtin_constant_p(nr) ? \
            constant_test_bit((nr),(addr)) : \
            variable_test_bit((nr),(addr)))
    很多计算或操作在参数为常数时有更优化的实现,在GNU C中用上面的方法可以根据参数是否为常数,只编译常数版本或非常数版本,这样既不失通用性,又能在参数是常数时编译出最优化的代码。

* __builtin_expect(EXP, C)
    内建函数__builtin_expect用于为编译器提供分支预测信息,其返回值是整数表达式EXP的值,C的值必须是编译时常数。例如:
      13: #define likely(x) __builtin_expect((x),1)
      14: #define unlikely(x) __builtin_expect((x),0)

      564: if (unlikely(in_interrupt())) {
      565: printk("Scheduling in interrupt\n");
      566: BUG();
      567: }
    这个内建函数的语义是 EXP 的预期值是 C,编译器可以根据这个信息适当地重排语句块的顺序,使程序在预期的情况下有更高的执行效率。上面的例子表示处于中断上下文是很少发生的,第 565-566 行的目标码可能会放在较远的位置,以保证经常执行的目标码更紧凑。
posted @ 2008-08-20 10:30 puppy 阅读(386) | 评论 (0)编辑 收藏

__FILE__
This macro expands to the name of the current input file, in the form of a C string constant. This is the path by which the preprocessor opened the file, not the short name specified in #include or as the input file name argument. For example, "/usr/local/include/myheader.h" is a possible expansion of this macro.

__LINE__
This macro expands to the current input line number, in the form of a decimal integer constant. While we call it a predefined macro, it's a pretty strange macro, since its "definition" changes with each new line of source code.
__FILE__ and __LINE__ are useful in generating an error message to report an inconsistency detected by the program; the message can state the source line at which the inconsistency was detected. For example,

     fprintf (stderr, "Internal error: "
                      "negative string length "
                      "%d at %s, line %d.",
              length, __FILE__, __LINE__);
    
An #include directive changes the expansions of __FILE__ and __LINE__ to correspond to the included file. At the end of that file, when processing resumes on the input file that contained the #include directive, the expansions of __FILE__ and __LINE__ revert to the values they had before the #include (but __LINE__ is then incremented by one as processing moves to the line after the #include).

A #line directive changes __LINE__, and may change __FILE__ as well. See Line Control.

C99 introduces __func__, and GCC has provided __FUNCTION__ for a long time. Both of these are strings containing the name of the current function (there are slight semantic differences; see the GCC manual). Neither of them is a macro; the preprocessor does not know the name of the current function. They tend to be useful in conjunction with __FILE__ and __LINE__, though.


__DATE__
This macro expands to a string constant that describes the date on which the preprocessor is being run. The string constant contains eleven characters and looks like "Feb 12 1996". If the day of the month is less than 10, it is padded with a space on the left.
If GCC cannot determine the current date, it will emit a warning message (once per compilation) and __DATE__ will expand to "??? ?? ????".


__TIME__
This macro expands to a string constant that describes the time at which the preprocessor is being run. The string constant contains eight characters and looks like "23:59:01".
If GCC cannot determine the current time, it will emit a warning message (once per compilation) and __TIME__ will expand to "??:??:??".


__STDC__
In normal operation, this macro expands to the constant 1, to signify that this compiler conforms to ISO Standard C. If GNU CPP is used with a compiler other than GCC, this is not necessarily true; however, the preprocessor always conforms to the standard unless the -traditional-cpp option is used.
This macro is not defined if the -traditional-cpp option is used.

On some hosts, the system compiler uses a different convention, where __STDC__ is normally 0, but is 1 if the user specifies strict conformance to the C Standard. CPP follows the host convention when processing system header files, but when processing user files __STDC__ is always 1. This has been reported to cause problems; for instance, some versions of Solaris provide X Windows headers that expect __STDC__ to be either undefined or 1. See Invocation.


__STDC_VERSION__
This macro expands to the C Standard's version number, a long integer constant of the form yyyymmL where yyyy and mm are the year and month of the Standard version. This signifies which version of the C Standard the compiler conforms to. Like __STDC__, this is not necessarily accurate for the entire implementation, unless GNU CPP is being used with GCC.
The value 199409L signifies the 1989 C standard as amended in 1994, which is the current default; the value 199901L signifies the 1999 revision of the C standard. Support for the 1999 revision is not yet complete.

This macro is not defined if the -traditional-cpp option is used, nor when compiling C++ or Objective-C.


__STDC_HOSTED__
This macro is defined, with value 1, if the compiler's target is a hosted environment. A hosted environment has the complete facilities of the standard C library available.

__cplusplus
This macro is defined when the C++ compiler is in use. You can use __cplusplus to test whether a header is compiled by a C compiler or a C++ compiler. This macro is similar to __STDC_VERSION__, in that it expands to a version number. A fully conforming implementation of the 1998 C++ standard will define this macro to 199711L. The GNU C++ compiler is not yet fully conforming, so it uses 1 instead. We hope to complete our implementation in the near future.

__OBJC__
This macro is defined, with value 1, when the Objective-C compiler is in use. You can use __OBJC__ to test whether a header is compiled by a C compiler or a Objective-C compiler.

__ASSEMBLER__
This macro is defined with value 1 when preprocessing assembly language.

GCC 中普通预定义宏
__GNUC__
__GNUC_MINOR__
__GNUC_PATCHLEVEL__
These macros are defined by all GNU compilers that use the C preprocessor: C, C++, and Objective-C. Their values are the major version, minor version, and patch level of the compiler, as integer constants. For example, GCC 3.2.1 will define __GNUC__ to 3, __GNUC_MINOR__ to 2, and __GNUC_PATCHLEVEL__ to 1. They are defined only when the entire compiler is in use; if you invoke the preprocessor directly, they are not defined.
__GNUC_PATCHLEVEL__ is new to GCC 3.0; it is also present in the widely-used development snapshots leading up to 3.0 (which identify themselves as GCC 2.96 or 2.97, depending on which snapshot you have).

If all you need to know is whether or not your program is being compiled by GCC, you can simply test __GNUC__. If you need to write code which depends on a specific version, you must be more careful. Each time the minor version is increased, the patch level is reset to zero; each time the major version is increased (which happens rarely), the minor version and patch level are reset. If you wish to use the predefined macros directly in the conditional, you will need to write it like this:

          /* Test for GCC > 3.2.0 */
          #if __GNUC__ > 3 || \
              (__GNUC__ == 3 && (__GNUC_MINOR__ > 2 || \
                                 (__GNUC_MINOR__ == 2 && \
                                  __GNUC_PATCHLEVEL__ > 0))
         
Another approach is to use the predefined macros to calculate a single number, then compare that against a threshold:

          #define GCC_VERSION (__GNUC__ * 10000 \
                               + __GNUC_MINOR__ * 100 \
                               + __GNUC_PATCHLEVEL__)
          ...
          /* Test for GCC > 3.2.0 */
          #if GCC_VERSION > 30200
         
Many people find this form easier to understand.


__GNUG__
The GNU C++ compiler defines this. Testing it is equivalent to testing (__GNUC__ && __cplusplus).

__STRICT_ANSI__
GCC defines this macro if and only if the -ansi switch, or a -std switch specifying strict conformance to some version of ISO C, was specified when GCC was invoked. It is defined to 1. This macro exists primarily to direct GNU libc's header files to restrict their definitions to the minimal set found in the 1989 C standard.

__BASE_FILE__
This macro expands to the name of the main input file, in the form of a C string constant. This is the source file that was specified on the command line of the preprocessor or C compiler.

__INCLUDE_LEVEL__
This macro expands to a decimal integer constant that represents the depth of nesting in include files. The value of this macro is incremented on every #include directive and decremented at the end of every included file. It starts out at 0, it's value within the base file specified on the command line.

__ELF__
This macro is defined if the target uses the ELF object format.

__VERSION__
This macro expands to a string constant which describes the version of the compiler in use. You should not rely on its contents having any particular form, but it can be counted on to contain at least the release number.

__OPTIMIZE__
__OPTIMIZE_SIZE__
__NO_INLINE__
These macros describe the compilation mode. __OPTIMIZE__ is defined in all optimizing compilations. __OPTIMIZE_SIZE__ is defined if the compiler is optimizing for size, not speed. __NO_INLINE__ is defined if no functions will be inlined into their callers (when not optimizing, or when inlining has been specifically disabled by -fno-inline).
These macros cause certain GNU header files to provide optimized definitions, using macros or inline functions, of system library functions. You should not use these macros in any way unless you make sure that programs will execute with the same effect whether or not they are defined. If they are defined, their value is 1.


__CHAR_UNSIGNED__
GCC defines this macro if and only if the data type char is unsigned on the target machine. It exists to cause the standard header file limits.h to work correctly. You should not use this macro yourself; instead, refer to the standard macros defined in limits.h.

__WCHAR_UNSIGNED__
Like __CHAR_UNSIGNED__, this macro is defined if and only if the data type wchar_t is unsigned and the front-end is in C++ mode.

__REGISTER_PREFIX__
This macro expands to a single token (not a string constant) which is the prefix applied to CPU register names in assembly language for this target. You can use it to write assembly that is usable in multiple environments. For example, in the m68k-aout environment it expands to nothing, but in the m68k-coff environment it expands to a single %.

__USER_LABEL_PREFIX__
This macro expands to a single token which is the prefix applied to user labels (symbols visible to C code) in assembly. For example, in the m68k-aout environment it expands to an _, but in the m68k-coff environment it expands to nothing.
This macro will have the correct definition even if -f(no-)underscores is in use, but it will not be correct if target-specific options that adjust this prefix are used (e.g. the OSF/rose -mno-underscores option).


__SIZE_TYPE__
__PTRDIFF_TYPE__
__WCHAR_TYPE__
__WINT_TYPE__
These macros are defined to the correct underlying types for the size_t, ptrdiff_t, wchar_t, and wint_t typedefs, respectively. They exist to make the standard header files stddef.h and wchar.h work correctly. You should not use these macros directly; instead, include the appropriate headers and use the typedefs.

__CHAR_BIT__
Defined to the number of bits used in the representation of the char data type. It exists to make the standard header given numerical limits work correctly. You should not use this macro directly; instead, include the appropriate headers.

__SCHAR_MAX__
__WCHAR_MAX__
__SHRT_MAX__
__INT_MAX__
__LONG_MAX__
__LONG_LONG_MAX__
Defined to the maximum value of the signed char, wchar_t, signed short, signed int, signed long, and signed long long types respectively. They exist to make the standard header given numerical limits work correctly. You should not use these macros directly; instead, include the appropriate headers.

__USING_SJLJ_EXCEPTIONS__
This macro is defined, with value 1, if the compiler uses the old mechanism based on setjmp and longjmp for exception handling.

__NEXT_RUNTIME__
This macro is defined, with value 1, if (and only if) the NeXT runtime (as in -fnext-runtime) is in use for Objective-C. If the GNU runtime is used, this macro is not defined, so that you can use this macro to determine which runtime (NeXT or GNU) is being used.

__LP64__
_LP64
These macros
posted @ 2008-08-20 10:27 puppy 阅读(272) | 评论 (0)编辑 收藏
 
 
 
 
start_kernel() -> setup_arch(&command_line) -> mdesc = setup_machine(machine_arch_type) -> list = lookup_machine_type(nr) -> return struct machine_desc.

内核在启动的时候就是最先运行start_kernel() , 然后她就会调用体系结构相关的setup_arch(&command_line), 如arm体系结构的在arch/arm/kernel/setup.c中, 进一步, 她就要初始化板级相关的设备,   但在此之前, 她必须找到这块目标板的描述结构, 所以她就先通过lookup_machine_type(nr)来查找这个结构, 传入的参数是又uboot传过来的, 存储在r2中. 如假设她传入的是'SMDK2410' 的nr, lookup_machine_type(nr) 就会找到如下的machine_desc. 这个结构在一个专门描述sddk2410的文件中--arch/arm/mach-s3c2410/mach-smdk2410.c


MACHINE_START(SMDK2410, "SMDK2410") /* @TODO: request a new identifier and switch
                    * to SMDK2410 */
    /* Maintainer: Jonas Dietsche */
    .phys_io    = S3C2410_PA_UART,
    .io_pg_offst    = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,
    .boot_params    = S3C2410_SDRAM_PA + 0x100,
//重要的板级初始化函数
    .map_io        = smdk2410_map_io,
    .init_irq    = s3c24xx_init_irq,
    .init_machine    = smdk2410_init,
    .timer        = &s3c24xx_timer,

MACHINE_END

MACHINE_START的第一个参数是用来构造机器的类型的, 如这里的SMDK2410 ,  
又下面的定义
.nr        = MACH_TYPE_##_type
有 nr = MACH_TYPE_SMDK2410
MACH_TYPE_SMDK2410这些机器类型则在/include/asm-arm/mach-types.h中定义:
#define MACH_TYPE_SMDK2410             193



完整的结构描述如下:

struct machine_desc {
    /*
    * Note! The first four elements are used
    * by assembler code in head-armv.S
    */
    unsigned int        nr;        /* architecture number    */
    unsigned int        phys_io;    /* start of physical io    */
    unsigned int        io_pg_offst;    /* byte offset for io
                        * page tabe entry    */

    const char        *name;        /* architecture name    */
    unsigned long        boot_params;    /* tagged list        */

    unsigned int        video_start;    /* start of video RAM    */
    unsigned int        video_end;    /* end of video RAM    */

    unsigned int        reserve_lp0 :1;    /* never has lp0    */
    unsigned int        reserve_lp1 :1;    /* never has lp1    */
    unsigned int        reserve_lp2 :1;    /* never has lp2    */
    unsigned int        soft_reboot :1;    /* soft reboot        */
    void            (*fixup)(struct machine_desc *,
                    struct tag *, char **,
                    struct meminfo *);
    void            (*map_io)(void);/* IO mapping function    */
    void            (*init_irq)(void);
    struct sys_timer    *timer;        /* system tick timer    */
    void            (*init_machine)(void);

};


/*
* Set of macros to define architecture features. This is built into
* a table by the linker.
*/
#define MACHINE_START(_type,_name)            \
static const struct machine_desc __mach_desc_##_type    \
__used                            \
__attribute__((__section__(".arch.info.init"))) = {    \
    .nr        = MACH_TYPE_##_type,        \
    .name        = _name,

#define MACHINE_END                \
};



在arch/arm/mach-s3c2410/mach-smdk2410.c 定义了以下两个函数实现:

static struct map_desc smdk2410_iodesc[] __initdata = {
/* nothing here yet */
};

static struct platform_device *smdk2410_devices[] __initdata = {
    &s3c_device_usb,
    &s3c_device_lcd,
    &s3c_device_wdt,
    &s3c_device_i2c,
    &s3c_device_iis,
};

static void __init smdk2410_map_io(void)
{
    //主要是做外围设备的物理地址到虚拟地址的映射
    s3c24xx_init_io(smdk2410_iodesc, ARRAY_SIZE(smdk2410_iodesc));
    //频率的初始化
    s3c24xx_init_clocks(0);
    //串口的初始化
    s3c24xx_init_uarts(smdk2410_uartcfgs, ARRAY_SIZE(smdk2410_uartcfgs));
}

static void __init smdk2410_init(void)
{
    //注册smdk2410特有的设备
    platform_add_devices(smdk2410_devices, ARRAY_SIZE(smdk2410_devices));

    //smdk系列的初始化, 我们知道smdk有2410, 2412, 2440等等, 所以这里她又抽象出了一些共有的相同的部分, 都使用下面的函数做初始化工作.
    smdk_machine_init();
}

/* devices we initialise */

static struct platform_device __initdata *smdk_devs[] = {
    &s3c_device_nand,
    &smdk_led4,
    &smdk_led5,
    &smdk_led6,
    &smdk_led7,
};


void __init smdk_machine_init(void)
{
    /* Configure the LEDs (even if we have no LED support)*/

    s3c2410_gpio_cfgpin(S3C2410_GPF4, S3C2410_GPF4_OUTP);
    s3c2410_gpio_cfgpin(S3C2410_GPF5, S3C2410_GPF5_OUTP);
    s3c2410_gpio_cfgpin(S3C2410_GPF6, S3C2410_GPF6_OUTP);
    s3c2410_gpio_cfgpin(S3C2410_GPF7, S3C2410_GPF7_OUTP);

    s3c2410_gpio_setpin(S3C2410_GPF4, 1);
    s3c2410_gpio_setpin(S3C2410_GPF5, 1);
    s3c2410_gpio_setpin(S3C2410_GPF6, 1);
    s3c2410_gpio_setpin(S3C2410_GPF7, 1);

    if (machine_is_smdk2443())
        smdk_nand_info.twrph0 = 50;

    //注意这一行, 注册了nand flash的分区信息
    s3c_device_nand.dev.platform_data = &smdk_nand_info;

    //再一次注册设备, 但这一次是smdk共有的. 如nand flash等等
    platform_add_devices(smdk_devs, ARRAY_SIZE(smdk_devs));

   //电源管理的初始化
    s3c2410_pm_init();
}
posted @ 2008-08-20 10:24 puppy 阅读(310) | 评论 (0)编辑 收藏
static struct map_desc smdk2410_iodesc[] __initdata = {
   {
   /* Map the ethernet controller CS8900A */
   .virtual =  vSMDK2410_ETH_IO,
   .pfn = __phys_to_pfn(pSMDK2410_ETH_IO),
   .length = SZ_1M,
   .type = MT_DEVICE
   },
};
这样的定义,无非是希望系统将 pSMDK2410_ETH_IO这个物理地址给映射到虚地址vSMDK2410_ETH_IO上,占用可操作的长度是1M.

接下来在我的CS8900的驱动里就能利用这个转换后的地址操作我的CS8900了。例如一下的代码:
dev->base_addr = vSMDK2410_ETH_IO + 0x300;
dev->irq = SMDK2410_ETH_IRQ;

if ((result = check_mem_region (dev->base_addr, 0xfff))) {
printk (KERN_ERR "%s: can't get I/O port address 0x%lx\n",dev->name,dev->base_addr);
return (result);
}

request_mem_region (dev->base_addr, 0xff, dev->name);

这里直接就可以使用vSMDK2410_ETH_IO 这个事先定义好的地址进行操作了,后面的几个语句无非的检查和对齐这个地址资源什么的。

而用smdk2410_devices定义的资源,需要在驱动代码中动态向系统申请映射,并返回一个可操作的虚地址才能操作的。例如以下的代码:
host->mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!host->mem) {
   printk(KERN_ERR PFX "failed to get io memory region resouce.\n");
   ret = -ENOENT;
   goto probe_free_host;
}

host->mem = request_mem_region(host->mem->start,
RESSIZE(host->mem), pdev->name);

if (!host->mem) {
   printk(KERN_ERR PFX "failed to request io memory region.\n");
   ret = -ENOENT;
   goto probe_free_host;
}

host->base = ioremap(host->mem->start, RESSIZE(host->mem));
if (host->base == 0) {
   printk(KERN_ERR PFX "failed to ioremap() io memory region.\n");
   ret = -EINVAL;
   goto probe_free_mem_region;
}

其中platform_get_resource是得到物理的IO或者寄存器的物理地址.
然后用request_mem_region申请一块区域,最后用host->base = ioremap(host->mem->start, RESSIZE(host->mem));将可操作的虚地址返回给驱动,驱动用这个转换后的地址就可以操作硬件的寄存器什么的了...这里面的物理和需地址的转化是怎样的。自己有兴趣自己了解.

posted @ 2008-08-20 10:22 puppy 阅读(721) | 评论 (0)编辑 收藏
仅列出标题
共5页: 1 2 3 4 5