2011年6月22日 星期三

mini2440-GPIO驅動相關筆記

GPIO驅動相關筆記

本文來自CSDN博客,轉載請標明出處:http://blog.csdn.net/llxmedici/archive/2011/03/27/6282372.aspx

打算跟著友善之臂的《mini2440 linux移植開發指南》來做個LED驅動,雖然LED的原理簡單得不能再簡單了,但是要把kernel中針對於s3c24**的GPIO的一些資料結構,還有函數搞清楚也不是那麼輕鬆的事,所以本文主要簡單地說明下LED驅動中的相關資料結構以及函數/巨集的定義,並對驅動加以驗證

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

注意:在/arch/arm/mach-s3c2410/include/mach/gpio-fns.h源代碼中有如下說明:
16/* These functions are in the to-be-removed category and it is strongly
17 * encouraged not to use these in new code. They will be marked deprecated
18 * very soon.
19 *
20 * Most of the functionality can be either replaced by the gpiocfg calls
21 * for the s3c platform or by the generic GPIOlib API.
22 *
23 * As of 2.6.35-rc, these will be removed, with the few drivers using them
24 * either replaced or given a wrapper until the calls can be removed.
25*/

該頭檔包括:
static inline void s3c2410_gpio_cfgpin(unsigned int pin, unsigned int cfg)

該函數直接使用
linux/arch/arm/plat-s3c/gpio-config.c中的
int s3c_gpio_cfgpin(unsigned int pin, unsigned int config)
即可

***************************************************************************
首先看一下設備初始化程式:
85 /*
86 * 設備初始化
87 */
88 static int __init dev_init(void)
89 {
90 int ret;
91 int i;
92 for (i = 0; i < 4; i++) {
93 //設置 LED 對應的埠寄存器為輸出(OUTPUT)
94 if (s3c_gpio_cfgpin(led_table[i], led_cfg_table[i])<0)
95 printk(KERN_INFO "config pin %d failed", i);
95 //設置 LED 對應的埠寄存器為低電平輸出,在模組載入> 結束後,四個 LED 應該是全部都是發光
96 狀態
97 s3c2410_gpio_setpin(led_table[i], 0);
98 }
99 ret = misc_register(&misc); //註冊設備
100 printk (DEVICE_NAME"\tinitialized\n"); //列印初始化資訊
101 return ret;
102 }
可以看到,這裏涉及到兩個函數,分別是s3c2410_gpio_cfgpin , s3c2410_gpio_setpin,這兩個函數分別對四個LED進行配置,從函數名來看,cfgpin對引腳寄存器狀態進行配置,而setpin應該是對寄存器資料值進行配置,我們在分析函數之前先弄清楚傳入的參數到底是什麼。

led_table[i]

28 //LED 對應的 GPIO 埠列表

29 static unsigned long led_table [] = {

30 S3C2410_GPB(5),

31 S3C2410_GPB(6),

32 S3C2410_GPB(7),

33 S3C2410_GPB(8),

34 };



這裏S3C2410_GPB巨集定義在mach/gpio-nrs.h中

/* GPIO bank sizes */

#define S3C2410_GPIO_A_NR (32)

#define S3C2410_GPIO_B_NR (32)

#define S3C2410_GPIO_C_NR (32)

#define S3C2410_GPIO_D_NR (32)

#define S3C2410_GPIO_E_NR (32)

#define S3C2410_GPIO_F_NR (32)

#define S3C2410_GPIO_G_NR (32)

#define S3C2410_GPIO_H_NR (32)

#define S3C2410_GPIO_J_NR (32) /* technically 16. */

#define S3C2410_GPIO_K_NR (32) /* technically 16. */

#define S3C2410_GPIO_L_NR (32) /* technically 15. */

#define S3C2410_GPIO_M_NR (32) /* technically 2. */



#if CONFIG_S3C_GPIO_SPACE != 0

#error CONFIG_S3C_GPIO_SPACE cannot be zero at the moment

#endif



#define S3C2410_GPIO_NEXT(__gpio) \

((__gpio##_START) + (__gpio##_NR) + CONFIG_S3C_GPIO_SPACE + 0)

//這裏的CONFIG_S3C_GPIO_SPAC是內核配置選項,在.config中可以找到,我的配置為:

CONFIG_S3C_GPIO_SPACE = 0



enum s3c_gpio_number {

S3C2410_GPIO_A_START = 0,

S3C2410_GPIO_B_START = S3C2410_GPIO_NEXT(S3C2410_GPIO_A),

S3C2410_GPIO_C_START = S3C2410_GPIO_NEXT(S3C2410_GPIO_B),

S3C2410_GPIO_D_START = S3C2410_GPIO_NEXT(S3C2410_GPIO_C),

S3C2410_GPIO_E_START = S3C2410_GPIO_NEXT(S3C2410_GPIO_D),

S3C2410_GPIO_F_START = S3C2410_GPIO_NEXT(S3C2410_GPIO_E),

S3C2410_GPIO_G_START = S3C2410_GPIO_NEXT(S3C2410_GPIO_F),

S3C2410_GPIO_H_START = S3C2410_GPIO_NEXT(S3C2410_GPIO_G),

S3C2410_GPIO_J_START = S3C2410_GPIO_NEXT(S3C2410_GPIO_H),

S3C2410_GPIO_K_START = S3C2410_GPIO_NEXT(S3C2410_GPIO_J),

S3C2410_GPIO_L_START = S3C2410_GPIO_NEXT(S3C2410_GPIO_K),

S3C2410_GPIO_M_START = S3C2410_GPIO_NEXT(S3C2410_GPIO_L),

};



#define S3C2410_GPB(_nr) (S3C2410_GPIO_B_START + (_nr))

因此,以S3C2410_GPB(5)為例,其宏展開為:

S3C2410_GPIO_NEXT(S3C2410_GPIO_A) +5 =>

(S3C2410_GPIO_A_START + S3C2410_GPIO_A_NR + CONFIG_S3C_GPIO_SPACE + 0) + 5 =>

很顯然, S3C2410_GPB(5)就是從GPA的首地址+GPA個數+GPB的offset就是當前GPB的IO偏移量,即

0+32+5=37, 同理

S3C2410_GPB(0) 相當於 32

30 S3C2410_GPB(5) 相當於 37

31 S3C2410_GPB(6) 相當於 38

32 S3C2410_GPB(7) 相當於 39

33 S3C2410_GPB(8) 相當於 40

***************************************************************************
led_cfg_table[i]
36 //LED 對應埠將要輸出的狀態列表
37 static unsigned int led_cfg_table [] = {
38 S3C2410_GPIO_OUTPUT,
39 S3C2410_GPIO_OUTPUT,
40 S3C2410_GPIO_OUTPUT,
41 S3C2410_GPIO_OUTPUT,
42 };
S3C2410_GPIO_OUTPUT定義在mach/regs-gpio.h
#define S3C2410_GPIO_LEAVE (0xFFFFFFFF) // 最後兩位是設置,11表示RESERVE
#define S3C2410_GPIO_INPUT (0xFFFFFFF0) /* not available on A */ // 最後兩位是設置,00表示INPUT
#define S3C2410_GPIO_OUTPUT (0xFFFFFFF1) // 最後兩位是設置,01表示OUTPUT
#define S3C2410_GPIO_IRQ (0xFFFFFFF2) /* not available for all */
#define S3C2410_GPIO_SFN2 (0xFFFFFFF2) /* bank A => addr/cs/nand */
#define S3C2410_GPIO_SFN3 (0xFFFFFFF3) /* not available on A */

***************************************************************************
根據前面的分析,s3c2410傳入了當前GPIO的偏移位址,以及OUTPUT狀態
現在我們深入前面的兩個函數:
定義在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;

if (!chip) return -EINVAL; // 沒找到的話,返回invalid
offset = pin - chip->chip.base; // 否則offset等於該GPIO引腳相對於GPX(0)的偏移量,每個偏移1
s3c_gpio_lock(chip, flags); // 自旋鎖鎖住該GPIO,通過chip指標指向lock,看下面的define和圖
ret = s3c_gpio_do_setcfg(chip, offset, config); //設置該GPIO狀態寄存器的數值為config
s3c_gpio_unlock(chip, flags); // 解鎖

// 自旋鎖操作
/* locking wrappers to deal with multiple access to the same gpio bank */
//#define s3c_gpio_lock(_oc, _fl) spin_lock_irqsave(&(_oc)->lock, _fl)
//#define s3c_gpio_unlock(_oc, _fl) spin_unlock_irqrestore(&(_oc)->lock, _fl)
//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 //
__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; // 將cfg的0,1兩位左移offset
}
con = __raw_readl(reg); // 讀對應的GPXCON值
con &= ~(0x3 << shift); // 將GPXCON(pin)的兩bits請0
con = cfg; // 設置config值

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

return 0;
}

return ret;
} // end s3c_gpio_cfgpin
這裏涉及到了一個重要的資料結構,s3c_gpio_chip,此資料結構比較複雜,我貼出這個資料結構的結構圖一:

這個重要的資料結構中可以記錄每個GPIO所需要的所有資料,後面會遇到的s3c24xx_gpios[]結構體就是該結構體的集合,描述了晶片中所有的GPIO埠,之後我們需要時時回頭看看這個結構。

我們先來看s3c_gpiolib_getchip ,它實現了返回對應pin值的GPIO結構體首指標的功能

#include

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]; //根據偏移,計算出對應pin的GPIO結構體指標

return ((pin - chip->chip.base) < chip->chip.ngpio) ? chip : NULL;// 這裏驗證,如果pin偏移超過了GPIO的個數,說明出錯了,否則就返回該GPIO的結構體指標

}

回想以下之前s3c2410_gpio_cfgpin中,我們傳入的參數是led_table[i]和 led_cfg_table[i],

/* GPIO sizes for various SoCs:

*

* 2442

* 2410 2412 2440 2443 2416

* ---- ---- ---- ---- ----

* A 23 22 25 16 25

* B 11 11 11 11 9

* C 16 15 16 16 16

* D 16 16 16 16 16

* E 16 16 16 16 16

* F 8   8   8   8   8

* G16 16 16 16 8

* H 11 11 9 15 15

* J -- -- 13 16 --

* K -- -- -- -- 16

* L -- -- -- 15 7

* M -- -- -- 2 2

*/

struct s3c_gpio_chip s3c24xx_gpios[] = {

[0] = {

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

//#define S3C2410_GPACON S3C2410_GPIOREG(0x00)

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

},

},

};

***************************************************************************
下面分析第二個函數,先看一下相關結構體

圖二:gpio_desc和gpio_chip結構圖

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_GPIOS在arch/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是自旋鎖,上鎖,保存FLAG在flags變數中
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->request在linux初始化時是沒有指向的,可以見後面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); // 返回對應於pin的gpio_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_chip與gpio_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函數分析:

/**

* gpiochip_add() - register a gpio_chip

* @chip: the chip to register, with chip->base initialized

* Context: potentially before irqs or kmalloc will work

*

* Returns a negative errno if the chip can't be registered, such as

* because the chip->base is invalid or already associated with a

* different chip. Otherwise it returns zero as a success code.

*

* When gpiochip_add() is called very early during boot, so that GPIOs

* can be freely used, the chip->dev device must be registered before

* the gpio framework's arch_initcall(). Otherwise sysfs initialization

* for GPIOs will fail rudely.

*

* If chip->base is negative, this requests dynamic assignment of

* a range of valid GPIOs.

*/

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 (i是gpio_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_desc與s3c_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的資料結構如圖三所示:

/**

* misc_register - register a miscellaneous device

* @misc: device structure

*

* Register a miscellaneous device with the kernel. If the minor

* number is set to %MISC_DYNAMIC_MINOR a minor number is assigned

* and placed in the minor field of the structure. For other cases

* the minor number requested is used.

*

* The structure passed is linked into the kernel and may not be

* destroyed until it has been unregistered.

*

* A zero is returned on success and a negative errno code for

* failure.

*/

int misc_register(struct miscdevice * misc)
{
struct miscdevice *c;
dev_t dev;
int err = 0;

INIT_LIST_HEAD(&misc->list); // 初始化鏈表頭,將misc->list的next和pre都指向自己
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);
1480/**
1481 * device_create - creates a device and registers it with sysfs
1482 * @class: pointer to the struct class that this device should be registered to
1483 * @parent: pointer to the parent struct device of this new device, if any
1484 * @devt: the dev_t for the char device to be added
1485 * @drvdata: the data to be added to the device for callbacks
1486 * @fmt: string for the device's name
1487 *
1488 * This function can be used by char device classes. A struct device
1489 * will be created in sysfs, registered to the specified class.
1490 *
1491 * A "dev" file will be created, showing the dev_t for the device, if
1492 * the dev_t is not 0,0.
1493 * If a pointer to a parent struct device is passed in, the newly created
1494 * struct device will be a child of that device in sysfs.
1495 * The pointer to the struct device will be returned from the call.
1496 * Any further sysfs files that might be required can be created using this
1497 * pointer.
1498 *
1499 * Returns &struct device pointer on success, or ERR_PTR() on error.
1500 *
1501 * Note: the struct class passed to this function must have previously
1502 * been created with a call to class_create().
1503 */
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函數

/**

* misc_deregister - unregister a miscellaneous device

* @misc: device to unregister

*

* Unregister a miscellaneous device that was previously

* successfully registered with misc_register(). Success

* is indicated by a zero return, a negative errno code

* indicates an error.

*/

int misc_deregister(struct miscdevice *misc)

{

int i = DYNAMIC_MINORS - misc->minor - 1;
if (WARN_ON(list_empty(&misc->list))) // 如果該misc->list的next指向自己,則出錯返回

return -EINVAL;
mutex_lock(&misc_mtx); // 上鎖
list_del(&misc->list); // 將misc從misc_list鏈表中刪除
device_destroy(misc_class, MKDEV(MISC_MAJOR, misc->minor)); // 對應device_create!
1524/**
1525 * device_destroy - removes a device that was created with device_create()
1526 * @class: pointer to the struct class that this device was registered with
1527 * @devt: the dev_t of the device that was previously registered
1528 *
1529 * This call unregisters and cleans up a device that was created with a
1530 * call to device_create().
1531 */
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_MAJOR(10)的字元設備,設備操作函數集為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);

***************************************************************************
好,到這裏基本把一些GPIO相關的基本函數和結構體都簡單說明了,雖然還有不少不清楚的地方,但還是有些幫助,文中還有些不清楚的地方還有待以後能一一解決,我會不斷補充!

2011年6月21日 星期二

Embedded Linux 技術與概念解析

參考來源
http://www.jollen.org/consulting/
Embedded Linux技術基於開放源碼的資源,並且已經是當今最重要的嵌入式應用技術之一。Embedded Linux是燒錄在目標裝置上的系統,1個Embedded Linux系統包含Linux kernel與 root filesystem 2大部分,Embedded Linux系統到底包含哪些組成要素構成,本文將由概念的層面進行解析。


本文:

由於目前的目標裝置,都必須嵌入極為複雜的功能,所以「嵌入式作業系統(Embedded system)」成為嵌入式系統不可或缺的要素。由於嵌入式系統是功能導向的系統,因此必須設計、選擇或購買正確(或適合)的目標裝置,才能開始實作並嵌入「嵌入式系統」。因此,嵌入式系統技術是以功能、與目標裝置為分類的1種技術。

例如,與PDA相關的目標裝置(即硬體)、與MP3播放器相關的目標裝置、與3G手機相關的目標裝置...等等;使用這些目標裝置所開發的特定功能系統,便是PDA的嵌入式系統、MP3音樂播放的嵌入式系統、3G手機的嵌入式系統。

Embedded Linux其實並不是1個作業系統,而是代表「應用Linux系統於Embedded system」的名詞。Embedded Linux的技術核心主軸是在研究「如何將Linux系統嵌入至嵌入式目標裝置裡」。

Embedded Linux是基於Linux系統的特殊應用,當然也要符合眾多標準才行。LSB與FHS標準是重要的2大標準,跟隨標準不但可以提供系統間的相容性,也可以提供我們1個Linux系統的建構依據。

※GNU/Linux的2個標準

由 FSG (Free Standards Group) 所主持的 LSB (Linux Standard Base) 專案即是在制定 GNU/Linux 標準。根據LSB標準所發展的GNU/Linux系統,才能提供應用程式最小的可執行環境,並且可在依循LSB標準的Linux distributions上執行無誤。例如,我們可以在符合LSB標準的Red Hat Linux上發展應用程式,只要自行發展的Embedded Linux系統符合LSB標準所訂定的規範,應用程式就可以順利移植到Embedded Linux上執行。

LSB標準提供我們發展Embedded Linux的依據,雖然Embedded Linux系統是最小化的Linux,但因為Embedded Linux是嵌入式系統的軟體平台,所以我們不能任意精簡Linux系統,在精簡的過程中仍要保留最基本的作業系統環境,而LSB的標準正是在制定這些基本的需求。

FHS全名為Filesystem Hierarchy Standard,是定義檔案與目錄標準的文件,FHS的標準,定義了目錄與檔案的擺放位置,而UNIX-like的系統則是根據這個標準,管理整個檔案結構。因此,不管是系統廠商、Linux/UNIX distribution發展者、應用程式作者、套件管理者、系統維護人員都應該要依照FHS的標準來管理UNIX系統的目錄與檔案。

Embedded Linux的特色是大量使用自由軟體、與開放源碼軟體(FOSS- Free & Open Source Softwar)資源,「任何你想要的軟體,幾乎都能在網路上找到自由軟體」已經成為Embedded Linux技術的重要支柱。自由軟體資源包山包海,舉凡應用程式、系統工具、網路工具、程式庫、圖形介面、小型瀏覽器、程式發展工具...等等都能找得到。

※Busybox

Busybox是重要的Embedded Linux工具箱,這個工具箱提供基本的UNIX指令、系統程式(daemon)與開機程序(init process)。Busybox用來建造1個基本、最小化且可開機的Linux系統,由於Busybox裡的指令與工具都經過最小化處理,因此已經是目前主要應用在Embedded Linux實作上的開放源碼專案了。

※Embedded Linux的組成

Embedded Linux平台除了Linux kernel外,還包含共享程式庫(shared library)。shared libraries是Linux kernel的重要支援,並且也是Linux架構裡獨立的1層。在應用程式方面,許多現存的開放源碼專案都可以直接「移植」到ARM9平台。但這裡所指的移植是對原始碼進行跨平台編譯(cross compile),並不是BSP(board support package)的移植。

※跨平台編譯

因為開放源碼開發工具的特性,在應用程式級別的移植工具上,可以有1套比較系統化的方法,也有相關的工具與環境可以使用,目前最熱門的跨平台編譯環境為OpenEmbedded。開放源碼軟體採用GNU Autoconf與GNU Automake來撰寫編譯法則(Makefile),因此實務上,要將應用程式移植到ARM9平台,大部分案例只需要做跨平台編譯即可。要了解如何將原始碼移植到ARM9平台,需要學會GNU Autoconf以及GNU Automake的使用。

※GNU Autoconf

Autoconf是m4巨集的擴充套件,可以用來自動設定軟體套件的原始碼。Autoconf會產生1個協助程式編譯的設定文稿執行檔(configuration script),以方便編譯原始碼前進行系統檢查與設定,使用GNU Autoconf時,必須安裝GNU m4套件。

※GNU Automake

Automake是自動產生Makefile.in的工具,需配合Autoconf使用,以產生可以讓GNU Make自動編譯原始碼的”Makefile”檔案。

※GNU Make

GNU Make會根據“Makefile”來自動編譯程式,而編譯完成的程式為執行檔。GNU Make的重要特點,是沒有特定程式語言限制,甚至可以應用在非程式語言編譯的環境中,例如:系統維護工作與套件安裝,因此GNU Make可以說是系統自動化的好工具。

GNU Make根據“Makefile”檔案裡所定義的規則,執行Unix命令,簡單的Makefile規格,可以利用編輯器手動撰寫,但較複雜且與針對不同平台的設定,則建議採用GNU Autoconf/GNU Automake來產生“Makefile”。當我們能夠產生使用cross toolchain的Makefile時,就可以將套件編譯成ARM9的執行檔。

※ARM 平台的選擇與支援

嵌入式裝置的硬體選擇當然沒有所謂的標準,但若是談論到嵌入式Linux的應用,在平台的選擇上就會有一些考慮。最重要的考慮因素,當然就是處理器對於作業系統的支援,如此一來,沒有MMU(記憶體管理單元)的ARM7平台,就不在主要的選擇範圍內。以下列出幾個目前普遍使用的ARM9應用程式處理器(application processor):

在選擇解決方案時,若是決定採用Linux做為嵌入式作業系統,首先當然就是要確定廠商是否提供完整的BSP。不過,由於Linux是由社群所維護發展,因此,選擇目前Linux kernel內有支援的平台,將會是較好的選擇,這也是為什麼有許多大廠,主動貢獻並提交BSP給kernel.org的原因。

目前在kernel社群比較活躍的ARM9廠商,或是社群主動積極協助維護的SOC平台,像是ATMEL、Samsung與TI OMAP等,這些都是kernel.org的Linux kernel就有支援的處理器,這表示讓Linux支援這些平台的方式也很簡單,就是到kernel.org下載官方的Linux kernel即可。

※Crosstool

針對ARM9或是其它平台的開發,最重要的工具就是Cross Toolchain。Cross Toolchain的製作一直是Embedded Linux開發者的夢靨,大多數人選擇由網路下載現成的開發工具,但經常會遇到缺乏程式庫的編譯錯誤。完整的Cross Toolchain包含1套基本的gcc cross compiler以及其它的應用程式庫;Cross Toolchain是製作基本gcc cross compiler的工具,透過crosstool即可製作ARM9的基本toolchain。

Root Filesystem概念

Root filesystem的建置,即是在建立1個基本的Linux系統(base system),讓kernel在完成開機後,進入user mode執行使用者程式。Root filesystem的建置主要是以Busybox為主,並加入(移植)客製化的開放源碼(open source)與自由軟體(free software)。

因為嵌入式Linux的root filesystem是依照需求加入套件,與桌面環境的Linux distribution不同,因此都是用從頭打造的方式做起。在建立root filesystem時,程式庫相依 (library dependencies) 的議題是相當重要的項目。當root filesystem缺少必要的library時,程式當然無法執行,甚至系統也會無法順利啟動。分析應用程式所需的相依程式庫,觀念如下:

(1)先利用Cross Toolchain的objdump指令觀察ELF格式裡的「NEEDED」項目。

(2)必須再檢查這些library是否相依其它library。

1個基本且可開機的root filesystem,也稱做bootstrap root filesystem,1個可用的bootstrap root filesystem只需要包含busybox與libc即可。傳統的Embedded Linux應用,大多是以NFS的方式來測試目標裝置的完整root filesystem(full root filesystem)。以NFS進行Embedded Linux開發測試,主要是針對目標裝置的full root filesystem做「立即(right now)」的系統執行測試(run-time),免除「不斷打包image file、開機」的惡夢。這是1種流行很久的Embedded Linux系統測試與開發方式,其概念如下:

(1)將target的完整root filesystem(例如ARM9 root filesystem)建置後,存放於host端的某個目錄下,例如/home/rootfs。

(2)為target製作1個「NFS root filesystem」,也就是bootstrap root filesystem+ NFS功能,並使用NFS root filesystem將目標裝置開機。

(3)設定host端為NFS server。

(4)以NFS mount方式將host端上的root filesystem目錄「mount」進來,即可在目標裝置上執行full root filesystem裡的應用程式。

這種方式不但簡單,而且方便,需要的基礎建設如下:

1.目標裝置使用的kernel必須支援NFS。

2.製作bootstrap root filesystem時,需要加入mount指令,並且開啟mount指令的NFS功能。

3.加入NFS functionality至bootstrap root filesystem。

4.設定NFS server。

製作完成的root filesystem必須做打包的動作,將整個root filesystem包裝成1個映像檔(image file)。根據目標裝置的不同,我們可以將映像檔包裝成ROM fs、Compress ROM fs、ext2fs或是compress RAM fs。

※ROM file system

ROM file system(romfs)是1種唯讀的檔案系統,在Embedded Linux裡的主要應用為製作romfs格式的檔案系統映像檔。我們將root filesystem製作成romfs filesystem的image檔。開機後,整個filesystem僅能讀取。要使用romfs filesystem必須將Linux kernel裡的CONFIG_ROMFS_FS功能選項打開。製作ROM fs映像檔所使用的工具為genromfs。

※Compressed ROM file system

Compressed ROM file system(cromfs)即是壓縮過的ROM file system,其製作方式相當簡單,只要使用gzip將ROM file system的映像檔壓縮即可。

※製作ext2fs映像檔

製作ext2fs映像檔的方式有2種。1種是使用dd指令產生1個空白的映像檔,接著再將此映像檔以mkfs.ext2指令格式化成ext2的格式。製作好的空白映像檔再以loopback mount方式掛載到1個目錄下,再將root filesystem整個複製到此目錄下,即可完成ext2fs映像檔的製作。

另外1種建立ext2fs映像檔的方式是使用genext2fs工具,此工具的好處是,當我們需要在root filesystem裡預先建立(pre-built)裝置檔時(device file),只需要編寫1個裝置檔表格,genext2fs工具會在打包映像檔時,自動在root filesystem裡建立裝置檔。

※Initial RAM disk(initrd)

RAM disk是存在於記憶體中的虛擬磁碟,也就是將RAM拿來當成磁碟使用。在Embedded Linux的應用中,我們通常會將ramdisk當成暫存目錄來使用。例如將/dev/ram1附掛到/tmp目錄,以便能讓應用程式存放暫時性檔案。/dev/ram?為ramdisk的device file。由於整個root filesystem是從真正的儲存裝置讀取並載入至ramdisk,因此有1個重要的特性是對file system所做的任何修改,都不會影響到真正root filesystem的內容。

initrd全名為initialize RAM disk,是1個特殊的RAM disk。bootloader會將initrd載至記憶體,Linux kernel則可在/dev/ram0找到initrd。initrd會在Linux kernel開機前就載入,initrd正式的用途是用來存放開機時所需要的驅動程式(因root filesystem尚未mount進來)。在Embedded Linux應用上,我們會利用initrd來存放整個檔案系統(root filesystem),也就是將root filesystem製作成ext2或romfs格式(或其它檔案系統)的映像檔,並在開機時由bootloader載入記憶體,initrd均位於/dev/ram0。要使用RAM disk與initrd,必須將Linux kernel的CONFIG_BLK_DEV_RAM以及CONFIG_BLK_DEV_INITRD)。

※使用initrd做為root filesystem裝置

將initial RAM disk當成root filesystem來使用,是在Embedded Linux應用上是相當常見的技巧,如果我們想將initial RAM disk當成存放root filesystem的裝置來使用,在開機時,只需要配合「root=」的kernel開機參數即可。

※initramfs

Linus本人在Linux 2.6時代所提出的 "initramfs" ,是1種更好的 "root=" 做法。簡單來說,initramfs就是「kernel 2.6 的 initrd」,initramfs是屬於1種compressed ramfs(ram filesystem)的映像檔。

※C程式庫

在C程式庫方面,除了標準的glibc也被廣泛應用在嵌入式系統領域外,也有一些專門針對嵌入式系統應用所發展的C程式庫,像是uClibc以及Diet libc。但是由於現在的ARM9處理器計算效能都很快,平台也多搭載大容量NAND快閃記憶體,所以許多實作都直接使用libc來實作root filesystem。

※Linux驅動程式

由於嵌入式系統整體來看,除了軟體開發外,也包含硬體客制化,因此驅動程式在嵌入式系統技術領域中,佔了舉足輕重的地位。學習驅動程式需要確實瞭解硬體的規格與微處理器架構,並且工程師還要能分得清楚哪些東西是介面(interfacing),也就是與硬體無關的程式(machine-independent);以及哪些是站在第一線做硬體控制的程式(machine-dependent)。各種軟體硬介面與?流排也都要精通。

了解Linux驅動程式的架構,是進入嵌入式Linux領域的重點功課,因為許多針對ARM9平台的驅動程式都是參考框架、或是針對特定開發板的實作,因此必須了解Linux驅動程式的架構,並進行修改,以符合自己的開發板與週邊規格。

Linux驅動程式,採取嚴謹的分層式架構設計(layered architecture),利用分層的架構設計來徹底區分generic device driver(machine independent)與machine dependent driver。

Linux驅動程式透過「註冊」與「回呼」的機制來清楚區分每1層的關係。分層架構的實作必須在下層將自己註冊給上層,上層再回呼下層;上層的驅動程式必須提供註冊函數供下層呼叫,下層驅動程式所使用的註冊函數也將決定自己的上層架構。

與user application如何互動,是撰寫驅動程式時所要考慮的重要一環,因此撰寫驅動程式時,要提供什麼「功能」給應用程式引用,就必須事先定義清楚。Linux的 generic device driver層已經幫我們把這些功能定義清楚了。Linux驅動程式如何透過I/O port或I/O memory來控制裝置,也就是與晶片組的溝通,方式是使用Linux kernel所提供的I/O函數來存取並控制實體硬體裝置。

※Linux驅動程式的裝置檔

Device files是UNIX系統的獨特觀念,在UNIX系統底下我們把外部的周邊裝置均視為1個檔案,並透過此檔案與實體硬體溝通,這樣的檔案就叫做device files或special files。

Device file的major number代表1個特定的裝置,例如major number 1為”null”虛擬裝置,major number定義於kernel文件目錄Documentation/devices.txt。Minor number代表裝置上的子裝置,例如同1個硬碟上的分割區就用不同的major number來代表,但其major number相同。

我們在設計device driver時,會先透過1個“註冊”(register)的動作,將自己註冊到kernel裡,註冊時,我們會指定1個major number參數,以指定此驅動程式所要實作的週邊裝置。當user開啟device file時,kernel便會根據device file的 major number找到對應的驅動程式回應使用者。Minor number則是device driver內部所使用,kernel並不會處理不同的minor number。

※Linux 2.6的kobject模型

Linux 2.6在驅動程式的架構方面,加入kobject的概念。kobject以更有系統、組織的方式維護系統裡的driver(集中式管理),但並非改變現有(kernel 2.4以來)的driver架構。在kobject的模型下,可以看到1個platform driver觀念。所謂「platform driver」就是machine- dependent driver,當驅動程式設計師在kernel 2.6底下實作machine-dependent driver時,就要以platform driver的架構來實作。例如,針對我們的目標裝置進行硬體層的驅動程式撰寫時,就要以platform driver的方式來撰寫,實作上,只是多1個註冊到platform driver層的動作而已。

※Flash裝置的支援

針對嵌入式系統經常使用的快閃記憶體(Flash)儲存裝置,Linux kernel支援JFFS2與NFTL 2個專門針對快閃記億體設計的檔案系統。JFFS2(Journaling Flash File System version 2)是專門針對 NOR 型快閃記憶體所設計的檔案系統。NFTL(NAND Flash Translation Layer)則是專門針對NAND型快閃記憶體設計的檔案系統。

結論

綜合而言,Embedded Linux是1個平台、也是一些工具的集合、也是1個嵌入式軟體的開發環境;實作上,Embedded Linux除了會進行kernel的修改、驅動程式的移植或開發外,也會是系統管理與系統整合的再應用,這是一門集大成的技術,並不只是1個嵌入式作業系統,也不只是1套開發工具。(本文為Jollen’s Consulting, Inc.技術顧問撰寫http://www.jollen.org/consulting/)

參考來源:http://tech.digitimes.com.tw/ShowNews.aspx?zCatId=126&zNotesDocId=0000080936_B9WL69LZUF2L4FY7IZ4C5

2011年6月17日 星期五

Cortex-M3在UCOS2實例演練

目前先將圖片貼上來;爾後再慢慢說明.
實作板子上的週邊功能主畫面

介紹畫面(並加入big5繁體顯示功能)


ADC範例

讀寫sram

實作rtc實鐘(GUI)

UCOS2開始畫面


2011年6月16日 星期四

GCC多版本共存

由于编译linux内核2.6.30时,提示gcc版本过高,网上有资料说需要使用至少gcc-4.1.2,而我使用的是fedora 5的gcc版本是gcc-4.1.0,所以想到使两个gcc版本共存,从网上找到解决方法,网址:http://hi.baidu.com/dos2004/blog/item/8aa64f455eca1023cffca3a8.html


原文如下:
fedora core8原装GCC 4.1.2,编译很多程序都报错(像MICROWIN0.90),需要GCC 3.X
下面介绍一下GCC编译,以及GCC多版本共存。
GCC编译:
1。先下载GCC原码包,gcc-3.4.2-tar.gz,
2。tar -xzvf gcc-3.4.2-tar.gz
3。建立一个目标文件夹以供编译过程用 mkdir gcc-obj(这个目录和GCC-3.4.2在同一级).再建一个程序目录,例如mkdir /usr/local/gcc-3.4.2
4。cd gcc-obj
../gcc-3.4.2/configure --prefix=/usr/local/gcc-3.4.2 --enable-threads=posix --disable-checking --enable--long-long --host=i386-redhat-linux --with-system-zlib --enable-languages=c,c++,java
5。成功以后
make 这个时间比较长,耐心等待.
6。make install 安装成功,这期间需要ROOT权限的.

GCC多版本共存:
vi /etc/profile
在环境变量的最前面加上这么一句:
export PATH=/usr/local/gcc-3.4.2/bin:$PATH
保存退出。再更新一下环境变量:
source /etc/profile
gcc --version GCC版本是不是改变了。。。
如果想要变回原来的版本,只需要把环境变量加的那句注释掉.
#export PATH=/usr/local/gcc-3.4.2/bin:$PATH
自此,GCC又变回了原来的版本.

另外再轉載一篇"如何安裝GCC編譯器"  轉載自:http://ccd9527.blogspot.com/2009/02/gcc.html
目前,GCC可以用來編譯C/C++、FORTRAN、JAVA、OBJC、ADA等語言的程序,可根據需要選擇安裝支持的語言。本文以在Redhat Linux上安裝GCC4.1.2為例(因在項目開發過程中要求使用,沒有用最新的GCC版本),介紹GCC的安裝過程。


安裝之前,系統中必須要有cc或者gcc等編譯器,並且是可用的,或者用環境變量CC指定系統上的編譯器。如果系統上沒有編譯器,不能安裝源 代碼形式的GCC 4.1.2。如果是這種情況,可以在網上找一個與你系統相適應的如RPM等二進制形式的GCC軟件包來安裝使用。本文介紹的是以源代碼形式提供的GCC軟 件包的安裝過程,軟件包本身和其安裝過程同樣適用於其它Linux和Unix系統。

系統上原來的GCC編譯器可能是把gcc等命令文件、庫文件、頭文件等分別存放到系統中的不同目錄下的。與此不同,現在GCC建議我們將一個版 本的GCC安裝在一個單獨的目錄下。這樣做的好處是將來不需要它的時候可以方便地刪除整個目錄即可(因為GCC沒有uninstall功能);缺點是在安 裝完成後要做一些設置工作才能使編譯器工作正常。在本文中採用這個方案安裝GCC 4.1.2,並且在安裝完成後,仍然能夠使用原來低版本的GCC編譯器,即一個系統上可以同時存在並使用多個版本的GCC編譯器。

按照本文提供的步驟和設置選項,即使以前沒有安裝過GCC,也可以在系統上安裝上一個可工作的新版本的GCC編譯器。

1. 下載
在GCC網站上(http://gcc.gnu.org)或者通過網上搜索可以查找到下載資源。目前GCC的最新版本為 4.2.1。可供下載的文件一般有兩種形式:gcc-4.1.2.tar.gz和gcc-4.1.2.tar.bz2,只是壓縮格式不一樣,內容完全一致,下載其中一種即可。


2. 解壓縮
拷貝gcc-4.1.2.tar.bz2(我下載的壓縮文件)到/usr/local/src(根據自己喜好選擇)下,根據壓縮格式,選擇下面相應的一種方式解包(以下的「%」表示命令行提示符):


% tar zxvf gcc-4.1.2.tar.gz
或者
% bzcat gcc-4.1.2.tar.bz2 | tar xvf -

新生成的gcc-4.1.2這個目錄被稱為源目錄,用${srcdir}表示它。以後在出現${srcdir}的地方,應該用真實的路徑來替換它。用pwd命令可以查看當前路徑。

在${srcdir}/INSTALL目錄下有詳細的GCC安裝說明,可用瀏覽器打開index.html閱讀。

3. 建立目標目錄
目標目錄(用${objdir}表示)是用來存放編譯結果的地方。GCC建議編譯後的文件不要放在源目錄${srcdir]中(雖然這樣做也可以),最好單獨存放在另外一個目錄中,而且不能是${srcdir}的子目錄。

例如,可以這樣建立一個叫 /usr/local/gcc-4.1.2的目標目錄:
  % mkdir /usr/local/gcc-4.1.2
  % cd gcc-4.1.2

以下的操作主要是在目標目錄 ${objdir} 下進行。
4. 配置
配置的目的是決定將GCC編譯器安裝到什麼地方(${destdir}),支持什麼語言以及指定其它一些選項等。其中,${destdir}不能與${objdir}或${srcdir}目錄相同。


配置是通過執行${srcdir}下的configure來完成的。其命令格式為(記得用你的真實路徑替換${destdir}):
% ${srcdir}/configure --prefix=${destdir} [其它選項]
例如,如果想將GCC 4.1.2安裝到/usr/local/gcc-4.1.2目錄下,則${destdir}就表示這個路徑。
在我的機器上,我是這樣配置的:


% ../gcc-4.1.2/configure --prefix=/usr/local/gcc-4.1.2 --enable-threads=posix --disable-checking --enable--long-long --host=i386-redhat-linux --with-system-zlib --enable-languages=c,c++,java


將GCC安裝在/usr/local/gcc-4.1.2目錄下,支持C/C++和JAVA語言,其它選項參見GCC提供的幫助說明。

5. 編譯
% make
6. 安裝
執行下面的命令將編譯好的庫文件等拷貝到${destdir}目錄中(根據你設定的路徑,可能需要管理員的權限):
% make install


至此,GCC 4.1.2安裝過程就完成了。

6. 其它設置
  GCC 4.1.2的所有文件,包括命令文件(如gcc、g++)、庫文件等都在${destdir}目錄下分別存放,如命令文件放在bin目錄下、庫文件在 lib下、頭文件在include下等。由於命令文件和庫文件所在的目錄還沒有包含在相應的搜索路徑內,所以必須要作適當的設置之後編譯器才能順利地找到 並使用它們。
6.1 gcc、g++、gcj的設置
要想使用GCC 4.1.2的gcc等命令,簡單的方法就是把它的路徑${destdir}/bin放在環境變量PATH中。我不用這種方式,而是用符號連接的方式實現,這樣做的好處是我仍然可以使用系統上原來的舊版本的GCC編譯器。


首先,查看原來的gcc所在的路徑:
  % which gcc
  在我的系統上,上述命令顯示:/usr/bin/gcc。因此,原來的gcc命令在/usr/bin目錄下。我們可以把GCC 4.1.2中的gcc、g++、gcj等命令在/usr/bin目錄下分別做一個符號連接:
  % cd /usr/bin
  % ln -s ${destdir}/bin/gcc gcc412
  % ln -s ${destdir}/bin/g++ g++412
  % ln -s ${destdir}/bin/gcj gcj412

  這樣,就可以分別使用gcc412、g++412、gcj412來調用GCC 4.1.2的gcc、g++、gcj完成對C、C++、JAVA程序的編譯了。同時,仍然能夠使用舊版本的GCC編譯器中的gcc、g++等命令。


6.2 庫路徑的設置
將${destdir}/lib路徑添加到環境變量LD_LIBRARY_PATH中,例如,如果GCC 4.1.2安裝在/usr/local/gcc-4.1.2目錄下,在RH Linux下可以直接在命令行上執行
% export LD_LIBRARY_PATH=/usr/local/gcc-4.1.2/lib

最好添加到系統的配置文件中,這樣就不必要每次都設置這個環境變量了,在文件$HOME/.bash_profile中添加下面兩句:
LD_LIBRARY_PATH=/usr/local/gcc-4.1.2/lib:$LD_LIBRARY_PATH
export LD_LIBRARY_PATH


重啟系統設置生效,或者執行命令
% source $HOME/.bash_profile


7. 測試
用新的編譯命令(gcc412、g++412等)編譯你以前的C、C++程序,檢驗新安裝的GCC編譯器是否能正常工作。