在http://chxxxyg.blog.163.com/blog/static/150281193201082623428605/看到下面這兩張 畫的真好
2012年7月18日 星期三
I2C簡介(二)
前一篇簡單說明了如果寫I2C device的driver。這篇主要要介紹I2C adapter(controller的Driver)。在這裡我們直接以Tegra3的I2C Controller為例來說明。要注意的是,一個板子可能會有很多controller,而一個controller可能會有多條bus。每一個bus會對應一個adapter。
- Board-Specific Code
Tegra3的board-specific code在arch/arm/mach-tegra/device.c 及arch/arm/mach-tegra/board-cardhu.c中。從source code我們可以看出Tegra 3有五組I2C controller。我們先看device.c的部份,在這裡主要是填寫platform_device的資料如下:
//arch/arm/mach-tegra/device.c static struct resource i2c_resource1[] = { [0] = { .start = INT_I2C, .end = INT_I2C, .flags = IORESOURCE_IRQ, }, [1] = { .start = TEGRA_I2C_BASE, .end = TEGRA_I2C_BASE + TEGRA_I2C_SIZE-1, .flags = IORESOURCE_MEM, }, }; . . . static struct resource i2c_resource5[] = { [0] = { .start = INT_I2C5, .end = INT_I2C5, .flags = IORESOURCE_IRQ, }, [1] = { .start = TEGRA_I2C5_BASE, .end = TEGRA_I2C5_BASE + TEGRA_I2C5_SIZE-1, .flags = IORESOURCE_MEM, }, }; #endif static struct tegra_i2c_platform_data tegra_i2c1_platform_data = { .bus_clk_rate = { 400000 }, }; . . . static struct tegra_i2c_platform_data tegra_dvc_platform_data = { .bus_clk_rate = { 400000 }, }; struct platform_device tegra_i2c_device1 = { .name = "tegra-i2c", .id = 0, .resource = i2c_resource1, .num_resources = ARRAY_SIZE(i2c_resource1), .dev = { .platform_data = &tegra_i2c1_platform_data, }, }; . . . struct platform_device tegra_i2c_device5 = { .name = "tegra-i2c", .id = 4, .resource = i2c_resource5, .num_resources = ARRAY_SIZE(i2c_resource5), .dev = { .platform_data = 0, }, };接著我們繼續看board-cardhu.c。不知道為什麼,在這邊又把platform_data蓋掉@@。
static struct tegra_i2c_platform_data cardhu_i2c1_platform_data = { .adapter_nr = 0, //設定這個controller有幾個bus .bus_count = 1, .bus_clk_rate = { 100000, 0 }, .scl_gpio = {TEGRA_GPIO_PC4, 0}, .sda_gpio = {TEGRA_GPIO_PC5, 0}, .arb_recovery = arb_lost_recovery, }; . . . static struct tegra_i2c_platform_data cardhu_i2c5_platform_data = { .adapter_nr = 4, .bus_count = 1, .bus_clk_rate = { 400000, 0 }, .scl_gpio = {TEGRA_GPIO_PZ6, 0}, .sda_gpio = {TEGRA_GPIO_PZ7, 0}, .arb_recovery = arb_lost_recovery, }; static struct i2c_board_info __initdata cardhu_codec_wm8903_info = { I2C_BOARD_INFO("wm8903", 0x1a), .irq = TEGRA_GPIO_TO_IRQ(TEGRA_GPIO_CDC_IRQ), .platform_data = &cardhu_wm8903_pdata, }; static struct i2c_board_info __initdata cardhu_codec_aic326x_info = { I2C_BOARD_INFO("aic3262-codec", 0x18), .irq = TEGRA_GPIO_TO_IRQ(TEGRA_GPIO_CDC_IRQ), }; static struct i2c_board_info __initdata cardhu_codec_max98095_info = { I2C_BOARD_INFO("max98095", 0x10), .irq = TEGRA_GPIO_TO_IRQ(TEGRA_GPIO_CDC_IRQ), .platform_data = &cardhu_max98095_pdata, }; static void cardhu_i2c_init(void) { tegra_i2c_device1.dev.platform_data = &cardhu_i2c1_platform_data; . . . tegra_i2c_device5.dev.platform_data = &cardhu_i2c5_platform_data; platform_device_register(&tegra_i2c_device5); . . . platform_device_register(&tegra_i2c_device1); }
- I2C Controller Driver
看完了platform_device的宣告後,我們理所當然的去看platform_driver的地方。實作的位置在driver/i2c/busses/i2c-tegra.c。這裡我們從tegra_i2c_init_driver()看起。
static int tegra_i2c_probe(struct platform_device *pdev) { struct tegra_i2c_dev *i2c_dev; struct tegra_i2c_platform_data *plat = pdev->dev.platform_data; struct resource *res; struct resource *iomem; struct clk *div_clk; struct clk *fast_clk = NULL; const unsigned int *prop; void *base; int irq; int nbus; int i = 0; int ret = 0; //取得定義在device.c的platform_device 的resource且flag為IORESOURCE_MEM 的資料 res = platform_get_resource(pdev, IORESOURCE_MEM, 0); //檢查從res->start~res->start+ resource_size(res)是否可用 iomem = request_mem_region(res->start, resource_size(res), pdev->name); //mapping到kernel虛擬空間 base = ioremap(iomem->start, resource_size(iomem)); div_clk = clk_get(&pdev->dev, "i2c-div"); fast_clk = clk_get(&pdev->dev, "i2c-fast"); i2c_dev = kzalloc(sizeof(struct tegra_i2c_dev) + (nbus-1) * sizeof(struct tegra_i2c_bus), GFP_KERNEL); i2c_dev->base = base; i2c_dev->div_clk = div_clk; i2c_dev->fast_clk = fast_clk; i2c_dev->iomem = iomem; i2c_dev->irq = irq; i2c_dev->cont_id = pdev->id; i2c_dev->dev = &pdev->dev; i2c_dev->is_clkon_always = plat->is_clkon_always; i2c_dev->last_bus_clk_rate = 100000; /* default clock rate */ i2c_dev->last_bus_clk_rate = plat->bus_clk_rate[0]; i2c_dev->is_high_speed_enable = plat->is_high_speed_enable; i2c_dev->last_bus_clk_rate = plat->bus_clk_rate[0] ?: 100000; i2c_dev->msgs = NULL; i2c_dev->msgs_num = 0; rt_mutex_init(&i2c_dev->dev_lock); spin_lock_init(&i2c_dev->fifo_lock); i2c_dev->slave_addr = plat->slave_addr; i2c_dev->hs_master_code = plat->hs_master_code; i2c_dev->is_dvc = plat->is_dvc; i2c_dev->arb_recovery = plat->arb_recovery; init_completion(&i2c_dev->msg_complete); if (irq == INT_I2C || irq == INT_I2C2 || irq == INT_I2C3) i2c_dev->is_slave = true; platform_set_drvdata(pdev, i2c_dev); if (i2c_dev->is_clkon_always) tegra_i2c_clock_enable(i2c_dev); ret = tegra_i2c_init(i2c_dev); if (ret) { dev_err(&pdev->dev, "Failed to initialize i2c controller"); goto err_free; } for (i = 0; i < nbus; i++) { struct tegra_i2c_bus *i2c_bus = &i2c_dev->busses[i]; i2c_bus->dev = i2c_dev; i2c_bus->mux = plat->bus_mux[i]; i2c_bus->mux_len = plat->bus_mux_len[i]; i2c_bus->bus_clk_rate = plat->bus_clk_rate[i] ?: 100000; i2c_bus->scl_gpio = plat->scl_gpio[i]; i2c_bus->sda_gpio = plat->sda_gpio[i]; i2c_bus->adapter.dev.of_node = pdev->dev.of_node; i2c_bus->adapter.algo = &tegra_i2c_algo; i2c_set_adapdata(&i2c_bus->adapter, i2c_bus); i2c_bus->adapter.owner = THIS_MODULE; i2c_bus->adapter.class = I2C_CLASS_HWMON; strlcpy(i2c_bus->adapter.name, "Tegra I2C adapter", sizeof(i2c_bus->adapter.name)); i2c_bus->adapter.dev.parent = &pdev->dev; i2c_bus->adapter.nr = plat->adapter_nr + i; //!!將此bus註冊進kernel中 ret = i2c_add_numbered_adapter(&i2c_bus->adapter); //註冊此bus的adapter of_i2c_register_devices(&i2c_bus->adapter); i2c_dev->bus_count++; } return 0; ... } static struct platform_driver tegra_i2c_driver = { .probe = tegra_i2c_probe, .remove = tegra_i2c_remove, .driver = { .name = "tegra-i2c", .owner = THIS_MODULE, .of_match_table = tegra_i2c_of_match, .pm = TEGRA_I2C_DEV_PM_OPS, }, }; static int __init tegra_i2c_init_driver(void) { //註冊tegra_i2c_driver,跳到tegra_i2c_probe繼續看 return platform_driver_register(&tegra_i2c_driver); } subsys_initcall(tegra_i2c_init_driver);
- Reference
I2C簡介(一)
- I2C簡介
I2C 全名是Inter IC,原先是Phillips發展的一個two-wire 的serial bus protocol。他是一種半雙工同步多組設備匯流排。只需要兩條線:串列資料線(SDA)與串列時脈線(SCL or SCK)。I2C 提供兩種定址模式與三種傳輸模式。定址方面分成10-bit(允許連接1024個裝置)與 7-bit (允許連接128個裝置)。傳輸方面則有10kbps 的低速模式、100kbps 的標準模式、及 400kbps 的快速模式。
I2C連接的裝置都是Open Drain的接腳,也就是說導通的時候電位是low,而不導通時為high。I2C通常用在嵌入式系統與不同的裝置溝通。在PC,I2C通常用來與不同的sensor 連結例如取得系統風扇的轉速、CPU的溫度等等。
- I2C in Linux
I2C 在kernel 2.x的時候開始支援。在Linux Kernel中,I2C的code拆成四個部份: I2C core、I2C bus drivers、I2C algorithm drivers 和 I2C chip drivers。
舉一個例子,一個PMU(power management unit)利用I2C與SOC連結。要使PMU能夠正確運作我們必須先撰寫I2C adapter(controller)的driver,讓I2C bus可以正常運作。I2C adapter(controller)可能是外接的chip也可能是在SOC裡頭例如Tegra3擁有五組I2C controller。再來,我們必須撰寫PMU的driver,註冊進正確的I2C bus中。透過adapter(controller)與device溝通。
這一篇的介紹是在於如何撰寫下圖的i2c client driver來控制(R/W)i2c device。
一個板子可能會有很多controller,而一個controller可能會有多條bus。每一個bus會對應一個adapter。
- 撰寫I2C Device Driver
在這裡我們直接以TI的PMU tps6591為例子說明。原始碼一樣可以透過下面 reference中抓取。
我們首先先看看tps6591 board-specific code的地方。我們看cardhu_regulator_init()。
//arch/arm/mach-tegra/board-cardhu-power.c static struct tps6591x_platform_data tps_platform = { //pmu的irq起始編號 .irq_base = TPS6591X_IRQ_BASE, //gpio的起始編號 .gpio_base = TPS6591X_GPIO_BASE, .dev_slp_en = true, .slp_keepon = &tps_slp_keepon, .use_power_off = true, }; static struct i2c_board_info __initdata cardhu_regulators[] = { { //設定pmu的i2c device number I2C_BOARD_INFO("tps6591x", 0x2D), .irq = INT_EXTERNAL_PMU, .platform_data = &tps_platform, }, }; int __init cardhu_regulator_init(void){ ... /**註冊i2c device進list: __i2c_board_list *bus number=4 *device info=cardhu_regulators *#device=1 */ i2c_register_board_info(4, cardhu_regulators, 1); ... }接著在tps6591的driver中,我們從tps6591x_init()看起。
//driver/mfd/tps6591x.c static int __devinit tps6591x_i2c_probe(struct i2c_client *client, const struct i2c_device_id *id) { struct tps6591x_platform_data *pdata = client->dev.platform_data; struct tps6591x *tps6591x; int ret; if (!pdata) { dev_err(&client->dev, "tps6591x requires platform data\n"); return -ENOTSUPP; } ret = i2c_smbus_read_byte_data(client, TPS6591X_VERNUM); if (ret < 0) { dev_err(&client->dev, "Silicon version number read" " failed: %d\n", ret); return -EIO; } dev_info(&client->dev, "VERNUM is %02x\n", ret); //same as dev_set_drvdata(&dev->dev, data); i2c_set_clientdata(client, tps6591x); mutex_init(&tps6591x->lock); ... tps6591x_i2c_client = client; return 0; err_add_devs: if (client->irq) free_irq(client->irq, tps6591x); err_irq_init: kfree(tps6591x); return ret; } static struct i2c_driver tps6591x_driver = { .driver = { .name = "tps6591x", .owner = THIS_MODULE, }, .probe = tps6591x_i2c_probe, .remove = __devexit_p(tps6591x_i2c_remove), #ifdef CONFIG_PM .suspend = tps6591x_i2c_suspend, .resume = tps6591x_i2c_resume, #endif .id_table = tps6591x_id_table, }; static int __init tps6591x_init(void) { /**註冊I2C device driver *將這個driver與bus做配對並呼叫probe */ return i2c_add_driver(&tps6591x_driver); }
- R/W VIA I2C
如何從I2C device讀取資料呢?我們以 tps6591x_read為例:
static inline int __tps6591x_read(struct i2c_client *client, int reg, uint8_t *val) { int ret; //read data from address:reg ret = i2c_smbus_read_byte_data(client, reg); if (ret < 0) { dev_err(&client->dev, "failed reading at 0x%02x\n", reg); return ret; } *val = (uint8_t)ret; return 0; }讀取資料呢?我們以 tps6591x_write為例:
static inline int __tps6591x_write(struct i2c_client *client, int reg, uint8_t val) { int ret; //write value:val to address:reg ret = i2c_smbus_write_byte_data(client, reg, val); if (ret < 0) { dev_err(&client->dev, "failed writing 0x%02x to 0x%02x\n", val, reg); return ret; } return 0; }
其他的 read/write可以參考driver/i2c/i2c-core.c
- Reference
2012年7月11日 星期三
Linux Kernel Portability-Data type & Data Structure in Linux kernel (二)
在上一篇著重於Linux Kernel 的Portability。這一篇將著重於Linux Kernel的Data Structure。這裡要介紹的Data Structure包括了Linked lists、Queues、Maps、及Binary trees。
- Linked List
這裡直接講用法。Linux Kernel的Linked List是Double Linked List,而且List裡面的元素必須是list_head的structure。舉個例好了,我們要做到如下圖(A B C都是struct sample好了):
struct sample{ struct list_head link; vold *content; } struct sample *A,*B,*C;現在我們要把ABC串起來。首先我們建立一個叫list_head的Linked list的變數,並初始化它。常見的方法有兩種,結果是一樣的。
/** *1. LIST_HEAD(name) *2. INIT_LIST_HEAD */ //method 1 static LIST_HEAD(list_head); //method 2 struct list_head list_head; INIT_LIST_HEAD(&list_head);接著假設我們要先加入A和C,利用list_add()將A加入到list_head後面。然後在將C加入到A後面。
//Add A after list_head,list_head is the head of the list //e.g list_head->A list_add(&A->link,&list_head); //Add C after A e.g list_head->A->C list_add(&C->link,&A->link);接著,我們要利用list_add_tail將B加在C之前
//Add B before C e.g list_head->A->B->C list_add_tail(&B->link,&C->link);加完了之後我們要怎麼列出List裡面的項目呢?可以利用list_for_each來達成。
//list_for_each(struct list_head *cursor, struct list_head *list) list_for_each(&A->link,&list_head){ }更多的用法可見<linux/list.h>
- Queue
Linux Kernel的queue叫做kfifo實做在kernel/kfifo.c 宣告在<linux/kfifo.h>。與我們所知的queue相同,kfifo支援enqueue(函式命名成inqueue)跟dequeue。同樣的,kfifo有兩個offset分別是記錄下一個插入位置的enqueue offset跟下一個取出位置的dequeue offset。
接下來介紹kfifo的使用方式分為 1. Declare 2. Enqueue/Dequeue 3. Other operation。
Declare
a. 利用kfifo_alloc() 動態宣告
struct kfifo fifo; int ret; //宣告一個大小為PAGE_SIZE的queue ret = kfifo_alloc(&kifo, PAGE_SIZE, GFP_KERNEL); if (ret) return ret;b. 利用DECLARE_KFIFO(name, size)建立 INIT_KFIFO(name)初始化。
Enqueue/Dequeue
kfifo_in(fifo, buf, n) 將n byte的buf讓入fifo裡。如果fifo free的大小<n 則只會copy free 的size的資料
kfifo_out(fifo, buf, n) copy n byte到buf中。如果fifo的大小<n 則只會copy fifo大小的data
kfifo_out_peek(fifo, buf, n) kfifo_out執行後data就會從fifo中刪除。如果想保留可以使用kfifo_out_peek
Other Operation
kfifo_size(fifo) 取得fifo大小
kfifo_len(fifo) 取得fifo已使用大小
kfifo_avail(fifo) 取得fifo可使用大小
kfifo_is_empty(fifo) 判斷fifo是否為空
kfifo_is_full(fifo) 判斷fifo是否已滿
kfifo_reset(fifo) 清空fifo。
kfifo_free(fifo) free掉fifo
實際的操作範例
struct kfifo fifo; int ret; //宣告一個大小為PAGE_SIZE的queue ret = kfifo_alloc(&kifo, PAGE_SIZE, GFP_KERNEL); if (ret) return ret; int i; //插入32個size為sizeof(int)的[0,31]到fifo中 for (i = 0; i < 32; i++) kfifo_in(fifo, &i; sizeof(i)); unsigned int val; int ret; //從fifo讀取一個integer到val 中。資料不會被刪除 ret = kfifo_out_peek(fifo, &val, sizeof(val)); if (ret != sizeof(val)) return -EINVAL; printk(KERN_INFO “%u\n”, val); /* should print 0 */ /* while there is data in the queue ... */ while (kfifo_avail(fifo)) { unsigned int val; int ret; /* ... read it, one integer at a time */ ret = kfifo_out(fifo, &val, sizeof(val)); if (ret != sizeof(val)) return -EINVAL; printk(KERN_INFO “%u\n”, val); }
- Red-Black Tree
紅黑樹是一種semi-balanced binary tree(自平衡二元樹)。主要有以下六種性質:
- 節點不是紅色就是黑色。
- 根節點是黑色。
- 所有葉子都是黑色(葉子是NIL節點 e.g不包含資料)。
- 每個紅色節點的兩個子節點都是黑色。(從每個葉子到根的所有路徑上不能有兩個連續的紅色節點)
- 從任一節點到其每個葉子的所有最短路徑都包含相同數目的黑色節點。
紅黑樹在Linux Kernel實作的地方是lib/rbtree.c宣告的地方在<linux/rbtree.h>。這裡直接以 driver/media/video/tegra/avp/avp.c裡面的實作來解釋rbtree的使用。avp.c定義了一個tegra_avp_info的struct,以下擷取其實做。要使用rbtree必須要宣告一個rb_root的變數如下:
struct tegra_avp_info { ... struct rb_root endpoints; ... };要開始使用endpoints這個rbtree之前必須給他初始的值"RB_ROOT"如下:
static int tegra_avp_probe(struct platform_device *pdev) { struct *avp; avp = kzalloc(sizeof(struct tegra_avp_info), GFP_KERNEL); ... avp->endpoints = RB_ROOT; ... }Linux Kernel的rbtree不包含插入及搜尋的函式,下面同樣以avp的"插入"來說明。
static struct remote_info *remote_find(struct tegra_avp_info *avp, u32 local_id) { struct rb_node *n = avp->endpoints.rb_node; struct remote_info *rinfo; while (n) { rinfo = rb_entry(n, struct remote_info, rb_node); if (local_id < rinfo->loc_id) n = n->rb_left; else if (local_id > rinfo->loc_id) n = n->rb_right; else return rinfo; } return NULL; }以上例,remote_find()實作搜尋的功能。while loop會traverse整個tree 直到找到相同的id。
static int remote_insert(struct tegra_avp_info *avp, struct remote_info *rinfo) { struct rb_node **p; struct rb_node *parent; struct remote_info *tmp; p = &avp->endpoints.rb_node; parent = NULL; while (*p) { parent = *p; tmp = rb_entry(parent, struct remote_info, rb_node); if (rinfo->loc_id < tmp->loc_id) p = &(*p)->rb_left; else if (rinfo->loc_id > tmp->loc_id) p = &(*p)->rb_right; else { pr_info("%s: avp endpoint id=%x (%s) already exists\n", __func__, rinfo->loc_id, trpc_name(rinfo->trpc_ep)); return -EEXIST; } } rb_link_node(&rinfo->rb_node, parent, p); rb_insert_color(&rinfo->rb_node, &avp->endpoints); rinfo_get(rinfo); return 0; }以上例,while loop會traverse rbtree去尋找適合的頁節點,然後透過rb_link_node()去link parent。接著再透過rb_insert_color()上色。
static void remote_remove(struct tegra_avp_info *avp, struct remote_info *rinfo) { rb_erase(&rinfo->rb_node, &avp->endpoints); rinfo_put(rinfo); }刪除節點很簡單,只要呼叫rb_erase()即可。
- Radix Tree
Radix Tree使用相對於Red Block Tree來說簡單許多。這裡以之前提過得irq實做為例看看Radix Tree怎麼使用。
Radix Tree實做在lib/radix- tree.c,下面以kernel/irqdesc.c來了解。
//Declare a radix tree called irq_desc_tree static RADIX_TREE(irq_desc_tree, GFP_KERNEL); static void irq_insert_desc(unsigned int irq, struct irq_desc *desc) { //Insertinto irq_desc_tree radix_tree_insert(&irq_desc_tree, irq, desc); } struct irq_desc *irq_to_desc(unsigned int irq) { //取得key為irq的value return radix_tree_lookup(&irq_desc_tree, irq); } static void delete_irq_desc(unsigned int irq) { //刪除key為irq的pair radix_tree_delete(&irq_desc_tree, irq); }
- Reference
- Linux Kernel Development Third Edition
Linux Device Drivers, Third Edition
Essential Linux Device Drivers
Writing Portable Device Driver
Trees I: Radix trees
Trees II: red-black trees
2012年7月10日 星期二
Linux Kernel Portability-Data type & Data Structure in Linux kernel (一)
- 前言
Linux作為一個跨平台(處理器)的作業系統必須兼容許多不同的CPU 架構設計。也因此,Linux Kernel裡面有許多是為了Portability而產生的包含衍生的資料型態及提供的資料結構。
為了讓我們的Kernel 的module可以順利在我們的Target上或在各平台上順利執行,我們勢必就需要了解哪些方面會是Platform-Independent,Linux Kernel如何解決這個問題。
- The Size of Data Types
在說明Kernel專有的資料型態前,我們先了解標準C資料型態在各平台的差異。以下圖為例,在常見的平台上,char、short、int、long long大小是一致的。但是long、ptr、word的大小則會因平台不同而有所差別。因此,如何在各平台確切的得到我們希望的變數大小就非常重要了。換句話說,假設我們希望某個變數他是8個bits,他就不會因為平台的不同而變成立如4個bits。
因此,Linux Kernel就定義了幾個標準大小的資料型態在include/linux/types.h。在include/linux/types.h裡他include了asm/types.h。以ARM平台為例,arch/arm/include/asm/types.h include了asm-generic/int-ll64.h。在int-ll64.h就定義了:
u8 unsigned byte (8 bits) u16 unsigned word (16 bits) u32 unsigned 32-bit value u64 unsigned 64-bit value s8 signed byte (8 bits) s16 signed word (16 bits) s32 signed 32-bit value s64 signed 64-bit valueu代表的是unsigned而s則是代表signed分別定義了8~64bits的標準大小型態。假設在user space的程式要使用固定長度的型別,記得前面要加上__例如"__u8"。
然而,C99規範了標準大小的資料型態uintN_t及intN_t對應著uN及sN定義在stdint.h。N代表8、16、 32、64。因此,不管是Kernel Module或是user space 的program特別是如果你的程式是想跨平台(OS)的,就盡量使用C99的資料型態而非Linux Kernel的資料型態。
- Interface-Specific Types
Linux Kernel為了移植性也定義了許多_t結尾的變數在 <include/types.h>。舉例來說常見的size_t、ssize_t、pid_t、time_t等等根據特定意義的資料型態就稱為Interface-Specific Type。以process 的id pid_t為例,在x86來說是 int,但是假設我們直接將例如getpid()的回傳值用 int用 接收,哪天另外一個平台的pid是long long的話勢必我們程式必須作修改才能正確運行。因此,Interface-Specific Type可以避免移植的問題。
- Data Alignment
大多數的CPU都會要求變數擺在特定的記憶體上。例如Intel x86的CPU要求4 byte的integer是放置在4的倍數的記憶體位置。這個就叫做Memory Alignment。但假設不是擺在4的倍數上呢?那就必須要讀取兩次才能得到這個變數。大多數的CPU都可以自己處理這個狀況,但是有些CPU沒辦法處理這個問題,必須要產生exception靠exception handler來處理這個問題,但是這樣效率會非常的慢。有個方法是使用get_unaligned(ptr)取得資料及set_unaligned(val,ptr)寫入資料。例如,在driver/power/bq27x00_battery.c裡面
if (!single) ret = get_unaligned_le16(data); else ret = data[0];如果資料是1個byte就直接讀取否則用get_unaligned_le16()。le代表little-endian。
順帶一提,compiler在做最佳化的時候可能會自動做Memory Aligned的動作。但也許我們定義的資料結構變數間的位址是必須連續的(的動作可能會在變數間填補一些變數讓memory可以對齊),這個時候我們可以利用__attribute__ ((packed))告訴編譯器不要亂動 。
struct{ u16 uid; u64 gid; u16 val0; u32 val1; }__attribute__ ((packed))structA;
- Byte Order
假設有一個4 byte的int不同的CPU可能會有不同的記憶體擺法,例如低位的值先放(Little-Endian)還是大的先放(Big-Endian)?舉例來說,假設有一個整數值是0x12345678 Little-Endian的擺法就會是:
address value 0x00000001 0x78 0x00000002 0x56 0x00000003 0x34 0x00000004 0x12而如果是Big-Endian的話則是:
address value 0x00000001 0x12 0x00000002 0x34 0x00000003 0x56 0x00000004 0x78如果我們要得到Little-Endian順序的值可以使用u32 cpu_to_le32(u32),更多的轉換函式可以在找到。
- Time
HZ定義了一秒鐘有幾次 Interrupt。他可以是100、250、300或是1000。預設的值可能會隨著 Kernel的版本或編譯Kernel 的設定而改變。因此,我們不可以假設HZ==1000(舊版的Kernel預設是1000)。
順帶一提,Tick是HZ的倒數,因此假設HZ=1000則一個tick就是1毫秒。
Jiffies是Linux Kernel用來紀錄系統自開機以來觸發了幾次Interrupt(開機時並非歸0)。Linux Kernelchewing每秒鐘會將jiffies加1HZ次。我們可以利用Linux Kernel定義的macro(timer_after、time_after_eq、time_before和time_before_eq)來判斷時間。因此,假設我們要判斷時間是否過了三秒可以:
//設定時間是現在的jiffies加上3秒的HZ次 unsigned long timeout = jiffies + (3*HZ); while (hwgroup->busy) { ... /*如果迴圈超過三秒(也就是說現在的jiffies數超過三秒前的jiffies+3*HZ次) *則return -EBUSY */ if (time_after(jiffies, timeout)) { return -EBUSY; } ... } return SUCCESS;
- Page Size
不要假設Page Size是4KB。用Linux Kernel提供的PAGE_SIZE 來取得系統的page size及PAGE_SHIFT取得 要左移的bits數達到其memory page的number。
- Reference Linux Kernel Development Third Edition
Linux Device Drivers, Third Edition
Essential Linux Device Drivers
Writing Portable Device Driver
2012年7月5日 星期四
Linux Interrupt 整理 (一)
- 前言
CPU的速度很快,假設每每都必須等待週邊的硬體動作完成CPU才能繼續工作的話,效率未免太低了。因此,我們的系統設計了中斷(Interrupt),當有 event 發生(例如 工作完成)就觸發一個Interrupt告訴CPU,這時候CPU就會"馬上"去處理。這麼一來就可以避免等待時間的浪費。
我們可以針對裝置的Interrupt建立Handler,讓不同的Device Interrupt發生後去執行相對應的"程序" ,這個程序就稱為 Interrupt Service Routine(ISR)。如果沒有相對應的ISR則僅會回覆ACK而不作任何事。
傳統電腦是利用兩個Programmable Interrupt Controller (PIC)提供15組Interrupt Line連接Device和CPU。Device 觸發中斷請求(Interrupt Request)給PIC然後在傳至CPU,也因為數量有限所以很寶貴。現代的設計是使用Advanced Programmable Interrupt Controller(APIC)
在Linux Kernel 中存在一個資料結構叫irq_desc 實作在kernel/irq用來鍊結IRQ及ISR,另外也建立Radix treer叫irq_desc_tree來存放irq_desc結構的變數。
- 建立IRQ
要使用IRQ必須要向核心註冊。註冊的方式則是利用request_irq長相如下:
//include/linux/interrupt.h /** *第一個參數是IRQ的號碼 *第二個參數是ISR的pointer *第三個參數 * IRQF_SHARED Interrupt is shared * IRQF_SAMPLE_RANDOM 代表這個interrupt可以作為系統random的依據(有固定週期的interrupt * 不適合當成random依據) * IRQF_TRIGGER_* Specify active edge(s) or level *第四個參數是顯示在例如/proc/interrupts的名稱 *第五個參數用途可以參考http://www.cnblogs.com/ITmelody/archive/2012/05/12/2497018.html *一般直接填NULL */ request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev) { //request_threaded_irq第三個參數是irq_handler_t thread_fn return request_threaded_irq(irq, handler, NULL, flags, name, dev); }從這裡我們也可以發現其實request_irq()會直接call request_threaded_irq()。因此我們也會看到有人就直接使用request_threaded_irq()。直接舉nVidia的mmc的例子。
//driver/mmc/host/sdhci-tegra.c static irqreturn_t carddetect_irq(int irq, void *data) { ... } static int __devinit sdhci_tegra_probe(struct platform_device *pdev) { struct sdhci_host *host; ... rc = request_threaded_irq(gpio_to_irq(plat->cd_gpio), NULL, carddetect_irq, IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, mmc_hostname(host->mmc), host); ... }上述的例子是利用GPIO的變化來當成Interrupt的條件。IRQ number是利用gpio_to_irq取得對應的IRQ;ISR是carddetect_irq(),觸發的條件是當此GPIO rising/falling都會觸發Interrupt。
當我們remove module的時候,我們可能需要free掉這個IRQ,這個時候就可以呼叫free_irq來達成。
static int __devexit sdhci_tegra_remove(struct platform_device *pdev) { ... if (gpio_is_valid(plat->cd_gpio)) { free_irq(gpio_to_irq(plat->cd_gpio), host); ... } ... }
- ISR Top and Bottom Halves
Interrupt設計的目的就是不希望卡在攏長的處理。因此,收到中斷後,我們也會希望可以快速處理完要作的事情。然而,常常我們需要作的事情就是這麼多無法快速的做完造成系統的遲鈍。因此,Linux Kernel提出了將ISR拆成top half 跟bottom half。當Interrupt發生後一定要馬上作的事情就屬於Top half直接寫在原本的ISR,而可以晚點做的就屬於Bottom half。我們可以利用SoftIRQ、Tasklet或Workqueu來排程bottom half的部份。下表比較了三種的差異。
下面我們分別介紹Tasklet及Workqueue。
Tasklet
Tasklets 與Linux Kernel的timer類似,他們都在Interrupt time(Atomic time)執行而且會執行在schedule他的CPU上。不同於timer,我們無法要求tasklet的task在我們期望的時間執行 。同樣的,我們使用nVidia的mmc driver為例來說明Tasklet。
首先,我們必須先註冊一個tasklet,指定這個tasklet的名字、func及data。
//mmc/host/sdhci.c static void sdhci_tasklet_card(unsigned long param) { ... } int sdhci_add_host(struct sdhci_host *host){ ... /* * Init tasklet. *void tasklet_init(struct tasklet_struct *t, * void (*func)(unsigned long), unsigned long data) *簡單來說就是tasklet_init(name, func,data) */ tasklet_init(&host->card_tasklet, sdhci_tasklet_card, (unsigned long)host); ... }
接著,這個task必須要在呼叫schedule_tasklet之後才會被排程執行。理所當然,我們會在top half執行完之後去啟動bottom half的部份。因此我們回到top half(carddetect_irq())看看我們如何去啟動bottom half。
//driver/mmc/host/sdhci-tegra.c static irqreturn_t carddetect_irq(int irq, void *data) { ... tasklet_schedule(&sdhost->card_tasklet); return IRQ_HANDLED; };
Workqueue
Workqueue的部份則利用omap的mmc driver來舉例。如同tasklet,我們必須利用INIT_WORK(name,func)先註冊一個workqueue。以下面的範例,利用schedule_work()將這個workqueue交給一個叫"event"的公用work thread來執行。
首先我們來看註冊 workqueue的部份。
//driver/mmc/omap_hsmmc.c static void omap_hsmmc_detect(struct work_struct *work) { ... } static int __init omap_hsmmc_probe(struct platform_device *pdev) { ... INIT_WORK(&host->mmc_carddetect_work, omap_hsmmc_detect); ... }而去啟動他的部份則是在top half的omap_hsmmc_cd_handler()則是...
//driver/mmc/omap_hsmmc.c static irqreturn_t omap_hsmmc_cd_handler(int irq, void *dev_id) { struct omap_hsmmc_host *host = (struct omap_hsmmc_host *)dev_id; if (host->suspended) return IRQ_HANDLED; //啟動Workqueue schedule_work(&host->mmc_carddetect_work); return IRQ_HANDLED; }
- Reference
Linux Kernel Development Third Edition
Linux Device Drivers, Third Edition
Intel APIC Architecture
Interrupt_request
Essential Linux Device Drivers
2012年7月4日 星期三
GPIO整理-GPIOLIB (二)
上一篇介紹了GPIO的基礎,在這篇將詳細介紹 GPIOLIB
- 前言
在以前,假設我們chip上面都有提供GPIO。假設我們今天要 call soc上面的GPIO我們可以使用gpio_direction_output()。但是如果要使用audio chip上面的GPIO以下圖為例,我們可能就得呼叫audio_gpio_direction_output() depends on chip 的driver實作。
為什麼呢?因為每個chip如何去控制他們的GPIO作法可能會不一樣,並且要填的memory位址也不一樣。這麼一來會造成什麼現象呢?如果chip一多,要分別控制上面的GPIO 我們可能就會看到一堆 audio_gpio_direction_output、 pmu_gpio_direction_output、 charger_gpio_direction_output。而且,以gpio_direction_output為例,可能每一個driver的實作都會有一些重複的code例如檢查是否valid等等。
因此,GPIOLIB的引入可以解決這個問題。每一個chip都使用gpio_chip結構來表示,每一個chip driver實作可以定義GPIO的個數、號碼及相關的callback function。
以gpio_direction_output為例。當我們呼叫gpio_direction_output(number)之後,gpio_direction_output實做在GPIOLIB中,它會去檢查這個number的GPIO是屬於哪個chip然後去call 該chip的driver的request實作。
- GPIOLIB分析
註冊gpio_chip
我們從nVidia的git抓下來的kernel裡面隨便找driver/gpio/wm8994-gpio.c為例子trace一下。他的probe如下:
static struct gpio_chip template_chip = { .label = "wm8994", .owner = THIS_MODULE, /** *如果執行gpio_direction_input的GPIO number屬於這個chip *則執行wm8994_gpio_direction_in這個callback function *以下同義 */ .direction_input = wm8994_gpio_direction_in, .get = wm8994_gpio_get, .direction_output = wm8994_gpio_direction_out, .set = wm8994_gpio_set, .dbg_show = wm8994_gpio_dbg_show, .can_sleep = 1, }; static int __devinit wm8994_gpio_probe(struct platform_device *pdev) { struct wm8994 *wm8994 = dev_get_drvdata(pdev->dev.parent); //取得platform_device定義的platform_data struct wm8994_pdata *pdata = wm8994->dev->platform_data; struct wm8994_gpio *wm8994_gpio; int ret; wm8994_gpio = kzalloc(sizeof(*wm8994_gpio), GFP_KERNEL); if (wm8994_gpio == NULL) return -ENOMEM; wm8994_gpio->wm8994 = wm8994; /** *我們剛剛有說每一個chip使用gpio_chip結構表示。 *上面宣告了template_chip的gpio_chip結構。 */ wm8994_gpio->gpio_chip = template_chip; wm8994_gpio->gpio_chip.ngpio = WM8994_GPIO_MAX; wm8994_gpio->gpio_chip.dev = &pdev->dev; //取得gpio_base。意思是這個chip的GPIO number從多少開始編號 if (pdata && pdata->gpio_base) wm8994_gpio->gpio_chip.base = pdata->gpio_base; else wm8994_gpio->gpio_chip.base = -1; //將這個gpio_chip 註冊進去。待會將分析gpiochip_add ret = gpiochip_add(&wm8994_gpio->gpio_chip); if (ret < 0) { dev_err(&pdev->dev, "Could not register gpiochip, %d\n", ret); goto err; } platform_set_drvdata(pdev, wm8994_gpio); return ret; err: kfree(wm8994_gpio); return ret; }接著我們看一下gpiochip_add() 做了什麼事。
//擷取gpio_desc的定義。gpio_desc定義了GPIO是屬於哪個chip,他的flag及label。 struct gpio_desc { struct gpio_chip *chip; unsigned long flags; #ifdef CONFIG_DEBUG_FS const char *label; #endif }; //gpio_desc陣列存放著所有gpio的定義 static struct gpio_desc gpio_desc[ARCH_NR_GPIOS]; int gpiochip_add(struct gpio_chip *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); if (base < 0) { status = base; goto unlock; } chip->base = base; } /** *these GPIO numbers must not be managed by another gpio_chip *換句話說就是沒有重複到其他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++) { //將這個chip上的所有GPIO的定義加入gpio_desc gpio_desc[id].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; } } 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; } EXPORT_SYMBOL_GPL(gpiochip_add);到這裡,我們的kernel已經多了wm8994這個chip的GPIO可以使用了。但是從我們呼叫gpio_direction_input是怎麼跑到driver定義的wm8994_gpio_direction_in呢?
GPIOLIB與CHIP DRIVER結合
我們看一下gpio_direction_input() 的code。
int gpio_direction_input(unsigned gpio) { unsigned long flags; struct gpio_chip *chip; struct gpio_desc *desc = &gpio_desc[gpio]; int status = -EINVAL; spin_lock_irqsave(&gpio_lock, flags); //先檢查這個gpio是否valid if (!gpio_is_valid(gpio)) goto fail; //找到這個gpio屬於哪個chip的 chip = desc->chip; //如果chip不存在或這個gpio不能設為input則return fail if (!chip || !chip->get || !chip->direction_input) goto fail; //下面兩行檢查該GPIO的編號是否在該chip的範圍內 gpio -= chip->base; if (gpio >= chip->ngpio) goto fail; status = gpio_ensure_requested(desc, gpio); if (status < 0) goto fail; /* now we know the gpio is valid and chip won't vanish */ spin_unlock_irqrestore(&gpio_lock, flags); might_sleep_if(chip->can_sleep); if (status) { //呼叫chip定義的request檢查是否可用 status = chip->request(chip, gpio); if (status < 0) { pr_debug("GPIO-%d: chip request fail, %d\n", chip->base + gpio, status); /* and it's not available to anyone else ... * gpio_request() is the fully clean solution. */ goto lose; } } //如果是可用的,call chip的direction_input() status = chip->direction_input(chip, gpio); if (status == 0) clear_bit(FLAG_IS_OUT, &desc->flags); trace_gpio_direction(chip->base + gpio, 1, status); lose: return status; fail: spin_unlock_irqrestore(&gpio_lock, flags); if (status) pr_debug("%s: gpio-%d status %d\n", __func__, gpio, status); return status; } EXPORT_SYMBOL_GPL(gpio_direction_input);至此,應該就都串起來了吧!簡單來說就如下圖:
- Reference
- http://www.kernel.org/doc/Documentation/gpio.txt
- http://nv-tegra.nvidia.com/gitweb/
- driver/gpio/gpiolib.c
- driver/gpio/gpio-wm8994.c
訂閱:
文章 (Atom)

.png)



.jpg)
