首先介紹一下註冊一個驅動的步驟:
1、定義一個platform_driver架構
2、起始化這個架構,指定其probe、remove等函數,并起始化其中的driver變數
3、實現其probe、remove等函數
看platform_driver架構,定義于include/linux/platform_device.h檔案中:
struct platform_driver {
int (*probe)(struct platform_device *);
int (*remove)(struct platform_device *);
void (*shutdown)(struct platform_device *);
int (*suspend)(struct platform_device *, pm_message_t state);
int (*suspend_late)(struct platform_device *, pm_message_t state);
int (*resume_early)(struct platform_device *);
int (*resume)(struct platform_device *);
struct device_driver driver;
};
可見,它包含了裝置作業的几個功能函數,同樣重要的是,它還包含了一個device_driver架構。剛才提到了驅動程式中需要起始化這個變數。下面看一下這個變數的定義,位于include/linux/device.h中:
struct device_driver {
const char * name;
struct bus_type * bus;
struct kobject kobj;
struct klist klist_devices;
struct klist_node knode_bus;
struct module * owner;
const char * mod_name; /* used for built-in modules */
struct module_kobject * mkobj;
int (*probe) (struct device * dev);
int (*remove) (struct device * dev);
void (*shutdown) (struct device * dev);
int (*suspend) (struct device * dev, pm_message_t state);
int (*resume) (struct device * dev);
};
需要注意這兩個變數:name和owner。那么的作用主要是為了和相關的platform_device關聯起來,owner的作用是說明模組的所有者,驅動程式中一般起始化為THIS_MODULE。
下面是一個platform_driver的起始化實例:
static struct platform_driver s3c2410iis_driver = {
.probe = s3c2410iis_probe,
.remove = s3c2410iis_remove,
.driver = {
.name = "s3c2410-iis",
.owner = THIS_MODULE,
},
};
上面的起始化是一個聲訊驅動的實例。注意其中的driver這個架構體,只起始化了其name和owner兩個量。接着看一下和driver相關的另一個架構,定義如下:
struct platform_device {
const char * name;
int id;
struct device dev;
u32 num_resources;
struct resource * resource;
};
該架構中也有一個name變數。platform_driver從字面上來看就知道是裝置驅動。裝置驅動是為誰服務的呢?當然是裝置了。platform_device就說明了裝置物件。下面是一個具體的實例:
struct platform_device s3c_device_iis = {
.name = "s3c2410-iis",
.id = -1,
.num_resources = ARRAY_SIZE(s3c_iis_resource),
.resource = s3c_iis_resource,
.dev = {
.dma_mask = &s3c_device_iis_dmamask,
.coherent_dma_mask = 0xffffffffUL
}
};
它的name變數和剛才上面的platform_driver的name變數是一致的,核心正是通過這個一致性來為驅動程式找到資源,即platform_device中的resource。這個架構的定義如下,位于include/linux/ioport.h中:
struct resource {
resource_size_t start;
resource_size_t end;
const char *name;
unsigned long flags;
struct resource *parent, *sibling, *child;
};
下面是一個具體的實例:
static struct resource s3c_iis_resource[] = {
[0] = {
.start = S3C24XX_PA_IIS,
.end = S3C24XX_PA_IIS + S3C24XX_SZ_IIS -1,
.flags = IORESOURCE_MEM,
}
};
這個架構的作用就是告訴驅動程式裝置的起始位址和終止位址和裝置的通訊埠類別。這裡的位址指的是實體位址。
另外還需要注意platform_device中的device架構,它詳細說明了裝置的情況,定義如下:
struct device {
struct klist klist_children;
struct klist_node knode_parent; /* node in sibling list */
struct klist_node knode_driver;
struct klist_node knode_bus;
struct device *parent;
struct kobject kobj;
char bus_id[BUS_ID_SIZE]; /* position on parent bus */
struct device_type *type;
unsigned is_registered:1;
unsigned uevent_suppress:1;
struct semaphore sem; /* semaphore to synchronize calls to
* its driver.
*/
struct bus_type * bus; /* type of bus device is on */
struct device_driver *driver; /* which driver has allocated this
device */
void *driver_data; /* data private to the driver */
void *platform_data; /* Platform specific data, device
core doesn't touch it */
struct dev_pm_info power;
#ifdef CONFIG_NUMA
int numa_node; /* NUMA node this device is close to */
#endif
u64 *dma_mask; /* dma mask (if dma'able device) */
u64 coherent_dma_mask;/* Like dma_mask, but for
alloc_coherent mappings as
not all hardware supports
64 bit addresses for consistent
allocations such descriptors. */
struct list_head dma_pools; /* dma pools (if dma'ble) */
struct dma_coherent_mem *dma_mem; /* internal for coherent mem
override */
/* arch specific additions */
struct dev_archdata archdata;
spinlock_t devres_lock;
struct list_head devres_head;
/* class_device migration path */
struct list_head node;
struct class *class;
dev_t devt; /* dev_t, creates the sysfs "dev" */
struct attribute_group **groups; /* optional groups */
void (*release)(struct device * dev);
};
上面把驅動程式中涉及到的主要架構都介紹了,下面主要說一下驅動程式中怎樣對這個架構進行處理,以使驅動程式能執行。
相信大家都知道module_init()這個巨集。驅動模組加載的時候會呼叫這個巨集。它接收一個函數為參數,作為它的參數的函數將會對上面提到的platform_driver進行處理。看一個實例:假如這裡module_init要接收的參數為s3c2410_uda1341_init這個函數,下面是這個函數的定義:
static int __init s3c2410_uda1341_init(void) {
memzero(&input_stream, sizeof(audio_stream_t));
memzero(&output_stream, sizeof(audio_stream_t));
return platform_driver_register(&s3c2410iis_driver);
}
注意函數體的最后一行,它呼叫的是platform_driver_register這個函數。這個函數定義于driver/base/platform.c中,原型如下:
int platform_driver_register(struct platform_driver *drv)
它的功能就是為上面提到的plarform_driver中的driver這個架構中的probe、remove這些變數指定功能函數。
到目前為止,核心就已經知道了有這么一個驅動模組。核心啟動的時候,就會呼叫與該驅動相關的probe函數。我們來看一下probe函數實現了什么功能。
probe函數的原型為
int xxx_probe(struct platform_device *pdev)
即它的傳回類別為int,接收一個platform_device類別的指標作為參數。傳回類別就是我們熟悉的錯誤程式碼了,而接收的這個參數呢,我們上面已經說過,驅動程式為裝置服務,就需要知道裝置的資訊。而這個參數,就包含了與裝置相關的資訊。
probe函數接收到plarform_device這個參數后,就需要從中擷取出需要的資訊。它一般會通過呼叫核心提供的platform_get_resource和platform_get_irq等函數來獲得相關資訊。如通過platform_get_resource獲得裝置的起始位址后,可以對其進行request_mem_region和ioremap等作業,以便應用程式對其進行作業。通過platform_get_irq得到裝置的中斷號以后,就可以呼叫request_irq函數來向系統申請中斷。這些作業在裝置驅動程式中一般都要完成。
在完成了上面這些工作和一些其他必須的起始化作業后,就可以向系統註冊我們在/dev目錄下能看在的裝置檔案了。舉一個例子,在聲訊晶片的驅動中,就可以呼叫register_sound_dsp來註冊一個dsp裝置檔案,lcd的驅動中就可以呼叫register_framebuffer來註冊fb裝置檔案。這個工作完成以后,系統中就有我們需要的裝置檔案了。而和裝置檔案相關的作業都是通過一個file_operations 來實現的。在呼叫register_sound_dsp等函數的時候,就需要傳遞一個file_operations 類別的指標。這個指標就提供了可以供用戶空間呼叫的write、read等函數。file_operations架構的定義位于include/linux/fs.h中,列出如下:
struct file_operations {
struct module *owner;
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
int (*readdir) (struct file *, void *, filldir_t);
unsigned int (*poll) (struct file *, struct poll_table_struct *);
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
int (*open) (struct inode *, struct file *);
int (*flush) (struct file *, fl_owner_t id);
int (*release) (struct inode *, struct file *);
int (*fsync) (struct file *, struct dentry *, int datasync);
int (*aio_fsync) (struct kiocb *, int datasync);
int (*fasync) (int, struct file *, int);
int (*lock) (struct file *, int, struct file_lock *);
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
int (*check_flags)(int);
int (*dir_notify)(struct file *filp, unsigned long arg);
int (*flock) (struct file *, int, struct file_lock *);
ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
int (*setlease)(struct file *, long, struct file_lock **);
};
到目前為止,probe函數的功能就完成了。
當用戶開啟一個裝置,并呼叫其read、write等函數的時候,就可以通過上面的file_operations來找到相關的函數。所以,用戶驅動程式還需要實現這些函數,具體實現和相關的裝置有密切的關係,這裡就不再介紹了。
留言列表