2014年8月28日 星期四

裝置實例-I2C (2)

    接下來就說明如何實現I2C控制器的驅動,當然也要讓這個裝置符合Linux的裝置架構,所以在說明它之前,要先知道它應該掛在哪個bus下。在嵌入式的系統中,這一類的裝置都是獨立屬於SoC記憶體空間的控制器,不依附像是PCI、USB...之類的匯流排,基於這個背景,Linux於是定義了一個虛擬的匯流排,platform_bus,相對於這個匯流排的裝置為platform_device,而驅動程式為platform_driver。因此在XXX_i2c.c中首要工作就是分別建立platform_device和platform_driver,並且註冊它們。

struct resource XXX_i2c_resource[]= { [0]={ .start = CMIC_I2C_CONFIG_BASE, .end = CMIC_I2C_CONFIG_END, .flags = IORESOURCE_MEM, }, [1]={ .start = CMIC_I2C_COMMAND_BASE, .end = CMIC_I2C_COMMAND_END, .flags = IORESOURCE_MEM, }, }

static struct platform_driver XXX_i2c_drv = { .driver = { .owner = THIS_MODULE, .name = "XXX-i2c", }, .probe = XXX_i2c_probe, .remove = XXX_i2c_remove, }

struct platform_device *XXX_i2c_dev; static int __init XXX_i2c_init(void) { int err = 0; XXX_i2c_dev = platform_device_alloc("XXX-i2c", -1); if (XXX_i2c_dev == NULL) { err = -ENOMEM; goto exit; } err = platform_device_add_resources(XXX_i2c_dev,XXX_i2c_resource, ARRAY_SIZE(XXX_i2c_resource)); if (err) goto exit_put; err = platform_device_add(XXX_i2c_dev); if(err) goto exit_put; err = platform_driver_register(&XXX_i2c_drv); if(err) goto exit_unregister; return 0; exit_unregister: platform_device_unregister(XXX_i2c_dev); exit_put: platform_device_put(XXX_i2c_dev); exit: return err; } static void __exit XXX_i2c_exit(void) { platform_driver_unregister(&XXX_i2c_drv); platform_device_unregister(XXX_i2c_dev); platform_device_put(XXX_i2c_dev); }

看完上面的程式碼,只有宣告platform_driver卻沒有發現platform_device,沒錯! 因為這一個驅動是編寫為一個module的形式,所以在載入函式中,platform_device採用動態分配的方式產生,platform_device裡面有一個resource的欄位,主要是用來描述這個控制器會用到的暫存器位址、io-port位址和中斷編號,建立好platform_device後便調用platform_device_add()來註冊這個platform_device。

    一般來說,platfrom_device都是在BSP套件中實現的,以一個arm架構的平台為例,它會被放在arch/arm/mach-XXX/XXX-mach.c中,以靜態的方式宣告如下,XXX可能是平台的名稱。

struce platform_device XXX_i2c_dev = { .name = "XXX-i2c", .id = -1, .num_resources = ARRAY_SIZE(XXX_i2c_resource), .resource = XXX_i2c_resource, };

一個SoC上通常會有很多個platform_device,所以它們會被歸納成一個陣列的形式,之後再調用platform_add_devices()函式統一註冊。

    接著看到platform_driver,完全就跟我們之前自己定義的test_driver一模一樣,一樣都有probe()和remove()方法,不過probe()方法在這裡除了分配記憶體外,還要請求資源並且把實體位址映射到虛擬的位址,之後才能用映射後的惠只存取各個暫存器,另外就是實現i2c_adapter這個資料結構了。卸載函式除了把裝置和驅動卸載外,不要忘了也要釋放資源!

    裝置和驅動都註冊之後不是一定就可以正常工作了喔,不要忘了bus_type的match()方法,match成功後才會調用驅動的probe()方法,那platform_bus會如何判斷驅動支援的裝置呢?讓我們來看看

static int platform_match(struct device *dev, struct device_driver *drv)
{
struct platform_device *pdev;

pdev = container_of(dev, struct platform_device, dev);
return (strncmp(pdev->name, drv->name, BUS_ID_SIZE) == 0);
}

是的,跟test_bus_match()做法是一樣的,只比較platform_device和platform_driver兩者的名稱。

struct XXX_i2c_adap { struct i2c_adapter adap; struct i2c_msg *msg; unsigned int msg_num; unsigned int msg_idx; unsigned int buf_len; struct resource *ioarea_cfg; struct resource *ioarea_cmd; void __iomem *regs_cfg; void __iomem *regs_cmd; unsigned int delay_time; };

static struct i2c_algorithm XXX_i2c_algo = { .master_xfer = XXX_i2c_xfer, .functionality = XXX_i2c_func, };

static ssize_t i2c_speed_get(struct device *dev, struct device_attribute *attr, char *buf) { struct i2c_adapter *adap = container_of(dev, struct i2c_adapter, dev); struct XXX_i2c_adap *i2c = container_of(adap, struct XXX_i2c_adap, adap); return sprintf(buf,"%s speed: 400KHz\n", i2c->adap.name); } static ssize_t i2c_speed_set(struct device *dev, struct device_attribute *attr, char *buf, size_t count) { struct i2c_adapter *adap = container_of(dev, struct i2c_adapter, dev); struct XXX_i2c_adap *i2c = container_of(adap, struct XXX_i2c_adap, adap); unsigned long tmp; strict_strtoul(buf, 0, &tmp); if (tmp == 400) __raw_writel(HIGH_SPEED, i2c->regs_cfg+XXX_I2C_TIMING); else if (tmp == 100) __raw_writel(NORMAL_SPEED, i2c->regs_cfg+XXX_I2C_TIMING); else { printk("invalid value\n"); return -EINVAL; } return count; } static DEVICE_ATTR(speed, S_IRUGO|S_IWUSR, i2c_speed_get, i2c_speed_set);

static void __inline i2c_adapter_setup(struct XXX_i2c_adap *i2c) { sprintf(i2c->adap.name, "XXX-i2c"); i2c->adap.owner = THIS_MODULE; i2c->adap.algo = &XXX_i2c_algo; i2c->adap.retries = 3; i2c->adap.class = I2C_CLASS_HWMON; i2c->adap.algo_data = i2c; i2c->delay_time = 50; } static int XXX_i2c_probe(struct platform_device *pdev) { int ret=0; unsigned long i2c_cmd = 0x00000000; struct XXX_i2c_adap *i2c; struct resource *res; i2c = kzalloc(sizeof(struct XXX_i2c_adap), GFP_KERNEL); if(i2c == NULL) return -ENOMEM; res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (res == NULL) { ret = -ENOENT; goto free_i2c; } i2c->ioarea_cfg = request_mem_region(res->start, resource_size(res), pdev->name); if (i2c->ioarea_cfg == NULL) { ret = -ENXIO; goto free_i2c; } i2c->regs_cfg = ioremap(res->start, resource_size(res)); if (i2c->regs_cfg == NULL) { ret = -ENXIO; goto release_cfg; } res = platform_get_resource(pdev, IORESOURCE_MEM, 1); if (res == NULL) { ret = -ENOENT; goto unmap_cfg; } i2c->ioarea_cmd = request_mem_region(res->start, resource_size(res), pdev->name); if(i2c->ioarea_cmd == NULL) { printk(KERN_ERR"request mem failed\n"); ret = -ENXIO; goto unmap_cfg; } i2c->regs_cmd = ioremap(res->start, resource_size(res)); if (i2c->regs_cmd == NULL) { ret = -ENXIO; goto release_cmd; } i2c_adapter_setup(i2c); i2c->adap.dev.parent = &pdev->dev; ret = i2c_add_adapter(&i2c->adap); if(ret < 0) goto unmap_cmd; ret = device_create_file(&i2c->adap.dev, &dev_attr_speed); if (ret) goto out; platform_set_drvdata(pdev, i2c); addr = addr << 1; i2c_cmd |= I2C_START; return 0; out: i2c_del_adapter(&i2c->adap); unmap_cmd: iounmap(i2c->regs_cmd); release_cmd: release_mem_region(i2c->ioarea_cmd->start, (i2c->ioarea_cmd->end - i2c->ioarea_cmd->start));
unmap_cfg: iounmap(i2c->regs_cfg); release_cfg: release_mem_region(i2c->ioarea_cfg->start, (i2c->ioarea_cfg->end - i2c->ioarea_cfg->start)); free_i2c: kfree(i2c); return ret; } static int XXX_i2c_remove(struct platform_device *pdev) { struct XXX_i2c_adap *i2c; i2c = platform_get_drvdata(pdev);
device_remove_file(&i2c->adap.dev, &dev_attr_speed); iounmap(i2c->regs_cmd); release_mem_region(i2c->ioarea_cmd->start, (i2c->ioarea_cmd->end - i2c->ioarea_cmd->start)); iounmap(i2c->regs_cfg); release_mem_region(i2c->ioarea_cfg->start, (i2c->ioarea_cfg->end - i2c->ioarea_cfg->start)); i2c_del_adapter(&i2c->adap); kfree(i2c); return 0; }

    上述程式碼中,我們也自己定義的一個資料結構XXX_i2c_adap,當然包含了i2c_adapter結構。i2c_msg結構則是i2c-dev和XXX_i2c這兩層溝通需要傳遞的訊息,結構如下

struct i2c_msg {
__u16 addr;         /* slave address         */
__u16 flags;
__u16 len; /* msg data length */
__u8 *buf; /* pointer to msg data */
};

註解已經解釋的很清楚了,而flag的部分標示了這個msg是讀或是寫,所以讀和寫會是兩個分別的msg,另外還有其他作用,有需要可參考include/linux/uapi/linux/i2c.h檔案。在XXX_i2c_adap還有兩個記錄暫存器base address的欄位,regs_cfg和regs_cmd,之後可以透過這個base address加上每個暫存器的偏移量就可以存去取相對的暫存器了。

    因為我們在resource中宣告了兩塊的記憶體位址,所以在XXX_i2c_probe()函式裡就要分別請求他們的資源並且映射到虛擬的記憶體空間,regs_cfg和regs_cmd就記錄了這兩塊虛擬記憶體的base address,接下來就是建立i2c_adapter結構了,其中比較重要的部分是XXX_i2c_algo結構,其中XXX_i2c_xfer()主要是一些暫存器讀寫的方式和流程,讓實體的SCL和SDA線路上能產生相對應的訊號,master_xfer()這個callback function會在i2c-core.c的i2c_transfer()被呼叫。XXX_i2c_func()記錄這個驅動具備怎樣的功能。完成i2c_adapter結構的建立後就調用i2c_add_adapter()註冊,如果SoC上有多個I2C控制器,那就要依照順序初始化i2c_adapter中的nr欄位,並且改調用i2c_add_numbered_adapter()來註冊。另外還提供了一個speed的屬性檔讓使用者可以設定或是讀取I2C bus的速度,最後把我們自己定義的結構放到platform_device的私有資料欄位去,其實也是調用dev_set_drvdata()

static int __inline is_lastmsg(struct XXX_i2c_adap *i2c) { return i2c->msg_idx == (i2c->msg_num-1); } static int __inline is_lastdata(struct XXX_i2c_adap *i2c) { return i2c->buf_len == 1; } static void __inline clear_event_status(struct XXX_i2c_adap *i2c) { unsigned int event_tmp; event_tmp = __raw_readl(i2c->regs_cmd+XXX_I2C_EVENT_STAT); __raw_writel(event_tmp, i2c->regs_cmd+XXX_I2C_EVENT_STAT); } static int XXX_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num) { int i, ret=0; unsigned long i2c_cmd, i2c_data, i2c_state, i2c_event, tmp; struct i2c_msg *msg = msgs; unsigned short addr; unsigned char *buffer; struct XXX_i2c_adap *i2c = adap->algo_data; clear_event_status(i2c); i2c_cmd = 0x00000000; i2c->msg_num = num; for (i2c->msg_idx=0; i2c->msg_idx < num; i2c->msg_idx++, msg++) { i2c->msg = msg; addr = msg->addr << 1; i2c->buf_len = msg->len; buffer = msg->buf; if (msg->len == 0) { addr |= (msg->flags == I2C_M_RD)?1:0; __raw_writel(addr, i2c->regs_cmd+XXX_I2C_MST_DATA_WR); break; } if (msg->flags & I2C_M_RD) { addr |= 1; i2c_cmd = BLOCK_READ|i2c->buf_len; __raw_writel(addr, i2c->regs_cmd+XXX_I2C_MST_DATA_WR); } else { __raw_writel(addr, i2c->regs_cmd+XXX_I2C_MST_DATA_WR); for (; i2c->buf_len > 0; i2c->buf_len--, buffer++) { if (is_lastmsg(i2c)) { if (is_lastdata(i2c)) { i2c_data = LAST_DATA|*buffer; __raw_writel(i2c_data, i2c->regs_cmd+XXX_I2C_MST_DATA_WR); i2c_cmd = BLOCK_WRITE; } } i2c_data = *buffer; __raw_writel(i2c_data, i2c->regs_cmd+XXX_I2C_MST_DATA_WR); } } } i2c_cmd |= I2C_START; __raw_writel(i2c_cmd, i2c->regs_cmd+XXX_I2C_MST_CMD); i2c_event = __raw_readl(i2c->regs_cmd+XXX_I2C_EVENT_STAT); while (!(i2c_event & START_BUSY_EVENT)) i2c_event = __raw_readl(i2c->regs_cmd+XXX_I2C_EVENT_STAT); __raw_writel(i2c_event, i2c->regs_cmd+XXX_I2C_EVENT_STAT); i2c_state = __raw_readl(i2c->regs_cmd+XXX_I2C_MST_CMD); if (!(i2c_state & I2C_BUSY)) { if (i2c_stat & I2C_LOST_ARB) { printk(KERN_ERR"Lost arbitration\n"); ret = -EAGAIN; } else if (i2c_state & I2C_DEV_NACK) { printk(KERN_ERR"No device acked\n"); ret = -ENODEV; } else if (i2c_state & I2C_REG_NACK) { printk(KERN_ERR"Register not found\n"); ret = -ENODEV; } else { if ((i2c->msg->flags & I2C_M_RD) && msg->len > 0) { for (i=0; i<i2c->msg->len; i++) { tmp = __raw_readl(i2c->regs_cmd+XXX_I2C_MST_DATA_RD); i2c->msg->buf[i] = tmp & 0xFF; } } ret = i2c->msg_idx; } } else { printk(KERN_ERR"Reset I2C controller\n"); __raw_writel(I2C_RESET, i2c->regs_cfg+XXX_I2C_CONFIG); mdelay(100); __raw_writel(I2C_ENABLE, i2c->regs_cfg+XXX_I2C_CONFIG); ret = -EFAULT; } udelay(i2c->delay_time); return ret; } static u32 XXX_i2c_func(struct i2c_adapter *adap) { return I2C_FUNC_I2C|I2C_FUNC_SMBUS_EMUL; }

    先來看XXX_i2c_func()這個函式,它只是回傳了這個I2C控制器有什麼樣的功能,這邊當然一定有基本的I2C功能,另外還可以模擬SMBUS,SMBUS跟I2C類似,差別是送出資料的格式不同,在訊號上時序的要求也有一點差異。

    XXX_i2c_xfer()函式就是主要I2C控制器的核心部分,這邊是透過對一些暫存器的讀寫讓資料能按照I2C要求的規格和時序傳送到每一個周邊裝置,其實每個SoC的做法都不一樣,下次遇到的時候可能全部要砍掉重練,不過還是記錄一下這次遇到的做法。這裡有一個需要注意的地方,slave address是7個bits(系統沒有需要10個bits周邊,所以沒有實做10個bits的傳輸),可是每次資料傳送都是送8個bits,所以會把msg的addr欄位往左shift一個bit,最後一個bit代表的是讀(1)或寫(0)的動作。



    要讓資料按照正確的格式送到I2C bus上,重點是要依照一定的流程對暫存器讀寫。我們可以把XXX_i2c_xfer()分成兩部分來看,第一部分從所有的i2c_msg中取得slave address、周邊設備的暫存器位址和要寫的資料並把他們都寫入I2C控制器的XXX_I2C_MST_WR暫存器,在XXX_I2C_MST_WR暫存器中的資料最後會被放到一個FIFO中,等待start被觸發後會依照順序送到I2C bus上,流程圖如下。



第一部分的程式碼就是依照這張流程圖寫出來的,還是有可以最佳化的空間。一開始判斷是否有資料要收送,資料長度為0的msg是SMBUS的QUICK command的部分,有一些工具程式會利用這個功能快速的探測整個I2C bus上所有的周邊。再講讀和寫的部份之前,先來看看I2C規格書裡的三張圖


Fig 11是寫資料到周邊,非常直覺應該一看就懂了。Fig 12是從周邊讀資料,可是目前很多週邊並不採用這種格式,反而是採用Fig 13的格式,一開始會先把周邊的slave address和要讀的暫存器位址送出去,這是一個寫的動作,接下來才是從周邊把資料讀回來,所以需要兩個i2c_msg來完成整的讀的流程。

    第一個部分在觸發start後就把資料都送出去了,接下來進到第二個部分,流程圖如下


首先會先等待整個傳輸的過程完成,這裡是一個busy waiting,也可以採用中斷的方式,當資料送出後就把這個task掛到wait queue上,等待中斷被觸發,中斷產生後喚醒task然後處理接下來的工作。接下來就是判斷I2C 控制器是不是仍然busy,會busy通常都是第一部分的流程出錯了,處理方式就是直接reset控制器。至於其他錯誤發生的情況,lost arbitration最常遇到的是在傳輸過程中時序的錯誤造成周邊誤判,導致雖然整個流程結束,可是SDA仍然被周邊佔用,Linux有提供一個機制來修復這個情形,方法是送出9個clock,為什麼是9個clock? 看一看規格書裡面的時序圖就知道囉! i2c_adapter的i2c_bus_recovery_info欄位就是做為這個用途,不過這需要硬體能夠配合,因為要單獨對SCL做high和low的控制。另外兩個錯誤就是找不到周邊或周邊的暫存器了。之前如果是讀動作,資料會被放在FIFO裡,再透過暫存器讀走就可以了。

    整個XXX_i2c.c的內容就寫到這裡,寫的有點多...幾乎大部分嵌入式系統I2C架構都差不多,會不一樣的地方應該主要都是跟硬體相關的部分,如果想深入了解I2C運作的情形,可以參考I2C規格書,如果想看看其他驅動的做法,在kernel原始碼drivers/i2c/資料夾下有其他平台的範例,不過還是要有其他平台的datasheet會比較知道他在做些什麼。下一次就來看看i2c-dev.c啦。

2014年8月26日 星期二

裝置實例-I2C (1)

    之前寫了這麼多,長篇大論的,可是看來都是自己弄出來的東西,一個虛擬的裝置和一些自己定義的資料結構,還說kernel裡面差不多都是這樣的架構,好像單純自爽而已吧。好吧!那我們就來看看一個"I2C"裝置的實例吧!在開始之前還事先來看看他在Linux下的架構是怎樣吧,順便也可以來跟之前我們自創的架構比較一下。


    如果有google過的話,類似的圖應該有看過,不過這邊就照我自己的理解重新描述一次。這就是從user space一直到硬體層的架構了,先不看硬體層的話,有沒有跟之前的架構很像。XXX_i2c.c是我們真正要實現的部分,裡面有一個很重要的資料結構i2c_adapter,他會提供一個方法來操作跟硬體相關的暫存器,並提供給上層一個callback function,master_xfer()。在上來一層就是i2c-core.c這層,它提供了下層註冊的方法和上層存取下層的方法i2c_transfer(),當然裡面還有很多kernel給的API。接下來再往上到i2c-dev.c,它就是一個字元裝置,提供給user space一個管道,並且之後再調用下層給的方法,裡面也有一個重要的資料結構i2c_client。如果和之前我們自己建立的架構比較起來,這邊其實只增加了i2c-core.c這一層。

    而有關硬體層的部分,主要就是I2C bus的線路,SCL上是clock,SDA上是data。每一個I2C裝置都掛在這個bus上,它們有各自的slave address,透過一些溝通的機制,可以知道哪些資料是要傳送給他們的,詳細的資料可以參考I2C的Spec。

    看到這邊,之前的長篇大論還是有點用處的啦,不是單純自爽,之後會先從我們要自己實現的XXX_i2c.c看起,就不會說明整個bus到device和driver的所有細節,不過遇到重點部分還是會提一下。最後就先列出剛才兩個重要的資料結構結束這篇吧! 暫時看不懂沒關係,之後會慢慢講囉,其實註解已經寫的滿清楚的了。

struct i2c_adapter {
struct module *owner;
unsigned int class;  /* classes to allow probing for */
const struct i2c_algorithm *algo; /* the algorithm to access the bus */
void *algo_data;

/* data fields that are valid for all devices */
struct rt_mutex bus_lock;

int timeout; /* in jiffies */
int retries;
struct device dev; /* the adapter device */

int nr;
char name[48];
struct completion dev_released;

struct mutex userspace_clients_lock;
struct list_head userspace_clients;

struct i2c_bus_recovery_info *bus_recovery_info;
}

struct i2c_client {
unsigned short flags; /* div., see below */
unsigned short addr; /* chip address - NOTE: 7bit */
                /* addresses are stored in the */
                /* _LOWER_ 7 bits */
char name[I2C_NAME_SIZE];
struct i2c_adapter *adapter; /* the adapter we sit on */
struct i2c_driver *driver; /* and our access routines */
struct device dev; /* the device structure */
int irq; /* irq issued by device */
struct list_head detected;

}


2014年8月22日 星期五

Linux典型裝置架構-通知機制 & 結論

    如果要提供一個方法讓新裝置註冊後在/dev目錄下建立相對應的裝置檔案,kernel為每個bus提供了一個通知的機制,他其實就是一個linked-list的架構,當有新的裝置註冊或是就的裝置移除時,就會走訪這個linked-list下掛的結構,並執行對應的callback function。也就是說test_chrdev如果要被通知,就必須註冊相對應的callback function到test_bus的通知鏈上,我們看看需要怎麼做 。

static int test_notifier_call(struct notifier_block *nb, unsigned long act, void *data)
{
    struct device *dev = data;
   
    switch (act) {
    case BUS_NOTIFY_ADD_DEVICE:
        printk("notify add device\n");
        return create_dev(dev, NULL);
    case BUS_NOTIFY_DEL_DEVICE:
        printk("notify delete device\n");
        return destroy_dev(dev, NULL);
    }

    return 0;
}


struct notifier_block test_notifier = {
    .notifier_call = test_notifier_call,
};


    test_notifier_call()就是我們的callback function,當有新裝置註冊,這個函式就會被執行,並且傳入BUS_NOTIFY_ADD_DEVICE的動作和註冊的device結構,當接收到這個動作後就可以調用之前提過的create_dev()函式在/dev下建立相對應的裝置檔案。那有裝置移除就會收到BUS_NOTIFY_DEL_DEVICE的動作,也是調用destroy_device()函式移除在/dev目錄下的裝置。notifier_block就是會被掛在每個bus下的linked-list的結構。

    那想要這個callback function的被test_bus通知的方式就是在test_chrdev_init()test_chrdev_exit()裡各增加註冊和移除的函式。

bus_register_notifier(&test_bus_type, &test_notifier);
bus_unregister_notifier(&test_bus_type, &test_notifier);

    到這邊Linux的裝置架構也講完了,每一層的分工也大概清楚了,那最後載入所有的module後在/sys目錄下會呈現怎樣的架構呢?請看下圖囉!


這張圖裡面藍色方塊都是預設會有的目錄,其他顏色的方塊是載入所有module後才會出現的目錄,而綠色的方塊其實只是一個symbolic link,它們會指向相對應的橘色方塊的目錄,test_inst在這邊並不會出現,所以用虛線表示,它只有在probe()方法裡面被建立,並沒有被註冊。最後也用一張圖來看一下從user space到底層的存取方式。


從user space一直到test_chrdev的存取方式應該很熟悉,在"一個簡單的字元裝置"曾經講過,而test_chrdev和test_device之間就是透過test_deivce提供的callback functions來溝通了。

    這個架構在kernel裡面應用的實例其實很多,例如I2C,SPI Flash,MTD(NOR Flash or NAND Flash),UART...等等。其實他們的abstarction layer已經開發得很完整了,我們只需要拿來直接用就可以了,需要重新開發的部份就是依照我們的platform 的datasheet寫出和硬體有關的部份。

    那照這樣講字元裝置的部份平常並不會接觸到囉?當然不是啦!我們在user space要使用這些裝置的時候必須要知道他們在abstarction layer是怎麼調用下層的裝置,並不是所有都可以用單純的read()write()來完成,舉例來說,I2C裝置因為傳輸格式的關係,read()write()沒辦法滿足這個需求,所以在abstraction layer幾乎都是用ioctl()跟下層溝通,這樣就要知道傳給ioctl()的命令號和參數(arg)的結構。如果板子上有用到CPLD,且他是接在Parallel Flash Bus上,那字元裝置的mmap()函式就很有用,可以把相對應的記憶體位址映射到user space,之後就直接用這個位址和每個CPLD功能的offset位置操作相對應的功能。所以不要忽視字元裝置,即使他是比較簡單的架構,他在kernel裡也扮演了舉足輕重的角色!

2014年8月21日 星期四

Linux典型裝置架構-Character Device

    到目前為止,也來到典型裝置的最後一部分test_chrdev,雖然說他跟之前的"一個簡單的字元裝置"是差不多的,可是還是有不一樣的地方,我們還是從載入和卸載函式看起吧!

static struct file_operations test_chrdev_fops = {
    .owner = THIS_MODULE,
    .read = test_chrdev_read,
    .write = test_chrdev_write,
    .unlocked_ioctl = test_chrdev_ioctl,
    .open = test_chrdev_open,
    .release = test_chrdev_release,
};


static int create_dev(struct device *dev, void *data)
{
    int ret = 0;
    dev_t devno;
    struct device *chrdev;
    struct test_device *tdev = container_of(dev, struct test_device, dev);

    devno = MKDEV(chrdev_major, tdev->id);

    chrdev = device_create(test_inst_class, &tdev->dev, devno, NULL, "%s-%d", tdev->name, tdev->id);
    if (IS_ERR(chrdev))
        ret = PTR_ERR(chrdev);

    return ret;
}

static int destroy_dev(struct device *dev, void *data)
{
    dev_t devno;
    struct test_device *tdev = container_of(dev, struct test_device, dev);

    devno = MKDEV(chrdev_major, tdev->id);

    device_destroy(test_inst_class, devno);
   
    return 0;
}

static int __init test_chrdev_init(void)
{
    int ret;

    ret = register_chrdev(chrdev_major, "test_chrdev", &test_chrdev_fops);
    if (ret < 0)
        goto out;

    if (!chrdev_major)
        chrdev_major = ret;
    ret = 0;
   
    test_inst_class = class_create(THIS_MODULE, "test_chrdev");
    if (IS_ERR(test_inst_class)) {
        ret = PTR_ERR(test_inst_class);
        goto release_chrdev;
    }

    bus_for_each_dev(&test_bus_type, NULL, NULL, create_dev);

    return ret;

destroy_class:
    class_destroy(test_inst_class);
release_chrdev:
    unregister_chrdev(chrdev_major, "test_chrdev");
out:
    return ret;
}

static void test_chrdev_exit(void)

{
    bus_for_each_dev(&test_bus_type, NULL, NULL, destroy_dev);
    class_destroy(test_inst_class);
    unregister_chrdev(chrdev_major, "test_chrdev");
}


    載入函式chrdev_init()中,看到了熟悉的字元裝置的註冊和class的建立,不過這邊用了kernel提供的bus_for_each_dev()走訪所有已經掛在test_bus_type下的device,並對每個device調用create_dev(),這個函式會先找到device所屬的test_device,在根據每個test_device的id欄位在/dev目錄下建立相對應的裝置檔案,跟"一個簡單的字元裝置"不同的是parent欄位是test_device,所以test_chrdev最後在/sys目錄下的位置會是/sys/devices/test_bus/test-1/test_chrdev。而卸載函式test_chardev_exit()則是移除裝置檔案,class和字元裝置。跟之前的file_operations比較可以發現少了mmap()的操作函式,因為沒有可以直接映射的記憶體位址,所以mmap()在這邊沒有一展所長的地方。

static int find_test_dev(struct device *dev, void *data)
{
    int id, ret = 0;
    struct dev_info *info = (struct dev_info*)data;
    struct test_device *tdev = container_of(dev, struct test_device, dev);

    id = iminor(info->inode);

    if (id == tdev->id) {
        info->filp->private_data = tdev;
        ret = 1;
    }

    return ret;
}

static int test_chrdev_open(struct inode *inode, struct file *filp)
{
    int ret;
    struct dev_info info = {inode, filp};

    ret = bus_for_each_dev(&test_bus_type, NULL, &info, find_test_dev);
    if (!ret)
        ret = -ENODEV;
    else       
        ret = 0;

    return ret;
}

static int test_chrdev_release(struct inode *inode, struct file *filp)

{
    return 0;
}


在open()函式裡也同樣調用bus_for_each_dev()來搜尋目前開啟的test_device是否已經被註冊,如果找到了就放到檔案的私有資料攔,方便之後的使用。release()在這邊同樣沒什工作要作。

static ssize_t test_chrdev_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos)
{
    int ret;
    char *buffer;
    struct test_msg *msg;
    struct test_device *tdev = filp->private_data;

    buffer = kzalloc(size, GFP_KERNEL);
    if (buffer == NULL) {
        ret = -ENOMEM;
        goto out;
    }

    msg = kzalloc(sizeof(struct test_msg), GFP_KERNEL);
    if (msg == NULL) {
        ret = -ENOMEM;
        goto free_buffer;
    }

    msg->flag = MSG_RD;
    msg->from = 0;
    msg->len = size;
    msg->buf = buffer;

    ret = tdev->ops->transfer(tdev, msg, 1);
    if (ret < 0)
        goto free_msg;

    if (copy_to_user(buf, msg->buf, msg->len))
        ret = -EFAULT;

free_msg:
    kfree(msg);
free_buffer:
    kfree(buffer);
out:
    return ret;
}


static ssize_t test_chrdev_write(struct file *filp, const char __user *buf, size_t size, loff_t *ppos)
{
    int ret;
    char *buffer;
    struct test_msg *msg;   
    struct test_device *tdev = filp->private_data;

    buffer = kzalloc(size, GFP_KERNEL);
    if (buffer == NULL) {
        ret = -ENOMEM;
        goto out;
    }

    if (copy_from_user(buffer, buf, size)) {
        ret = -EFAULT;
        goto free_buffer;
    }

    msg = kzalloc(sizeof(struct test_msg), GFP_KERNEL);
    if (msg == NULL) {
        ret = -ENOMEM;
        goto free_buffer;
    }

    msg->flag = MSG_WR;
    msg->from = 0;
    msg->len = size;
    msg->buf = buffer;

    ret = tdev->ops->transfer(tdev, msg, 1);
   
    kfree(msg);
free_buffer:
    kfree(buffer);
out:
    return ret;
}


    讀寫函式一樣是從user space接收一個size的資料(buf),一樣是從位置0地方開始讀寫,不過這邊需要自己建構一個test_msg的結構,之後調用test_device所提供的transfer() callback function把建構好的test_msg往下傳,一次只會傳一個msg。因為分層的架構,讀寫函式裡已經看不到任何跟硬體相關的操作,所有有關硬體的操作都在transfer()函式裡。

static int test_ioctl_rdwr(struct file *filp, unsigned long arg)
{
    int i, j, ret;
    char **buffer;
    struct test_msg *msg;
    struct test_ioctl_data test_data;
    struct test_device *tdev = filp->private_data;

    if (copy_from_user(&test_data, (struct test_ioctl_data*)arg, sizeof(test_data)))
        return -EFAULT;

    msg = kzalloc(test_data.num_msg*sizeof(struct test_msg), GFP_KERNEL);
    if (msg == NULL)
        return -ENOMEM;
    if (copy_from_user(msg, test_data.msg, test_data.num_msg*sizeof(struct test_msg))) {
        kfree(msg);
        return -EFAULT;
    }

    buffer = kzalloc(test_data.num_msg*sizeof(char), GFP_KERNEL);
    if (buffer == NULL) {
        kfree(msg);
        return -ENOMEM;
    }

    for (i = 0; i < test_data.num_msg; i++) {
        buffer[i] = msg[i].buf;

        msg[i].buf = kzalloc(msg[i].len, GFP_KERNEL);
        if (msg[i].buf == NULL)
            break;

        if (msg[i].flag & MSG_WR) {
            if (copy_from_user(msg[i].buf, buffer[i], msg[i].len)) {
                kfree(msg[i].buf);
                return -EFAULT;
            }
        }
    }

    ret = tdev->ops->transfer(tdev, msg, test_data.num_msg);

    for (j= 0; j < ret; j++) {
        if (msg[j].flag & MSG_RD) {
            if (copy_to_user(buffer[j], msg[j].buf, msg[j].len))
                break;
        }
    }       
   
    ret = j;

    for (j = 0; j < i; j++)
        kfree(msg[j].buf);
    kfree(buffer);
    kfree(msg);
    return ret;
}

static int test_ioctl_erase(struct file *filp)
{
    int ret;
    struct test_device *tdev = filp->private_data;

    ret = tdev->ops->erase(tdev);

    return ret;
}

static long test_chrdev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)

{
    int ret;

    switch(cmd) {
    case TEST_RDWR:
        ret = test_ioctl_rdwr(filp, arg);
        break;
    case TEST_ERASE:
        ret = test_ioctl_erase(filp);
        break;
    default:
        ret = -EINVAL;
    }

    return ret;
}


    ioctl()一樣有讀寫和擦除的兩個工作,對照"一個簡單的字元裝置",這邊的讀寫動作只是之前前半部的動作,把user space傳過來的所有messages分別複製一份到kernel space,最後也是調用test_device提供的transfer() callback function。擦除的動作則直接調用erase() callback function。

    好啦!所有的操作函式都介紹完了,以為一切都結束了嗎?我們還有一些後續的事情要做阿。test_chrdev當然也是編寫成一個module,所以目前我們有三個modules,那每個module載入的順序呢?一般認為當然是test_bus.ko -> test_module.ko -> test_chrdev.ko了,那有沒有想過,如果有一個新的裝置註冊,有沒有辦法同時在/dev目錄下也建立相對應的裝置檔案呢?這屬於kernel的通知機制,在下一篇再做介紹吧!

2014年8月20日 星期三

Linux典型裝置架構-Device & Driver

    延續之前的話題,這次來看看test_module的部份,包含在test_module.c這個檔案裡。可以把這個檔案再分為兩個部份,test_device和test_driver來看,首先先來看test_device。

struct test_operations test_inst_ops = {
    .transfer = test_dev_xfer,
    .erase = test_dev_erase,
};

struct test_device test_dev = {
    .name = DEV_NAME,
    .id = 1,
    .ops = &test_inst_ops,
};


struct test_inst {
    struct test_device *tdev;
    unsigned int    size;
    char        *buffer;
    unsigned long    last_wr;
    unsigned long    last_rd;
};


static __inline void test_set_drvdata(struct test_device *tdev, void *data)
{
    dev_set_drvdata(&tdev->dev, data);
}


static __inline void *test_get_drvdata(struct test_device *tdev)
{
    return dev_get_drvdata(&tdev->dev);
}


static int test_dev_xfer(struct test_device *tdev, struct test_msg *msg, int num_msg)
{
    int i;
    struct test_inst *inst = test_get_drvdata(tdev);

    for (i = 0; i < num_msg; i++) {
        int from = msg[i].from;
        int len = msg[i].len;

        if (from > inst->size)
            break;
        if ((from+len) > inst->size)
            len = inst->size - from;
        if (msg[i].flag == MSG_RD) {
            inst->last_wr = jiffies;
            memcpy(msg[i].buf, inst->buffer+from, len);
        } else {
            inst->last_rd = jiffies;
            memcpy((inst->buffer+from), msg[i].buf, len);
        }
    }

    return i;
}

static int test_dev_erase(struct test_device *tdev)
{
    struct test_inst *inst = test_get_drvdata(tdev);

    memset(inst->buffer, 0, inst->size);

    return 0;
}


在這邊給了test_device一個名稱,一個ID,也實現了要提供給abstraction layer(字元裝置)的一組callback function。test_get_drvdata()只是直接調用dev_get_drvdata(),從device結構中得到私有資料。test_inst的結構,他其實就是test_device的包裝,test_device提供一些基本的成員,而test_inst則是依需求或功能在test_device上擴充,有點像是物件導向繼承的概念,test_inst的功能還是分配一塊可讀寫的記憶體空間,另外也紀錄最後讀寫的時間,這個時間是以jiffies為單位,他是電腦一開機後就隨著timer跳動的計數器。

    跟"一個簡單的字元裝置(2)"中的chrdev_ioctl_rdwr()函數比較一下,可以發現test_dev_xfer()做的事情是流程中比較後面有關處理記憶體複製的動作,也就是屬於直接控制硬體的部份,而在其他裝置這類的callback function做的事情就可能是讀寫暫存器,在"一個簡單的字元裝置(3)"結尾的部份也有提過這是屬於platform dependent的部分。那其他跟硬體無關的部份久則還是留在字元裝置裡。這樣很明顯的形成兩個分層,系統需要移植的時候只要重新開發底層跟硬體有關的部份,因此減少了很多開發的時間。

    再來看看test_driver的部份

struct test_driver test_drv = {
    .driver = {
        .owner = THIS_MODULE,
        .name = DEV_NAME,
    },
    .probe = test_inst_probe,
    .remove = test_inst_remove,
};


static ssize_t inst_show_info(struct device *dev, struct device_attribute *attr, char *buf)
{
    struct test_device *tdev = container_of(dev, struct test_device, dev);
    struct test_inst *inst = test_get_drvdata(tdev);

    return snprintf(buf, PAGE_SIZE, "ID: %d\nBuffer size: %d\n", tdev->id, inst->size);
}

static DEVICE_ATTR(info, S_IRUSR, inst_show_info, NULL);



static int test_inst_probe(struct test_device *tdev)
{
    int ret = 0;
    struct test_inst *inst;

    inst = kzalloc(sizeof(struct test_inst), GFP_KERNEL);
    if (inst == NULL) {
        ret = -ENOMEM;
        goto out;
    }

    inst->tdev = tdev;
    inst->size = BUFFER_SIZE;
    inst->buffer = kzalloc(inst->size, GFP_KERNEL);
    if (inst->buffer == NULL) {
        ret = -ENOMEM;
        goto free_inst;
    } 

    ret = device_create_file(&tdev->dev, &dev_attr_info);
    if (ret)
        goto release_buffer;

    test_set_drvdata(tdev, inst);

    return ret;

release_buffer:
    kfree(inst->buffer);
free_inst:
    kfree(inst);
out:
    return ret;
}

static int test_inst_remove(struct test_device *tdev)
{   
    struct test_inst *inst = test_get_drvdata(tdev);
   
    device_remove_file(&tdev->dev, &dev_attr_info);
    kfree(inst->buffer);
    kfree(inst);
    test_set_drvdata(tdev, NULL);

    return 0;
}


    因為要能夠跟test_device match,所以test_driver的名稱必須和test_device一樣,另外重要的則是實現了probe()remove()方法,實際的裝置結構是在probe()方法理時現的,主要工作就是分配記憶體並對test_inst結構初始化,之後再把他放到device結構的私有資料裡,如果曾經有開發驅動的經驗,會發現在probe()方法裡還會註冊實際的裝置。當然這裡也提供一個屬性檔(info)可以獲取裝置的ID和記憶體區塊的大小。

    test_module這部份也編寫成module的形式,所以載入和卸載函式也是不可或缺的,其實也只有註冊test_device和test_driver。

static int __init test_inst_init(void)
{
    int ret = 0;

    ret = test_device_register(&test_dev);
    if (ret)
        goto out;
   
    ret = test_driver_register(&test_drv);
    if (ret)
        goto release_dev;

    return ret;

release_dev:
    test_device_unregister(&test_dev);
out:
    return ret;
}

static void __exit test_inst_exit(void)
{
    test_driver_unregister(&test_drv);
    test_device_unregister(&test_dev);
}


    這個Linux典型的裝置架構幾乎快要完成了,剩下的就是要提供一個入口給user space的process,test_chrdev就是完成這個工作的字元裝置,其實他跟之前"一個簡單的字元裝置"是差不多的,只是每個動作最後改由調用test_device提供的callback function。下次我們在來看這部份吧!

2014年8月18日 星期一

Linux典型裝置架構-Bus

    這次們把之前的記憶體字元裝置作一些改變,讓他符合linux典型分層的架構,也就是在sysfs裝置架構裡面右半邊在bus目錄下的裝置和驅動,當然還包括了一個中繼站,字元裝置,提供給user space存取記憶體裝置的方法。這個裝置可以分成三個部份來看,test_bus(註冊bus和提供test_device及test_drive的註冊),test_module(主要是對test_device的包裝和test_driver的實現),test_chrdev(字元裝置),這些在之後都會看到。首先就來看看test_bus部份,他是在test_bus.c這個檔案裡面建構和註冊的,這個檔案也另外提供了test_device和test_driver註冊的方法

static __inline struct test_device *to_test_dev(struct device *dev)
{
    struct test_device *tdev = container_of(dev, struct test_device, dev);
   
    return tdev;
}

static __inline struct test_driver *to_test_drv(struct device_driver *drv)
{
    struct test_driver *tdrv = container_of(drv, struct test_driver, driver);

    return tdrv;


static int test_bus_match(struct device *dev, struct device_driver *drv)
{
    struct test_device *tdev = to_test_dev(dev);

    return (strcmp(tdev->name, drv->name) == 0);
}


struct bus_type test_bus_type = {
    .name = "test_bus",
    .match = test_bus_match,
};
EXPORT_SYMBOL(test_bus_type);

struct device test_bus = {
    .init_name = "test_bus",
};
EXPORT_SYMBOL(test_bus);

static int __init test_bus_init(void)
{

    int ret;

    ret = bus_register(&test_bus_type);
    if (ret)
        goto out;

    ret = device_register(&test_bus);
    if (ret)
        goto bus_dev_fail;

    return ret;

bus_dev_fail:
    bus_unregister(&test_bus_type);
out:
    return ret;
}

static void __exit test_bus_exit(void)
{
    device_unregister(&test_bus);
    bus_unregister(&test_bus_type);
}


    首先建構了test_bus_type結構,成員有bus名稱和一個test_bus_match()的callback function,這個match()函式提供設備註冊時從已經掛在bus上的驅動中搜尋是否有支援目前這個設備的驅動,判斷是否支援的方法是比較設備和驅動的名稱,如果都沒有找到,就會把這些裝置或驅動掛在對應的device的linked-list上。在其他的bus,例如PCI bus在搜尋支援的驅動時則需要比較pci_id_table內的vendor_id和device_id或更多,而驅動註冊時則同樣也會搜尋是否有被支援的設備已經掛在bus下了。這個bus也是一個裝置,因此初始化test_bus的device結構,提供bus裝置的名稱。EXPORT_SYMBOL()的目的是讓其他的module可以利用這個結構或函式。

    這個test_bus實際上編寫為一個module,所以也是要有載入和卸載函式的,載入函式,test_bus_init(),主要就是註冊test_bus_type和test_bus,test_bus_exit()則是移除他們。

    tese_bus.c裡還定義了test_device的結構,包含一些基本的成員和一組存取這個裝置的操作函式(test_operations),之後實際的裝置會以這個結構的基礎再作功能上擴充。這裡還提供裝置註冊的函式test_device_register(),設定test_device的parent, 所屬的bus和裝置在/sys目錄下的名稱,之後調用kernel提供的裝置註冊函式device_register(),裝置移除函式則直接調用device_unregister()。

struct test_device {
    char        name[256];
    int         id;
    struct test_operations *ops;
    struct device    dev;
};


struct test_operations {
    int (*transfer)(struct test_device *, struct test_msg *, int);
    int (*erase)(struct test_device *);
};


int test_device_register(struct test_device *tdev)
{
    int ret;

    tdev->dev.parent = &test_bus;
    tdev->dev.bus = &test_bus_type;

    dev_set_name(&tdev->dev, "%s-%d", tdev->name, tdev->id);

    ret = device_register(&tdev->dev);

    return ret;
}
EXPORT_SYMBOL(test_device_register);

void test_device_unregister(struct test_device *tdev)
{
    device_unregister(&tdev->dev);
}
EXPORT_SYMBOL(test_device_unregister);


    驅動註冊的部份工作比較多一點,基本上一定要提供probe()remove()的方法,其他還有suspend()resume()...等等的方法實做有關電源管理的部分。probe()在什麼時候會用到呢?當裝置(驅動)註冊且找到支援的驅動(裝置),match成功後就會調用probe()方法,probe()方法主要工作是記憶體空間的分配,資源的申請(request_mem_region),實體位址映射到虛擬位址(ioremap)和中斷的申請(irq_request),remove()則需要釋放申請的所有資源。

 struct test_driver {
    struct device_driver driver;
    int (*probe)(struct test_device *);
    int (*remove)(struct test_device *);
};


static int test_drv_probe(struct device *dev)
{
    struct test_device *tdev = to_test_dev(dev);
    struct test_driver *tdrv = to_test_drv(dev->driver);

    return tdrv->probe(tdev);
}

static int test_drv_remove(struct device *dev)

{
    struct test_device *tdev = to_test_dev(dev);
    struct test_driver *tdrv = to_test_drv(dev->driver);

    return tdrv->remove(tdev);
}

int test_driver_register(struct test_driver *tdrv)

{
    int ret;
   
    tdrv->driver.owner = THIS_MODULE;
    tdrv->driver.bus = &test_bus_type;

    if (tdrv->probe)
        tdrv->driver.probe = test_drv_probe;
    if (tdrv->remove)
        tdrv->driver.remove = test_drv_remove;

    ret = driver_register(&tdrv->driver);

    return ret;
}
EXPORT_SYMBOL(test_driver_register);

void test_driver_unregister(struct test_driver *tdrv)

{
    driver_unregister(&tdrv->driver);
}
EXPORT_SYMBOL(test_driver_unregister);


    剛剛有講到可是在程式碼裡面沒出現的之後都會有機會遇到,就留待遇到再講了,下次就開始另外的兩個部份,test_module和test_chrdev。

2014年8月13日 星期三

一個簡單的字元裝置(3)

    上次留了一個問題還沒回答,user space的process要如何知道每個驅動的ioctl的命令號是多少?回想一下命令號是怎麼被建立出來的

_IO(type, nr)
_IOR(typr, nr, size)
_IOW(type, nr, size)
_IORW(type, nr, size)

    可以看到需要傳入幾個參數,type(也有人說他是一個magic number), nr(一個8 bits的數字), size(傳給ioctl()第三個參數arg的大小),另外不要忘了,還有dir(read和write的動作),這四個巨集會把這四個參數照一個順序排列形成一個32 bits的命令號,排列的順序如下圖:


知道了這個規則後,我們就能直接建立自己的命令號了,在記憶體字元裝置驅動裡面用的是

#define CHRDEV_TYPE 'k'
#define CHRDEV_ERASE _IO(CHRDEV_TYPE, 0x50)
#define CHRDEV_RDWR _IO(CHRDEV_TYPE, 0x51)

得到兩個命令號,現在改用上述的排序規則把他們建立出來,type在這邊是'k'這個字母的ASCII code,也就是0x6b, nr分別是0x50和0x51,沒有size和dir,所以兩個命令號會是

CHRDEV_ERASE: 0x6b50
CHRDEV_RDWR: 0x6b51

    因此不管在驅動中或是在process都可套用相同規則,建立出來命令號彼此都可以認得,而且不會跟系統中已經有的命令號衝突。如果想驗證一下自己建立的命令號跟原來的是不是一樣,可以用printk()輸出用_IO()建立的命令號到klog,再用dmesg指令查看。

    剛好這邊提到獲得ioctl命令的資訊,那就順便來講講獲得裝置資訊的方法吧!還記得在/sys每個裝置的目錄下有一些屬性可以獲取裝置的資訊,這邊也用這個方法show出我們記憶體裝置的ID, 記憶體大小和ioctl cmd的資訊。kernel提供了一個方法讓我們可以建立自己的屬性

DEVICE_ATTR(_name, _mode, _show, _store)

這個巨集會建立屬於裝置的屬性結構(struct dev_attr_##_name),_name為屬性名稱,_mode為存取權限(S_IRUSR,S_IWUSR...),一般當用cat指令讀這個屬性時會用到_show方法,寫的時候會用到_store方法。因為我們目前只提供裝置資訊,所以只需要實現_show方法,_show方法是將資訊寫到一個buffer裡面後回傳總共寫的多少bytes。

static ssize_t chrdev_show_info(struct device *dev, struct device_attribute *attr, char *buf)
{
    struct chrdev_inst *inst = dev_get_drvdata(dev);

    return snprintf(buf, PAGE_SIZE, "ID: %d\nMemory size: %d bytes\nioctl command:\n\tCHRDEV_ERASE: %#x\n\tCHRDEV_RDWR :%#x\n",c_dev.id, inst->size,CHRDEV_ERASE, CHRDEV_RDWR);
}


static DEVICE_ATTR(info, S_IRUSR, chrdev_show_cmd, NULL);



    除此之外,在裝置驅動的載入和卸載函式也要建立和移除這個屬性,對應的API原型為

device_create_file(struct device *dev, const struct device_attribute *attr);
device_remove_file(struct device *dev, const struct device_attribute *attr);

所以在chrdev_init()裡要增加

device_create_file(c_dev.dev, &dev_attr_info);

chrdev_exit()裡面也增加

device_remove_file(c_dev.dev, &dev_attr_info);

在載入函式那時候也有提過最後我們的裝置目錄會被建立成/sys/devices/virtual/chrdev_class/chrdev-1的一個目錄,info屬性檔也理所當然的會出現在這個目錄下,
最後在用cat指令讀取info可以得知這個記憶體裝置的一些資訊了。

    到目前為止,我們的字元裝置也大概看完了,對字元裝置驅動的結構也有更進一步的認識,當然這只是一部分,有很多檔案操作函式這邊並沒有提到,不過對這個裝置已經夠足夠提供需要的功能了。在kernel裡字元驅動通常是以一個中繼的角色存在,或者可以稱為abstraction layer,對user space的process提供操作其他沒有出現在/dev目錄下裝置的方法, 而這些裝置通常都是platform dependent的裝置,這個好處是移植到不同嵌入式系統上時可以幾少開發的時間。下次我們再來看看linux典型的裝置架構,也就是掛在bus下的那些裝置,並且改變目前這個字元驅動,讓他符合一個典型的架構。


2014年8月12日 星期二

一個簡單的字元裝置(2)

    接著繼續來看看我們虛擬字元裝置的檔案操作函式吧!在這之前先用一張圖來看看user space是怎麼存取字元裝置

  
    一不小心放了太多東西進去了,不過多看一點也不錯。user space的process會先發出一個system call,例如read, write,好奇system call是怎麼被定義的嗎?其實他們長這樣(參數對應process裡傳給readwrite的參數):

SYSCALL_DEFINE3(read, unsigned int, fd, char __user *, buf, size_t, count)
SYSCALL_DEFINE3(write, unsigned int, fd, const char __user *, buf, size_t, count) 

在system call裡面會根據傳入的fd找到對應的file資料結構,再調用檔案操作結構裡面的readwrite

     以上圖來看,我們目前的位置在charecter device的地方,下面那個other device是...?那個就是依附在bus下的裝置啦,他們沒辦法以一個檔案的方式出現在/dev目錄下,所以只好請求字元裝置來幫忙囉!他們也會提供一些callback functions,字元裝置就可以利用這些callback functions存取依附在bus下的裝置了。

     上圖中的file-system就是常見的ext3,ubifs...這些檔案系統啦,他們跟block device之間的資料傳輸是透過request的方式,以後如果有機會在看看block device的部份。socket-protocol(TCP, UDP, IP)-net_device(網路裝置)之間溝通是透過sk_buf的資料結構,經過一個protocol就在在資料上加上一個頭部,最後透過網路裝置送出去,封包收進來後就一層一層的把頭部摘掉。

    嗯...好像扯太遠了,把話題拉回檔案操作結構,先來看看我們的檔案操作結構長什麼樣吧。

static struct file_operations chrdev_fops = {
    .read = chrdev_read,
    .write = chrdev_write,
    .unlocked_ioctl = chrdev_ioctl,
    .mmap = chrdev_mmap,
    .open = chrdev_open,
    .release = chrdev_release,
};


其實整個完整的結構裡面的callback function有很多,可是有些並不一定會用到,所以我們根據想要的功能只實現其中的某一部份。接下來就一一來探討這些callback function做了哪些事。
    首先來看看chrdev_open()chrdev_release()

int chrdev_open(struct inode *inode, struct file *filp)
{
    int id = iminor(inode);
    struct chrdev_inst *inst;

    if (id == c_dev.id) {
        inst = dev_get_drvdata(c_dev.dev);
        filp->private_data = inst;
        return 0;
    } else {
        return -ENODEV;
    }
   
}

int chrdev_release(struct inode *inode, struct file *filp)
{
    return 0;
}


在linux的世界裡,每個檔案就是一個inode,可以藉由inode區分這個檔案是字元裝置(i_cdev)還是區塊裝置(i_bdev),同時他還會紀錄裝置編號(i_rdev),還記得MKDEV函式產生裝置編號時傳入的次編號是什麼嗎?沒錯!就是字元裝置的ID,所以可以用iminor()來獲取裝置的ID。

    因為沒有把裝置的結構(inst)宣告成全域變數,只有在建立device時把他當成私有資料傳入,dev_get_drvdata()就是用來得到私有資料,接著再把他當成file結構的私有資料,這樣作的原因是之後在readwriteioctl...裡都會傳入file的結構,可以利用這個方法直接取得裝置的結構(inst)。在open()函式裡沒有分配記憶體,也沒有註冊IRQ,所以release()其實沒事可作。

    接下來看看chrdev_read()chrdev_write()

static ssize_t chrdev_read(struct file *filp, char __user *buff, size_t count, loff_t *ppos)
{
    int ret;    
    struct chrdev_inst *inst = filp->private_data;
   
    if(count > inst->size)
        count = inst->size;
    if (copy_to_user(buff, inst->buf, count))
        ret = -EFAULT;
    else      
        ret = count;

    return ret;
}

static ssize_t chrdev_write(struct file *filp, const char __user *buff, size_t count, loff_t *ppos)
{
    int ret;
    struct chrdev_inst *inst = filp->private_data;

    if (count > inst->size)
        count = inst->size;
    if (copy_from_user(inst->buf, buff, count))
        ret = -EFAULT;
    else
        ret = count;

    return ret;
}


這兩個函式會收到一樣的參數,一個指向user space資料的指標(buff),資料的大小(count)和目前的位置(*ppos),目前的位置在這邊並沒有被用到,也就是說每次讀寫都是從第0個byte開始寫或從第0個byte開始讀。因為user space和kernel space之間的記憶體空間不能夠互相直接存取,所以必須藉由copy_to_user()copy_from_user()複製資料。如果成功讀寫就回傳資料的大小。

    接著是chrdev_mmap()的部份

static int chrdev_mmap(struct file *filp, struct vm_area_struct *vma)
{
    int ret = 0;
    unsigned long len = vma->vm_end - vma->vm_start;
    unsigned long offset = vma->vm_pgoff<<PAGE_SHIFT;
    struct chrdev_inst *inst = filp->private_data;

    if (offset > inst->size)
        return -EINVAL;
    if ((len+offset) > inst->size)
        return -EINVAL;

    offset = virt_to_phys(offset + inst->buf) >> PAGE_SHIFT;
    if (remap_pfn_range(vma, vma->vm_start, offset, len, vma->vm_page_prot))
        ret = -EAGAIN;
    return ret;
}


在看這個函式之前,我們先來看看在user space是怎麼呼叫的

void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);

addr指的是希望map到的virtual address,不過通常都是0,由kernel自己分配。len是map的檔案長度,以byte為單位。prot是這個區間可讀或可寫,如果有多個process同時map相同的檔案,flag標示如果檔案被一方修改另一方式否可見。offset指的是從檔案的哪裡開始map,必須是PAGE_SIZE大小的整數倍,PAGE_SIZE大小應系統而異,在我的電腦上是4KB。 當user space呼叫mmap()時,會先分配一塊virtual address,實際map到physical address是靠檔案操作函式的mmap()完成。
    回到檔案操作函式的mmap(),vm_area_struct結構紀錄的相關的資訊,結構成員vm_start和vm_end是在virtual address開始和結束的位址,vm_pgoff是offset,不過是以PAGE為單位,所以需要往左shift PAGE_SHIFT(12)的長度。
    接下來就是利用remap_pfn_range()把我們的裝置記憶體從offset的地方map一個len長度的大小給user space了,傳入的offset參數必須是實體記憶體對應的page frame number,所以要透過virt_to_phys()轉成實體記憶體後再往右shift PAGE_SHIFT

    最後就是chrdev_ioctl()

#define CHRDEV_TYPE 'k'
#define CHRDEV_ERASE _IO(CHRDEV_TYPE, 0x50)
#define CHRDEV_RDWR _IO(CHRDEV_TYPE, 0x51)

struct chrdev_msg {
    int flag;
#define MSG_WR 1
#define MSG_RD 2
    unsigned int from;
    unsigned int len;
    char *buf;
};

struct chrdev_ioctl_data {
    int num_msg;
    struct chrdev_msg *msg;
};


static int chrdev_ioctl_rdwr(struct file *filp, unsigned long arg)
{
    int i, j, ret;
    struct chrdev_ioctl_data chrdev_data;
    struct chrdev_msg *msg;
    char *buffer[MAX_NUM_MSG];
    struct chrdev_inst *inst = filp->private_data;

    if (copy_from_user(&chrdev_data, (struct chrdev_ioctl_data*)arg, sizeof(chrdev_data)))
        return -EFAULT;

    msg = kzalloc(chrdev_data.num_msg*sizeof(struct chrdev_msg), GFP_KERNEL);
    if (msg == NULL)
        return -ENOMEM;
    if (copy_from_user(msg, chrdev_data.msg, chrdev_data.num_msg*sizeof(struct chrdev_msg))) {
        kfree(msg);       
        return -EFAULT;
    }

    for (i=0; i<chrdev_data.num_msg; i++) {
        buffer[i] = kzalloc(msg[i].len, GFP_KERNEL);
        if (buffer[i] == NULL)
            break;

        if (msg[i].flag & MSG_WR) {
            if (copy_from_user(buffer[i], msg[i].buf, msg[i].len)) {
                kfree(buffer[i]);
                break;
            }
        }
    }

    for (j = 0; j < i; j++) {
        int from = msg[j].from;
        int len = msg[j].len;

        if (from > inst->size) {
            ret = -EINVAL;
            goto free_all;
        }

        if ((len+from) > inst->size)
            len = inst->size - from;

        if (msg[j].flag & MSG_RD) {
            memcpy(buffer[j], (inst->buf+from), msg[j].len);

            if (copy_to_user(msg[j].buf, buffer[j], msg[j].len))
                break;
        

        } else
            memcpy((inst->buf+from), buffer[j], msg[j].len);
    }
    ret = j;

free_all:
    for (j=0; j<i; j++)
        kfree(buffer[j]);
       
    kfree(msg);
    return ret;
}


static long chrdev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
    struct chrdev_inst *inst = filp -> private_data;
   
    switch(cmd) {

    case CHRDEV_RDWR:
        ret = chrdev_ioctl_rdwr(filp, arg);
        break;
    case CHRDEV_ERASE:
        memset(inst->buf, 0, BUFFER_SIZE);
        break;
    default:
        return -EINVAL;
    }

    return 0;
}


這個函式會接收一個命令(cmd)和一個參數(arg),參數的部份可以自行定義,不過process和裝置驅動都要對這個參數有共識,型態轉換的時候才不會出錯。命令的部份就不能隨便定義了,因為有可能會跟系統中已有的命令相衝突,來看看kernel裡面是怎麼做的吧!

SYSCALL_DEFINE3(ioctl, unsigned int, fd, unsigned int, cmd, unsigned long, arg)
{
    struct file *filp;
    int error = -EBADF;
    int fput_needed;

    filp = fget_light(fd, &fput_needed);
    if (!filp)
        goto out;

    error = security_file_ioctl(filp, cmd, arg);
    if (error)
        goto out_fput;

    error = do_vfs_ioctl(filp, fd, cmd, arg);
 out_fput:
    fput_light(filp, fput_needed);
 out:
    return error;
}


int do_vfs_ioctl(struct file *filp, unsigned int fd, unsigned int cmd, unsigned long arg)
{
    int error = 0;
    int __user *argp = (int __user *)arg;

    switch (cmd) {
    case FIOCLEX:
        set_close_on_exec(fd, 1);
        break;

    case FIONCLEX:
        set_close_on_exec(fd, 0);
        break;

    case FIONBIO:
        error = ioctl_fionbio(filp, argp);
        break;

    case FIOASYNC:
        error = ioctl_fioasync(fd, filp, argp);
        break;

    case FIOQSIZE:
        if (S_ISDIR(filp->f_path.dentry->d_inode->i_mode) ||
            S_ISREG(filp->f_path.dentry->d_inode->i_mode) ||
            S_ISLNK(filp->f_path.dentry->d_inode->i_mode)) {
            loff_t res =
                inode_get_bytes(filp->f_path.dentry->d_inode);
            error = copy_to_user((loff_t __user *)arg, &res,
                         sizeof(res)) ? -EFAULT : 0;
        } else
            error = -ENOTTY;
        break;

    case FIFREEZE:
        error = ioctl_fsfreeze(filp);
        break;

    case FITHAW:
        error = ioctl_fsthaw(filp);
        break;

    case FS_IOC_FIEMAP:
        return ioctl_fiemap(filp, arg);

    case FIGETBSZ:
    {
        struct inode *inode = filp->f_path.dentry->d_inode;
        int __user *p = (int __user *)arg;
        return put_user(inode->i_sb->s_blocksize, p);
    }

    default:
        if (S_ISREG(filp->f_path.dentry->d_inode->i_mode))
            error = file_ioctl(filp, cmd, arg);
        else
            error = vfs_ioctl(filp, cmd, arg);
        break;
    }
    return error;
}

chrdev_ioctl()要到vfs_ioctl()這邊才會被調用,在這之前命令會被用來判斷是否是FIOCLEX,FIONCLEX,...,FIGETBSZ這些情況,至少我們的命令不可以跟這些衝突,不過保險起見還是參考一下Documentation/ioctl/ioctl-number.txt裡面所列已經被使用的命令。要怎麼設定一個命令呢?kernel提供了幾個方法:

_IO(type, nr)
_IOR(typr, nr, size)
_IOW(type, nr, size)
_IORW(type, nr, size)

可是...這樣在process裡面怎麼知道命令是什麼?嗯...這真是個好問題,今天就先看看ioctl()裡面在做些什麼,這個問題就留到下次探討吧!

     chrdev_ioctl()裡面只做兩件事,一個是user space的process透過chrdev_ioctl_data這個資料結構存取裝置的記憶體,令一個就是清除裝置的記憶體內容。chrdev_ioctl_data這是自己定義的一個結構,成員有chrdev_msg結構的數量(num_msg)和chrdev_msg結構的指標(msg),也可以看成一個陣列。而chrdev_msg結構可以把他看成一個message,他的成員有flag(這個message是讀或寫),from(從裝置記憶體哪裡開始讀或寫),len(讀或寫的大小)和一個指向user space的記憶體指標(buf)。

    實際實現這個方法是在chrdev_ioctl_rdwr()這個函式裡面,他會根據chrdev_ioctl_data和chrdev_msg裡面的欄位分配message和buffer的記憶體大小,並且把需要的資料從user space複製到kernel space來,最後就是對裝置的記憶體作讀寫了。如果在處理某個message上出錯了,之前成功的message還是可以被用來讀寫,並且回傳成功的message的數量。

    那如果process同時傳來兩個message都是寫資料到一樣的裝置記憶體位址,有沒有需要作錯誤處理呢?這個問題就牽涉到策略(policy)的部份,而linux開發者希望把有關策略的部份放到user space處理,kernel裡面只留能力(ability)或功能(functionality)的部份。udev也是因為這個原因才被開發出來的。

    有關這個字元裝置的檔案作函式大概就介紹到這邊啦!如果覺得意猶未盡,想要了解更多其他的操作函式,可以參考kernel裡面其他driver的作法,或是上google找找囉!


2014年8月11日 星期一

一個簡單的字元裝置(1)

    這次就讓我們來實作一個字元驅動,可是沒有硬體,是要驅動什麼?沒關係,沒硬體有沒硬體的作法,就拿記憶體來當一個虛擬的裝置吧!雖然讀寫記憶體和讀寫暫存器的方法不一樣,還是可以讓我們學到很多東西的。

    首先必須要先決定這個虛擬的裝置應該長的怎麼樣,下面就是裝置的資料結構:
struct chrdev {
    int id;
    struct device *dev;
};

struct chrdev_inst {
    struct chrdev *c_dev;
    int size;
    char *buf;
};

struct chrdev c_dev = {
    .id = 1,
};
給了這個裝置一個ID,分配的記憶體大小(size),buf這個指標指的就是到時後要存取的記憶體。

    因為這個裝置是實驗性質,不會被編進kernel,是以module的方式存在,所以要建立當下insmodrmmod指令時相對應的載入和卸載函式,其中載入函式如下:

static struct file_operations chrdev_fops = {
    .read = chrdev_read,
    .write = chrdev_write,
    .unlocked_ioctl = chrdev_ioctl,
    .mmap = chrdev_mmap,
    .open = chrdev_open,
    .release = chrdev_release,
};


struct class *chrdev_class;

static struct chrdev_inst *alloc_chrdev_inst(void)
{
    struct chrdev_inst *inst;

    inst = kzalloc(sizeof(struct chrdev_inst), GFP_KERNEL);
    if (inst == NULL)
        goto out;

    inst->buf = kzalloc(BUFFER_SIZE, GFP_KERNEL);
    if (inst->buf == NULL)
        goto free_inst;

    inst->size = BUFFER_SIZE;

    return inst;

free_inst:
    kfree(inst);
out:
    return NULL;
}


int __init chrdev_init(void)
{
    int ret;
    dev_t devno;
    struct chrdev_inst *inst;

    inst = alloc_chrdev_inst();
    if (inst == NULL) {
        ret = -ENOMEM;
        goto out;
    }
    inst->c_dev = &c_dev;

    ret = register_chrdev(chrdev_major, "chrdev", &chrdev_fops);;
    if(ret < 0)
        goto free_inst;

    if (chrdev_major == 0)
        chrdev_major = ret;
    ret = 0;

    chrdev_class = class_create(THIS_MODULE, "chrdev_class");
    if (IS_ERR(chrdev_class)) {
        ret = PTR_ERR(chrdev_class);
        goto release_chrdev;
    }
   
    devno = MKDEV(chrdev_major, c_dev.id);
    c_dev.dev = device_create(chrdev_class, NULL, devno, inst, "%s-%d", "chrdev", c_dev.id);

    return ret;

release_chrdev:
    unregister_chrdev(chrdev_major, "chrdev");
free_inst:
    free_chrdev_inst(inst);
out:
    return ret;
}

module_init(chrdev_init);

這個函式的主要工作:
  1. 分配記憶體給struct chrdev_inst
  2. 為讀寫的區域也分配一個BUFFER_SIZE大小的記憶體
  3. 註冊字元裝置的裝置編號
  4. 建立class和device
    分配記憶體的部份這邊採用kernel提供的kzalloc()的方法,GFP_KERNEL是一個mask, 用來分配一般kernel的記憶體,其他還有GFP_ATOMIC(不會block,可用在interrupt處理函式裡) ,GFP_HIGHUSER(分配高端記憶體)...等等。

    註冊字元裝置的裝置編號用的是register_chrdev()方法,需要傳入主裝置編號,裝置名稱,檔案操作資料結構(struct file_operations)。如果主裝置編號為0,他會自動分配一個沒被使用的主編號,那次要編號咧?register_chrdev()會分配255個次要編號,我們也可以選擇分配一個range的次要編號,那就要改用alloc_chrdev_region()register_chrdev_region(),這兩個函式只單純分配裝置編號,所以字元裝置的結構(struct cdev)需要我們自己處理,不過kernel很好心的提供了cdev_init()cdev_add()...等API讓我們減少工作量。檔案操作資料結構裡面定義了一些callback functions,這就是字元裝置能被當成一般檔案處理的原因了,在驅動裡面這些callback functions需要我們自己實現 。

    為了要讓udev幫我們自動在/dev目錄下面建立一個裝置檔案,所以這邊需要建立class和device兩個資料結構,要建立class的原因是這個device必須屬於某個class或bus才能觸發hotplug,device_create()傳入裝置編號(MKDEV用來產生裝置編號),裝置私有資料(inst), 裝置在/dev目錄下的名稱。其中NULL那一個參數是這個device的parent device,如果是NULL,那他的parent就是virtual,所以我們的device會出現在/sys/devices/virtual/chrdev_class/chrdev-1。

    這邊看到很多地方用了goto,回想以前唸書的時候老師都說不用要用goto,為什麼這邊還用,而且用了一堆!其實整個kernel裡面用的才多咧,不過只要是用在錯誤的處理上,處理得當其實無傷大雅啦!

    接下來還看卸載函式吧!卸載函式如下:

static void free_chrdev_inst(struct chrdev_inst *inst)
{
    kfree(inst->buf);
    kfree(inst);
}


void __exit chrdev_exit(void)
{
    struct chrdev_inst *inst = dev_get_drvdata(c_dev.dev);

    device_destroy(chrdev_class, MKDEV(chrdev_major, c_dev.id));
    class_destroy(chrdev_class);
    unregister_chrdev(chrdev_major, "chrdev");
    free_chrdev_inst(inst);

}

module_exit(chrdev_exit);

卸載函式主要作的事跟載入函式相反,移除device和class,釋放裝置編號,釋放分配的記憶體空間。看完了我們的虛擬字元裝置載入和卸載函式,接下來就是檔案操作函式啦,不過這篇已經有點太長了,下集待續啦!!

2014年8月5日 星期二

sysfs裡的裝置和驅動

    離職後時間變得真多,整天游手好閒也不是辦法,剛好離職前有稍微碰到linux driver的部份,而且之後要找的工作也會是相關的性質,那就來研究一下吧!
    雖然自己的電腦就是用Fedora的作業系統,kernel也不算陌生,時間久了很多事情是會忘記的,而且kernel日新月異,變化之快,以前看過的現在可能又完全不一樣了!為了減輕我這個腦容量不高的人的負擔,用網誌當作紀錄似乎是個好方法。
    好吧!既然要認識linux的裝置和驅動,首先還是要先知道他們會出現在哪裡,答案就是/sys這個目錄裡面了,這個目錄其實還滿複雜的,大概的架構就像下圖,不過我省略了很多東西,沒關係,慢慢來吧!
  

    這張圖的所有方塊都是一個目錄, dev目錄下分為字元裝置和區塊裝置,這兩類的裝置都有自己的裝置編號,而且能夠以一個檔案的方式出現在/dev的目錄下,如此一來可以用對待一般檔案的方式對待他們(其實在linux的世界裡,幾乎大部分的動作都是在存取檔案,例如網路系統的socket),也就是說可以用open,read,write這些方式來開啟和讀寫。
    另外bus目錄下就算是linux裝置和驅動的主要架構,bus_type就是一般所知的PCI,USB,SCSI...這些匯流排,devices和drivers就是依附在這些bus下的裝置和驅動,那如果都不屬於這些bus的會放在哪?他們就會依附在linux的虛擬bus(platform_bus)下。
    除此之外,/sys裡面還有很多其他的目錄,例如devices目錄裡面放置了所有的裝置...等等。大部分這些目錄裡面會有一些檔案,又可以稱為屬性(driver code裡面是這樣宣告的),例如dev屬性紀錄了或區塊裝置的主要編號和次要標號,uevent屬性紀錄了當hotplug時需要傳給udev的一些資訊(有關hotplug的部份如果以後有時間在寫吧),當然我們也可以自己建立需要的屬性,可以透過這些屬性獲取資訊或設定裝置。
    如果想要了解的更仔細,直接打開/sys目錄會是一個好的開始,之後如果有機會遇到相關的情形也會順便拿出來看看。看完一些簡單的架構後,那就來實作一個簡單的字元裝置吧,透過這個裝置可以大致窺探一些細部的架構,以及了解他提供了哪些方法讓user space可以把他們當成一般的檔案對待。