内核初始化与gpio子系统

来源:互联网 发布:出去旅游哪个软件好 编辑:程序博客网 时间:2024/04/30 03:07

定义在linux/arch/arm/plat-s3c/gpio-config.c

int s3c_gpio_cfgpin(unsigned int pin, unsigned int config)

{

struct s3c_gpio_chip *chip = s3c_gpiolib_getchip(pin);  //得到对应GPIO结构体首指针,里面包含了该GPIO的各种参数

unsigned long flags;

int offset;

int ret;

 offset = pin - chip->chip.base;    // 否则offset等于该GPIO引脚相对于GPX0)的偏移量,每个偏移1

ret = s3c_gpio_do_setcfg(chip, offset, config);   //设置该GPIO状态寄存器的数值为config

//s3c_gpio_do_setcfg操作

static inline int s3c_gpio_do_setcfg(struct s3c_gpio_chip *chip,unsigned int off, unsigned int config)

{

     return (chip->config->set_config)(chip, off, config);

}

//这里的set_config是一个函数指针,由后面的分析知道,如果针对GPA,该函数指针指向s3c_gpio_setcfg_s3c24xx_a ,如果针对GPX应该是指向s3c_gpio_setcfg_s3c24xx——但发现,如果是其他GPX,根本没有定义set_config!!!(这个问题已经解决,见后文s3c24xx_gpiolib_init函数,事实上,其余的config的确指向s3c_gpio_do_setcfg函数)

struct s3c_gpio_cfg s3c24xx_gpiocfg_default = {

           .set_config = s3c_gpio_setcfg_s3c24xx,

           .get_config = s3c_gpio_getcfg_s3c24xx,

};

 

int s3c_gpio_setcfg_s3c24xx_a(struct s3c_gpio_chip *chip, unsigned int off, unsigned int cfg)

{

void __iomem *reg = chip->base;   // GPXCON的物理基地址

unsigned int shift = off;    // 每个GPA对应一位

u32 con;

 if (s3c_gpio_is_cfg_special(cfg)) {     //OUTPUT状态是否为(0xfffffffX),是,返回1

         cfg &= 0xf;  // cfg = 0xX

  /* Map output to 0, and SFN2 to 1 */ 本实验不会运行到这

cfg -= 1;

if (cfg > 1)

        return -EINVAL;

cfg <<= shift;

}

con = __raw_readl(reg);     // 先读出该GPXCON的值,32

con &= ~(0x1 << shift);     // 

con |= cfg;                         //

 __raw_writel(con, reg);    // 将新值写入GPXCON

 

PS:   

#define __raw_writeb(v,a) (__chk_io_ptr(a), *(volatile unsigned char __force *)(a) = (v))

#define __raw_writew(v,a) (__chk_io_ptr(a), *(volatile unsigned short __force *)(a) = (v))

#define __raw_writel(v,a) (__chk_io_ptr(a), *(volatile unsigned int __force *)(a) = (v))

#define __raw_readb(a) (__chk_io_ptr(a), *(volatile unsigned char __force *)(a))

#define __raw_readw(a) (__chk_io_ptr(a), *(volatile unsigned short __force *)(a))

#define __raw_readl(a) (__chk_io_ptr(a), *(volatile unsigned int __force *)(a))

return 0;

}

如果针对GPX情况

int s3c_gpio_setcfg_s3c24xx(struct s3c_gpio_chip *chip,

unsigned int off, unsigned int cfg)

{

void __iomem *reg = chip->base;

 unsigned int shift = off * 2;    // 每个GPX对应2

u32 con;

if (s3c_gpio_is_cfg_special(cfg)) {

          cfg &= 0xf;

if (cfg > 3)

          return -EINVAL;

  cfg <<= shift;        // cfg0,1两位左移offset

}

 con = __raw_readl(reg);         // 读对应的GPXCON

 con &= ~(0x3 << shift);          // GPXCONpin)的两bits0

 con |= cfg;                               // 设置config

 __raw_writel(con, reg);           // 写入新的GPXCON

return 0;

}

 

return ret;

我们先来看s3c_gpiolib_getchip,它实现了返回对应pin值的GPIO结构体首指针的功能

#include<mach/gpio-core.h> 

 static inline struct s3c_gpio_chip *s3c_gpiolib_getchip(unsigned int pin)

{

struct s3c_gpio_chip *chip;

 if (pin > S3C_GPIO_END)    //如果超过GPJ(32)return NULL

     return NULL;

chip = &s3c24xx_gpios[pin/32];     //根据偏移,计算出对应pinGPIO结构体指针

     return ((pin - chip->chip.base) < chip->chip.ngpio) ? chip : NULL;

     //  这里验证,如果pin偏移超过了GPIO的个数,说明出错了,否则就返回该GPIO的结构体指针

}

回想以下之前s3c2410_gpio_cfgpin中,我们传入的参数是led_table[i]和 led_cfg_table[i]

struct s3c_gpio_chip s3c24xx_gpios[] = {

[0] = {

.base = S3C2410_GPACON,  // datasheet上地址为0x56000000

#define S3C2410_GPIOREG(x) ((x) + S3C24XX_VA_GPIO) 

#define S3C24XX_VA_GPIO     ((S3C24XX_PA_GPIO - S3C24XX_PA_UART) + S3C24XX_VA_UART)

S3C24XX_PA_GPIO相当于(0x15600000) 

S3C24XX_PA_UART相当于(0x15000000) 

#define S3C_VA_UART    S3C_ADDR(0x01000000)    /* UART */ 

#define S3C_ADDR_BASE 0xF6000000

#ifndef __ASSEMBLY__

#define S3C_ADDR(x) ((void __iomem __force *)S3C_ADDR_BASE + (x))

#else

#define S3C_ADDR(x) (S3C_ADDR_BASE + (x))

#endif

0x15600000-15000000+F7000000这里的S3C2410_GPACON应该怎么算?

 

.pm = __gpio_pm(&s3c_gpio_pm_1bit),

.config = &s3c24xx_gpiocfg_banka,   // 设置GPIO的函数指针

                     static struct s3c_gpio_cfg s3c24xx_gpiocfg_banka = {

                         .set_config = s3c_gpio_setcfg_s3c24xx_a,

                         .get_config = s3c_gpio_getcfg_s3c24xx_a,

                      };

.chip = {

.base = S3C2410_GPA(0),   //基地址,也是偏移量

.owner = THIS_MODULE,

.label = "GPIOA",

.ngpio = 24,

.direction_input = s3c24xx_gpiolib_banka_input,

.direction_output = s3c24xx_gpiolib_banka_output,

},

},

[1] = {

.base = S3C2410_GPBCON,

.pm = __gpio_pm(&s3c_gpio_pm_2bit),

.chip = {

.base = S3C2410_GPB(0),

.owner = THIS_MODULE,

.label = "GPIOB",

.ngpio = 16,

},

},

[2] = {

.base = S3C2410_GPCCON,

.pm = __gpio_pm(&s3c_gpio_pm_2bit),

.chip = {

.base = S3C2410_GPC(0),

.owner = THIS_MODULE,

.label = "GPIOC",

.ngpio = 16,

},

},

[3] = {

.base = S3C2410_GPDCON,

.pm = __gpio_pm(&s3c_gpio_pm_2bit),

.chip = {

.base = S3C2410_GPD(0),

.owner = THIS_MODULE,

.label = "GPIOD",

.ngpio = 16,

},

},

[4] = {

.base = S3C2410_GPECON,

.pm = __gpio_pm(&s3c_gpio_pm_2bit),

.chip = {

.base = S3C2410_GPE(0),

.label = "GPIOE",

.owner = THIS_MODULE,

.ngpio = 16,

},

},

[5] = {

.base = S3C2410_GPFCON,

.pm = __gpio_pm(&s3c_gpio_pm_2bit),

.chip = {

.base = S3C2410_GPF(0),

.owner = THIS_MODULE,

.label = "GPIOF",

.ngpio = 8,

.to_irq = s3c24xx_gpiolib_bankf_toirq,

},

},

[6] = {

.base = S3C2410_GPGCON,

.pm = __gpio_pm(&s3c_gpio_pm_2bit),

.irq_base = IRQ_EINT8,

.chip = {

.base = S3C2410_GPG(0),

.owner = THIS_MODULE,

.label = "GPIOG",

.ngpio = 16,

.to_irq = samsung_gpiolib_to_irq,

},

}, {

.base = S3C2410_GPHCON,

.pm = __gpio_pm(&s3c_gpio_pm_2bit),

.chip = {

.base = S3C2410_GPH(0),

.owner = THIS_MODULE,

.label = "GPIOH",

.ngpio = 11,

},

},

/* GPIOS for the S3C2443 and later devices. */2440用不到

{

.base = S3C2440_GPJCON,

.pm = __gpio_pm(&s3c_gpio_pm_2bit),

.chip = {

.base = S3C2410_GPJ(0),

.owner = THIS_MODULE,

.label = "GPIOJ",

.ngpio = 16,

},

}, {

.base = S3C2443_GPKCON,

.pm = __gpio_pm(&s3c_gpio_pm_2bit),

.chip = {

.base = S3C2410_GPK(0),

.owner = THIS_MODULE,

.label = "GPIOK",

.ngpio = 16,

},

}, {

.base = S3C2443_GPLCON,

.pm = __gpio_pm(&s3c_gpio_pm_2bit),

.chip = {

.base = S3C2410_GPL(0),

.owner = THIS_MODULE,

.label = "GPIOL",

.ngpio = 15,

},

}, {

.base = S3C2443_GPMCON,

.pm = __gpio_pm(&s3c_gpio_pm_2bit),

.chip = {

.base = S3C2410_GPM(0),

.owner = THIS_MODULE,

.label = "GPIOM",

.ngpio = 2,

},

},

};

*************************************************************************** 

下面分析第二个函数,先看一下相关结构体

void s3c2410_gpio_setpin(unsigned int pin, unsigned int to)

{

/* do this via gpiolib until all users removed */

      gpio_request(pin, "temporary");

      gpio_set_value(pin, to);

      gpio_free(pin);

}

又出现了三个函数,我们一一说明:

1169/* These "optional" allocation calls help prevent drivers from stomping

1170 * on each other, and help provide better diagnostics in debugfs.

1171 * They're called even less than the "set direction" calls.

1172 */

PS:static struct gpio_desc gpio_desc[ARCH_NR_GPIOS];

其中ARCH_NR_GPIOSarch/arm/mach-s3c2410/include/mach/gpio.h中定义

#define ARCH_NR_GPIOS (32 * 9 + CONFIG_S3C24XX_GPIO_EXTRA)

因此,每个引脚都分配了一个gpio_desc数据结构

1173int gpio_request(unsigned gpio, const char *label)        // 这个函数还不是很明白

1174{

1175 struct gpio_desc *desc;

1176 struct gpio_chip *chip;

1177 int status = -EINVAL;

1178 unsigned long flags;

1179

1180 spin_lock_irqsave(&gpio_lock, flags);    // gpio_lock是自旋锁,上锁,保存FLAGflags变量中

1181

1182 if (!gpio_is_valid(gpio))     // 不符合要求,跳转到done

1183     goto done;

1184 desc = &gpio_desc[gpio];    // desc = &gpio_desc[pin]

1185 chip = desc->chip;

1186 if (chip == NULL)              // gpio_desc.chip指向NULL,跳转到done

1187     goto done;

1188

1189 if (!try_module_get(chip->owner))   // 该函数用于增加模块使用计数;若返回为0,表示调用失败,希望使用的模块没有被加载或正在被卸载中

1190     goto done;

1191

1192 /* NOTE: gpio_request() can be called in early boot,

1193 * before IRQs are enabled, for non-sleeping (SOC) GPIOs.

1194 */

1195

1196 if (test_and_set_bit(FLAG_REQUESTED, &desc->flags) == 0) {    // 原子操作,将flags的第FLAG_REQUESTED位置1,并返回其原值

1197     desc_set_label(desc, label ? : "?");    // 如果原来的值是0, 执行desc_set_label, desc->chip.label赋值,如果label有定义,直接用定义,比如上面的“temporary”,否则用“?”

static inline void desc_set_label(struct gpio_desc *d, const char *label)

{

#ifdef CONFIG_DEBUG_FS

 d->label = label;               // 为什么不是d->chip.label = label; ?

#endif

}

1198 status = 0;

1199 } else {                      // 如果flags的第FLAG_REQUESTED位原来的值是1

1200 status = -EBUSY;

1201 module_put(chip->owner);    // 该函数用于减少模块使用计数

1202     goto done;

1203 }

1204

1205 if (chip->request) {    // chip->requestlinux初始化时是没有指向的,可以见后面s3c_gpiolib_add

1206 /* chip->request may sleep */

1207                    spin_unlock_irqrestore(&gpio_lock, flags);            // 如果chip->request不为0, 解锁,因为后面调用的chip->request有可能睡眠

1208                    status = chip->request(chip, gpio - chip->base);

1209                    spin_lock_irqsave(&gpio_lock, flags);                    // 执行完后,继续上锁

1210

1211                    if (status < 0) {        // status返回负数,说明出错

1212                              desc_set_label(desc, NULL);

1213                              module_put(chip->owner);

1214                              clear_bit(FLAG_REQUESTED, &desc->flags);

1215                     }

1216 }

1217

1218done:

1219 if (status)

1220     pr_debug("gpio_request: gpio-%d (%s) status %d/n",  gpio, label ? : "?", status);

1221    // 如果状态不为0, 打印gpio-pin"****"的状态

1222 spin_unlock_irqrestore(&gpio_lock, flags);     // 解锁

1223 return status;    // 返回状态

1224}

*************************************************************************** 

下面先分析gpio_free函数

void gpio_free(unsigned gpio)           // 待分析

{

unsigned long flags;

struct gpio_desc *desc;

struct gpio_chip *chip;

might_sleep();

if (!gpio_is_valid(gpio)) {

        WARN_ON(extra_checks);

        return;

}

gpio_unexport(gpio);

spin_lock_irqsave(&gpio_lock, flags);

desc = &gpio_desc[gpio];

chip = desc->chip;

if (chip && test_bit(FLAG_REQUESTED, &desc->flags)) {

     if (chip->free) {

          spin_unlock_irqrestore(&gpio_lock, flags);

          might_sleep_if(chip->can_sleep);

          chip->free(chip, gpio - chip->base);

          spin_lock_irqsave(&gpio_lock, flags);

      }

desc_set_label(desc, NULL);

module_put(desc->chip->owner);

clear_bit(FLAG_ACTIVE_LOW, &desc->flags);

clear_bit(FLAG_REQUESTED, &desc->flags);

} else

WARN_ON(extra_checks);

spin_unlock_irqrestore(&gpio_lock, flags);

}

EXPORT_SYMBOL_GPL(gpio_free);

*************************************************************************** 

arch/arm/mach-s3c2410/include/mach/gpio.h 

#define gpio_set_value __gpio_set_value 

void __gpio_set_value(unsigned gpio, int value)

{

struct gpio_chip *chip;

chip = gpio_to_chip(gpio);        // 返回对应于pingpio_desc[pin].chip指针

WARN_ON(chip->can_sleep);

chip->set(chip, gpio - chip->base, value);       // 这里调用的是s3c_gpiolib_set函数!!!

}

/* caller holds gpio_lock *OR* gpio is marked as requested */

static inline struct gpio_chip *gpio_to_chip(unsigned gpio)

{

      return gpio_desc[gpio].chip;

}

看到这里,一直有个问题让我百思不得其解,这里的chip按理说应该是s3c_gpio_chip中的chip成员,但是之前都没有代码交代他们是如何联系到一起的,s3c_gpio_chipgpio_desc结构体如何联系在一起,也没有函数交代,并且这里的chip->set函数指针也没有实现的代码,但是,经实验确认没有问题后,我开始查找plat-s3c24xx/gpiolib.c中的函数希望能有些线索,果然,找到了这么一个函数:

static __init int s3c24xx_gpiolib_init(void)

{

struct s3c_gpio_chip *chip = s3c24xx_gpios;

int gpn;

for (gpn = 0; gpn < ARRAY_SIZE(s3c24xx_gpios); gpn++, chip++) {

        if (!chip->config)

                  chip->config = &s3c24xx_gpiocfg_default;          // 原来chip->config默认函数也是在这里!!!

        s3c_gpiolib_add(chip);           // 之前的疑惑都在这里实现!!!

}

return 0;

}

core_initcall(s3c24xx_gpiolib_init);

但是,这个s3c24xx_gpiolib_init函数又是在什么时候执行的呢?可以看到,在该函数的下面,有一句:core_initcall(s3c24xx_gpiolib_init);查阅相关资料发现, 在linux初始化的过程中,内核采用了一种initcall的机制,它利用gcc的扩展功能以及ld的连接控制脚本实现了在内核初始化的过程中通过简单的循环就实现了相关驱动的初始化

也就是说,在linux初始化期间,就已经执行了s3c24xx_gpiolib_init,现在我们可以分析下s3c_gpiolib_add(chip);这个函数了,

__init void s3c_gpiolib_add(struct s3c_gpio_chip *chip)

{

struct gpio_chip *gc = &chip->chip;

int ret;

BUG_ON(!chip->base);

BUG_ON(!gc->label);

BUG_ON(!gc->ngpio);

 spin_lock_init(&chip->lock);     // 初始化s3c_gpio_chip的自旋锁

if (!gc->direction_input)

           gc->direction_input = s3c_gpiolib_input;         // direction_input 函数指针

if (!gc->direction_output)

           gc->direction_output = s3c_gpiolib_output;    // direction_output 函数指针

if (!gc->set)

           gc->set = s3c_gpiolib_set;        // set函数指针

if (!gc->get)

           gc->get = s3c_gpiolib_get;        // get函数指针

#ifdef CONFIG_PM

if (chip->pm != NULL) {

          if (!chip->pm->save || !chip->pm->resume)

                    printk(KERN_ERR "gpio: %s has missing PM functions/n", gc->label);

          } else

          printk(KERN_ERR "gpio: %s has no PM function/n", gc->label);

#endif

/* gpiochip_add() prints own failure message on error. */

ret = gpiochip_add(gc);

if (ret >= 0)

          s3c_gpiolib_track(chip);

}

 

gpiochip_add函数分析:

int gpiochip_add(struct gpio_chip *chip)      // gpio_desc[]中分配空间,并链接chip结构

{

unsigned long flags;

int status = 0;

unsigned id;

int base = chip->base;

if ((!gpio_is_valid(base) || !gpio_is_valid(base + chip->ngpio - 1))

&& base >= 0) {

        status = -EINVAL;

        goto fail;

}

 spin_lock_irqsave(&gpio_lock, flags);       // 上锁

if (base < 0) {

         base = gpiochip_find_base(chip->ngpio);  // 这个函数在gpiolib.c中,在gpio_desc[]中分配chip->ngpio个空间(从最后往前分配),返回第一个index

         if (base < 0) {          // 分配不到

                status = base;

                goto unlock;      // 解锁退出

         }

         chip->base = base;    // gpio_chip *chip->base = i (igpio_desc[i]中的index)

}

/* these GPIO numbers must not be managed by another gpio_chip */

for (id = base; id < base + chip->ngpio; id++) {

       if (gpio_desc[id].chip != NULL) {

            status = -EBUSY;

            break;

       }

}

if (status == 0) {         // 分配到空间,正常情况下

         for (id = base; id < base + chip->ngpio; id++) {

              gpio_desc[id].chip = chip;    // 这里将gpio_descs3c_gpio_chip联系起来,他们的chip成员指向的是同一个数据结构

/* REVISIT: most hardware initializes GPIOs as

* inputs (often with pullups enabled) so power

* usage is minimized. Linux code should set the

* gpio direction first thing; but until it does,

* we may expose the wrong direction in sysfs.

*/

              gpio_desc[id].flags = !chip->direction_input ? (1 << FLAG_IS_OUT) : 0;  // 设置flags

          }

 } // end if

of_gpiochip_add(chip);    // 没操作

unlock:

 spin_unlock_irqrestore(&gpio_lock, flags);         // 解锁

if (status)

      goto fail;

status = gpiochip_export(chip);        //×××××××××××××××待分析

if (status)

      goto fail;

return 0;

fail:

/* failures here can mean systems won't boot... */

pr_err("gpiochip_add: gpios %d..%d (%s) failed to register/n",

chip->base, chip->base + chip->ngpio - 1,

chip->label ? : "generic");

 return status;       // 返回状态

}

下面是s3c_gpiolib_track函数

#ifdef CONFIG_S3C_GPIO_TRACK

struct s3c_gpio_chip *s3c_gpios[S3C_GPIO_END];

static __init void s3c_gpiolib_track(struct s3c_gpio_chip *chip)       // 没完全理解,待分析

{

unsigned int gpn;

int i;

gpn = chip->chip.base;

for (i = 0; i < chip->chip.ngpio; i++, gpn++) {

           BUG_ON(gpn >= ARRAY_SIZE(s3c_gpios));

           s3c_gpios[gpn] = chip;

}

}

#endif /* CONFIG_S3C_GPIO_TRACK */

*************************************************************************** 

 好,现在我们开始分析设备注册与卸载函数,在初始化程序中,有如下语句:

 ret = misc_register(&misc); //注册设备

 其中的misc_register就是杂项设备的注册函数,首先关注下这里的参数misc数据结构

75 /*

76 * 把 LED 驱动注册为 MISC 设备

77 */

78 static struct miscdevice misc = {

79           .minor = MISC_DYNAMIC_MINOR, //动态设备号

80           .name = DEVICE_NAME,

81           .fops = &dev_fops,

82 };

miscdevice的数据结构如图所示:

int   misc_register(struct miscdevice * misc)

{

struct miscdevice *c;

dev_t dev;

int err = 0;

 INIT_LIST_HEAD(&misc->list);      // 初始化链表头,将misc->listnextpre都指向自己

 mutex_lock(&misc_mtx);           // 获取互斥锁, or睡眠

 list_for_each_entry(c, &misc_list, list) {    // 遍历整个misc_list链表,所有的杂项驱动设备都有一个miscdevice数据结构,这些杂项驱动设备通过一个全局的misc_list链表连在一起, 相当一个记录

        if (c->minor == misc->minor) {     // 如果misc_list中已经有了这个设备(minor相同),则解锁返回,这里c是遍历时的tmp miscdevice,指向当前遍历节点

                 mutex_unlock(&misc_mtx);

                 return -EBUSY;

         }

}

 if (misc->minor == MISC_DYNAMIC_MINOR) {        // 如果misc_list中没有该设备,判断minor是否准备动态分配,实验中如此设置

        int i = find_first_zero_bit(misc_minors, DYNAMIC_MINORS);   // misc_minors是杂项设备位图,总共有64个位DYNAMIC_MINORS=64,表示可以注册64个杂项设备,这句代码找到位图中的空闲位置(表示还能加新设备)

        if (i >= DYNAMIC_MINORS) {    // 如果超过总设备数,则解锁返回

                 mutex_unlock(&misc_mtx);

                 return -EBUSY;

        }

        misc->minor = DYNAMIC_MINORS - i - 1;       // 计算子设备号,赋值到misc->minor

        set_bit(i, misc_minors);        // 对应的位图也置位

}

 dev = MKDEV(MISC_MAJOR, misc->minor);      // 生成设备号

// sysfs中创建并注册一个设备,可以在/dev下面看到misc->name

misc->this_device = device_create(misc_class, misc->parent, dev, misc, "%s", misc->name);

1504struct device *device_create(struct class *class, struct device *parent,          // 这个函数以后会详细看

1505 dev_t devt, void *drvdata, const char *fmt, ...)

1506{

1507 va_list vargs;

1508 struct device *dev;

1509

1510 va_start(vargs, fmt);

1511 dev = device_create_vargs(class, parent, devt, drvdata, fmt, vargs);

1512 va_end(vargs);

1513 return dev;

1514} 

 

// this_device是在创建设备节点时指向函数device_create()返回的设备结构

 if (IS_ERR(misc->this_device)) {              // 如果创建节点出错,并且

       int i = DYNAMIC_MINORS - misc->minor - 1;        // 计算子设备号之前misc->minor的值

       if (i < DYNAMIC_MINORS && i >= 0)   // 计算位图位i,如果在0-64之间,说明在set_bit中置位了,则清楚位图,处理错误,准备返回

             clear_bit(i, misc_minors);

             err = PTR_ERR(misc->this_device);

       goto out;

}

/*

* Add it to the front, so that later devices can "override"

* earlier defaults

*/

 list_add(&misc->list, &misc_list);               // 以上操作都没有问题后,将新设备加入misc_list链表,解锁返回

out:

mutex_unlock(&misc_mtx);

return err;

}

*************************************************************************** 

同样,对应misc_register函数,在exit中会调用misc_deregister函数

int misc_deregister(struct miscdevice *misc)

{

int i = DYNAMIC_MINORS - misc->minor - 1;

 if (WARN_ON(list_empty(&misc->list)))    // 如果该misc->listnext指向自己,则出错返回

     return -EINVAL;

 mutex_lock(&misc_mtx);       // 上锁

 list_del(&misc->list);            // miscmisc_list链表中删除

device_destroy(misc_class, MKDEV(MISC_MAJOR, misc->minor));        // 对应device_create!

1532void device_destroy(struct class *class, dev_t devt)

1533{

1534 struct device *dev;

1535

1536 dev = class_find_device(class, NULL, &devt, __match_devt);

1537 if (dev) {

1538 put_device(dev);

1539 device_unregister(dev);

1540 }

1541}

1542EXPORT_SYMBOL_GPL(device_destroy); 

if (i < DYNAMIC_MINORS && i >= 0)

     clear_bit(i, misc_minors);       // 计算位图位i,如果在0-64之间,说明在set_bit中置位了,清楚位图

mutex_unlock(&misc_mtx);         // 解锁返回

return 0;

}

*************************************************************************** 

总结杂项设备驱动的注册与卸载流程:

misc_register:找到空闲设备位图位置 -> 计算子设备号(如果动态的话),位图位置位 - >  device_creat() -> miscdevice结构体加入misc_list链表中

misc_deregister:  miscdevice结构体从misc_list链表中删除 -> device_destory() -> 位图位清零

*************************************************************************** 

s3c24xx_gpiolib_init函数一样,misc也有一个初始化函数会在linux初始化时运行,下面来分析这个函数

static int __init misc_init(void)

{

int err;

#ifdef CONFIG_PROC_FS    //proc文件系统下创建一个"misc"目录。 misc_proc_fops是该文件系统下文件的操作函数集

           proc_create("misc", 0, NULL, &misc_proc_fops);

#endif

 misc_class = class_create(THIS_MODULE, "misc");   // 前面device_create()中的misc_class就是在这里初始化的

 err = PTR_ERR(misc_class);

 if (IS_ERR(misc_class))        // 出错处理

     goto fail_remove;

err = -EIO;

if (register_chrdev(MISC_MAJOR,"misc",&misc_fops))   //注册一个主设备号为MISC_MAJOR10)的字符设备,设备操作函数集为misc_fops

goto fail_printk;

misc_class->devnode = misc_devnode;

return 0;

fail_printk:       // 错误处理

printk("unable to get major %d for misc devices/n", MISC_MAJOR);

class_destroy(misc_class);

fail_remove:

remove_proc_entry("misc", NULL);

return err;

}

subsys_initcall(misc_init);









整个arm linux内核的启动可分为三个阶段:第一阶段主要是进行cpu和体系结构的检查、cpu本身的初始化以及页表的建立等;第二阶段主要是对系统中的一些基础设施进行初始化;最后则是更高层次的初始化,如根设备和外部设备的初始化。
1. 内核移植
2.2. 涉及的头文件
/linux-2.6.18.8/include
[root@localhost include]# tree -L 1
|-- asm-arm   ------------------------------->(1)
|-- linux ------------------------------->(2)
内核移植过程中涉及到的头文件包括处理器相关的头文件(1)和处理器无关的头文件(2)。
2.3. 内核移植2.4. 涉及的源文件
/linux-2.6.18.8/arch/arm
[root@localhost arm]# tree -L 1
|-- boot  ------------------------------->(2)
|-- kernel  ------------------------------->(3)
|-- mach-s3c2410   ------------------------------->(4)
|-- mm    ------------------------------->(5)
|-- tools    ------------------------------->(1)

(1)/linux-2.6.18.8/arch/arm/tools
[root@localhost tools]# tree -L 1
.
|-- Makefile
|-- gen-mach-types
`-- mach-types
Mach-types 文件定义了不同系统平台的系统平台号。移植linux内核到新的平台上需要对新的平台登记系统平台号。
Mach-types文件格式如下:
# machine_is_xxx CONFIG_xxxx MACH_TYPE_xxx number
s3c2410 ARCH_S3C2410 S3C2410          182
smdk2410 ARCH_SMDK2410 SMDK2410            193
之所以需要这些信息,是因为脚本文件linux/arch/arm/tools/gen-mach-types需要linux/arch/tools/mach-types来产生linux/include/asm-arm/mach-types.h文件,该文件中设置了一些宏定义,需要这些宏定义来为目标系统选择合适的代码。
(2)linux-2.6.18.8/arch/arm/boot/compressed
[root@localhost compressed]# tree -L 1
|-- Makefile
|-- Makefile.debug
|-- big-endian.S
|-- head-at91rm9200.S
2 浅谈分析Arm linux 内核移植及系统初始化的过程 
  |-- head.S
|-- ll_char_wr.S
|-- misc.c
|-- ofw-shark.c
|-- piggy.S
`-- vmlinux.lds.in
Head.s 是内核映像的入口代码,是自引导程序。自引导程序包含一些初始化程序,这些程序都是体系结构相关的。在对系统作完初始化设置工作后,调用misc.c文件中的decompress_kernel()函数解压缩内核映像到指定的位置,然后跳转到kernel的入口地址。
Vmlinux.lds.in用来生成内核映像的内存配置文件。
(3)linux-2.6.18.8/arch/arm/kernel
[root@localhost kernel]# tree -L 1
|-- Makefile
|-- apm.c
|-- armksyms.c
|-- arthur.c
|-- asm-offsets.c
|-- bios32.c
|-- calls.S
|-- dma.c
|-- ecard.c
|-- entry-armv.S
|-- entry-common.S
|-- entry-header.S
|-- fiq.c
|-- head-common.S
|-- head-nommu.S
|-- head.S
|-- init_task.c
|-- io.c
|-- irq.c
|-- isa.c
|-- module.c
|-- process.c
|-- ptrace.c
|-- ptrace.h
|-- semaphore.c
|-- setup.c
|-- smp.c
|-- sys_arm.c
|-- time.c
|-- traps.c
`-- vmlinux.lds.S
内核入口处也是由一段汇编语言实现的,由head.s和head-common.s两个文件组成。
Head.s 是内核的入口文件, 在head.s的末尾处 #i nclude "head-common.S"。 经过一系列的初始化后,跳转到linux-2.6.18.8/init/main.c中的start_kernel()函数中,开始内核的基本初始化过程。

/linux-2.6.18.8/init
[root@localhost init]# tree
|-- Kconfig
|-- Makefile
|-- calibrate.c
|-- do_mounts.c
|-- do_mounts.h
|-- do_mounts_initrd.c
|-- do_mounts_md.c
|-- do_mounts_rd.c
|-- initramfs.c
|-- main.c
`-- version.c
(4)/linux-2.6.18.8/arch/arm/mach-s3c2410
[root@localhost mach-s3c2410]# tree -L 1
|-- Kconfig
|-- Makefile
|-- Makefile.boot
|-- bast-irq.c
|-- bast.h
|-- clock.c
|-- clock.h
|-- common-smdk.c
|-- common-smdk.h
|-- cpu.c
|-- cpu.h
|-- devs.c
|-- devs.h
|-- dma.c
|-- gpio.c
|-- irq.c
|-- irq.h
|-- mach-anubis.c
|-- mach-smdk2410.c
|-- pm-simtec.c
|-- pm.c
|-- pm.h
|-- s3c2400-gpio.c
|-- s3c2400.h
|-- s3c2410-clock.c
|-- s3c2410-gpio.c
|-- s3c2410.c
|-- s3c2410.h
|-- sleep.S
|-- time.c
|-- usb-simtec.c
`-- usb-simtec.h
这个目录中的文件都是板级相关的,其中比较重要是如下几个:
linux/arch/arm/mach-s3c2410/cpu.c 
linux/arch/arm/mach-s3c2410/common-smdk.c
linux/arch/arm/mach-s3c2410/devs.c
linux/arch/arm/mach-s3c2410/mach-smdk2410.c
linux/arch/arm/mach-s3c2410/Makefile.boot
linux/arch/arm/mach-s3c2410/s3c2410.c
3. 处理器和设备4. 
这里主要介绍处理器和设备的描述和操作过程。设备描述在linux/arch/arm/mach-s3c2410/devs.c和linux/arch/arm/mach-s3c2410/common-smdk.c中实现。最后以nand flash为例具体介绍。
  4.1. 处理器、设备4.2. 描述
设备描述主要两个结构体完成:struct resource和struct platform_device。
先来看看着两个结构体的定义:
struct resource {
resource_size_t start;
resource_size_t end;
const char *name;
unsigned long flags;
struct resource *parent, *sibling, *child;
};
Resource结构体主要是描述了设备在系统中的起止地址、名称、标志以及为了链式描述方便指向本结构体类型的指针。Resource定义的实例将被添加到platform_device结构体对象中去。
struct platform_device {
const char * name;
u32 id;
struct device dev;
u32 num_resources;
struct resource * resource;
};
Platform_device结构体包括结构体的名称、ID号、平台相关的信息、设备的数目以及上面定义的resource信息。Platform_device结构对象将被直接通过设备操作函数注册导系统中去。具体注册和注销过程在下一节介绍。
4.3. 处理器、设备4.4. 操作
(1) int platform_device_register(struct platform_device * pdev);    注册设备
(2) void platform_device_unregister(struct platform_device * pdev); 注销设备
(3) int platform_add_devices(struct platform_device **devs, int num);添加设备,通过调用上面两个函数实现。
4.5. 添加Nand flash设备4.6. 
下面以nand flash 设备的描述为例,具体介绍下设备的描述和注册过程。
// resource结构体实例s3c_nand_resource 对nand flash 控制器描述,包括控制器的起止地址和标志。
static struct resource s3c_nand_resource[] = {
[0] = {
.start = S3C2410_PA_NAND,
.end   = S3C2410_PA_NAND + S3C24XX_SZ_NAND - 1,
.flags = IORESOURCE_MEM,
}
};
//platform_device结构体实例s3c_device_nand定义了设备的名称、ID号并把resource对象作为其成员之一。
struct platform_device s3c_device_nand = {
.name   = "s3c2410-nand",
.id   = -1,
.num_resources   = ARRAY_SIZE(s3c_nand_resource),
.resource   = s3c_nand_resource,
};
// nand flash 的分区情况,由mtd_partition结构体定义。
static struct mtd_partition smdk_default_nand_part[] = {
[0] = {
.name = "Boot Agent",
.size = SZ_16K,
.offset = 0,
},
[1] = {
.name = "S3C2410 flash partition 1",
.offset = 0,
.size = SZ_2M,
},
[2] = {
.name = "S3C2410 flash partition 2",
.offset = SZ_4M,
.size = SZ_4M,
},
[3] = {
.name = "S3C2410 flash partition 3",
.offset = SZ_8M,
.size = SZ_2M,
},
[4] = {
.name = "S3C2410 flash partition 4",
4、浅谈分析Arm linux 内核移植及系统初始化的过程 
 .offset = SZ_1M * 10,
.size = SZ_4M,
},
[5] = {
.name = "S3C2410 flash partition 5",
.offset = SZ_1M * 14,
.size = SZ_1M * 10,
},
[6] = {
.name = "S3C2410 flash partition 6",
.offset = SZ_1M * 24,
.size = SZ_1M * 24,
},
[7] = {
.name = "S3C2410 flash partition 7",
.offset = SZ_1M * 48,
.size = SZ_16M,
}
};

static struct s3c2410_nand_set smdk_nand_sets[] = {
[0] = {
.name = "NAND",
.nr_chips = 1,
.nr_partitions = ARRAY_SIZE(smdk_default_nand_part),
.partitions = smdk_default_nand_part,
},
};static struct s3c2410_platform_nand smdk_nand_info = {
.tacls = 20,
.twrph0 = 60,
.twrph1 = 20,
.nr_sets = ARRAY_SIZE(smdk_nand_sets),
.sets = smdk_nand_sets,
};
/* devices we initialise */
// 最后将nand flash 设备加入到系统即将注册的设备集合中。 
static struct platform_device __initdata *smdk_devs[] = {
&s3c_device_nand,
&smdk_led4,
&smdk_led5,
&smdk_led6,
&smdk_led7,
};
然后通过smdk_machine_init()函数,调用设备添加函数platform_add_devices(smdk_devs, ARRAY_SIZE(smdk_devs)) 完成设备的注册。具体过程参见系统初始化的相关部分。
5. 系统初始化
5.1. 系统初始化的主干线
Start_kernel() èsetup_arch() èreset_init() è kernel_thread(init …) è init() è do_basic_setup() èdriver_init() è do_initcall()
Start_kernel()函数负责初始化内核各个子系统,最后调用reset_init(),启动一个叫做init的内核线程,继续初始化。Start_kernel()函数在init/main.c中实现。
asmlinkage void __init start_kernel(void)
{
char * command_line;
extern struct kernel_param __start___param[], __stop___param[];
smp_setup_processor_id();
lockdep_init();
local_irq_disable();
early_boot_irqs_off();
early_init_irq_lock_class();
lock_kernel();
boot_cpu_init();
page_address_init();
printk(KERN_NOTICE);
printk(linux_banner);
setup_arch(&command_line); 

5、浅谈分析Arm linux 内核移植及系统初始化的过程
setup_per_cpu_areas();
smp_prepare_boot_cpu(); 
sched_init();
preempt_disable();
build_all_zonelists();
page_alloc_init();
printk(KERN_NOTICE "Kernel command line: %s/n", saved_command_line);
parse_early_param();
parse_args("Booting kernel", command_line, __start___param,
   __stop___param - __start___param,
   &unknown_bootoption);
sort_main_extable();
unwind_init();
trap_init();
rcu_init();
init_IRQ();
pidhash_init();
init_timers();
hrtimers_init();
softirq_init();
timekeeping_init();
time_init();
profile_init();
early_boot_irqs_on();
local_irq_enable();

console_init();
if (panic_later)
panic(panic_later, panic_param);

lockdep_info();

locking_selftest();

#ifdef CONFIG_BLK_DEV_INITRD
if (initrd_start && !initrd_below_start_ok &&
initrd_start < min_low_pfn << PAGE_SHIFT) {
printk(KERN_CRIT "initrd overwritten (0x%08lx < 0x%08lx) - "
 
6、浅谈分析Arm linux 内核移植及系统初始化的过程  咨询QQ:313807838
     "disabling it./n",initrd_start,min_low_pfn << PAGE_SHIFT);
initrd_start = 0;
}
#endif
vfs_caches_init_early();
cpuset_init_early();
mem_init();
kmem_cache_init();
setup_per_cpu_pageset();
numa_policy_init();
if (late_time_init)
late_time_init();
calibrate_delay();
pidmap_init();
pgtable_cache_init();
prio_tree_init();
anon_vma_init();
#ifdef CONFIG_X86
if (efi_enabled)
efi_enter_virtual_mode();
#endif
fork_init(num_physpages);
proc_caches_init();
buffer_init();
unnamed_dev_init();
key_init();
security_init();
vfs_caches_init(num_physpages);
radix_tree_init();
signals_init();
/* rootfs populating might need page-writeback */
page_writeback_init();
#ifdef CONFIG_PROC_FS
proc_root_init();
#endif
cpuset_init();
taskstats_init_early();
delayacct_init();

check_bugs();

acpi_early_init(); /* before LAPIC and SMP init */

/* Do the rest non-__init'ed, we're now alive */
rest_init();
}

分析start_kernel()源码, 其中setup_arch() 和 reset_init()是两个比较关键的函数。下面将具体分析这两个函数。
5.2. setup_arch()函数分析
首先我们来分析下setup_arch()函数。
Setup_arch()函数主要工作是安装cpu和machine,并为start_kernel()后面的初始化函数指针指定值。
其中setup_processor()函数调用linux/arch/arm/kernel/head_common.S 中的lookup_processor_type函数查询处理器的型号并安装。

Setup_machine()函数调用inux/arch/arm/kernel/head_common.S 中的lookup_machine_type(__machine_arch_type)函数根据体系结构号__machine_arch_type,在__arch_info_begin和__arch_info_end段空间查询体系结构。问题是__machine_arch_type是在什么时候赋的初值?__arch_info_begin和__arch_info_end段空间到底放的是什么内容?
__machine_arch_type是一个全局变量,在linux/boot/decompress/misc.c的解压缩函数中得以赋值。
decompress_kernel(ulg output_start, ulg free_mem_ptr_p, ulg free_mem_ptr_end_p, int arch_id)
{
__machine_arch_type = arch_id;
}

__arch_info_begin和__arch_info_end段空间到底放的内容由链接器决定,存放是.arch.info.init段的内容。这个段是通过段属性__attribute__指定的。Grep一下.arch.info.init 得到./include/asm/mach/arch.h:53: __attribute__((__section__(".arch.info.init"))) = {       / 在linux/include/asm-arm/mach/arch.h 中发现MACHINE_START宏定义。

#define MACHINE_START(_type,_name) /
static const struct machine_desc __mach_desc_##_type /
 __attribute_used__ /
 __attribute__((__section__(".arch.info.init"))) = { /
.nr = MACH_TYPE_##_type, /
.name = _name,

#define MACHINE_END /
};

inux/arch/arm/mach-s3c2410/mach-smdk2410.c中对.arch.info.init段的初始化如下。
7、浅谈分析Arm linux 内核移植及系统初始化的过程  
 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 = smdk_machine_init,
.timer = &s3c24xx_timer,
MACHINE_END

由此可见在.arch.info.init段内存放了__desc_mach_desc_SMDK2410结构体。初始化了相应的初始化函数指针。问题又来了, 这些初始化指针函数是什么时候被调用的呢?
分析发现,不一而同。
如s3c24xx_init_irq()函数是通过start_kernel()里的init_IRQ()函数调用init_arch_irq()实现的。因为在MACHINE_START结构体中  .init_irq = s3c24xx_init_irq,而在setup_arch()函数中init_arch_irq = mdesc->init_irq, 所以调用init_arch_irq()就相当于调用了s3c24xx_init_irq()。
又如smdk_machine_init()函数的初始化。在MACHINE_START结构体中,函数指针赋值,.init_machine = smdk_machine_init。而init_machine()函数被linux/arch/arm/kernel/setup.c文件中的customize_machine()函数调用并被arch_initcall(Fn)宏处理,arch_initcall(customize_machine)。 被arch_initcall(Fn)宏处理过函数将linux/init/main.c
do_initcalls()函数调用。 具体参看下边的部分。

void __init setup_arch(char **cmdline_p)
{
struct tag *tags = (struct tag *)&init_tags;
struct machine_desc *mdesc;
char *from = default_command_line;

setup_processor();
mdesc = setup_machine(machine_arch_type);//machine_arch_type =SMDK2410  by edwin
machine_name = mdesc->name;

if (mdesc->soft_reboot)
reboot_setup("s");

if (mdesc->boot_params)
tags = phys_to_virt(mdesc->boot_params);

/*
 * If we have the old style parameters, convert them to
 * a tag list.
 */
if (tags->hdr.tag != ATAG_CORE)
convert_to_tag_list(tags);
if (tags->hdr.tag != ATAG_CORE)
tags = (struct tag *)&init_tags;

if (mdesc->fixup)
mdesc->fixup(mdesc, tags, &from, &meminfo);

if (tags->hdr.tag == ATAG_CORE) {
if (meminfo.nr_banks != 0)
squash_mem_tags(tags);
parse_tags(tags);
}

init_mm.start_code = (unsigned long) &_text;
init_mm.end_code   = (unsigned long) &_etext;
init_mm.end_data   = (unsigned long) &_edata;
init_mm.brk    = (unsigned long) &_end;

memcpy(saved_command_line, from, COMMAND_LINE_SIZE);
  
8、浅谈分析Arm linux 内核移植及系统初始化的过程  咨询QQ:313807838
 saved_command_line[COMMAND_LINE_SIZE-1] = '/0';
parse_cmdline(cmdline_p, from);
paging_init(&meminfo, mdesc);
request_standard_resources(&meminfo, mdesc);

#ifdef CONFIG_SMP
smp_init_cpus();
#endif

cpu_init();

/*
 * Set up various architecture-specific pointers
 */
init_arch_irq = mdesc->init_irq;
system_timer = mdesc->timer;
init_machine = mdesc->init_machine;

#ifdef CONFIG_VT
#if defined(CONFIG_VGA_CONSOLE)
conswitchp = &vga_con;
#elif defined(CONFIG_DUMMY_CONSOLE)
conswitchp = &dummy_con;
#endif
#endif
}
5.3. rest_init()函数分析
下面我们来分析下rest_init()函数。
Start_kernel()函数负责初始化内核各子系统,最后调用reset_init(),启动一个叫做init的内核线程,继续初始化。在init内核线程中,将执行下列init()函数的程序。Init()函数负责完成根文件系统的挂接、初始化设备驱动程序和启动用户空间的init进程等重要工作。

static void noinline rest_init(void)
__releases(kernel_lock)
{
kernel_thread(init, NULL, CLONE_FS | CLONE_SIGHAND);
numa_default_policy();
unlock_kernel();

preempt_enable_no_resched();
schedule();
preempt_disable();

cpu_idle();
}
static int init(void * unused)
{
lock_kernel();
set_cpus_allowed(current, CPU_MASK_ALL);
child_reaper = current;

smp_prepare_cpus(max_cpus);

do_pre_smp_initcalls();

smp_init();
sched_init_smp();
cpuset_init_smp();
populate_rootfs();   //挂接根文件系统
do_basic_setup();   //初始化设备驱动程序
  //启动用户空间的init进程
 
9、浅谈分析Arm linux 内核移植及系统初始化的过程 
if (!ramdisk_execute_command)
ramdisk_execute_command = "/init";

if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0) {
ramdisk_execute_command = NULL;
prepare_namespace();
}

/*
 * Ok, we have completed the initial bootup, and
 * we're essentially up and running. Get rid of the
 * initmem segments and start the user-mode stuff..
 */
free_initmem();
unlock_kernel();
mark_rodata_ro();
system_state = SYSTEM_RUNNING;
numa_default_policy();

if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0)
printk(KERN_WARNING "Warning: unable to open an initial console./n");

(void) sys_dup(0);
(void) sys_dup(0);

if (ramdisk_execute_command) {
run_init_process(ramdisk_execute_command);
printk(KERN_WARNING "Failed to execute %s/n",
ramdisk_execute_command);
}
if (execute_command) {
run_init_process(execute_command);
printk(KERN_WARNING "Failed to execute %s.  Attempting "
"defaults.../n", execute_command);
}
run_init_process("/sbin/init");
run_init_process("/etc/init");
run_init_process("/bin/init");
run_init_process("/bin/sh");

panic("No init found.  Try passing init= option to kernel.");
}

5.3.1. 挂接根文件系统
Linux/init/ramfs.c
void __init populate_rootfs(void)
{
char *err = unpack_to_rootfs(__initramfs_start,
 __initramfs_end - __initramfs_start, 0);
if (err)
panic(err);
#ifdef CONFIG_BLK_DEV_INITRD
if (initrd_start) {
#ifdef CONFIG_BLK_DEV_RAM
int fd;
printk(KERN_INFO "checking if image is initramfs...");
err = unpack_to_rootfs((char *)initrd_start,
initrd_end - initrd_start, 1);
if (!err) {
printk(" it is/n");
unpack_to_rootfs((char *)initrd_start,
initrd_end - initrd_start, 0);
free_initrd();
return;
}

10、浅谈分析Arm linux 内核移植及系统初始化的过程  咨询QQ:313807838 
 fd = sys_open("/initrd.image", O_WRONLY|O_CREAT, 0700);
if (fd >= 0) {
sys_write(fd, (char *)initrd_start,
initrd_end - initrd_start);
sys_close(fd);
free_initrd();
}
#else
printk(KERN_INFO "Unpacking initramfs...");
err = unpack_to_rootfs((char *)initrd_start,
initrd_end - initrd_start, 0);
if (err)
panic(err);
printk(" done/n");
free_initrd();
#endif
}
#endif
}
5.3.2. 初始化设备5.3.3. 驱动程序
linux/init/main.c
static void __init do_basic_setup(void)
{
/* drivers will send hotplug events */
init_workqueues();
usermodehelper_init();
driver_init();   /* 初始化驱动程序模型。调用驱动初始化函数初始化子系统。 */

#ifdef CONFIG_SYSCTL
sysctl_init();
#endif

do_initcalls();
}
linux/init/main.c
extern initcall_t __initcall_start[], __initcall_end[];

static void __init do_initcalls(void)
{
initcall_t *call;
int count = preempt_count();

for (call = __initcall_start; call < __initcall_end; call++) {
char *msg = NULL;
char msgbuf[40];
int result;

if (initcall_debug) {
printk("Calling initcall 0x%p", *call);
print_fn_deor_symbol(": %s()",
(unsigned long) *call);
printk("/n");
}

result = (*call)();

……
……
……
}

/* Make sure there is no pending stuff from the initcall sequence */
flush_scheduled_work();
}
分析上面一段代码可以看出,设备的初始化是通过do_basic_setup()函数调用do_initcalls()函数,实现__initcall_start, __initcall_end段之间的指针函数执行的。而到底是那些驱动函数怎么会被集中到这个段内的呢?我们知道系统内存空间的分配是由链接器ld读取链接脚本文件决定。链接器将同样属性的文件组织到相同的段里面去,如所有的.text段都被放在一起。在链接脚本里面可以获得某块内存空间的具体地址。我们来看下linux-2.6.18.8/arch/arm/kernel/vmlinux.lds.S文件。由于文件过长,只贴出和__initcall_start, __initcall_end相关的部分。
__initcall_start = .;
*(.initcall1.init)
*(.initcall2.init)
*(.initcall3.init)
*(.initcall4.init)
*(.initcall5.init)
*(.initcall6.init)
*(.initcall7.init)
__initcall_end = .;
从脚本文件中我们可以看出, 在__initcall_start, __initcall_end之间放置的是属行为(.initcall*.init)的函数数据 。在linux/include/linux/init.h文件中可以知道,(.initcall*.init)属性是由__define_initcall(level, fn)宏设定的。
#define __define_initcall(level,fn) /
static initcall_t __initcall_##fn __attribute_used__ /
11、浅谈分析Arm linux 内核移植及系统初始化的过程 咨询QQ:313807838 
 __attribute__((__section__(".initcall" level ".init"))) = fn
#define core_initcall(fn) __define_initcall("1",fn)
#define postcore_initcall(fn) __define_initcall("2",fn)
#define arch_initcall(fn) __define_initcall("3",fn)
#define subsys_initcall(fn) __define_initcall("4",fn)
#define fs_initcall(fn) __define_initcall("5",fn)
#define device_initcall(fn) __define_initcall("6",fn)
#define late_initcall(fn) __define_initcall("7",fn)
#define __initcall(fn)      device_initcall(fn)


内核提供了一个重要的结构体struct machine_desc ,通过machine_desc结构体来控制系统体系架构相关部分的初始化。machine_desc结构体的成员包含了体系架构相关部分的几个最重要的初始化函数,包括map_io,init_irq, init_machine以及phys_io , timer成员等。

machine_desc结构体定义如下:

struct machine_desc {

                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);

        };

machine_desc结构体通过MACHINE_START宏来初始化,这里以s3c2410平台为例:

s3c2410 machine_desc结构体定义如下:

        /* 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_STARTMACHINE_END定义如下:

        #define MACHINE_START(_type,_name) \

        const struct machine_desc __mach_desc_##_type \

          __attribute__((__section__(".arch.info.init"))) = { \

                .nr = MACH_TYPE_##_type, \

                .name = _name,

#define MACHINE_END \

        };

MACHINE_START主要是定义了"struct machine_desc"的类型,放在 section(".arch.info.init"),是初始化数据,其所占用的内存在内核起来之后将会被释放。

这里的map_io成员即内核提供给用户的创建外设I/O资源到内核虚拟地址静态映射表的接口函数。

map_io成员函数会在系统初始化过程中被调用,流程如下:

        start_kernel -> setup_arch() --> paging_init()中被调用

        struct machine_desc 结构体的各个成员函数在不同时期被调用:

        1. .init_machine 在 arch/arm/kernel/setup.c 中被 customize_machine 调用,放在 arch_initcall( ) 段里面,会自动按顺序被调用(另外博客分析,敬请关注)。

        2. init_irqstart_kernel( ) --> init_IRQ( ) --> init_arch_irq( ) 被调用

        3. map_io 在 setup_arch( ) --> paging_init( )被调用

其他主要都在 setup_arch() 中用到。

用户可以在定义machine_desc结构体时指定map_io的接口函数,我们也正是这样做的。

接下来我们继续分析smdk2410_map_io的执行过程,流程如下:

smdk2410_map_io-> s3c24xx_init_io(smdk2410_iodesc, ARRAY_SIZE(smdk2410_iodesc))

下面来看一下s3c24xx_init_io函数:

void __init s3c24xx_init_io(struct map_desc *mach_desc, int mach_size)

        {

                /* register our io-tables */

                iotable_init(s3c_iodesc, ARRAY_SIZE(s3c_iodesc));

                ……

        }

iotable_init内核提供,定义如下:

/*

        * Create the architecture specific mappings

        */

        void __init iotable_init(struct map_desc *io_desc, int nr)

        {

                int i;

                for (i = 0; i nr; i++)

                create_mapping(io_desc + i);

        }

由上知道,smdk2410_map_io最终调用iotable_init建立映射表。

iotable_init函数的参数有两个:一个是map_desc类型的结构体,另一个是该结构体的数量nr。这里最关键的就是struct map_descmap_desc结构体定义如下:

/* include/asm-arm/mach/map.h */

                struct map_desc {

                unsigned long virtual;

                unsigned long physical;

                unsigned long length;

                unsigned int type;

        };

create_mapping( )函数就是通过map_desc提供的信息创建线性映射表的。

这样的话我们就知道了创建I/O映射表的大致流程为:只要定义相应I/O资源的map_desc结构体,并将该结构体传给iotable_init函数执行,就可以创建相应的I/O资源到内核虚拟地址空间的映射表了。

我们来看看s3c2410是怎么定义map_desc结构体的(即上面iotable_init()函数内的s3c_iodesc)

[arch/arm/mach-s3c2410/cpu.c]

        /* minimal IO mapping */

        static struct map_desc s3c_iodesc[] __initdata = {

                IODESC_ENT(GPIO),

                IODESC_ENT(IRQ),

                IODESC_ENT(MEMCTRL),

                IODESC_ENT(UART)

        };

IODESC_ENT宏如下:

#define IODESC_ENT(x) { (unsigned long)S3C24XX_VA_##x, S3C2410_PA_##x, S3C24XX_SZ_##x, MT_DEVICE }

展开后等价于:

static struct map_desc s3c_iodesc[] __initdata = {

                {

                        .virtual = S3C24XX_VA_GPIO,

                        .physical = S3C24XX_PA_GPIO,

                        .length = S3C24XX_SZ_GPIO,

                        .type = MT_DEVICE

                },

                ……

        };

至此,我们可以比较清晰看到GPIO被静态映射的过程,由于我们在前面的静态映射中已经做好了GPIO的映射,也就是我们写GPIO相关驱动的时候可以如下配置引脚的原因:

        s3c2410_gpio_cfgpinxxx,xxx);

其实,s3c2410_gpio_cfgpin定义如下:

void s3c2410_gpio_cfgpin(unsigned int pin, unsigned int function)

        {

                void __iomem *base = S3C2410_GPIO_BASE(pin);

                unsigned long mask;

                unsigned long con;

                unsigned long flags;

        if (pin < S3C2410_GPIO_BANKB) {

                        mask = 1 << S3C2410_GPIO_OFFSET(pin);

                } else {

                        mask = 3 << S3C2410_GPIO_OFFSET(pin)*2;

                }

        local_irq_save(flags);

        con = __raw_readl(base + 0x00);

                con &= ~mask;

                con |= function;

        __raw_writel(con, base + 0x00);

        local_irq_restore(flags);

        }

其中,比较关键的一个地方:

        void __iomem *base = S3C2410_GPIO_BASE(pin);

        这一行中,S3C2410_GPIO_BASE定义如下:

#define S3C2410_GPIO_BASE(pin) ((((pin) & ~31) >> 1) + S3C24XX_VA_GPIO)

至此,GPIO的静态映射就看得很明白了。

下面来看其他外设的静态映射:

s3c24xx_init_io()函数中,除了iotable_init()以为,还会在最后调用,

        (cpu->map_io)(mach_desc, size);

CPU的这个map_ioarch/arm/mach-s3c2410/cpu.c里面定义如下:

static struct cpu_table cpu_ids[] __initdata = {

                {

                        .idcode = 0x32410000,

                        .idmask = 0xffffffff,

                        .map_io = s3c2410_map_io,

                        .init_clocks = s3c2410_init_clocks,

                        .init_uarts = s3c2410_init_uarts,

                        .init = s3c2410_init,

                        .name = name_s3c2410

                },

                        ...

         }

再查看s3c2410_map_io(),函数代码如下:

void __init s3c2410_map_io(struct map_desc *mach_desc, int mach_size)

        {

                /* register our io-tables */

        iotable_init(s3c2410_iodesc, ARRAY_SIZE(s3c2410_iodesc));

                iotable_init(mach_desc, mach_size);

        }

接下来看结构s3c2410_iodesc [arch/arm/mach-s3c2410/s3c2410.c],代码如下,

/* Initial IO mappings */

        static struct map_desc s3c2410_iodesc[] __initdata = {

                IODESC_ENT(USBHOST),

                IODESC_ENT(USBDEV),

                IODESC_ENT(CLKPWR),

                IODESC_ENT(LCD),

                IODESC_ENT(TIMER),

                IODESC_ENT(ADC),

                IODESC_ENT(WATCHDOG),

        };

赫然发现IODESC_ENT(TIMER)这一行,结合之前GPIO的类似分析,IODESC_ENT宏如下:

        #define IODESC_ENT(x) { (unsigned long)S3C24XX_VA_##x, S3C2410_PA_##x, S3C24XX_SZ_##x, MT_DEVICE }

至此,TIMER, USBHOST,USBDEV,lcd,adc,watchdog等的静态映射都看得很明白了。




gpio使用0~MAX_INT之间的整数标识,linux有一个框架处理gpio,能够使用统一的接口来操作gpio
二 内核中gpio的使用
     1 测试gpio端口是否合法 int gpio_is_valid(int number); 
     2 申请某个gpio端口当然在申请之前需要显示的配置该gpio端口的pinmux
        int gpio_request(unsigned gpio, const char *label)
     3 标记gpio的使用方向包括输入还是输出
       /*成功返回零失败返回负的错误值*/ 
       int gpio_direction_input(unsigned gpio); 
       int gpio_direction_output(unsigned gpio, int value); 
     4 获得gpio引脚的值和设置gpio引脚的值(对于输出)
        int gpio_get_value(unsigned gpio);
        void gpio_set_value(unsigned gpio, int value); 
     5 gpio当作中断口使用
        int gpio_to_irq(unsigned gpio); 
        返回的值即中断编号可以传给request_irq()和free_irq()
        内核通过调用该函数将gpio端口转换为中断,在用户空间也有类似方法
     6 导出gpio端口到用户空间
        int gpio_export(unsigned gpio, bool direction_may_change); 
        内核可以对已经被gpio_request()申请的gpio端口的导出进行明确的管理,
        参数direction_may_change表示用户程序是否允许修改gpio的方向,假如可以
        则参数direction_may_change为真
        /* 撤销GPIO的导出 */ 
        void gpio_unexport(); 
三 用户空间gpio的调用 
          用户空间访问gpio,即通过sysfs接口访问gpio,下面是/sys/class/gpio目录下的三种文件: 
            --export/unexport文件
            --gpioN指代具体的gpio引脚
            --gpio_chipN指代gpio控制器
            必须知道以上接口没有标准device文件和它们的链接。 
 (1) export/unexport文件接口:
               /sys/class/gpio/export,该接口只能写不能读
               用户程序通过写入gpio的编号来向内核申请将某个gpio的控制权导出到用户空间当然前提是没有内核代码申请这个gpio端口
               比如  echo 19 > export 
               上述操作会为19号gpio创建一个节点gpio19,此时/sys/class/gpio目录下边生成一个gpio19的目录
               /sys/class/gpio/unexport和导出的效果相反。 
               比如 echo 19 > unexport
               上述操作将会移除gpio19这个节点。 
 (2) /sys/class/gpio/gpioN
       指代某个具体的gpio端口,里边有如下属性文件
      direction 表示gpio端口的方向,读取结果是in或out。该文件也可以写,写入out 时该gpio设为输出同时电平默认为低。写入low或high则不仅可以
                      设置为输出 还可以设置输出的电平。 当然如果内核不支持或者内核代码不愿意,将不会存在这个属性,比如内核调用了gpio_export(N,0)就
                       表示内核不愿意修改gpio端口方向属性 
      value      表示gpio引脚的电平,0(低电平)1(高电平),如果gpio被配置为输出,这个值是可写的,记住任何非零的值都将输出高电平, 如果某个引脚
                      能并且已经被配置为中断,则可以调用poll(2)函数监听该中断,中断触发后poll(2)函数就会返回。                      
      edge      表示中断的触发方式,edge文件有如下四个值:"none", "rising", "falling","both"。
           none表示引脚为输入,不是中断引脚
           rising表示引脚为中断输入,上升沿触发
           falling表示引脚为中断输入,下降沿触发
           both表示引脚为中断输入,边沿触发
                      这个文件节点只有在引脚被配置为输入引脚的时候才存在。 当值是none时可以通过如下方法将变为中断引脚
                      echo "both" > edge;对于是both,falling还是rising依赖具体硬件的中断的触发方式。此方法即用户态gpio转换为中断引脚的方式
      active_low 不怎么明白,也木有用过                                                                
 (3)/sys/class/gpio/gpiochipN
      gpiochipN表示的就是一个gpio_chip,用来管理和控制一组gpio端口的控制器,该目录下存在一下属性文件: 
      base   和N相同,表示控制器管理的最小的端口编号。 
      lable   诊断使用的标志(并不总是唯一的) 
      ngpio  表示控制器管理的gpio端口数量(端口范围是:N ~ N+ngpio-1) 
四 用户态使用gpio监听中断      
首先需要将该gpio配置为中断
echo  "rising" > /sys/class/gpio/gpio12/edge       
以下是伪代码
int gpio_id;
struct pollfd fds[1];
gpio_fd = open("/sys/class/gpio/gpio12/value",O_RDONLY);
if( gpio_fd == -1 )
   err_print("gpio open");
fds[0].fd = gpio_fd;
fds[0].events  = POLLPRI;
ret = read(gpio_fd,buff,10);
if( ret == -1 )
    err_print("read");
while(1){
     ret = poll(fds,1,-1);
     if( ret == -1 )
         err_print("poll");
       if( fds[0].revents & POLLPRI){
           ret = lseek(gpio_fd,0,SEEK_SET);
           if( ret == -1 )
               err_print("lseek");
           ret = read(gpio_fd,buff,10);
           if( ret == -1 )
               err_print("read");
            /*此时表示已经监听到中断触发了,该干事了*/
            ...............
    }
}
记住使用poll()函数,设置事件监听类型为POLLPRI和POLLERR在poll()返回后,使用lseek()移动到文件开头读取新的值或者关闭它再重新打开读取新值。必须这样做否则poll函数会总是返回

Omap3530 的GPIO中断设置:
1.配置成GPIO,申请GPIO中断
omap_cfg_reg(OMAP3_KBD_GPIO);配置成gpio
if (gpio_request(OMAP3_KBD_GPIO, "kbd7279 IRQ") < 0)
printk(KERN_ERR "Failed to request GPIO%d for kbd IRQ\n");//申请GPIO为中断引脚。
2. 设置中断触发模式
set_irq_type(OMAP_GPIO_IRQ(OMAP3_KBD_GPIO),IRQ_TYPE_EDGE_FALLING);
3.使能中断
enable_irq(gpio_to_irq(OMAP3_KBD_GPIO));
4.申请中断
result = request_irq(OMAP_GPIO_IRQ(OMAP3_KBD_GPIO), &Kbd7279_ISR,0, "Ds7279", NULL)


linux提供一个模型来让驱动统一处理GPIO,即各个板卡都有实现自己的gpio_chip控制模块:request, free, input,output, get,set,irq...然后把控制模块注册到内核中,这时会改变全局gpio数组

gpio_desc[]. 当用户请求gpio时,就会到这个数组中找到,并调用这个GPIO对应的gpio_chip的处理函数。

gpio实现为一组可用的 gpio_chip, 由驱动传入对应 gpio的全局序号 去 request, dataout ,datain, free. 这时会调用gpio_chip中具体的实现。

寄存器读写函数:   __raw_writel()   __raw_writeb()   __raw_readl()   __raw_readb()

//****************linux 中 GPIO模型****************************************//

注册方法:

1struct gpio_chip: 表示一个gpio controller.通过这个结构抽象化所有的 GPIO源,而让板上其它的模块可以用相同的接口调用使用这些GPIO

2: struct gpio_desc: 表示一个gpio口,含对应的 gpio_chip.

3: ARCH_NR_GPIOS:  与板相关的GPIO口数量,即是全局GPIO数组:static struct gpio_desc gpio_desc[ARCH_NR_GPIOS];

4: 注册 gpio_chip时,就是根据 chip 的数据 修改全局 GPIO数组中 gpio_desc 字段(chip, flags)。

//***********************************************//

static struct gpio_desc gpio_desc[ARCH_NR_GPIOS]; //全局的gpio_chip 存放数组

static struct gpio_chip twl_gpiochip = {

 .label   = "twl4030",

 .owner   = THIS_MODULE,

 .request  = twl_request,

 .free   = twl_free,

 .direction_input = twl_direction_in,

 .get   = twl_get,

 .direction_output = twl_direction_out,

 .set   = twl_set,

 .to_irq   = twl_to_irq,

 .can_sleep  = 1,

};

struct gpio_desc {

 struct gpio_chip *chip;

 unsigned long  flags;

/* flag symbols are bit numbers */

#define FLAG_REQUESTED 0

#define FLAG_IS_OUT 1

#define FLAG_RESERVED 2

#define FLAG_EXPORT 3 /* protected by sysfs_lock */

#define FLAG_SYSFS 4 /* exported via /sys/class/gpio/control */

#ifdef CONFIG_DEBUG_FS

 const char  *label;

#endif

};

struct gpio_chip {

 const char  *label;

 struct device  *dev;

 struct module  *owner;

 int   (*request)(struct gpio_chip *chip,

      unsigned offset);

 void   (*free)(struct gpio_chip *chip,

      unsigned offset);

 int   (*direction_input)(struct gpio_chip *chip,

      unsigned offset);

 int   (*get)(struct gpio_chip *chip,

      unsigned offset);

 int   (*direction_output)(struct gpio_chip *chip,

      unsigned offset, int value);

 void   (*set)(struct gpio_chip *chip,

      unsigned offset, int value);

 int   (*to_irq)(struct gpio_chip *chip,

      unsigned offset);

 void   (*dbg_show)(struct seq_file *s,

      struct gpio_chip *chip);

 int   base;

 u16   ngpio;

 unsigned  can_sleep:1;

 unsigned  exported:1;

};



GPIO的驱动主要就是读取GPIO口的状态,或者设置GPIO口的状态。就是这么简单,但是为了能够写好的这个驱动,在LINUX上作了一些软件上的分层。
为了让其它驱动可以方便的操作到GPIO,在LINUX里实现了对GPIO操作的统一接口,这个接口实则上就是GPIO驱动的框架,具体的实现文件为gpiolib.c
 在配置内核的时候,我们必须使用CONFIG_GENERIC_GPIO这个宏来支持GPIO驱动。
 这里我们把目光放到gpiolib.c上,主要对外提供的接口函数,在其头文件gpio.h里可以看到:
 具体的GPIO描述符:
 struct gpio_chip {
int (*request)(struct gpio_chip *chip, unsigned offset);
void (*free)(struct gpio_chip *chip, unsigned offset);
int (*direction_input)(struct gpio_chip *chip, unsignedoffset);
int (*get)(struct gpio_chip *chip, unsigned offset);
int (*direction_output)(struct gpio_chip *chip, unsignedoffset, int value);
int (*set_debounce)(struct gpio_chip *chip, unsigned offset,
unsigneddebounce);
void (*set)(struct gpio_chip *chip, unsigned offset, int value);
int (*to_irq)(struct gpio_chip *chip, unsigned offset);
void (*dbg_show)(struct seq_file *s, struct gpio_chip *chip);
int base;
u16 ngpio;
… ...
};
申请和释放GPIO资源:
externint gpio_request(unsigned gpio, const char *label);
externvoid gpio_free(unsigned gpio);
设置GPIO口方向的操作:
externint gpio_direction_input(unsigned gpio);
externint gpio_direction_output(unsigned gpio, int value);
设置GPIO口高低电平值操作:
externint gpio_get_value_cansleep(unsigned gpio);
externvoid gpio_set_value_cansleep(unsigned gpio, int value)
externint __gpio_get_value(unsigned gpio);
externvoid __gpio_set_value(unsigned gpio, int value);
一全局数组,记录各个GPIO的描述符,即对应gpio_desc结构体,其中gpio_chip指向硬件层的GPIO,flags为一标志位,用来指示当前GPIO是否已经占用,当用gpio_request申请GPIO资源时,flags位就会置位,当调用gpio_free释放GPIO资源时,flags就会清零。label是一个字符串指针,用来作说明。
在软件上,我们首先通过函数gpiochip_add注册一个gpio_chip对应的gpio_desc到全局数组gpio描述符中。其中,一个描述符对应一个GPIO,所以如果我们要使用多个GPIO,那么就在gpio_chip结构体的ngpio指定个数,base为起始的GPIO号。
如果你想使用GPIO驱动,那么在配置内核的时候请把该驱动选上,即定义宏CONFIG_GENERIC_GPIO,然后在你的驱动里加入头文件linux/gpio.h,这样就可以用那些操作函数了。


kernel/arch/arm/include/asm/mach/arch.h文件中

#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             \

};

pxa920的板文件ttc_dkb.c的最后:

MACHINE_START(TTC_DKB, "PXA910-based TTC_DKB Development Platform")

    .phys_io        = APB_PHYS_BASE,

    .boot_params    = 0x00000100,

    .io_pg_offst    = (APB_VIRT_BASE >> 18) & 0xfffc,

    .map_io     = pxa_map_io,

    .init_irq       = pxa910_init_irq,

    .timer          = &pxa910_timer,

    .init_machine   = ttc_dkb_init,

MACHINE_END

将宏展开后得到,

static const struct machine_desc __mach_desc_TTC_DKB __used __attribute__((__section__("arch.info.init"))) = {

    .nr    = MACH_TYPE_TTC_DKB,

    .name    = "PXA910-based TTC_DKB Development Platform",

    .phys_io        = APB_PHYS_BASE,

    .boot_params    = 0x00000100,

    .io_pg_offst    = (APB_VIRT_BASE >> 18) & 0xfffc,

    .map_io     = pxa_map_io,

    .init_irq       = pxa910_init_irq,

    .timer          = &pxa910_timer,

    .init_machine   = ttc_dkb_init,

}

kernel/include/asm-arm/mach-types.h文件中有 “MACH_TYPE_TTC_DKB“ 的定义

#define MACH_TYPE_TTC_DKB              2045

同时在arch/arm/tools/mach-types文件中,也有“MACH_TYPE_TTC_DKB“的配置

  ttc_dkb           MACH_TTC_DKB     TTC_DKB            2045

kernel boot 起来的时候,bootloader(uboot) 会传参数进来,其中包括 Machine Type,然后参考 arch/arm/tools/mach-types 并和 MACHINE_START() 第一个参数“.nr“对上号,即都为2045。因此,哪个MACHINE被初始化是在运行时由传入的参数决定的。

MACHINE_START()的各个成员函数在不同时期被调用:

1. .init_machine 在 arch/arm/kernel/setup.c 中被 customize_machine 调用,放在 arch_initcall() 段里面,会自动按顺序被调用 start_kernel,参考 init/main.c

2. init_irqstart_kernel() --> init_IRQ() --> init_arch_irq() 被调用

3. map_io 在 setup_arch() --> paging_init() --> devicemaps_init(),其他主要都在 setup_arch() 中用到

然后我们看init_irq成员,该成员值是 pxa910_init_irq

该函数定义如下(文件:kernel/arch/arm/mach-mmp/pxa910.c):

void __init pxa910_init_irq(void)

{       

    icu_init_irq();        // 初始化IRQ

    pxa910_init_gpio();        // 初始化GPIO

}

static void __init pxa910_init_gpio(void)

{   

    int i;

    /* enable GPIO clock */

    __raw_writel(APBC_APBCLK | APBC_FNCLK, APBC_PXA910_GPIO);

    

    /* unmask GPIO edge detection for all 4 banks - APMASKx */

    for (i = 0; i < 4; i++)

        __raw_writel(0xffffffff, APMASK(i));

    pxa_init_gpio(IRQ_PXA910_AP_GPIO, 0, 127, NULL);

}

在文件 kernel/arch/arm/plat-pxa/gpio.c中,定义pxa_init_gpio函数

void __init pxa_init_gpio(int mux_irq, int start, int end, set_wake_t fn)

{

    struct pxa_gpio_chip *c;

    int gpio, irq;

    pxa_last_gpio = end;

    

    /* Initialize GPIO chips */

    pxa_init_gpio_chip(end);        // 初始化GPIO

    /* clear all GPIO edge detects */

    for_each_gpio_chip(gpio, c) {

        __raw_writel(0, c->regbase + GFER_OFFSET);

        __raw_writel(0, c->regbase + GRER_OFFSET);

        __raw_writel(~0,c->regbase + GEDR_OFFSET);

    }

    for (irq  = gpio_to_irq(start); irq <= gpio_to_irq(end); irq++) {

        set_irq_chip(irq, &pxa_muxed_gpio_chip);

        set_irq_handler(irq, handle_edge_irq);

        set_irq_flags(irq, IRQF_VALID | IRQF_PROBE);

    }

    /* Install handler for GPIO>=2 edge detect interrupts */

    set_irq_chained_handler(mux_irq, pxa_gpio_demux_handler);

    pxa_muxed_gpio_chip.set_wake = fn;

}

pxa_init_gpio_chip函数中,GPIO chips,如下代码

static int __init pxa_init_gpio_chip(int gpio_end)

{

    int i, gpio, nbanks = gpio_to_bank(gpio_end) + 1;

    struct pxa_gpio_chip *chips;

        

    chips = kzalloc(nbanks * sizeof(struct pxa_gpio_chip), GFP_KERNEL);

        

    for (i = 0, gpio = 0; i < nbanks; i++, gpio += 32) {

        struct gpio_chip *c = &chips[i].chip;

    

        sprintf(chips[i].label, "gpio-%d", i);

        chips[i].regbase = (void __iomem *)GPIO_BANK(i);

        c->base  = gpio;

        c->label = chips[i].label;

        c->direction_input  = pxa_gpio_direction_input;        // 将该成员指向pxa_gpio_direction_input函数

        c->direction_output = pxa_gpio_direction_output;    // 将该成员指向pxa_gpio_direction_output函数

        c->get = pxa_gpio_get;

        c->set = pxa_gpio_set;

        /* number of GPIOs on last bank may be less than 32 */

        c->ngpio = (gpio + 31 > gpio_end) ? (gpio_end - gpio + 1) : 32;

        gpiochip_add(c);

    }

    pxa_gpio_chips = chips;

    return 0;

}

实际上这个注册过程也就是对gpio_chip的初始化过程,有了这个初始化,后续代码对GPIO的操作将直接调用到pxa_gpio_direction_input等函数。

GPIO是与硬件体系密切相关的,linux提供一个模型来让驱动统一处理GPIO,即各个板都有实现自己的gpio_chip控制模块:request, free, input,output, get,set,irq...

然后把控制模块注册到内核中,这时会改变全局gpio数组:gpio_desc[]。当用户请求gpio时,就会到这个数组中找到,并调用这个GPIO对应的gpio_chip的处理函数。

gpio实现为一组可用的 gpio_chip, 由驱动传入对应 gpio的全局序号 去 request, dataout ,datain, free. 这时会调用gpio_chip中具体的实现。

寄存器读写函数:   __raw_writel()   __raw_writeb()   __raw_readl()   __raw_readb() ----这些函数会在特定硬件的具体GPIO操作函数中调用,如pxa_gpio_direction_input

gpio是一组可控件的脚,由多个寄存器同时控制。通过设置对应的寄存器可以达到设置GPIO口对应状态与功能。数据状态,输入输出方向,清零,中断(那个边沿触发), 一般是以组(bank)为一个单元,如920中分为4组。

gpio_direction_output函数调用为例,

该函数定义在kernel/drivers/gpio/gpiolib.c文件中,实现如下:

int gpio_direction_output(unsigned gpio, int value)

{

    unsigned long       flags;

    struct gpio_chip    *chip;

    struct gpio_desc    *desc = &gpio_desc[gpio];

    spin_lock_irqsave(&gpio_lock, flags);

    chip = desc->chip;            // 取出gpio_chip

    gpio -= chip->base;

    status = gpio_ensure_requested(desc, gpio);

    /* now we know the gpio is valid and chip won't vanish */

    spin_unlock_irqrestore(&gpio_lock, flags);

    might_sleep_if(extra_checks && chip->can_sleep);

    status = chip->direction_output(chip, gpio, value);        // 调用该chip的成员direction_output,注意这里的成员在开机初始化时,已经初始为pxa_gpio_direction_output函数

    if (status == 0)

        set_bit(FLAG_IS_OUT, &desc->flags);

}




  为了给不同GPIO控制器提供一个统一的编程接口,内核提供了一个可选择的实现架构。这个架构被称作"gpiolib".在这个架构下,每个GPIO控制器被抽象成“struct gpio_chip",这里GPIO控制器的所有常规信息:

-设定传输方向(输入/输出)的函数

-读写GPIO值的函数

-是否调用可睡眠的函数的flag

-可选择的用来调试的输出(dump method)

-用来诊断的标志

还有一些来自device.platform_data等与体系相关的数据,比如gpio起始号和多少可用的gpio号。

  向内核注册gpio_chip是调用gpiochip_add(),注销时调用gpiochip_remove()

  通常gpio_chip是被包含在一个体系相关的结构体内,这个结构体里有一些与gpio状态相关的成员,比如如何寻址,电源管理等等。

    为了支持gpio实现框架(已经说过,是可选择的),Kconfig里应该选择ARCH_REQUIRE_GPIOLIB 或 ARCH_WANT_OPTIONAL_GPIOLIB,并且让<asm/gpio.h>包含<asm-generic/gpio.h>(只要包含就可以,直接间接无所谓)并且定义三个函数:gpio_get_value(), gpio_set_value(), and gpio_cansleep().通常还需要提供ARCH_NR_GPIOS的值(代表有几个GPIO分组)。

    上面3个函数实际上要调用底层函数,自己实现后就可以用gpio的架构了。

  #define gpio_get_value        __gpio_get_value

  #define gpio_set_value        __gpio_set_value

  #define gpio_cansleep         __gpio_cansleep

     对于SOC,与平台相关的代码为每组gpio注册一个gpio_chip,里面是参照硬件原理图和数据手册定义的。通常在平台初始化时gpio也要初始化,通过调用arch_initcall或者更早的函数,他们也可作为IRQ来用。

     还有对应外部GPIO控制器的描诉,比如I2CSPI扩展器,ASICsFPGAs等,当加载模块时,初始化函数要分配好相应的数据,然后probe()会调用gpiochip_add()注册(没详细看,具体见文档)。

GPIO是与硬件体系密切相关的,linux提供一个模型来让驱动统一处理GPIO,即各个板卡都有实现自己的gpio_chip控制模块:request, free, input,output, get,set,irq... 然后把控制模块注册到内核中,这时会改变全局gpio数组:gpio_desc[]. 

当用户请求gpio时,就会到这个数组中找到,并调用这个GPIO对应的gpio_chip的处理函数。

寄存器读写函数:   __raw_writel()   __raw_writeb()   __raw_readl()   __raw_readb()

gpio是一组可控件的脚,由多个寄存器同时控制。通过设置对应的寄存器可以达到设置GPIO口对应状态与功能。

数据状态,输入输出方向,清零,中断(那个边沿触发), 一般是一组(bank)一组的。

//

注册方法:

1struct gpio_chip: 表示一个gpio controller.通过这个结构抽象化所有的 GPIO源,而让板上其它的模块可以用相同的接口调用使用这些GPIO

2: struct gpio_desc: 表示一个gpio口,含对应的 gpio_chip.

3: ARCH_NR_GPIOS:  与板相关的GPIO口数量,即是全局GPIO数组:static struct gpio_desc gpio_desc[ARCH_NR_GPIOS];

4: 注册 gpio_chip时,就是根据 chip 的数据 修改全局 GPIO数组中 gpio_desc 字段(chip, flags)。

gpiolib架构会在/sys/class/gpio下提供用户编程接口,可以通过这个接口去控制gpio状态。(具体见文档)

struct gpio_desc {

struct gpio_chip *chip;

unsigned long flags;

#define FLAG_REQUESTED 0

#define FLAG_IS_OUT 1

#define FLAG_RESERVED 2

#define FLAG_EXPORT 3

#define FLAG_SYSFS 4

#ifdef CONFIG_DEBUG_FS

const char *label;

#endif

};

struct gpio_chip {

const char *label;

struct device *dev;

struct module *owner;

int (*request)(struct gpio_chip *chip,

unsigned offset);

void (*free)(struct gpio_chip *chip,

unsigned offset);

int (*direction_input)(struct gpio_chip *chip,

unsigned offset);

int (*get)(struct gpio_chip *chip,

unsigned offset);

int (*direction_output)(struct gpio_chip *chip,

unsigned offset, int value);

void (*set)(struct gpio_chip *chip,

unsigned offset, int value);

int (*to_irq)(struct gpio_chip *chip,

unsigned offset);

void (*dbg_show)(struct seq_file *s,

struct gpio_chip *chip);

int base;

u16 ngpio;

unsigned can_sleep:1;

unsigned exported:1;

};





批量初始化方法:

申请:

err = gpio_request_array(leds_gpios, ARRAY_SIZE(leds_gpios));

释放:

gpio_free_array(leds_gpios, ARRAY_SIZE(leds_gpios));

导出gpio到用户空间:int gpio_export(unsigned gpio, bool direction_may_change);

创建一个sysfs连接到已导出的GPIO节点:

int gpio_export_link(struct device *dev, const char *name, unsigned gpio)

取消导出:void gpio_unexport();

Gpio设置中断:

    gpio  --->  irq        int gpio_to_irq(unsigned gpio);

    首先应该设置此gpio为输入状态,然后获取对应的中断号(或错误吗)。返回编号调用:

request_irq()free_irq()

    Irq ---> gpio        int irq_to_gpio(unsigned irq);

    返回gpio编号,再调用gpio_get_value()获取相应的值。(避免使用反向映射,不支持)

----------------------------------------------------------------------------------------    

gpiolib.c gpio框架)   drivers/gpio/gpiolib.c + include/asm-generic/gpio.h [gpio chip接口]

-----------------------------------------------------------------------------------------

gpio_chip作为一个接口负责框架层与控制器层的通讯,主要关注点有:

其申请/释放/方向/获取输入/设置输出/irq/base+ngpio[见第三部分控制器驱动]

在框架层的主要关注点在:

1. 如何分配不同chipgpio

2. 如何管理隶属与不同chipgpio,并反向追溯到chip以调用控制器的具体寄存器操作

3. 统一提前管理了哪些gpio状态以及是否有必要

gpiochip_add()中是对gpio chip的注册,并插入到框架gpio的管理中,

全局变量 static struct gpio_desc gpio_desc[ARCH_NR_GPIOS];

gpio_desc作为整个系统的gpio的管理者,主要包含两个成员:chip 与 flags.

flags为框架层对gpio的整体管理标识,起MASK的作用。[有:是否已申请/是否是输出/是否保留等]

插入的规则实现在:gpiochip_find_base(int ngpio)

从后往前遍历全局gpio desc, 只要是非保留gpio且无宿主chip的连续gpio的空间起址作为base, ngpio则依次向下扩展。

简单的初始化:挂上对应的chip,检查是否有设置输入函数,没有则设置 IS_OUT位到flags.

[其中关于gpio的设备树管理暂时不予关注]

request流程: 检查对应gpioflags是否FLAG_REQUESTED,如果未被request则调用chiprequest接口实现芯片级别的调用。

free流程: 确认已经被REQUESTED, 后启用芯片级的free.

从系统gpio接口传递下来的gpio均是以base为基址,而传递到芯片上都是回归到原始gpio

------------------------------------------------------------------------------------------

SC8810 gpio控制器驱动

-------------------------------------------------------------------------------------------

这里是整个gpio系统的核心,初步总结需要关注以下几点:

chip支持的gpio section如何划分

gpio如何配置使能即芯片如何管理众多gpio口的多个标识位的功能选项

申请/释放/方向/获取输入/设置输出/irq/base+ngpio的处理流程及原理

gpio对应irq号的分配及映射规则

配置一个系统gpio需要的必要步骤

section:(GPIO_BASE:0xE0031000/SPRD_MISC_BASE:0xE0037000)

     {   (GPIO_BASE + 0*0x80),    0x10,    GPIO_SECTION_GPIO    },

     {   (GPIO_BASE + 1*0x80),    0x10,    GPIO_SECTION_GPIO    },

    {   (GPIO_BASE + 2*0x80),    0x10,    GPIO_SECTION_GPIO    },

    {   (GPIO_BASE + 3*0x80),    0x10,    GPIO_SECTION_GPIO    },

    {   (GPIO_BASE + 4*0x80),    0x10,    GPIO_SECTION_GPIO    },

    {   (GPIO_BASE + 5*0x80),    0x10,    GPIO_SECTION_GPIO    },

    {   (GPIO_BASE + 6*0x80),    0x10,    GPIO_SECTION_GPIO    },

    {   (GPIO_BASE + 7*0x80),    0x10,    GPIO_SECTION_GPIO    },

    {   (GPIO_BASE + 8*0x80),    0x10,    GPIO_SECTION_GPIO    },

    {   (SPRD_MISC_BASE + 0x480),   0x10, GPIO_SECTION_GPIO    },

    {   (SPRD_MISC_BASE + 0x4c0),   0xe,  GPIO_SECTION_GPIO    },

当获取一个gpio号后,需要获取的基本信息为:在哪个section/偏移量是多大/是何种芯片gpio

获取方法: 

a.((gpio_id>>4) -1) * 0x80 + (u32) GPIO_BASE;

b.gpio_id & 0xF

c.gpio是在数字芯片上还是模拟芯片上:

#define NR_D_DIE_GPIOS 147

即:芯片上的gpio号小于147即位于数字芯片,否则位于模拟芯片

在一个芯片的整个寄存器内存中,采取以section + 功能的管理方式,也就是说对于同一个gpio的不同功能配置需要通过三个步骤,第一首先

需要找到段寄存器(section的基址);第二步是功能偏移的管理寄存器(功能偏移),第三步是根据段内偏移定位到某一位(bit)来配置。

注:

根据不同类型的gpio类型:

enum gpio_section_type {

    GPIO_SECTION_GPI = 0x0,

    GPIO_SECTION_GPO,

    GPIO_SECTION_GPIO,

    GPIO_SECTION_INVALID

};

其相应的功能偏移页不同。

申请:设置其功能寄存器的GPIO_DMSK功能偏移,在对应段内偏移处置位。

释放:清除对应MASK

输出方向:三个步骤,1.设置GPIO_DIR对应位为12.清除GPIO_INEN对应位;3.设置GPIO_DATA对应位为需要输出的值。

输入方向:第一二步相反,无第三步。

设置及获取值:直接设置/读取GPIO_DATA功能寄存器[需要检查一些相关的方向等信息]

to irq

全局的映射数组来管理所有的gpioirq的映射

static struct gpio_irq_map gpio_irq_table[NR_GPIO_IRQS];

映射规则是:从0-10[仅限映射10个中断号],遍历映射表找到第一个gpio offset相同的表项,返回其对应的irq值。

方向 to gpio 一般kernel不支持使用,但实现原理与上相同。

gpio芯片管理中,最重要的一块就是irq的管理,包括irq的所有属性管理,如:irq屏蔽使能/触发条件等等。

下一篇笔记将详细描述 kernel irq的管理框架以及gpioirq分配规则及触发原理。

原创粉丝点击