2009年7月18日星期六

linux2.6 makefile 编译模块示例

linux2.6 makefile 编译模块示例 


下面是《linux 设备驱动程序》的一个makefile 例子
# Comment/uncomment the following line to enable/disable debugging
#DEBUG = y


ifeq ($(DEBUG),y)
  DEBFLAGS = -O -g -DSCULLD_DEBUG # "-O" is needed to expand inlines
else
  DEBFLAGS = -O2
endif

CFLAGS += $(DEBFLAGS) -I$(LDDINC)

TARGET = sculld

ifneq ($(KERNELRELEASE),)

sculld-objs := main.o mmap.o ##改模块包含2个.o文件

obj-m    := sculld.o

else

KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD       := $(shell pwd)

modules:
    $(MAKE) -C $(KERNELDIR) M=$(PWD) LDDINC=$(PWD) modules

endif


install:
    install -d $(INSTALLDIR)
    install -c $(TARGET).o $(INSTALLDIR)

clean:
    rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions

depend .depend dep:
    $(CC) $(CFLAGS) -M *.c > .depend

ifeq (.depend,$(wildcard .depend))
include .depend
endif

1.   $(shell   uname   -r)会调用uname  
-r命令并把结果展开作为值,返回值是当前内核版本号,整体来说KERNELDIR   ?=   /lib/modules/$(shell  
uname   -r)/build的含义就是在KERNELDIR未定义时赋值为:/lib/modules/2.6.xx/build  
(假设当前内核版本号为2.6.xx),这就是一个路径,一般来说会是一个符号链接,指向真正的内核源码的路径   build -> /usr/src/linux-headers-2.6.28-14-generic

2.  
modules不是伪目标,$(MAKE)   -C   $(KERNELDIR),   M=$(PWD),  
LDDINC=$(PWD)/../include   modules的含义:-C  
$(KERNELDIR)表示在读取makefile前进入$(KERNELDIR)目录,M=$(PWD),  
LDDINC=$(PWD)/../include则只是传了2个变量给Makefile,modules则是$(KERNELDIR)中的
Makefile中的target。
3.  
这个Makefile调用时有2种可能,一种是KERNELRELEASE已定义,那实际上是在内核整体编译中调用,自己参考一下现有内核部分编译的方
式;另一种则是KERNELRELEASE未定义,调用的方式应该会是类似make   modules的方式调用。

digium pci模拟板卡 硬件访问分析

digium pci模拟板卡 硬件访问分析

static struct pci_device_id wctdm_pci_tbl[] = {
    { 0xe159, 0x0001, 0xa159, PCI_ANY_ID, 0, 0, (unsigned long) &wctdm },
    { 0xe159, 0x0001, 0xe159, PCI_ANY_ID, 0, 0, (unsigned long) &wctdm },
    { 0xe159, 0x0001, 0xb100, PCI_ANY_ID, 0, 0, (unsigned long) &wctdme },
    { 0xe159, 0x0001, 0xb1d9, PCI_ANY_ID, 0, 0, (unsigned long) &wctdmi },
    { 0xe159, 0x0001, 0xb118, PCI_ANY_ID, 0, 0, (unsigned long) &wctdmi },
    { 0xe159, 0x0001, 0xb119, PCI_ANY_ID, 0, 0, (unsigned long) &wctdmi },
    { 0xe159, 0x0001, 0xa9fd, PCI_ANY_ID, 0, 0, (unsigned long) &wctdmh },
    { 0xe159, 0x0001, 0xa8fd, PCI_ANY_ID, 0, 0, (unsigned long) &wctdmh },
    { 0xe159, 0x0001, 0xa800, PCI_ANY_ID, 0, 0, (unsigned long) &wctdmh },
    { 0xe159, 0x0001, 0xa801, PCI_ANY_ID, 0, 0, (unsigned long) &wctdmh },
    { 0xe159, 0x0001, 0xa908, PCI_ANY_ID, 0, 0, (unsigned long) &wctdmh },
    { 0xe159, 0x0001, 0xa901, PCI_ANY_ID, 0, 0, (unsigned long) &wctdmh },
#ifdef TDM_REVH_MATCHALL
    { 0xe159, 0x0001, PCI_ANY_ID, PCI_ANY_ID, 0, 0, (unsigned long) &wctdmh },
#endif
    { 0 }
};
上面的table分别设定了digium公司的wctdm系列的pci的板卡以第一个为例 vendor ID是0xe159,  deviceID是0x0001,  subvendorID是0xa159, subdeviceID是PCI_ANY_ID,callsID和class_mask都是0,最后的是driver_data 用来区别不同设备的信息,保存了设备名例如:
static struct wctdm_desc wctdm = { "Wildcard S400P Prototype", 0 };
操作系统就是根据这些ID来自动调用dahdi的driver的。

pci设备好像会自动映射寄存器到IO端口。
下面的是pci控制相关 访问方法:
首先或者IO端口的映射:
wc->ioaddr = pci_resource_start(pdev, 0);
request_region(wc->ioaddr, 0xff, "wctdm")
然后通过outb inb端口访问函数进行访问。
例如:
outb(0x01, wc->ioaddr + WC_CNTL);
inb(wc->ioaddr + WC_CNTL);
pci 板卡寄存器地址(偏移)
#define WC_CNTL        0x00//写入0x0e reset pci chip and registers
            //写入0x01 reset signal

#define WC_OPER        //控制TDM 写入0x01 reset  tdm 开始dma
    //写入0x00 stop dma
      
#define WC_AUXC        0x02
#define WC_AUXD        0x03  ////spi 信号寄存器
#define WC_MASK0       0x04
#define WC_MASK1       0x05 ////中断相关
#define WC_INTSTAT     0x06////中断相关
#define WC_AUXR        0x07 ////spi的接收缓冲器

#define WC_DMAWS    0x08
#define WC_DMAWI    0x0c
#define WC_DMAWE    0x10
#define WC_DMARS    0x18
#define WC_DMARI    0x1c
#define WC_DMARE    0x20

#define WC_AUXFUNC    0x2b
#define WC_SERCTL    0x2d  //Configure serial port for MSB->LSB operation
#define WC_FSCDELAY    0x2f  //Delay FSC by 0 so it's properly aligned






通过spi的访问板卡芯片: 首先进行片选 然后通过spi总线进行读写。
示例代码:
static unsigned char __wctdm_getreg(struct wctdm *wc, int card, unsigned char reg)
{
    __wctdm_setcard(wc, card);////通过给wc_cs寄存器赋值 选择板卡。
    if (wc->modtype[card] == MOD_TYPE_FXO) { ////fxo的寄存器读
        __write_8bits(wc, 0x60);
        __write_8bits(wc, reg & 0x7f);
    } else {
        __write_8bits(wc, reg | 0x80); ////fxs的寄存器读
    }
    return __read_8bits(wc); ////读取数据
}


/////片选的实现 通过对wc_cs寄存去赋值 实现片选
#define WC_REGBASE    0xc0  ////这个是spi寄存器的起始地址
#define WC_SYNC        0x0
#define WC_TEST        0x1
#define WC_CS        0x2////片选寄存器 选择哪个fxo口或者fxs口
#define WC_VER        0x3
以上4个寄存器的访问方法:
示例代码:
static unsigned char __wctdm_getcreg(struct wctdm *wc, unsigned char reg)
{
    return inb(wc->ioaddr + WC_REGBASE + ((reg & 0xf) << 2));
}





spi bus访问的的实现
wc_auxd 寄存器是控制spi。
spi的输入输出片选时钟信号分别对应 wc_auxd寄存器的一个bit 对应关系如下:
#define BIT_CS        (1 << 2)
#define BIT_SCLK    (1 << 3)
#define BIT_SDI        (1 << 4)
#define BIT_SDO        (1 << 5)
ios 是spi的4条信号线的对应位置的数据将ios的相应位置的数据赋值为0 或者1 然后将ios写入wc_auxd寄存去实现对spi的4条信号线的控制。
示例代码:
static inline void __write_8bits(struct wctdm *wc, unsigned char bits)
{
    /* Drop chip select */
    int x;
    wc->ios |= BIT_SCLK;
    outb(wc->ios, wc->ioaddr + WC_AUXD);
    wc->ios &= ~BIT_CS;
    outb(wc->ios, wc->ioaddr + WC_AUXD);
    for (x=0;x<8;x++) {
        /* Send out each bit, MSB first, drop SCLK as we do so */
        if (bits & 0x80)
            wc->ios |= BIT_SDI;
        else
            wc->ios &= ~BIT_SDI;
        wc->ios &= ~BIT_SCLK;
        outb(wc->ios, wc->ioaddr + WC_AUXD);
        /* Now raise SCLK high again and repeat */
        wc->ios |= BIT_SCLK;
        outb(wc->ios, wc->ioaddr + WC_AUXD);
        bits <<= 1;
    }
    /* Finally raise CS back high again */
    wc->ios |= BIT_CS;
    outb(wc->ios, wc->ioaddr + WC_AUXD);
    
}

linux2.6 makefiles.txt学习及实例分析(转)




linux2.6 makefiles.txt学习及实例分析(转)








    本篇blog主要分为四部分,地一部分和第二部分主要是参考网上的文章,第三部分为自己在学习过程中总结的一些知识,第四部分想自己编写一个简单的Makefile,以巩固学习成果!

    本篇blog目的:通过对Makefile的学习,进一步理解linux内核如何通过makefile实现对make过程的自动化,掌握makefile语言编写规则,最终实现自己能够编写出makefile文件。

    本篇blog预计完成时间一个礼拜。

    这篇文章主要是参考资料,不敢邀功,现给出原地址和参考书籍,感谢这两位大虾的慷慨分享。
参考文件:

http://forum.eepw.com.cn/forum/main?url=http%3A%2F%2Fbbs.edw.com.cn%2Fthread%2F128730%2F1
http://blog.chinaunix.net/u2/66601/showart_1150788.html   Author:cyliu <liu_chun_yuan@126.com>
《嵌入式linux系统开发技术详解-基于arm》孙纪坤 张小全 人民邮电出版社




第一部分:对makefiles.txt的学习
内核目录documention/kbuild/makefiles.txt中文版的翻译
This document describes the Linux kernel Makefiles

=== 目录
       === 1 概述
        === 2 用户与作用
        === 3 Kbuild文件
       --- 3.1 目标定义
       --- 3.2 编译进内核 - obj-y
       --- 3.3 编译可装载模块 - obj-m
       --- 3.4 输出的符号
       --- 3.5 目标库文件 - lib-y
       --- 3.6 递归躺下访问目录
       --- 3.7 编辑标志
       --- 3.8 命令行的依赖关系(原文中没有写:-))
       --- 3.9 跟踪依赖
       --- 3.10 特殊规则
       --- 3.11 $(CC) 支持的函数

        === 4 本机程序支持
       --- 4.1 简单的本机程序
       --- 4.2 复合的本机程序
       --- 4.3 定义共享库
       --- 4.4 使用用C++编写的本机程序
       --- 4.5 控制本机程序的编译选项
       --- 4.6 编译主机程序时
       --- 4.7 使用 hostprogs-$(CONFIG_FOO)
    
        === 5 Kbuild清理

        === 6 架构Makefile
       --- 6.1 调整针对某一具体架构生成的镜像
       --- 6.2 将所需文件加到 archprepare 中
       --- 6.3 递归下向时要访问的目录列表
       --- 6.4 具体架构的启动镜像
       --- 6.5 构造非Kbuild目标
       --- 6.6 构建启动镜像的命令
       --- 6.7 Kbuild自定义命令
       --- 6.8 联接器预处理脚本

        === 7 Kbuild 变量
        === 8 Makefile语言
        === 9 关于作者
        === 10 TODO




=== 1 概述

Linux内核的Makefile分为5个部分:
         
     Makefile                 顶层Makefile
     .config                  内核配置文件
     arch/$(ARCH)/Makefile    具体架构的Makefile
     scripts/Makefile.*       通用的规则等。面向所有的Kbuild Makefiles。
     kbuild Makefiles         内核源代码中大约有500个这样的文件

顶层Makefile阅读的.config文件,而该文件是由内核配置程序生成的。


层Makefile负责制作:vmlinux(内核文件)与模块(任何模块文件)。制作的过程主要是通过递归向下访问子目录的形式完成。并根据内核配置文
件确定访问哪些子目录。顶层Makefile要原封不动的包含一具体架构的Makefile(由顶层Makefile语句include
$(srctree)/arch/$(ARCH)/Makefile指明),其名字类似于
arch/$(ARCH)/Makefile。该架构Makefile向顶层Makefile提供其架构的特别信息。

每一个子目录都有一个Kbuild Makefile文件,用来执行从其上层目录传递下来的命令。Kbuild Makefile从.config文件中提取信息,生成Kbuild完成内核编译所需的文件列表。

scripts/Makefile.*包含了所有的定义、规则等信息。这些文件被用来编译基于kbuild Makefile的内核。(**有点不通**)

=== 2 用户与作用

可以将人们与内核Makefile的关系分成4类。

*使用者* 编译内核的人。他们只是键入"make menuconfig"或"make"这样的命令。一般
情况下是不会读或编辑任何内核Makefile(或者任何的源文件)。

*普通开发人员* 这是一群工作在内核某一功能上的人,比如:驱动开发,文件系统或
网络协议。他们所需要维护的只是他们所工作的子系统的Kbuild Makefile。为了提高
工作的效率,他们也需要对内核Makefile有一个全面的认识,并且要熟悉Kbuild的接口


*架构开发人员* 这是一些工作在具体架构,比如sparc 或者ia64,上面的人。架构开
发者需要在熟悉kbuild Makefile的同时,也要熟悉他所工作架构的Makefile。

*Kbuild开发者* 维护Kbuild系统的人。他们需要知晓内核Makefile的方方面面。

该文件是为普通开发人员与架构开发人员所写。


=== 3 Kbuild文件

大部分内核中的Makefile都是使用Kbuild组织结构的Kbuild Makefile。这章介绍了
Kbuild Makefile的语法。
Kbuild文件倾向于"Makefile"这个名字,"Kbuild"也是可以用的。但如果"Makefile"
"Kbuild"同时出现的话,使用的将会是"Kbuild"文件。

3.1节 目标定义是一个快速介绍,以后的几章会提供更详细的内容以及实例。

--- 3.1 目标定义

        目标定义是Kbuild Makefile的主要部分,也是核心部分。主要是定义了要编
    译的文件所有的选项,以及到哪些子目录去执行递归操作

        最简单的Kbuild makefile 只包含一行:

        例子:
          obj-y += foo.o

        该例子告诉Kbuild在这目录里,有一个名为foo.o的目标文件。foo.o将从foo.c
    或foo.S文件编译得到。

        如果foo.o要编译成一模块,那就要用obj-m了。所采用的形式如下:

        例子:
          obj-$(CONFIG_FOO) += foo.o

        $(CONFIG_FOO)可以为y(编译进内核) 或m(编译成模块)。如果CONFIG_FOO不是y
    和m,那么该文件就不会被编译联接了。

--- 3.2 编译进内核 - obj-y

        Kbuild Makefile 规定所有编译进内核的目标文件都存在$(obj-y)列表中。而
    这些列表依赖内核的配置。

        Kbuild编译所有的$(obj-y)文件。然后,调用"$(LD) -r"将它们合并到一个
    build-in.o文件中。稍后,该build-in.o会被其父Makefile联接进vmlinux中。

        $(obj-y)中的文件是有顺序的。列表中有重复项是可以的:当第一个文件被联
    接到built-in.o中后,其余文件就被忽略了。

        联接也是有顺序的,那是因为有些函数(module_init()/__initcall)将会在启
    动时按照他们出现的顺序进行调用。所以,记住改变联接的顺序可能改变你
    SCSI控制器的检测顺序,从而导致你的硬盘数据损害。

        例子:
          #drivers/isdn/i4l/Makefile
          # Makefile for the kernel ISDN subsystem and device drivers.
          # Each configuration option enables a list of files.
          obj-$(CONFIG_ISDN)        += isdn.o
          obj-$(CONFIG_ISDN_PPP_BSDCOMP)    += isdn_bsdcomp.o

--- 3.3 编译可装载模块 - obj-m

        $(obj-m) 列举出了哪些文件要编译成可装载模块。

        一个模块可以由一个文件或多个文件编译而成。如果是一个源文件,Kbuild  Makefile只需简单的将其加到$(obj-m)中去就可以了。

        例子:
          #drivers/isdn/i4l/Makefile
          obj-$(CONFIG_ISDN_PPP_BSDCOMP) += isdn_bsdcomp.o

        注意:此例中 $(CONFIG_ISDN_PPP_BSDCOMP) 的值为'm'

        如果内核模块是由多个源文件编译而成,那你就要采用上面那个例子一样的方法去声明你所要编译的模块。

        Kbuild需要知道你所编译的模块是基于哪些文件,所以你需要通过变量  $(<module_name>-objs)来告诉它。

        例子:
          #drivers/isdn/i4l/Makefile
          obj-$(CONFIG_ISDN) += isdn.o
          isdn-objs := isdn_net_lib.o isdn_v110.o isdn_common.o

        在这个例子中,模块名将是isdn.o,Kbuild将编译在$(isdn-objs)中列出的所有文件,然后使用"$(LD) -r"生成isdn.o。

        Kbuild能够识别用于组成目标文件的后缀-objs后缀-y。这就让Kbuild
    Makefile可以通过使用 CONFIG_ 符号来判断该对象是否是用来组合对象的。

        例子:
          #fs/ext2/Makefile
          obj-$(CONFIG_EXT2_FS)        += ext2.o
          ext2-y                 := balloc.o bitmap.o
          ext2-$(CONFIG_EXT2_FS_XATTR)    += xattr.o

        在这个例子中,如果 $(CONFIG_EXT2_FS_XATTR) 是 'y',xattr.o将是复合对象 ext2.o的一部分。

        注意:当然,当你要将其编译进内核时,上面的语法同样适用。所以,如果你的 CONFIG_EXT2_FS=y,那Kbuild会按你所期望的那样,生成 ext2.o文件,然后将其联接到 built-in.o中。

--- 3.4 输出的符号
 
        在Makefile中,没有对模块输出的符号有特殊要求。

--- 3.5 目标库文件 - lib-y

        在 obj-* 中所列文件是用来编译模块或者是联接到特定目录中的 built-in.o。同样,也可以列出一些将被包含在lib.a库中的文件。在 lib-y 中所列出的文件用来组成该目录下的一个库文件。

        在 obj-y 与 lib-y 中同时列出的文件,因为都是可以访问的,所以该文件是不会被包含在库文件中的。同样的情况,lib-m 中的文件就要包含在lib.a库文件中。

        注意,一个Kbuild makefile可以同时列出要编译进内核的文件与要编译成库的文件。所以,在一个目录里可以同时存在 built-in.o 与 lib.a 两个文件。

        例子:
          #arch/i386/lib/Makefile
          lib-y    := chechsum.o delay.o

       
这将由 checksum.o 和delay.o 两个文件创建一个库文件 lib.a。为了让 Kbuild 真正认识到这里要有一个库文件
lib.a 要创建,其所在的目录要加到 libs-y 列表中。 还可参考"6.3 递归下向时要访问的目录列表" lib-y 使用一般限制在 lib/ 和 arch/*/lib 中

--- 3.6 递归向下访问目录

        一个Makefile只对编译所在目录的对象负责。在子目录中的文件的编译要由其所在的子目录的Makefile来管理。只要你让Kbuild知道它应该递归操作,那么该系统就会在其子目录中自动的调用 make 递归操作。
    
        这就是 obj-y 和 obj-m 的作用。
        ext2 被放的一个单独的目录下,在fs目录下的Makefile会告诉Kbuild使用下面的赋值进行向下递归操作。

          例子:
          #fs/Makefile
          obj-$(CONFIG_EXT2_FS) += ext2/

   
如果 CONFIG_EXT2_FS 被设置为 'y'(编译进内核)或是'm'(编译成模块),相应的 obj-
变量就会被设置,并且Kbuild就会递归向下访问 ext2
目录。Kbuild只是用这些信息来决定它是否需要访问该目录,而具体怎么编译由该目录中的Makefile来决定。

    将 CONFIG_ 变量 设置成目录名是一个好的编程习惯。这让Kbuild在完全忽略那些相应的 CONFIG_ 值不是'y'和'm'的目录。

--- 3.7 编辑标志

    EXTRA_CFLAGS, EXTRA_AFLAGS, EXTRA_LDFLAGS, EXTRA_ARFLAGS

    所有的 EXTRA_ 变量只在所定义的 Kbuild Makefile 中起作用。EXTRA_ 变量可
    以在Kbuild Makefile中所有命令中使用。

    $(EXTRA_CFLAGS) 是用 $(CC) 编译C源文件时的选项。

    例子:
          # drivers/sound/emu10kl/Makefile
          EXTRA_CFLAGS += -I$(obj)
          ifdef DEBUG
              EXTRA_CFLAGS += -DEMU10KL_DEBUG
          endif


    该变量是必须的,因为顶层Makefile拥有变量 $(CFLAGS) 并用来作为整个源
    代码树的编译选项。

    $(EXTRA_AFLAGS) 也是一个针对每个目录的选项,只不过它是用来编译汇编
    源代码的。

    例子:
        #arch/x86_64/kernel/Makefile
        EXTRA_AFLAGS := -traditional


    $(EXTRA_LDFLAGS) 和 $(EXTRA_ARFLAGS)分别与 $(LD)和 $(AR)类似,只不
    过,他们是针对每个目录的。

    例子:
        #arch/m68k/fpsp040/Makefile
        EXTRA_LDFLAGS := -x

        CFLAGS_$@, AFLSGA_$@

    CFLAGS_$@ 和 AFLAGS_$@ 只能在当前Kbuild Makefile中的命令中使用。

    $(CFLAGS_$@) 是 $(CC) 针对每个文件的选项。$@ 表明了具体操作的文件。

    例子:
        # drivers/scsi/Makefile
        CFLAGS_aha152x.o =  -DAHA152X_STAT -DAUTOCONF
        CFLAGS_gdth.o    =  # -DDEBUG_GDTH=2 -D__SERIAL__ -D__COM2__ \
                      -DGDTH_STATISTICS
        CFLAGS_seagate.o =  -DARBITRATE -DPARITY -DSEAGATE_USE_ASM

    以上三行分别设置了aha152x.o,gdth.o 和 seagate.o的编辑选项

    $(AFLAGS_$@) 也类似,只不是是针对汇编语言的。

    例子:
        # arch/arm/kernel/Makefile
        AFLAGS_head-armv.o := -DTEXTADDR=$(TEXTADDR) -traditional
        AFLAGS_head-armo.o := -DTEXTADDR=$(TEXTADDR) -traditional

--- 3.9 跟踪依赖

    Kbuild 跟踪在以下方面依赖:
    1) 所有要参与编译的文件(所有的.c 和.h文件)
    2) 在参与编译文件中所要使用的 CONFIG_ 选项
    3) 用于编译目标的命令行

    因此,如果你改变了 $(CC) 的选项,所有受影响的文件都要重新编译。

--- 3.10 特殊规则

    特殊规则就是那Kbuild架构不能提供所要求的支持时,所使用的规则。一个典型的例子就是在构建过程中生成的头文件。另一个例子就是那些需要采用特殊规则来准备启动镜像

    特殊规则的写法与普通Make规则一样。Kbuild并不在Makefile所在的目录执行,所以所有的特殊规则都要提供参与编译的文件和目标文件的相对路径。

    在定义特殊规则时,要使用以下两个变量:

    $(src)
    $(src) 表明Makefile所在目录的相对路径。经常在定位源代码树中的文件时,使用该变量。

    $(obj)
    $(obj) 表明目标文件所要存储目录的相对路径。经常在定位所生成的文件时,使用该变量。

    例子:
        #drivers/scsi/Makefile
        $(obj)/53c8xx_d.h: $(src)/53c7,8xx.scr $(src)/script_asm.pl
            $(CPP) -DCHIP=810 - < $< | ... $(src)/script_asm.pl

    这就是一个特殊规则,遵守着make所要求的普通语法。目标文件依赖于两个源文件。用$(obj)来定位目标文件,用$(src)来定位源文件(因为它们不是我们生成的文件)。

--- 3.11 $(CC) 支持的函数

    内核可能由多个不同版本的$(CC)编译,而每个版本都支持一不同的功能集选项集。Kbuild提供了检查 $(CC) 可用选项的基本功能。$(CC)一般情况下是gcc编译器,但也可以使用其它编译器来代替gcc。

    as-option
    as-option,当编译汇编文件(*.S)时,用来检查 $(CC) 是否支持特定选项。如果第一个选项不支持的话,可选的第二个选项可以用来指定。

    例子:
        #arch/sh/Makefile
        cflags-y += $(call as-option,-Wa$(comma)-isa=$(isa-y),)

    在上面的例子里,如果 $(CC) 支持选项 -Wa$(comma)-isa=$(isa-y),cflags-y就会被赋予该值。第二个参数是可选的,当第一个参数不支持时,就会使用该值。

    ld-option
    ld-option,当链接目标文件时,用来检查 $(CC) 是否支持特定选项。如果第一个选项不支持的话,可选的第二个选项可以用来指定。

    例子:
        #arch/i386/kernel/Makefile
        vsyscall-flags += $(call ld-option, -Wl$(comma)--hash-style=sysv)

    在上面的例子中,如果 $(CC)支持选项 -Wl$(comma)--hash-style=sysv,ld-option就会被赋予该值。第二个参数是可选的,当第一个参数不支持时,就会使用该值。


    cc-option
    cc-option,用来检查 $(CC) 是否支持特定选项,并且不支持使用可选的第二项。

    例子:
        #arch/i386/Makefile
        cflags-y += $(call cc-option,-march=pentium-mmx,-march=i586)

  
 在上面的例子中,如果 $(CC)支持选项 -march=pentium-mmx,cc-option就会被赋予该值,否则就赋
-march-i586。cc-option的第二个参数是可选的。如果忽略的话,当第一个选项不支持时,cflags-y 不会被赋值。

    cc-option-yn
        cc-option-yn,用来检查 gcc 是否支持特定选项返回'y'支持,否则为'n'

    例子:
        #arch/ppc/Makefile
        biarch  := $(call cc-option-yn, -m32)
        aflags-$(biarch) += -a32
        cflags-$(biarch) += -m32

    在上面的例子里,当 $(CC) 支持 -m32选项时,$(biarch)设置为y。当
    $(biarch) 为y时,扩展的 $(aflags-y) 和 $(cflags-y)变量就会被赋值为
    -a32 和 -m32。

    cc-option-align
    gcc版本大于3.0时,改变了函数,循环等用来声明内存对齐的选项。当用到对齐选项时,$(cc-option-align) 用来选择正确的前缀:
    gcc < 3.00
        cc-option-align = -malign
    gcc >= 3.00
        cc-option-align = -falign

    例子:
        CFLAGS += $(cc-option-align)-functions=4

    在上面的例子中,选项 -falign-funcions=4 被用在gcc >= 3.00的时候。对于小于3.00时, 使用 -malign-funcions=4 。

    cc-version
    cc-version以数学形式返回 $(CC) 编译器的版本号
  
 其格式是:<major><minor>,二者都是数学。比如,gcc 3.41 会返回 0341。当某版本的$(CC)
在某方面有缺陷时,cc-version就会很有用。比如,选项-mregparm=3 虽然会被gcc接受,但其实现是有问题的。

    例子:
        #arch/i386/Makefile
        cflags-y += $(shell \
        if [ $(call cc-version) -ge 0300 ] ; then \
            echo "-meregparm=3"; fi ;)

    在上面的例子中,-mregparm=3只会在gcc的版本号大于等于3.0的时候使用。

    cc-ifversion
    cc-ifversion测试 $(CC) 的版本号,如果版本表达式为真,就赋值为最后的参数。

    例子:
        #fs/reiserfs/Makefile
        EXTRA_CFLAGS := $(call cc-ifversion, -lt, 0402, -O1)

  
 在这个例子中,如果 $(CC) 的版本小于4.2,EXTRA_CFLAGS就被赋值 -O1。cc-ifversion 可使用所有的shell
操作符:-eq,-ne,-lt,-le,-gt,和-ge。第三个参数可以像上面例子一样是个文本,但也可以是个扩展的变量或宏。

/*这段翻译的不好*/
=== 4 本机程序支持

Kbuild 支持编译那些将在编译阶段使用的可执行文件。为了使用该可执行文件,要将编译分成二个阶段。

第一阶段是告诉Kbuild存在哪些可执行文件。这是通过变量 hostprogs-y来完成的。
第二阶段是添加一个对可执行文件的显性依赖。有两种方法:增加依赖关系到一个规则
中,或是利用变量 $(always)。
以下是详细叙述.

--- 4.1 简单的本机程序

    在编译内核时,有时会需要编译并运行一个程序。
    下面这行就告诉了kbuild,程序bin2hex应该在本机上编译。

    例子:
        hostprogs-y := bin2hex

    在上面的例子中,Kbuild假设bin2hex是由一个与其在同一目录下,名为
    bin2hex.c 的C语言源文件编译而成的。

--- 4.2 复合的本机程序

    本机程序可以由多个文件编译而成。
    所使用的语法与内核的相应语法很相似。
    $(<executeable>-objs) 列出了联接成最后的可执行文件所需的所有目标文件。

    例子:
        #scripts/lxdialog/Makefile
        hostprogs-y    := lxdialog
        lxdialog-objs    := checklist.o lxdialog.o

  
 扩展名为.o的文件是从相应的.c文件编译而来的。在上面的例子中,checklist.c
编译成了checklist.o,lxdialog.c编译成了lxdialog.o。最后,两个.o文件联接成了一可执行文件,lxdialog。注
意:语法 <executable>-y不是只能用来生成本机程序。

--- 4.3 定义共享库

    扩展名为so的文件称为共享库,被编译成位置无关对象。Kbuild也支持共享库,但共享库的使用很有限。在下面的例子中,libconfig.so共享库用来联接到可执行文件 conf中。

    例子:
        #scripts/kconfig/Makefile
        hostprogs-y    := conf
        conf-objs    := conf.o libkconfig.so
        libkcofig-objs    := expr.o type.o

  
 共享库文件经常要求一个相应的 -objs,在上面的例子中,共享库libkconfig是由 expr.o 和
type.o两个文件组成的。expr.o 和 type.o
将被编译成位置无关码,然后联接成共享库文件libkconfig.so。C++并不支持共享库。

--- 4.4 使用用C++编写的本机程序

    kbuild也支持用C++编写的本机程序。在此专门介绍是为了支持kconfig,并且
    在一般情况下不推荐使用。

    例子:
        #scripts/kconfig/Makefile
        hostprogs-y    := qconf
        qconf-cxxobjs    := qconf.o

    在上面的例子中,可执行文件是由C++文件 qconf.cc编译而成的,由$(qconf-cxxobjs)来标识。

    如果qconf是由.c和.cc一起编译的,那么就需要专门来标识这些文件了。

    例子:
        #scripts/kconfig/Makefile
        hostprogs-y    := qconf
        qconf-cxxobjs    := qconf.o
        qconf-objs    := check.o

--- 4.5 控制本机程序的编译选项

    当编译本机程序时,有可能使用到特殊选项。程序经常是利用$(HOSTCC)编译,其选项在 $(HOSTCFLAGS)变量中。可通过使用变量 HOST_EXTRACFLAGS,影响所有在Makefile文件中要创建的主机程序。

    例子:
        #scripts/lxdialog/Makefile
        HOST_EXTRACFLAGS += -I/usr/include/ncurses
    为一单个文件设置选项,可按形式进行:

    例子:
        #arch/ppc64/boot/Makefile
        HOSTCFLAGS_pinggyback.o    := -DKERNELBASE=$(KERNELBASE)
    同样也可以给联接器声明一特殊选项。

    例子:
        #scripts/kconfig/Makefile
        HOSTLOADLIBES_qconf    := -L$(QTDIR)/lib

    当联接qconf时,将会向联接器传递附加选项 "-L$(QTDIR)/lib"。

--- 4.6 编译主机程序时

    Kbuild只在需要时编译主机程序。
    有两种方法:

    (1) 在一具体的规则中显性列出所需要的文件

    例子:
        #drivers/pci/Makefile
        hostprogs-y := gen-devlist
        $(obj)/devlist.h: $(src)/pci.ids $(obj)/gen-devlist
            ( cd $(obj); ./gen-devlist ) < $<

    目标 $(obj)/devlist.h 是不会在 $(obj)/gen-devlist 更新之前编译的。注意
    在该规则中所有有关主机程序的命令必须以$(obj)开头。

    (2) 使用 $(always)
    当Makefile要编译主机程序,但没有适合的规则时,使用 $(always)。

    例子:
        #scripts/lxdialog/Makefile
        hostprogs-y    := lxdialog
        always        := $(hostprogs-y)

    这就是告诉Kbuild,即使没有在规则中声明,也要编译 lxdialog。

--- 4.7 使用 hostprogs-$(CONFIG_FOO)

    一个典型的Kbuild模式如下:

    例子:
        #scripts/Makefile
        hostprogs-$(CONFIG_KALLSYMS) += kallsyms

  
 Kbuild 知道 'y' 是编译进内核,而 'm'
是编译成模块。所以,如果配置符号是'm',Kbuild仍然会编译它。换句话说,Kbuild处理 hostprogs-m
与 hostprogs-y 的方式是完全一致的。只是,如果不用 CONFIG,最好用hostprogs-y。

=== 5 Kbuild清理(clean)

"make
clean"删除几乎所有的在编译内核时生成的文件,包括了主机程序在内。Kbuild 通过列表
$(hostprogs-y),$(hostprogs-m),$(always),$(extra-y) 和$(targets)
知道所要编译的目标。这些目标文件都会被 "make clean" 删除。另外,在"make clean"还会删除匹
"*.oas]","*.ko" 的文件,以及由 Kbuild生成的辅助文件。

辅助文件由 Kbuild Makefile 中的 $(clean-files) 指明。

    例子:
        #drivers/pci/Makefile
        clean-files  := devlist.h classlist.h

当执行 "make clean" 时,"devlist.h classlist.h"这两个文件将被删除。如果不使用绝对路径(路径以'/'开头)的话,Kbuild假设所要删除的文件与Makefile在同一个相对路径上。

要删除一目录:
    例子:
        #scripts/package/Makefile
        clean-dirs := $(objtree)/debian/

这就会删除目录 debian,包括其所有的子目录。如果不使用绝对路径(路径以'/'开头)的话,Kbuild假设所要删除的目录与Makefile在同一个相对路径上。

一般情况下,Kbuild会根据 "obj-* := dir/" 递归访问其子目录,但有的时候,Kbuild架构还不足以描述所有的情况时,还要显式的指明所要访问的子目录。

    例子:
        #arch/i386/boot/Makefile
        subdir-  := compressed/

上面的赋值命令告诉Kbuild,当执行"make clean"时,要递归访问目录 compressed/。为了支持在最终编译完成启动镜像后的架构清理工作,还有一可选的目标 archclean:

    例子:
        #arch/i386/Makefile
        archclean:
            $(Q)$(MAKE) $(clean)=arch/i386/boot

当"make clean"执行时,make会递归访问并清理 arch/i386/boot。在 arch/i386/boot中的Makefile可以用来提示make进行下一步的递归操作。

注意1:arch/$(ARCH)/Makefile 不能使用"subdir-",因为该Makefile被包含在顶层的Makefile中,Kbuild是不会在此处进行操作的。

注意2:"make clean" 会访问在 core-y,libs-y,drivers-y 和 net-y 列出的所有目录。

=== 6 架构Makefile

在递归访问目录之前,顶层Makefile要完成设置环境变量以及递归访问的准备工作。顶层Makefile包含的公共部分,而 arch/$(ARCH)/Makefile 包含着针对某一特定架构的配置信息。所以,要在 arch/$(ARCH)/Makefile 中设置一部分变量,并定义一些目标。

Kbuild执行的几个步驟(大致):
1) 根据内核配置生成文件 .config
2) 将内核的版本号存储在 include/linux/version.h
3) 生成指向 include/asm-$(ARCH) 的符号链接
4) 更新所有编译所需的文件:
   -附加的文件由 arch/$(ARCH)/Makefile 指定。
5) 递归向下访问所有在下列变量中列出的目录: init-* core-* drivers-* net-*  libs-*,并编译生成目标文件。
   -这些变量的值可以在 arch/$(ARCH)/Makefile 中扩充。
6) 联接所有的目标文件,在源代码树顶层目录中生成 vmlinux。最先联接是在 head-y中列出的文件,该变量由 arch/$(ARCH)/Makefile 赋值。
7) 最后完成具体架构的特殊要求,并生成最终的启动镜像。
   -包含生成启动指令
   -准备 initrd 镜像或类似文件


--- 6.1 调整针对某一具体架构生成的镜像

    LDFLAGS        一般是 $(LD) 选项
    
    该选项在每次调用联接器时都会用到。一般情况下,只用来指明模拟器。

    例子:
        #arch/s390/Makefile
        LDFLAGS        := -m elf_s390
    注意:EXTRA_LDFLAGS 和 LDFLAGS_$@ 可用来进一步自定义选项。请看第七章。

    LDFLAGS_MODULE    联接模块时的联接器的选项

    LDFLAGS_MODULE 所设置的选项将在联接器在联接模块文件 .ko 时使用。
    默认值为 "-r",指定输出文件是可重定位的。

    LDFLAGS_vmlinux    联接vmlinux时的选项

    LDFLAGS_vmlinux用来传递联接vmlinux时的联接器的选项。
    LDFLAGS_vmlinux需 LDFLAGS_$@ 支持。

    例子:
        #arch/i386/Makefile
        LDFLAGS_vmlinux := -e stext

    OBJCOPYFLAGS    objcopy 选项

    当用 $(call if_changed,objcopy) 来转换(translate)一个.o文件时,该选项就会被使用。
    $(call if_changed,objcopy) 经常被用来为vmlinux生成原始的二进制代码

    例子:
        #arch/s390/Makefile
        OBJCOPYFLAGS    := -O binary

        #arch/s390/boot/Makefile
        $(obj)/image: vmlinux FORCE
            $(call if_changed,objcopy)

    在此例中,二进制文件 $(obj)/image 是 vmlinux 的一个二进制版本
    $(call if_chagned,xxx)的用法稍后描述。

    AFLAGS        $(AS) 汇编编译器选项

    默认值在顶层Makefile 扩充或修改在各具体架构的Makefile

    例子:
        #arch/sparc64/Makefile
        AFLAGS += -m64 -mcpu=ultrasparc

    CFLAGS        $(CC) 编译器选项

    默认值在顶层Makefile 扩充或修改在各具体架构的Makefile。

    一般,CFLAGS要根据内核配置设置。

    例子:
        #arch/i386/Makefile
        cflags-$(CONFIG_M386)  += -march=i386
        CFLAGS += $(cflags-y)

    许多架构Makefile都通过调用所要使用的C编译器,动态的检查其所支持的选项:

        #arch/i386/Makefile

        ...
        cflags-$(CONFIG_MPENTIUMII)    += $(call cc-option,\
                        -march=pentium2,-march=i686)
        ...
        # Disable unit-at-a-time mode ...
        CFLAGS += $(call cc-option,-fno-unit-at-a-time)
        ...


    第一个例子利用了一个配置选项,当其为'y'时,扩展。

    CFLAGS_KERNEL        :

        #arch/i386/Makefile

        ...
        cflags-$(CONFIG_MPENTIUMII)    += $(call cc-option,\
                        -march=pentium2,-march=i686)
        ...
        # Disable unit-at-a-time mode ...
        CFLAGS += $(call cc-option,-fno-unit-at-a-time)
        ...


    第一个例子利用了一个配置选项,当其为'y'时,扩展。

    CFLAGS_KERNEL    编译进内核时,$(CC) 所用的选项

    $(CFLAGS_KERNEL) 包含了用于编译常驻内核代码的附加编译器选项。

    CFLAGS_MODULE    编译成模块时,$(CC)所用的选项

    $(CFLAGS_MODULE) 包含了用于编译可装载模块的附加编译器选项。


--- 6.2 将所需文件加到 archprepare 中:

    archprepare规则在递归访问子目录之前,列出编译目标文件所需文件。
    一般情况下,这是一个包含汇编常量的头文件。(assembler constants)

        例子:
        #arch/arm/Makefile
        archprepare: maketools

    此例中,目标文件 maketools 将在递归访问子目录之前编译。 在TODO一章可以看到,Kbuild是如何支持生成分支头文件的。(offset header files)

--- 6.3 递归下向时要访问的目录列表

    如何生成 vmlinux,是由架构makefile顶层Makefile一起来定义的。注意,架构Makefile是不会定义与模块相关的内容的,所有构建模块的定义是与架构无关的。


    head-y,init-y,core-y,libs-y,drivers-y,net-y

    $(head-y) 列出了最先被联接进 vmlinux 的目标文件。
    $(libs-y) 列出了生成的所有 lib.a 所在的目录。
    其余所列的目录,是 built-in.o 所在的目录。

    $(init-y) 在 $(head-y) 之后所要使用的文件。
    然后,剩下的步骤如下:
    $(core-y),$(libs-y),$(drivers-y)和$(net-y)。

    顶层makefile定义了通用的部分,arch/$(ARCH)/Makefile 添加了架构的特殊要求。

    例子:
        #arch/sparc64/Makefile
        core-y += arch/sparc64/kernel/
        libs-y += arch/sparc64/prom/ arch/sparc64/lib/
        drivers-$(CONFIG_OPROFILE)  += arch/sparc64/oprofile/


--- 6.4 具体架构的启动镜像

    一具体架构Makefile的具体目的就是,将生成并压缩 vmlinux 文件写入启动代码,并将其拷贝到正确的位置。这就包含了多种不同的安装命令。该具体目的也无法在各个平台间进行标准化。

    一般,附加的处理命令入在 arch/$(ARCH)/下的boot目录。

    Kbuild并没有为构造boot所指定的目标提供任何更好的方法。所以,arch/$(ARCH)/Makefile 将会调用 make 以手工构造 boot的目标文件。

比较好的方法是,在arch/$(ARCH)/Makefile中包含快捷方式,并在arch/$(ARCH)/boot/Makefile 中使用全部路径。

    例子:
        #arch/i386/Makefile
        boot  := arch/i386/boot
        bzImage: vmlinux
            $(Q)$(MAKE) $(build)=$(boot) $(boot)/$@

    当在子目录中调用 make 时,推荐使用 "$(Q)$(MAKE) $(build)=<dir>" 。

    并没有对架构特殊目标的命名规则,但用命令 "make help" 可以列出所有的相关目标。
    为了支持 "make help",$(archhelp) 必须被定义。

    例子:
        #arch/i386/Makefile
        define archhelp
          echo  '* bzImage    - Image (arch/$(ARCH)/boot/bzImage)'
        endef

    当make 没带参数执行时,所遇到的第一个目标将被执行。在顶层,第一个目标就是 all:。
    每个架构Makefile都要默认构造一可启动的镜像文件。在 "make help"中,默认目标就是被加亮的'*'。添加一新的前提文件到 all:,就可以构造出一不同的vmlinux。

    例子:
        #arch/i386/Makefile
        all: bzImage

    当 make 没有参数时,bzImage将被构造。

--- 6.5 构造非Kbuild目标

    extra-y

    extra-y 列出了在当前目录下,所要创建的附加文件,不包含任何已包含在obj-* 中的文件。

    用 extra-y 列目标,主要是两个目的
    1) 可以使Kbuild检查命令行是否发生变化
       - 使用 $(call if_changed,xxx) 的时候
    2) 让Kbuild知道哪些文件要在 "make clean" 时删除

    例子:
        #arch/i386/kernel/Makefile
        extra-y := head.o init_task.o

    在此例子中,extra-y用来列出所有只编译,但不联接到 built-in.o的目标文件。

--- 6.6 构建启动镜像的命令

    Kbuild 提供了几个用在构建启动镜像时的宏。

    if_changed

    if_changed 为下列命令的基础。

    使用方法:
        target: source(s) FORCE
            $(call if_changed,ld/objcopy/gzip)

    当执行该规则时,就检查是否有文件需要更新
或者在上次调用以后,命令行发生了改变。如果有选项发生了改变,后者会导致重新构造。只有在
$(targets)列出的的目标文件,才能使用if_changed,否则命令行的检查会失败,并且目标总会被重建。给
$(targets)的赋值没有前缀 $(obj)/ 。 if_changed
可用来联接自定义的Kbuild命令,关于Kbuild自定义命令请看 6.7节。

    注意:忘记 FORCE 是一种典型的错误。还有一种普遍的错误是,空格有的时候是有意义的;比如。下面的命令就会错误(注意在逗号后面的那个多余的空格):
        target: source(s) FORCE
    #WRONG!#    $(call if_changed, ld/objcopy/gzip)

    ld
        联接目标。经常是使用LDFLAGS_$@来设置ld的特殊选项。


    objcopy
        拷贝二进制代码。一般是在 arch/$(ARCH)/Makefile 中使用 OBJCOPYFLAGS。

    OBJCOPYFLAGS_$@ 可以用来设置附加选项。

    gzip
        压缩目标文件。尽可能的压缩目标文件。

    例子:
        #arch/i386/boot/Makefile
        LDFLAGS_bootsect  := -Ttext 0x0 -s --oformat binary
        LDFLAGS_setup      := -Ttext 0x0 -s --oformat binary -e begtext

        targets += setup setup.o bootsect bootsect.o
        $(obj)/setup $(obj)/bootsect: %: %.o FORCE
            $(call if_changed,ld)

    在这个例子中,有两个可能的目标文件,分别要求不同的联接选项。定义联接器的选项使用的是 LDFLAGS_$@ 语法,每个潜在的目标一个。$(targets) 被分配给所有的潜在目标,因此知道目标是哪些,并且还会:
        1) 检查命令行是否改变
        2) 在 "make clean" 时,删除目标文件

    前提部分中的 ": %: %.o" 部分使我们不必在列出文件 setup.o 和 bootsect.o 。
    注意:一个普遍的错误是忘记了给 "target"赋值,导致在target中的文件总是无缘无故的被重新编译。


--- 6.7 Kbuild自定义命令

    当Kbuild的变量 KBUILD_VERBOSE 为0时,只会显示命令的简写。如果要为自定义命令使用这一功能,需要设置2个变量:
    quiet_cmd_<command>    - 要显示的命令
          cmd_<command>    - 要执行的命令

    例子:
        #
        quiet_cmd_image = BUILD   $@
              cmd_image = $(obj)/tools/build $(BUILDFLAGS) \
                               $(obj)/vmlinux.bin > $@

        targets += bzImage
        $(obj)/bzImage: $(obj)/vmlinux.bin $(obj)/tools/build FORCE
            $(call if_changed,image)
            @echo 'Kernel: $@ is ready'

    当用"make KBUILD_VERBOSE=0"更新 $(obj)/bzImage 目标时显示:

    BUILD    arch/i386/boot/bzImage


--- 6.8 联接器预处理脚本

    当构造 vmlinux 镜像时,使用联接器脚本:
    arch/$(ARCH)/kernel/vmlinux.lds。
    该脚本是由在同一目录下的 vmlinux.lds.S 生成的。Kbuild认识.lds文件,并包含由*.lds.S文件生成*.lds文件的规则。

    例子:
        #arch/i386/kernel/Makefile
        always := vmlinux.lds

        #Makefile
        export CPPFLAGS_vmlinux.lds += -P -C -U$(ARCH)

    $(always)的值是用来告诉Kbuild,构造目标 vmlinux.lds。
    $(CPPFLAGS_vmlinux.lds),Kbuild在构造目标vmlinux.lds时所用到的特殊选项。

    当构造 *.lds 目标时,Kbuild要用到下列变量:
    CPPFLAGS    : 在顶层目录中设置
    EXTRA_CPPFLAGS    : 可以在Kbuild Makefile中设置
    CPPFLAGS_$(@F)    : 目标特别选项
              注意,此处的赋值用的完整的文件名。

    针对*.lds文件的Kbuild构架还被用在许多具体架构的文件中。(***不通***)

=== 7 Kbuild 变量

顶层Makefile输出以下变量

    1、VERSION,PATCHLEVEL,SUBLEVEL,EXTRAVERSION

    这些变量定义了当前内核的版本号。只有很少一部分Makefile会直接用到这些
    变量;可使用 $(KERNELRELEASE)代替。

    $(VERSION),$(PATCHLEVEL),和$(SUBLEVEL) 定义了最初使用的三个数字的版本
    号,比如"2""4"和"0"。这三个值一般是数字。

    $(EXTRAVERSION) 为了补丁定义了更小的版本号。一般是非数字的字符串,比如
    "-pre4" ,或就空着。

    KERNELRELEASE

    $(KERNELRELEASE) 是一个字符串,类似"2.4.0-pre4",用于安装目录的命名或
       显示当前的版本号。一部分架构Makefile使用该变量。

    2、ARCH

    该变量定义了目标架构,比如"i386","arm" 或"sparc"。有些Kbuild Makefile
    根据 $(ARCH) 决定编译哪些文件。

    默认情况下,顶层Makefile将其设置为本机架构。如果是跨平台编译,用户可以
    用下面的命令覆盖该值:

        make ARCH=m68k ...


    3、INSTALL_PATH

    该变量为架构Makefile定义了安装内核镜像与 System.map 文件的目录。主要用来指明架构特殊的安装路径。

    4、INSTALL_MOD_PATH,MODLIB

    $(INSTALL_MOD_PATH) 为了安装模块,给 $(MODLIB) 声明了前缀。该变量不能
    在Makefile中定义,但可以由用户传给Makefile。

    $(MODLIB) 具体的模块安装的路径。顶层Makefile将$(MODLIB)定义为$(INSTALL_MOD_PATH)/lib/modules/$(KERNELRELEASE)。用户可以通过命令行参数的形式将其覆盖。

   5、INSTALL_MOD_STRIP
        
    如果该变量有定义,模块在安装之前,会被剥出符号表。如果
    INSTALL_MOD_STRIP 为 "1",就使用默认选项 --strip-debug。否则,
    INSTALL_MOD_STRIP 将作为命令 strip 的选项使用。


=== 8 Makefile语言

内核的Makefile使用的是GNU Make。该Makefile只使用GNU Make已注明的功能,并使用
了许多GNU 的扩展功能。

GNU Make支持基本的显示处理过程的函数。内核Makefile 使用了一种类似小说的方式
,显示"if"语句的构造、处理过程。

GNU Make 有2个赋值操作符,":="和"="。":=",将对右边的表达式求值,并将所求的值
赋给左边。"="更像是一个公式定义,只是将右边的值简单的赋值给左边,当左边的表达
式被使用时,才求值。

有时使用"="是正确的。但是,一般情况下,推荐使用":="。

=== 9 关于作者
第一版由 Michael Elizabeth Chastain,<mailto:mec@shout.net>
修改:kai Germaschewski <kai@tpl.ruhr-uni-bochum.de>
      Sam Ravnborg <sam@ravnborg.org>

=== 10 TODO

- 描述Kbuild是如何用 _shipped 来支持 shipped 文件的。
- 生成分支头文件
- 在第7节加入更多的变量




第二部分:Makefile 实例分析

VERSION = 2
PATCHLEVEL = 6
SUBLEVEL = 20
EXTRAVERSION = .7
NAME = Homicidal Dwarf Hamster

# 以上表明了内核版本。组合起来就是:2.6.20.7 ,yeah,这就是我分析的内核版本

# 注意写makefile时不要使用makefile的内建的规则和变量

#要想不打印"Entering directory ..."字样,请使用no-print-directory选项
MAKEFLAGS += -rR --no-print-directory

# 因为需要递归执行build, 所以必须注意要保证按照正确顺序执行make.

# 使用 'make V=1' 可以看到完整命令

ifdef V
  ifeq ("$(origin V)", "command line")
    KBUILD_VERBOSE = $(V)
  endif
endif
ifndef KBUILD_VERBOSE
  KBUILD_VERBOSE = 0
endif

# 使用 'make C=1' 仅检查需要重新使用c编译器编译的文件
# 使用 'make C=2' 检查所有c编译器编译的文件

ifdef C
  ifeq ("$(origin C)", "command line")
    KBUILD_CHECKSRC = $(C)
  endif
endif
ifndef KBUILD_CHECKSRC
  KBUILD_CHECKSRC = 0
endif

# 使用 make M=dir 表明需要编译的模块目录
# 旧的语法make ... SUBDIRS=$PWD 依然支持
# 这里通过环境变量 KBUILD_EXTMOD来表示
ifdef SUBDIRS
  KBUILD_EXTMOD ?= $(SUBDIRS)
endif
ifdef M
  ifeq ("$(origin M)", "command line")
    KBUILD_EXTMOD := $(M)
  endif
endif


# kbuild 可以把输出文件放到一个分开的目录下
# 定位输出文件目录有两种语法支持:
# 两种方法都要求工作目录是内核源文件目录的根目录
# 1) O=
# 使用 "make O=dir/to/store/output/files/"
#
# 2) 设置 KBUILD_OUTPUT
# 设置环境变量 KBUILD_OUTPUT 来指定输出文件存放的目录
# export KBUILD_OUTPUT=dir/to/store/output/files/
# make
#
# 但是 O= 方式优先于KBUILD_OUTPUT环境变量方式

# KBUILD_SRC 会再obj目录的make调用
# KBUILD_SRC 到目前还不是为了给一般用户使用的
ifeq ($(KBUILD_SRC),)

ifdef O
  ifeq ("$(origin O)", "command line")
    KBUILD_OUTPUT := $(O)
  endif
endif

# 缺剩目标
PHONY := _all
_all:

ifneq ($(KBUILD_OUTPUT),)
# 调用输出目录的第二个make, 传递相关变量检查输出目录确实存在

saved-output := $(KBUILD_OUTPUT)
KBUILD_OUTPUT := $(shell cd $(KBUILD_OUTPUT) && /bin/pwd)
$(if $(KBUILD_OUTPUT),, \
    $(error output directory "$(saved-output)" does not exist))

PHONY += $(MAKECMDGOALS)

$(filter-out _all,$(MAKECMDGOALS)) _all:
    $(if $(KBUILD_VERBOSE:1=),@)$(MAKE) -C $(KBUILD_OUTPUT) \
    KBUILD_SRC=$(CURDIR) \
    KBUILD_EXTMOD="$(KBUILD_EXTMOD)" -f $(CURDIR)/Makefile $@

# Leave processing to above invocation of make
skip-makefile := 1
endif # ifneq ($(KBUILD_OUTPUT),)
endif # ifeq ($(KBUILD_SRC),)

ifeq ($(skip-makefile),)

PHONY += all
ifeq ($(KBUILD_EXTMOD),)
_all: all
else
_all: modules
endif

srctree        := $(if $(KBUILD_SRC),$(KBUILD_SRC),$(CURDIR))
TOPDIR        := $(srctree)
# FIXME - TOPDIR is obsolete, use srctree/objtree
objtree        := $(CURDIR)
src        := $(srctree)
obj        := $(objtree)

VPATH        := $(srctree)$(if $(KBUILD_EXTMOD),:$(KBUILD_EXTMOD))

export srctree objtree VPATH TOPDIR


# SUBARCH 告诉 usermode build 当前的机器是什么体系.  它是第一个设置的,并且如果
# 是usermode build ,命令行中的 "ARCH=um" 优先下面的 ARCH 设置. 如果是 native build ,
# 设置ARCH , 获取正常的数值, 将会忽略SUBARCH .

SUBARCH := $(shell uname -m | sed -e s/i.86/i386/ -e s/sun4u/sparc64/ \
                 -e s/arm.*/arm/ -e s/sa110/arm/ \
                 -e s/s390x/s390/ -e s/parisc64/parisc/ \
                 -e s/ppc.*/powerpc/ -e s/mips.*/mips/ )

# 交叉编译和选择不同的gcc/bin-utils
# ---------------------------------------------------------------------------
#
# 当交叉编译其他体系的内核时,ARCH 应该设置为目标体系.
# ARCH 可以再make执行间设置:
# make ARCH=ia64
# 另外一种方法是设置 ARCH 环境变量.
# 默认 ARCH 是当前执行的宿主机.

# CROSS_COMPILE 作为编译时所有执行需要使用的前缀
# 只有gcc 和 相关的 bin-utils 使用 $(CROSS_COMPILE)设置的前缀.
# CROSS_COMPILE 可以再命令行设置:
# make CROSS_COMPILE=ia64-linux-
# 另外 CROSS_COMPILE 也可以再环境变量中设置.
# CROSS_COMPILE 的默认值是空的
# Note: 一些体系的 CROSS_COMPILE 是再其 arch/*/Makefile中设置的

ARCH        ?= $(SUBARCH)
CROSS_COMPILE    ?=

# Architecture as present in compile.h
UTS_MACHINE := $(ARCH)

KCONFIG_CONFIG    ?= .config

# SHELL used by kbuild
CONFIG_SHELL := $(shell if [ -x "$$BASH" ]; then echo $$BASH; \
     else if [ -x /bin/bash ]; then echo /bin/bash; \
     else echo sh; fi ; fi)

HOSTCC      = gcc
HOSTCXX      = g++
HOSTCFLAGS  = -Wall -Wstrict-prototypes -O2 -fomit-frame-pointer
HOSTCXXFLAGS = -O2

# Decide whether to build built-in, modular, or both.
# Normally, just do built-in.

KBUILD_MODULES :=
KBUILD_BUILTIN := 1

#    如果仅编译模块 "make modules", 就不会编译内建的 objects.
#    当使用modversions编译 modules 时 , 就需要考虑build-in objects,
#    目的是再记录前确认 checksums 已经更新.

ifeq ($(MAKECMDGOALS),modules)
  KBUILD_BUILTIN := $(if $(CONFIG_MODVERSIONS),1)
endif

#    If we have "make <whatever> modules", compile modules
#    in addition to whatever we do anyway.
#    Just "make" or "make all" shall build modules as well

ifneq ($(filter all _all modules,$(MAKECMDGOALS)),)
  KBUILD_MODULES := 1
endif

ifeq ($(MAKECMDGOALS),)
  KBUILD_MODULES := 1
endif

export KBUILD_MODULES KBUILD_BUILTIN
export KBUILD_CHECKSRC KBUILD_SRC KBUILD_EXTMOD

# Beautify output
# ---------------------------------------------------------------------------
#
# 一般,make再执行命令前会打印整个命令信息. 现在通过$($(quiet)$(cmd))方式
# 可以设置 $(quiet) 来选择不同的命令输出方式等.
#
#        quiet_cmd_cc_o_c = Compiling $(RELDIR)/$@
#        cmd_cc_o_c      = $(CC) $(c_flags) -c -o $@ $<
#
# 如果 $(quiet) 为空, 整个命令行将会打印.
# 如果 $(quiet)设置为 "quiet_", 只打印简短的版本信息.
# 如果 $(quiet)设置为 "silent_", 将不会打印任何信息, 因为没有$(silent_cmd_cc_o_c) 变量存在.
#
# 一个简单的前缀 $(Q)放到命令前面,以便再 non-verbose 模式下可以隐藏命令:
#
#    $(Q)ln $@ :<
#
# 如果 KBUILD_VERBOSE 等于 0 ,那么上面的命令将隐藏.
# 如果 KBUILD_VERBOSE 等于 1 ,那么上面的命令将显示.

ifeq ($(KBUILD_VERBOSE),1)
  quiet =
  Q =
else
  quiet=quiet_
  Q = @
endif

# 如果make -s (silent mode), 不会显示命令

ifneq ($(findstring s,$(MAKEFLAGS)),)
  quiet=silent_
endif

export quiet Q KBUILD_VERBOSE


# Look for make include files relative to root of kernel src
MAKEFLAGS += --include-dir=$(srctree)

# We need some generic definitions.
include $(srctree)/scripts/Kbuild.include

# Make variables (CC, etc...)

AS        = $(CROSS_COMPILE)as
LD        = $(CROSS_COMPILE)ld
CC        = $(CROSS_COMPILE)gcc
CPP        = $(CC) -E
AR        = $(CROSS_COMPILE)ar
NM        = $(CROSS_COMPILE)nm
STRIP        = $(CROSS_COMPILE)strip
OBJCOPY        = $(CROSS_COMPILE)objcopy
OBJDUMP        = $(CROSS_COMPILE)objdump
AWK        = awk
GENKSYMS    = scripts/genksyms/genksyms
DEPMOD        = /sbin/depmod
KALLSYMS    = scripts/kallsyms
PERL        = perl
CHECK        = sparse

CHECKFLAGS    := -D__linux__ -Dlinux -D__STDC__ -Dunix -D__unix__ -Wbitwise $(CF)
MODFLAGS    = -DMODULE
CFLAGS_MODULE  = $(MODFLAGS)
AFLAGS_MODULE  = $(MODFLAGS)
LDFLAGS_MODULE  = -r
CFLAGS_KERNEL    =
AFLAGS_KERNEL    =


# Use LINUXINCLUDE when you must reference the include/ directory.
# Needed to be compatible with the O= option
LINUXINCLUDE    := -Iinclude \
                  $(if $(KBUILD_SRC),-Iinclude2 -I$(srctree)/include) \
           -include include/linux/autoconf.h

CPPFLAGS        := -D__KERNEL__ $(LINUXINCLUDE)

CFLAGS          := -Wall -Wundef -Wstrict-prototypes -Wno-trigraphs \
                  -fno-strict-aliasing -fno-common
AFLAGS          := -D__ASSEMBLY__

# Read KERNELRELEASE from include/config/kernel.release (if it exists)
KERNELRELEASE = $(shell cat include/config/kernel.release 2> /dev/null)
KERNELVERSION = $(VERSION).$(PATCHLEVEL).$(SUBLEVEL)$(EXTRAVERSION)

export VERSION PATCHLEVEL SUBLEVEL KERNELRELEASE KERNELVERSION
export ARCH CONFIG_SHELL HOSTCC HOSTCFLAGS CROSS_COMPILE AS LD CC
export CPP AR NM STRIP OBJCOPY OBJDUMP MAKE AWK GENKSYMS PERL UTS_MACHINE
export HOSTCXX HOSTCXXFLAGS LDFLAGS_MODULE CHECK CHECKFLAGS

export CPPFLAGS NOSTDINC_FLAGS LINUXINCLUDE OBJCOPYFLAGS LDFLAGS
export CFLAGS CFLAGS_KERNEL CFLAGS_MODULE
export AFLAGS AFLAGS_KERNEL AFLAGS_MODULE

# When compiling out-of-tree modules, put MODVERDIR in the module
# tree rather than in the kernel tree. The kernel tree might
# even be read-only.
export MODVERDIR := $(if $(KBUILD_EXTMOD),$(firstword $(KBUILD_EXTMOD))/).tmp_versions

# Files to ignore in find ... statements

RCS_FIND_IGNORE
:= \( -name SCCS -o -name BitKeeper -o -name .svn -o -name CVS -o -name
.pc -o -name .hg -o -name .git \) -prune -o
export RCS_TAR_IGNORE :=
--exclude SCCS --exclude BitKeeper --exclude .svn --exclude CVS
--exclude .pc --exclude .hg --exclude .git
[未完,待续]











cyliu2007-07-29 14:21
# ===========================================================================
# 下面是设置config和build 内核时共同使用的规则

# scripts里面是最基本的帮助builds,在scripts/basic目录下的makefile是所有build时都会用到的工具:fixdep/
PHONY += scripts_basic
scripts_basic:
    $(Q)$(MAKE) $(build)=scripts/basic

# 把对scripts/basic/makefile转化到scripts_basic处理
scripts/basic/%: scripts_basic ;

#这里先说说script目录里面的makefile文件作用
#makefile:配置目标,包括编译岛内核和模块方式的target
#makefile_clean:当然是删除了
#kbuild_include:一般的配置选项,会在makefile_build中调用
#makefile_build:build配置
#makefile_lib:module,vmlinux的配置
#makefile_host:配置binary
#makefile_modinst:安装module
#makefile_modpost:

PHONY += outputmakefile
# outputmakefile规则目的是在输出目录产生一个makefile文件。这会为make提供方便。这里是调用scripts/mkmakefile创建makfile文件
outputmakefile:
ifneq ($(KBUILD_SRC),)
    $(Q)$(CONFIG_SHELL) $(srctree)/scripts/mkmakefile \
       $(srctree) $(objtree) $(VERSION) $(PATCHLEVEL)
endif

# 设置是.config,还是module,还是build命令

no-dot-config-targets := clean mrproper distclean \
             cscope TAGS tags help %docs check% \
             include/linux/version.h headers_% \
             kernelrelease kernelversion

config-targets := 0
mixed-targets  := 0
dot-config    := 1

ifneq ($(filter $(no-dot-config-targets), $(MAKECMDGOALS)),)
    ifeq ($(filter-out $(no-dot-config-targets), $(MAKECMDGOALS)),)
        dot-config := 0
    endif
endif

ifeq ($(KBUILD_EXTMOD),)
        ifneq ($(filter config %config,$(MAKECMDGOALS)),)
                config-targets := 1
                ifneq ($(filter-out config %config,$(MAKECMDGOALS)),)
                        mixed-targets := 1
                endif
        endif
endif

ifeq ($(mixed-targets),1)
# ===========================================================================
# We're called with mixed targets (*config and build targets).
# Handle them one by one.

%:: FORCE
    $(Q)$(MAKE) -C $(srctree) KBUILD_SRC= $@

else
ifeq ($(config-targets),1)
# ===========================================================================
#仅配置linux

# Read arch specific Makefile to set KBUILD_DEFCONFIG as needed.
# KBUILD_DEFCONFIG may point out an alternative default configuration
# used for 'make defconfig'
include $(srctree)/arch/$(ARCH)/Makefile
export KBUILD_DEFCONFIG

config %config: scripts_basic outputmakefile FORCE
    $(Q)mkdir -p include/linux include/config
    $(Q)$(MAKE) $(build)=scripts/kconfig $@

else
# ===========================================================================
# 仅编译内核

ifeq ($(KBUILD_EXTMOD),)
# Additional helpers built in scripts/
# Carefully list dependencies so we do not try to build scripts twice
# in parallel
PHONY += scripts
scripts: scripts_basic include/config/auto.conf
    $(Q)$(MAKE) $(build)=$(@)

#下面就不用说了吧
# Objects we will link into vmlinux / subdirs we need to visit
init-y        := init/
drivers-y    := drivers/ sound/
net-y        := net/
libs-y        := lib/
core-y        := usr/
endif # KBUILD_EXTMOD

ifeq ($(dot-config),1)
# Read in config
-include include/config/auto.conf

ifeq ($(KBUILD_EXTMOD),)
# Read in dependencies to all Kconfig* files, make sure to run
# oldconfig if changes are detected.
-include include/config/auto.conf.cmd

# To avoid any implicit rule to kick in, define an empty command
$(KCONFIG_CONFIG) include/config/auto.conf.cmd: ;

# If .config is newer than include/config/auto.conf, someone tinkered
# with it and forgot to run make oldconfig.
# if auto.conf.cmd is missing then we are probably in a cleaned tree so
# we execute the config step to be sure to catch updated Kconfig files
include/config/auto.conf: $(KCONFIG_CONFIG) include/config/auto.conf.cmd
    $(Q)$(MAKE) -f $(srctree)/Makefile silentoldconfig
else
# external modules needs include/linux/autoconf.h and include/config/auto.conf
# but do not care if they are up-to-date. Use auto.conf to trigger the test
PHONY += include/config/auto.conf

include/config/auto.conf:
    $(Q)test -e include/linux/autoconf.h -a -e $@ || (        \
    echo;                                \
    echo "  ERROR: Kernel configuration is invalid.";        \
    echo "        include/linux/autoconf.h or $@ are missing.";    \
    echo "        Run 'make oldconfig && make prepare' on kernel src to fix it.";    \
    echo;                                \
    /bin/false)

endif # KBUILD_EXTMOD

else
# Dummy target needed, because used as prerequisite
include/config/auto.conf: ;
endif # $(dot-config)








cyliu2007-07-29 14:40
# ===========================================================================
# 仅编译内核

ifeq ($(KBUILD_EXTMOD),)
# Additional helpers built in scripts/
# Carefully list dependencies so we do not try to build scripts twice
# in parallel
PHONY += scripts
scripts: scripts_basic include/config/auto.conf
    $(Q)$(MAKE) $(build)=$(@)

#下面就不用说了吧
# Objects we will link into vmlinux / subdirs we need to visit
init-y        := init/
drivers-y    := drivers/ sound/
net-y        := net/
libs-y        := lib/
core-y        := usr/
endif # KBUILD_EXTMOD

.....

all: vmlinux

#配置gcc选项
ifdef CONFIG_CC_OPTIMIZE_FOR_SIZE
CFLAGS        += -Os
else
CFLAGS        += -O2
endif
....

# 缺剩的编译的内核镜像
export KBUILD_IMAGE ?= vmlinux

#
# vmlinux与map安装路径,默认时boot目录
export    INSTALL_PATH ?= /boot

#
#module安装目录
#

MODLIB    = $(INSTALL_MOD_PATH)/lib/modules/$(KERNELRELEASE)
export MODLIB

#
#  如果定义了INSTALL_MOD_STRIP,在安装module后会strip这些安装的modules

ifdef INSTALL_MOD_STRIP
ifeq ($(INSTALL_MOD_STRIP),1)
mod_strip_cmd = $(STRIP) --strip-debug
else
mod_strip_cmd = $(STRIP) $(INSTALL_MOD_STRIP)
endif # INSTALL_MOD_STRIP=1
else
mod_strip_cmd = true
endif # INSTALL_MOD_STRIP
export mod_strip_cmd

#如果不是安装module
ifeq ($(KBUILD_EXTMOD),)
core-y        += kernel/ mm/ fs/ ipc/ security/ crypto/ block/

vmlinux-dirs    := $(patsubst %/,%,$(filter %/, $(init-y) $(init-m) \
            $(core-y) $(core-m) $(drivers-y) $(drivers-m) \
            $(net-y) $(net-m) $(libs-y) $(libs-m)))

vmlinux-alldirs    := $(sort $(vmlinux-dirs) $(patsubst %/,%,$(filter %/, \
            $(init-n) $(init-) \
            $(core-n) $(core-) $(drivers-n) $(drivers-) \
            $(net-n)  $(net-)  $(libs-n)    $(libs-))))

init-y        := $(patsubst %/, %/built-in.o, $(init-y))
core-y        := $(patsubst %/, %/built-in.o, $(core-y))
drivers-y    := $(patsubst %/, %/built-in.o, $(drivers-y))
net-y        := $(patsubst %/, %/built-in.o, $(net-y))
libs-y1        := $(patsubst %/, %/lib.a, $(libs-y))
libs-y2        := $(patsubst %/, %/built-in.o, $(libs-y))
libs-y        := $(libs-y1) $(libs-y2)

# Build vmlinux
...

#配置syms
...

#以下是内核link时的配置
....

S3C2410读写Nand Flash分析

S3C2410读写Nand Flash分析










作者:刘洪涛


一、结构分析


S3C2410处理器集成了8位NandFlash控制器。目前市场上常见的8位NandFlash有三星公司的
k9f1208、k9f1g08、k9f2g08等。k9f1208、k9f1g08、k9f2g08的数据页大小分别为512Byte、2kByte、
2kByte。它们在寻址方式上有一定差异,所以程序代码并不通用。本文以S3C2410处理器和k9f1208系统为例,讲述NandFlash的读写
方法。


NandFlash的数据是以bit 的方式保存在memory cell里的,一般来说,一个cell
中只能存储一个bit,这些cell 以8 个或者16 个为单位,连成bit
line,形成所谓的byte(x8)/word(x16),这就是NAND Device 的位宽。这些Line 组成Page, page
再组织形成一个Block。k9f1208的相关数据如下:


1block=32page;1page=528byte=512byte(Main Area)+16byte(Spare Area)。


总容量为=4096(block数量)*32(page/block)*512(byte/page)=64Mbyte


NandFlash以页为单位读写数据,而以块为单位擦除数据。按照k9f1208的组织方式可以分四类地址:
Column Address、halfpage pointer、Page Address 、Block
Address。A[0:25]表示数据在64M空间中的地址。


Column Address表示数据在半页中的地址,大小范围0~255,用A[0:7]表示;


halfpage pointer表示半页在整页中的位置,即在0~255空间还是在256~511空间,用A[8]表示;


Page Address表示页在块中的地址,大小范围0~31,用A[13:9]表示;


Block Address表示块在flash中的位置,大小范围0~4095,A[25:14] 表示;


二、读操作过程


K9f1208的寻址分为4个cycle。分别是:A[0:7]、A[9:16]、A[17:24]、A[25]。


读操作的过程为: 1、发送读取指令;2、发送第1个cycle地址;3、发送第2个cycle地址;4、发送第3个cycle地址;5、发送第4个cycle地址;6、读取数据至页末。


K9f1208提供了两个读指令,‘0x00’、‘0x01’。这两个指令区别在于‘0x00’可以将A[8]置为0,选中上半页;而‘0x01’可以将A[8]置为1,选中下半页。
虽然读写过程可以不从页边界开始,但在正式场合下还是建议从页边界开始读写至页结束。下面通过分析读取页的代码,阐述读过程。
static void ReadPage(U32 addr, U8 *buf) //addr表示flash中的第几页,即‘flash地址>>9’
{
U16 i;
NFChipEn(); //使能NandFlash
WrNFCmd(READCMD0); //发送读指令‘0x00’,由于是整页读取,所以选用指令‘0x00’
WrNFAddr(0); //写地址的第1个cycle,即Column Address,由于是整页读取所以取0
WrNFAddr(addr); //写地址的第2个cycle,即A[9:16]
WrNFAddr(addr>>8); //写地址的第3个cycle,即A[17:24]
WrNFAddr(addr>>16); //写地址的第4个cycle,即A[25]。
WaitNFBusy(); //等待系统不忙
for(i=0; i<512; i++)
buf[i] = RdNFDat(); //循环读出1页数据
NFChipDs(); //释放NandFlash
}


三、写操作过程


写操作的过程为: 1、发送写开始指令;2、发送第1个cycle地址;3、发送第2个cycle地址;4、发送第3个cycle地址;5、发送第4个cycle地址;6、写入数据至页末;7、发送写结束指令


下面通过分析写入页的代码,阐述读写过程。
static void WritePage(U32 addr, U8 *buf) //addr表示flash中的第几页,即‘flash地址>>9’
{
U32 i;
NFChipEn(); //使能NandFlash
WrNFCmd(PROGCMD0); //发送写开始指令’0x80’
WrNFAddr(0); //写地址的第1个cycle
WrNFAddr(addr); //写地址的第2个cycle
WrNFAddr(addr>>8); //写地址的第3个cycle
WrNFAddr(addr>>16); 写地址的第4个cycle
WaitNFBusy(); //等待系统不忙
for(i=0; i<512; i++)
WrNFDat(buf[i]); //循环写入1页数据
WrNFCmd(PROGCMD1); //发送写结束指令’0x10’
NFChipDs(); //释放NandFlash
}



[转]NAND Flash的驱动程序设计 Driver Design of...


[转]NAND Flash的驱动程序设计 Driver Design of NAND Flash

摘要 以三星公司K9F2808UOB为例,设计了NAND Flash与S3C2410的接口电路,介绍了NAND Flash在ARM嵌入式系统中的设计与实现方法,并在UBoot上进行了验证。所设计的驱动易于移植,可简化嵌入式系统开发



关键词  ARM  UBoot  NAND Flash  K9F2808UOB  驱动程序



引言



  当前各类嵌入式系统开发设计中,存储模块设计是不可或缺的重要方面。NOR和 NAND是目前市场上两种主要的非易失闪存技术。NOR Flash存储器的容量较小、写入速度较慢,但因其随机读取速度快,因此在嵌入式系统中,常用于程序代码的存储。与NOR相比,NAND闪存的优点是容量大,但其速度较慢,因为它的I/O端口只有8或16个,要完成地址和数据的传输就必须让这些信号轮流传送。NAND型Flash具有极高的单元密度,容量可以比较大,价格相对便宜。



  本文以三星公司的 K9F2808UOB芯片为例,介绍了NAND Flash的接口电路与驱动的设计方法。文中介绍了开发NAND Flash驱动基本原理,意在简化嵌入式系统开发过程。



1  NAND Flash工作原理



  S3C2410板的NAND Flash支持由两部分组成:集成在S3C2410 CPU上的NAND Flash控制器

和NAND Flash存储芯片。要访问NAND Flash中的数据,必须通过NAND Flash控制器发送命令才能完成。所以, NAND Flash相当于S3C2410的一个外设,并不位于它的内存地址区。



1.1  芯片内部存储布局及存储操作特点



  一片NAND Flash为一个设备, 其数据存储分层为:1设备=4
096块;1块=32页;1页=528字节=数据块大小(512字节)+OOB块大小(16字节)。在每一页中,最后16字节(又称
OOB,OutofBand)用于NAND Flash命令执行完后设置状态用,剩余512字节又分为前半部分和后半部分。可以通过NAND Flash命令00h/01h/50h分别对前半部、后半部、OOB进行定位,通过NAND Flash内置的指针指向各自的首地址。



  存储操作特点有: 擦除操作的最小单位是块;NAND
Flash芯片每一位只能从1变为0,而不能从0变为1,所以在对其进行写入操作之前一定要将相应块擦除(擦除即是将相应块的位全部变为1);OOB部分
的第6字节(即517字节)标志是否是坏块,值为FF时不是坏块,否则为坏块。除OOB第6字节外,通常至少把OOB的前3字节用来存放NAND
Flash硬件ECC码。



1.2  NAND Flash接口电路



  首先介绍开发板的硬件设计,图1为NAND
Flash接口电路。其中开关SW的1、2连接时R/B表示准备好/忙,2、3连接时nWAIT可用于增加读/写访问的额外等待周期。在S3C2410处
理器中已经集成了NAND Flash控制器,图2为微控制器与NAND Flash连接的方式。











200902_10_1.gif





1.3  控制器工作原理



  NAND Flash控制器在其专用寄存器区(SFR)地址空间中映射有属于自己的特殊功能寄存器,就是通过将NAND
Flash芯片的内设命令写到其特殊功能寄存器中,从而实现对NAND
Flash芯片读、检验和编程控制。特殊功能寄存器有:NFCONF、NFCMD、NFADDR、NFDATA、NFSTAT、NFECC。












200902_10_2.gif







2  Flash烧写程序原理及结构



  基本原理:将在SDRAM中的一段存储区域中的数据写到NAND Flash存储空间中。烧写程序在纵向上分三层完成。第一层:
主烧写函数,将SDRAM中一段存储区域的数据写到NAND Flash存储空间中。第二层: 该层提供对NAND
Flash进行操作的页读、写及块擦除等函数。第三层:为第二层提供具体NAND
Flash控制器中对特殊功能寄存器进行操作的核心函数,该层也是真正将数据在SDRAM和NAND
Flash之间实现传送的函数。其中第二层为驱动程序的设计关键所在,下面对该层的读、写(又称编程)、擦除功能编码进行详细介绍。



2.1  NAND Flash Read



  功能:读数据操作以页为单位,读数据时首先写入读数据命令00H,然后输入要读取页的地址,接着从数据寄存器中读取数据,最后进行ECC校验。



  参数说明:block,块号;page,页号;buffer,指向将要读取到内存中的起始位置;返回值1,读成功,返回值0:读失败。



static int NF_ReadPage(unsigned int block, unsigned int page, unsigned char *buffer){

  NF_RSTECC(); /* 初始化 ECC */

  NF_nFCE_L(); /* 片选NAND Flash芯片*/

  NF_CMD(0x00); /* 从A区开始读 *//* A0~A7(列地址) */

  NF_ADDR(0); /* A9A16(页地址) */

  NF_ADDR(blockPage&0xff); /* A17A24,(页地址) */

  NF_ADDR((blockPage>>8)&0xff);/* A25, (页地址) */

  NF_ADDR((blockPage>>16)&0xff);/* 等待NAND Flash处于再准备状态 */

  ReadPage();/* 读整个页, 512字节 */

  ReadECC();/* 读取ECC码 */

  ReadOOB();/* 读取该页的OOB块 *//* 取消NAND Flash 选中*/

  NF_nFCE_H();/* 校验ECC码, 并返回 */

  Return (checkEcc())}



2.2  NAND Flash Program



  功能:对页进行编程命令, 用于写操作。



  命令代码:首先写入00h(A区)/01h(B区)/05h(C区), 表示写入那个区; 再写入80h开始编程模式(写入模式),接下来写入地址和数据; 最后写入10h表示编程结束。图3为程序流程图。







200902_10_3.gif
(9.45 KB)




下载次数:0


2009-5-31 23:01











200902_10_3.gif


参数说明:block,块号;page,页号;buffer,指向内存中待写入NAND Flash中的数据起始位置;返回值0,写错误,返回值1,写成功。



static int NF_WritePage(unsigned int block, unsigned int page, unsigned char *buffer){

  NF_RSTECC(); /* 初始化 ECC */

  NF_nFCE_L(); /* 片选NAND Flash芯片*/

  NF_CMD(0x0); /* 从A区开始写 */

  NF_CMD(0x80); /* 写第一条命令 *//* A0~A7(列地址) */

  NF_ADDR(0);/* A9A16(页地址) */

  NF_ADDR(blockPage&0xff);/* A17A24(页地址) */

  NF_ADDR((blockPage>>8)&0xff); /* A25(页地址) */

  NF_ADDR((blockPage>>16)&0xff);/* 写页为512B到NAND Flash芯片 */

  WRDATA(); /*OOB一共16字节,每一个字节存放什么由程序员自己定义, 在Byte0 Byte2存ECC检验码,Byte6 存放坏块标志*/

  WRDATA(); /* 写该页的OOB数据块 */

  CMD(0x10); /* 结束写命令 */

  WAITRB();/* 等待NAND Flash处于准备状态 *//* 发送读状态命令给NAND Flash */

  CMD(0x70);

  if (RDDATA()&0x1) { /*如果写有错, 则标示为坏块,取消NAND Flash 选中*/

  MarkBadBlock(block);

  return 0;

  } else { /* 正常退出, 取消NAND Flash 选中*/

  return 1;}



2.3  NAND Flash Erase



  功能:块擦除命令。



  命令代码:首先写入60h进入擦写模式,然后输入块地址,接下来写入D0h, 表示擦写结束。



  参数说明:block,块号;返回值0,擦除错误(若是坏块直接返回0;若擦除出现错误则标记为坏块然后返回0),返回值1,成功擦除。



static int NF_EraseBlock(unsigned int block){/* 如果该块是坏块, 则返回 */

  if(NF_IsBadBlock(block)) return 0;

  NF_nFCE_L(); /* 片选NAND Flash芯片*/

  NF_CMD(0x60); /* 设置擦写模式 *//* A9A16(Page Address) , 是基于块擦除*/

  NF_ADDR(blockPage&0xff);

  NF_ADDR((blockPage>>8)&0xff); /* A25(Page Address) */

  NF_ADDR((blockPage>>16)&0xff); NF_CMD(0xd0); WAITRB();CMD(0x70);

  if(RDDATA()&0x1){/*如有错,标为坏块,取消Flash选中*/

  MarkBadBlock(block);

  return 0;

  } else { /* 退出, 取消Flash 选中*/

  return 1;}





3  ECC校检原理与实现



  由于NAND Flash的工艺不能保证NAND的Memory
Array在其生命周期中保持性能可靠,因此在NAND的生产及使用过程中会产生坏块。为了检测数据的可靠性,在应用NAND
Flash的系统中一般都会采用一定的坏区管理策略,而管理坏区的前提是能比较可靠地进行坏区检测。如果操作时序和电路稳定性不存在问题的话,NAND
Flash出错的时候一般不会造成整个块或是页不能读取或全部出错,而是整个页(例如512字节)中只有一位或几位出错。对数据的校验常用的有奇偶校验、
CRC校验等,而在NAND
Flash处理中,一般使用一种专用的校验——ECC。ECC能纠正单位错误和检测双位错误,而且计算速度很快,但对1位以上的错误无法纠正,对2位以上
的错误不保证能检测。ECC一般每256字节原始数据生成3字节ECC校验数据,这3字节共24位分成两部分:6位的列校验和16位的行校验,多余的2位
置1,如表1所列。







200902_10_4.gif
(4.47 KB)




下载次数:0


2009-5-31 23:02











200902_10_4.gif


首先介绍ECC的列校检。ECC的列校验和生成规则如图4所示,“^”表示“位异或”操作。由于篇幅关系,行校检不作介绍,感兴趣的读者可以参考芯片datasheet,在三星公司网站可以免费下载。








200902_10_5.gif
(12.59 KB)




下载次数:0


2009-5-31 23:02











200902_10_5.gif


数学表达式为:








200902_10_6.gif
(3.12 KB)




下载次数:0


2009-5-31 23:03











200902_10_6.gif



向NAND Flash的页中写入数据时,每256字节生成一个ECC校验和,称之为原ECC校验和,保存到页的OOB数据区中。当从NAND
Flash中读取数据时,每256字节生成一个ECC校验和,称之为新ECC校验和。校验的时候,根据上述ECC生成原理不难推断:将从OOB区中读出的
原ECC校验和与新ECC校验和按位异或,若结果为0,则表示无错(或者出现了
ECC无法检测的错误);若3字节异或结果中存在11位为1,表示存在一个位错误,且可纠正;若3个字节异或结果中只存在1位为1,表示
OOB区出错;其他情况均表示出现了无法纠正的错误。



4  UBOOT下功能验证



  实现UBOOT对NAND Flash的支持主要是在命令行下实现对NAND Flash的操作。对NAND Flash实现的命令为:nand
info、nand device、nand read、nand write、nand erease、nand
bad。用到的主要数据结构有:struct nand_flash_dev和struct
nand_chip,前者包括主要的芯片型号、存储容量、设备ID、I/O总线宽度等信息,后者是对NAND
Flash进行具体操作时用到的信息。由于将驱动移植到UBoot的方法不是本文重点,故不作详细介绍。



  验证方式:通过TFTP将数据下载到SDRAM中,利用nand read、nand write、nand erease三个命令对NAND
Flash进行读、编程、擦写测试。测试结果如表2所列。和datasheet中数据对比,可以得出结论,驱动在系统中运行良好。








200902_10_7.gif
(2.82 KB)




下载次数:0


2009-5-31 23:03











200902_10_7.gif


结语



  现在嵌入式系统应用越来越广泛,而存储器件又是嵌入式系统必不可少的一部分,NAND Flash在不超过4
GB容量的需求下,较其他存储器件优势明显。本文所设计的驱动并未基于任何操作系统,可以方便地移植到多种操作系统和Boot
Loader下,对于简化嵌入式系统开发有一定的实际意义。



参考文献



[1] Yang S,Chen X,Alty L.Design issues and implementation of
Internetbased process control[J]. Control Engineering
Practice,2003,11(6):709720.

[2] A Wilson.The challenge of embedded Internet[J]. Electronic Product Design,1998,12(3):12.

[3] 刘淼. 嵌入式系统接口设计与Linux驱动程序开发[M].北京:北京航空航天大学出版社,2006.

[4] Samsung.K9F2808U0CFLASHMemory[OL].http://www.samsung.com/Products/ ... nfo/Datasheets.htm.





















NandFlash驱动超详细分析(三)(转)





NandFlash驱动超详细分析(三)(转)



2410NandFlash控制器


管脚配置


D[7:0]: DATA0-7 数据/命令/地址/的输入/输出口(与数据总线共享)


CLE : GPA17  命令锁存使能 (输出)


ALE : GPA18  地址锁存使能(输出)


nFCE : GPA22 NAND Flash 片选使能(输出)


nFRE : GPA20 NAND Flash 读使能 (输出)


nFWE : GPA19 NAND Flash 写使能 (输出)


R/nB : GPA21 NAND Flash 准备好/繁忙(输入)


 


相关寄存器:


NFCONF   NandFlash控制寄存器


 [15]NandFlash控制器使能/禁止     0 = 禁止   1 = 使能


 [14:13]保留


 [12]初始化ECC解码器/编码器    0 = 不初始化   1 = 初始化


 [11]芯片使能  nFCE控制        0 = 使能       1 = 禁止


 [10:8]TACLS   持续时间 = HCLK*(TACLS+1)


 [6:4] TWRPH0


 [2:0] TWRPH1


 


NFCMD  命令设置寄存器


 [7:0] 命令值


NFADDR 地址设置寄存器


 [7:0] 存储器地址


NFDATA 数据寄存器


 [7:0] 存放数据


NFSTAT 状态寄存器


 [0]    0 = 存储器忙     1 = 存储器准备好


NFECC  ECC寄存器


 [23:16]ECC校验码2


 [15:8] ECC校验码1


 [8:0]  ECC校验码0


 


写操作:


写入操作以页为单位。写入必须在擦除之后,否则写入将出错。


页写入周期中包括以下步骤:


写入串行数据输入指令(80h)。然后写入4个字节的地址,最后串行写入数据(528Byte)。串行写入的数据最多为528byte。


串行数据写入完成后,需要写入“页写入确认”指令10h,这条指令将初始化器件内部写入操作。


10h写入之后,nand flash的内部写控制器将自动执行内部写入和校验中必要的算法和时序,


系统可以通过检测R/B的输出,或读状态寄存器的状态位(I/O 6)来判断内部写入是否结束


 


擦除操作:


擦除操作时以块(16K Byte)为单位进行的


擦除的启动指令为60h,随后的3个时钟周期是块地址。其中只有A14到A25是有效的,而A9到A13是可以忽略的。


块地址之后是擦除确认指令D0h,用来开始内部的擦除操作。


器件检测到擦除确认命令后,在/WE的上升沿启动内部写控制器,开始执行擦除和擦除校验。内部擦除操作完成后,应该检测写状态位(I/O 0),从而了解擦除操作是否成功完成。


 


读操作有两种读模式:


读方式1用于读正常数据;


读方式2用于读附加数据


在初始上电时,器件进入缺省的“读方式1模式”。在这一模式下,页读取操作通过将00h指令写入指令寄存器,接着写入3个地址(一个列地址和2个行地址)来启动。一旦页读指令被器件锁存,下面的页操作就不需要再重复写入指令了。


写入指令和地址后,处理器可以通过对信号线R//B的分析来判断该才作是否完成。


外部控制器可以再以50ns为周期的连续/RE脉冲信号的控制下,从I/O口依次读出数据


备用区域的从512到527地址的数据,可以通过读方式2指令进行指令进行读取(命令为50h)。地址A0~A3设置了备用区域的起始地址,A4~A7被忽略掉


 


时序要求:


写地址、数据、命令时,nCE、nWE信号必须为低电平,它们在nWE信号的上升沿被锁存。命令锁存使能信号CLE和地址锁存信号ALE用来区分I/O引脚上传输的是命令还是地址。


 


寻址方式:


NAND Flash的寻址方式和NAND Flashmemory组织方式紧密相关。NAND Flash的数据以bit的方式保存在memory cell,一个cell中只能存储一个bit。这些cell8个或者16个为单位,连成bit line,形成byte(x8)/word(x16),这就是NAND的数据宽度。


      


       这些Line会再组成Page典型情况下:通常是528Byte/page或者264Word/page。然后,每32page形成一个BlockSizeof(block)=16.5kByte。其中528Byte = 512Byte+16Byte,前512Byte为数据区,后16Byte存放数据校验码等,因此习惯上人们称1page512个字节,每个Block16Kbytes


      现在在一些大容量的FLASH存贮设备中也采用以下配置:2112 Byte /page 1056 Word/page64page/BlockSizeof(block) = 132kByte;同上:2112 = 2048 +64,人们习惯称一页含2k个字节,一个Block含有64个页,容量为128KB


 


       BlockNAND Flash中最大的操作单元,擦除可以按照blockpage为单位完成,而编程/读取是按照page为单位完成的



       所以,按照这样的组织方式可以形成所谓的三类地址:


         -Block  Address   块地址


         -Page   Address   页地址


         -Column Address  列地址


      首先,必须清楚一点,对于NAND Flash来讲,地址和命令只能在I/O[7:0]上传递,数据宽度可以是8位或者16位,但是,对于x16NAND DeviceI/O[15:8]只用于传递数据。


    清楚了这一点,我们就可以开始分析NAND Flash的寻址方式了。


528Byte/page 总容量64M Byte+512kbyteNAND器件为例:


因为


1page=528byte=512byte(Main Area)+16byte(Spare Area)


1block=32page = 16kbyte


64Mbyte = 4096 Block


 


用户数据保存在main area中。


512byte需要9bit来表示,对于528byte系列的NAND,这512byte被分成1st half2nd half,各自的访问由所谓的pointer operation命令来选择,也就是选择了bit8的高低。因此A8就是halfpage pointerA[7:0]就是所谓的column address


 


32page需要5bit来表示,占用A[13:9]即该page在块内的相对地址。


Block的地址是由A14以上的bit来表示,例如64MBNAND,共4096block,因此,需要12bit来表示,即A[25:14],如果是1Gbit528byte/pageNAND Flash,共8192block,则block addressA[30:14]表示。


 


NAND Flash的地址表示为:


Block Address  Page Address in block  |  half page pointer |  Column Address


地址传送顺序是Column Address , Page Address , Block Address


 


例如一个地址:0x00aa55aa


         0000 0000  1010  1010  0101  0101  1010  1010


 


由于地址只能在I/O[7:0]上传递,因此,必须采用移位的方式进行。


例如,对于64MBx8NAND flash,地址范围是0~0x3FF_FFFF,只要是这个范围内的数值表示的地址都是有效的。


      


       NAND_ADDR为例:


       1步是传递column address,就是NAND_ADDR[7:0],不需移位即可传递到I/O[7:0]上, halfpage pointerbit8是由操作指令决定的,即指令决定在哪个halfpage上进行读写,而真正的bit8的值是don't care的。


       2步就是将NAND_ADDR右移9位,将NAND_ADDR[16:9]传到I/O[7:0];


       3步将NAND_ADDR[24:17]放到I/O;


       4步需要将NAND_ADDR[25]放到I/O;


       因此,整个地址传递过程需要4步才能完成,即4-step addressing


       如果NAND Flash的容量是32MB以下,那么,block adress最高位只到bit24,因此寻址只需要3步。