2011年4月26日 星期二

如何撰寫Driver支援sysfs檔案系統

前言
在現在Linux的系統中,有一個新的虛擬檔案系統,叫做sysfs,它只要是來管理Linux系統中的週邊元件,及配合使用公用程式來自動產生device node。其實它是一個虛擬的記憶體檔案系統,它是由proc所變化而來的,它是由各個driver所產生,換另一句話就是要Driver有撰寫才會有這樣的功能,它是繼承kobject的資料結構,另外其檔名及內容都是driver自行解決,目前主要都是來設定driver自己本身的一些功能,這樣可以讓驅動程式更多元化,或者可以讀取驅動程式現在的情形為何,這可以來維護系統的運作,現在有很多驅動程式已經支援這個功能,所以有許多的系統管理程式,也是透過這個介面來管理系統核心。現在我們來介紹如何在driver中撰這些程式碼。
如何啓動這個目錄
因為它是一個虛擬的記憶體檔案系統,所以將使用Linux系統中管理檔案系統一樣的方法,就是使用mount的指令將其掛載在作業系統的檔案系統中,以下為單獨載入的指令:
> mount  –t  sysfs  sysfs  /sys
另外在kernel的選項中必須要啓動以下選項:
CONFIG_SYSFS=y
系統驅動程式結構
我們先來了解一下linux kernel其中驅動程式的結構,在linux kernel的驅動程式中會將各種元件給予分門別類,並且在各種類別中會給予建構一個管理層的驅動程式,其主要目的是要建立和應用程式之間的標準介面,並且管理相同類別中的各家不同的硬體廠商,如此一來即可規範硬體廠商如何撰寫廠商,避免因不同的廠商而產生不同的驅動程式的撰寫格式不同,或者造成應用程式的介面,進而造成使用者因不同的廠商而要修改軟體。其架構圖如下:

圖一:驅動程式系統架構
以圖一中來看將會有一個RTC class產生,它將會統籌管理系統中所有的RTC元件,而系統中所有的RTC將會和它來做一個註冊,如此會產生一個RTC的class及其底下的RTC0, RTC1 ….. RTCn,其會在/sys/class之下會建立一個RTC的目錄,而每一個註冊的RTC元件將會在RTC class目錄之下產生RTC0至RTCn的子目錄,而每一個子目錄將會各別產生或者說是繼承RTC class的各個檔案。
Kernel API
在介紹如何撰寫前,先來了解有那些kernel API可供使用,其所用的include file是,而API如下:
struct class *class_create(struct module *owner, const char *name)
這是建立一個模組的類別,其input/output說明如下:
Input :
struct module *owner – 模組的擁有者,通常是設定為THIS_MODULE
const char *name –要建立之類別名稱,之後可以建立在這個類別之下的檔案。
Output :
其返回值為一個class資料結構的位址point,這個資料結構會在後面介紹,這個point位址將會給予保留,因為我們需要在其中幾個call back欄位填入我們要實現的功能,也就是提供給AP如何溝通的介面。
extern struct device *device_create(struct class *cls, struct device *parent, dev_t devt, void *drvdata, const char *fmt, ...) __attribute__((format(printf, 5, 6)));
#define device_create_drvdata   device_create
 這是在該類別之下建立一個週邊元件,也就是建立一個device。
Input :
struct class *cls – 是前一個API所return回來的class pointer。
struct devuce *parent – 為該元件的上一層driver為何若没有可以設定為NULL。
dev_t devt – 這是該元件的major number & minor number的結合,可以使用巨集指令MKDEV()來產生。
const char *fmt – 為該元件的device node名稱。
Output :
其返回值為一個device的資料結構,可讓driver其它地方使用。
資料結構
如前面所提,其中最重要的資料結構宣告如下:
struct class {
        const char              *name;
        struct module           *owner;
        struct class_attribute          *class_attrs;
        struct device_attribute         *dev_attrs;
        struct kobject                  *dev_kobj;
        int (*dev_uevent)(struct device *dev, struct kobj_uevent_env *env);
        void (*class_release)(struct class *class);
        void (*dev_release)(struct device *dev);
        int (*suspend)(struct device *dev, pm_message_t state);
        int (*resume)(struct device *dev);
        struct pm_ops *pm;
        struct class_private *p;
};
其中的成員struct device_attribute *dev_attrs; 是比較重要,這是設定在這類別之下所建立的元件都會繼承這些基設定,而每一個設都可以設定為唯讀或可讀寫,可以參考以下範例得到較清楚的解答。
範例程式
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/spinlock.h>
#include <linux/mutex.h>
#include <linux/tty.h>
#include <linux/proc_fs.h>
#include <linux/timer.h>
#include <linux/sysfs.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <asm/uaccess.h>
#define VICTOR_DEBUG
#include "../include/mydebug.h"
#define MY_MAJOR        40
struct class    *sysfs_class;
static ssize_t
sysfs_show_name(struct device *dev, struct device_attribute *attr, char *buf)
{
        return sprintf(buf, "show name\n");
}
static ssize_t
sysfs_show_date(struct device *dev, struct device_attribute *attr, char *buf)
{
        ssize_t retval;
        retval = sprintf(buf, "show date\n");
        return retval;
}
/*
這是一個讀取的call back function,必需返回共有多少的bytes放在buffer中。
*/
static ssize_t
sysfs_show_time(struct device *dev, struct device_attribute *attr, char *buf)
{
        ssize_t retval;
        retval = sprintf(buf, "show time\n");
        return retval;
}
static ssize_t
sysfs_show_max_user_freq(struct device *dev, struct device_attribute *attr, char *buf)
{
        return sprintf(buf, "show max user freq\n");
}
/*
這是一個寫入的call back functio,一樣必需返回共有多少bytes被處理。
*/
static ssize_t
sysfs_set_max_user_freq(struct device *dev, struct device_attribute *attr, const char *buf, size_t n)
{
        printk("get string = %s\n", buf);
        return n;
}
/*
以下設定中,S_IRUGO表示唯讀而己,S_IWUR表示可以寫入,而相對應的call back function必需要實現,其定義在linux/stat.h之。
*/
static struct device_attribute sysfs_attrs[] = {
        __ATTR(name, S_IRUGO, sysfs_show_name, NULL),
        __ATTR(date, S_IRUGO, sysfs_show_date, NULL),
        __ATTR(time, S_IRUGO, sysfs_show_time, NULL),
        __ATTR(max_user_freq, S_IRUGO | S_IWUSR, sysfs_show_max_user_freq, sysfs_set_max_user_freq),
        { },
};
static int sysfs_suspend(struct device *dev, pm_message_t mesg)
{
        dbg_printk("\n");
        return 0;
}
static int sysfs_resume(struct device *dev)
{
        dbg_printk("\n");
        return 0;
}
static int      __init mysysfs_init(void)
{
        dbg_printk("\n");
        sysfs_class = class_create(THIS_MODULE, "mysysfs");
        if (IS_ERR(sysfs_class)) {
                printk(KERN_ERR "%s: couldn't create class\n", __FILE__);
                return PTR_ERR(sysfs_class);
        }
        dbg_printk("class create OK.\n");
        sysfs_class->suspend = sysfs_suspend;
        sysfs_class->resume = sysfs_resume;
        sysfs_class->dev_attrs = sysfs_attrs;
        device_create_drvdata(sysfs_class, NULL, MKDEV(200, 0), NULL, "mysysfs0");
        return 0;
}
static void     __exit mysysfs_exit(void)
{
        dbg_printk("\n");
        device_destroy(sysfs_class, MKDEV(200, 0));
        class_destroy(sysfs_class);
}
module_init(mysysfs_init);
module_exit(mysysfs_exit);

2011年4月6日 星期三

如何撰寫Driver支援proc檔案系統

前言
在Linux的系統中,通常我們都會有一個目錄 – proc,其實它是一個虛擬的記憶體檔案系統,它通常是一個應用程式和kernel直接溝通的一個介面,它是由各個driver所產生,換另一句話就是要Driver有撰寫才會有這樣的功能,另外其檔名及內容都是driver自行解決,目前主要都是來設定driver自己本身的一些功能,這樣可以讓驅動程式更多元化,或者可以讀取驅動程式現在的情形為何,這可以來維護系統的運作,現在有很多驅動程式已經支援這個功能,所以有許多的系統管理程式,也是透過這個介面來管理系統核心。現在我們來介紹如何在driver中撰這些程式碼。
如何啓動這個目錄
因為它是一個虛擬的記憶體檔案系統,所以將使用Linux系統中管理檔案系統一樣的方法,就是使用mount的指令將其掛載在作業系統的檔案系統中,以下為單獨載入的指令:
> mount –t proc proc /porc
另外在kernel的選項中必須要啓動以下選項:
CONFIG_PROC_FS=y
CONFIG_PROC_SYSCTL=y
Kernel API
在介紹如何撰寫前,先來了解有那些kernel API可供使用,其所用的include file是,而API如下:
struct proc_dir_entry *create_proc_entry(const char *name, mode_t mode, struct proc_dir_entry *parent)
這是建立一個在相對目錄之下的一個檔名或目錄,其input/output說明如下:
Input :
const char *name – 想要建立的檔名,相對於後面的parent的目錄之下,若後面的parent為NULL,則其相對目錄為/proc之下,另外這個檔名也可以包含目錄。
mode_t mode – 前面所要建立之名稱為檔案或者是目錄,0為檔案,S_IFDIR為目錄。
struct proc_dir_entry *parent – 這次要建立的檔名或者目錄,其上層的目錄為何,若這個參數為NULL則表示為/proc目錄。
Output :
其返回值為一個資料結構的位址point,這個資料結構會在後面介紹,這個point位址將會給予保留,因為我們需要在其中幾個call back欄位填入我們要實現的功能,也就是提供給AP如何溝通的介面。
void remove_proc_entry(const char *name, struct proc_dir_entry *parent);
這是移除已建立的檔案或目錄,其中輸入的參數皆和建立時相同。
以下為一個簡單的範例:
struct proc_dir_entry *MyProcEntry;
MyProcEntry = create_proc_entry(“driver/myproc”, 0, NULL);
MyProcEntry->read = MyProcRead;
MyProcEntry->write = MyProcWrite;
……………………………….
remove_proc_entry(“driver/myproc”, NULL);
資料結構
如前面所提,其中最重要的資料結構宣告如下:
struct proc_dir_entry {
        unsigned int low_ino;
        unsigned short namelen;
        const char *name;
        mode_t mode;
        nlink_t nlink;
        uid_t uid;
        gid_t gid;
        loff_t size;
        const struct inode_operations *proc_iops;
        /*
         * NULL ->proc_fops means "PDE is going away RSN" or
         * "PDE is just created". In either case, e.g. ->read_proc won't be
         * called because it's too late or too early, respectively.
         *
         * If you're allocating ->proc_fops dynamically, save a pointer
         * somewhere.
         */
        const struct file_operations *proc_fops;
        struct proc_dir_entry *next, *parent, *subdir;
        void *data;
        read_proc_t *read_proc;
        write_proc_t *write_proc;
        atomic_t count;         /* use count */
        int pde_users;  /* number of callers into module in progress */
        spinlock_t pde_unload_lock; /* proc_fops checks and pde_users bumps */
        struct completion *pde_unload_completion;
        struct list_head pde_openers;   /* who did ->open, but not ->release */
};
在這個資料結構中有兩個重要的call back function,一個是read_proc,另一個是write_proc,其實就是寫入及讀出。以下為其簡單的範例:
static int      my_read_proc(char *page, char **start, off_t off, int count, int *eof, void *data)
{
        char    *out = page;
        int     len;
        dbg_printk("page=0x%p,off=0x%x,count=%d\n", page, off, count);
        out += sprintf(out, "This is a read proc testing.\n");
        dbg_printk("out=0x%p\n", out);
        len = out - page - off;
        dbg_printk("len=%d,out=0x%p\n", len, out);
        if (len < count)  {
                *eof = 1;
                if (len <= 0)
                        return 0;
        }  else
                len = count;
        *start = page + off;
        dbg_printk("start=0x%p\n", start);
        return len;
}
static int      my_write_proc(struct file *file, const char __user *buffer, unsigned long count, void *data)
{
        char    *buf;
        dbg_printk("count=%d\n", count);
        buf = kmalloc(count+1, GFP_KERNEL);
        if ( copy_from_user(buf, (char*)buffer , count) ) {
                kfree(buf);
                return -EFAULT;
        }
        buf[count] = 0;
        dbg_printk("string=%s\n", buf);
        kfree(buf);
        return count;
}
在read call back function中所傳入的page為要存放輸出內容的buffer所在page address,其off為相對於page的offset位置,通常為0,不管read or write其所return的值為實際處理的大小,因為其處理方式為page的方式,所以一次可處理的資料不會大於一個page的大小,現在default值為4096bytes,以上的範例若是成功將可以產生一個虛擬檔案/proc/driver/myproc,你將可以使用簡單的echo & cat來對其做讀寫的動作。