由于工作的需要,需要去看emmc驱动的代码,根据我的学习流程,先总结一下对驱动架构的了解。
一、构造和运行模块
1.1、模块的加载和卸载
Linux有许多功能是通过模块的方式,在需要时才载入kernel,如此可使kernel较为精简,进而提高效率,以及保有较大的弹性。这类可载入的模块,通常是设备驱动程序。
insmod:加载模块,需要指定完整的路径和模块名字
modprobe:加载有依赖的模块,根据depmod -a的输出/lib/modules/version/modules.dep来加载全部的所需要模块。
rmmod:卸载模块。
1.2、模块初始化和清除
二、总线
总线是处理器和设备之间的通道,在设备模型中,所有的设备都通过总线相连,以总线来管理设备和驱动函数。
2.1、总线结构:
struct bus_type {
const char *name;
const char *dev_name;
struct device *dev_root;
struct device_attribute *dev_attrs; /* use dev_groups instead */
const struct attribute_group **bus_groups;
const struct attribute_group **dev_groups;
const struct attribute_group **drv_groups;
int (*match)(struct device *dev, struct device_driver *drv);
int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
int (*probe)(struct device *dev);
int (*remove)(struct device *dev);
void (*shutdown)(struct device *dev);
int (*online)(struct device *dev);
int (*offline)(struct device *dev);
int (*suspend)(struct device *dev, pm_message_t state);
int (*resume)(struct device *dev);
const struct dev_pm_ops *pm;
struct iommu_ops *iommu_ops;
struct subsys_private *p;
struct lock_class_key lock_key;
};
介绍几个比较关键的成员变量:
name:总线的名字,总线注册后,该名字会出现在"/sys/bus/"目录下;
函数:http://blog.csdn.net/cppgp/article/details/6333359
match:判定设备和驱动是否匹配(根据设备和驱动结构体中的总线名是否一致进行判断),是总线体系相关的;
uevent:在发送热插拔事件消息到用户空间之前添加环境变量。
probe:当match成功后,设备通过此函数来执行设备相关的匹配探测、设备初始化、资源分配等。(http://blog.csdn.net/zirconsdu/article/details/8792184)
remove:移除设备;
2.1、总线注册和注销
int bus_register(struct bus_type *bus);
注册总线,总线注册后,在/sys/bus目录下会显示注册的总线的名称,就是bus_type中的name;
void bus_unregister(struct bus_type *bus)
注销总线,清除在/sys/bus下对应的目录。(如果已经在该总线上注册了设备或者驱动,需要先注销设备和驱动)
三、块设备
3.1、设备结构体
struct device {
struct device *parent;
struct device_private *p;
struct kobject kobj;
const char *init_name; /* initial name of the device */
const struct device_type *type;
struct mutex mutex; /* mutex 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 *platform_data; /* Platform specific data, device
core doesn't touch it */
struct dev_pm_info power;
struct dev_pm_domain *pm_domain;
#ifdef CONFIG_PINCTRL
struct dev_pin_info *pins;
#endif
#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 device_dma_parameters *dma_parms;
struct list_head dma_pools; /* dma pools (if dma'ble) */
struct dma_coherent_mem *dma_mem; /* internal for coherent mem
override */
#ifdef CONFIG_DMA_CMA
struct cma *cma_area; /* contiguous memory area for dma
allocations */
#endif
/* arch specific additions */
struct dev_archdata archdata;
struct device_node *of_node; /* associated device tree node */
struct acpi_dev_node acpi_node; /* associated ACPI device node */
dev_t devt; /* dev_t, creates the sysfs "dev" */
u32 id; /* device instance */
spinlock_t devres_lock;
struct list_head devres_head;
struct klist_node knode_class;
struct class *class;
const struct attribute_group **groups; /* optional groups */
void (*release)(struct device *dev);
struct iommu_group *iommu_group;
bool offline_disabled:1;
bool offline:1;
};
这么长一个结构体,要搞清楚每个字段的意思也太难了,当前我们只关注两个字段:
const char *init_name; /* initial name of the device */// 设备的名称
struct bus_type *bus;/* type of bus device is on */// 设备的总线
3.2、块设备的注册和注销
int register_blkdev(unsigned int major, const char *name)
该函数用来向内核注册自己。
参数是该设备的主设备号及其名字(内核在/proc/devices中显示的名字)。如果传递的主设备号是0,内核将分派一个新的主设备号给设备,并将该设备号返回给调用者,如果返回一个负值,说明发生了错误。在linux设备驱动程序一书中指出:在内核2.6中,对register_blkdev的调用时完全可选的,该函数所执行的功能随时间的推移而越来越少。 事实上,应该比较核心的向内核注册设备的工作应该是放在add_disk函数中实现,在第3.3节中描述该函数。
与register_blkdev对应的注销块设备的驱动程序的函数是:
void unregister_blkdev(unsigned int major, const char *name)
这里,参数必须传递与传递给register_blkdev的参数相匹配,否则函数返回-EINVAL,且不做任何注销工作。
3.3、注册磁盘
void add_disk(struct gendisk *disk)
一旦调用了add_disk,磁盘设备将被“激活”,并随时会调用
它提供的方法。实际上第一次对这些方法的调用可能在add_disk返回前就发生了,这是因为,内核可能会读取前面几个块的数据以获得分区表。因此在驱动程序完全被初始化并且能够影响应对磁盘的请求前,请不要调用add_disk.
那么它提供了哪些方法呢?此时我们看一下gendisk是个什么东西。
struct gendisk {
/* major, first_minor and minors are input parameters only,
* don't use directly. Use disk_devt() and disk_max_parts().
*/
int major; /* major number of driver */
int first_minor;
int minors; /* maximum number of minors, =1 for
* disks that can't be partitioned. */
char disk_name[DISK_NAME_LEN]; /* name of major driver */
char *(*devnode)(struct gendisk *gd, umode_t *mode);
unsigned int events; /* supported events */
unsigned int async_events; /* async events, subset of all */
/* Array of pointers to partitions indexed by partno.
* Protected with matching bdev lock but stat and other
* non-critical accesses use RCU. Always access through
* helpers.
*/
struct disk_part_tbl __rcu *part_tbl;
struct hd_struct part0;
const struct block_device_operations *fops;
struct request_queue *queue;
void *private_data;
int flags;
struct device *driverfs_dev; // FIXME: remove
struct kobject *slave_dir;
struct timer_rand_state *random;
atomic_t sync_io; /* RAID */
struct disk_events *ev;
#ifdef CONFIG_BLK_DEV_INTEGRITY
struct blk_integrity *integrity;
#endif
int node_id;
};
它的前面几个成员变量的作用,通过注释大概明白它的意思,这里,我们主要关注一下以下几个:
3.3.1、block_device_operations
const struct block_device_operations *fops;
该结构体源码如下:
struct block_device_operations {
int (*open) (struct block_device *, fmode_t);
void (*release) (struct gendisk *, fmode_t);
int (*ioctl) (struct block_device *, fmode_t, unsigned, unsigned long);
int (*compat_ioctl) (struct block_device *, fmode_t, unsigned, unsigned long);
int (*direct_access) (struct block_device *, sector_t,
void **, unsigned long *);
unsigned int (*check_events) (struct gendisk *disk,
unsigned int clearing);
/* ->media_changed() is DEPRECATED, use ->check_events() instead */
int (*media_changed) (struct gendisk *);
void (*unlock_native_capacity) (struct gendisk *);
int (*revalidate_disk) (struct gendisk *);
int (*getgeo)(struct block_device *, struct hd_geometry *);
/* this callback is with swap_lock and sometimes page table lock held */
void (*swap_slot_free_notify) (struct block_device *, unsigned long);
struct module *owner;
};
该结构体用来告诉系统块设备提供的操作接口,而字符设备对应的结构体为file_operations,有兴趣的自己理解。
这里简单介绍一下个函数的作用:
int (*open) (struct block_device *, fmode_t);
void (*release) (struct gendisk *, fmode_t);
当设备被打开或者关闭时调用它们。一个块设备驱动程序可能用旋转盘片、锁住仓门等来响应open调用。
如果用户将介质放入设备中锁住,那么在release函数中当然要进行解锁。
int (*ioctl) (struct block_device *, fmode_t, unsigned, unsigned long);
实现ioctl系统调用的函数。块设备层会首先截取大量的标准请求,因此大多数设备的ioctl函数都十分短小。
int (*media_changed) (struct gendisk *);
判断驱动器内的介质(可移动介质)是否更换,如果更换返回一个非0值。
int (*revalidate_disk) (struct gendisk *);
当介质被更换时,调用该函数做出响应;它会告诉驱动程序完成必要的工作,以便使用新的介质。返回值被内核忽略。
struct module *owner;
一个指向拥有该结构的模块指针,通常它都被初始化为THIS_MODULE.
这里我们是不是有疑问,到底是哪个函数在负责读和写数据的功能呢?
再回到gendisk结构体,并关注以下成员变量
3.3.2、request_queue
struct request_queue *queue;
内核使用该结构为设备管理I/O请求;
3.3.3、blk_init_queue
struct request_queue *blk_init_queue(request_fn_proc *rfn, spinlock_t *lock)
该函数用来申请一个消息对列。
3.3.4、单个请求介绍
单个请求可以是:
a、向磁盘读出写入数据的请求;
b、请求生产商信息;
c、底层诊断操作;
d、特殊设备模式相关的指令:对可记录介质的写模式的设定等。
结构体如下:
struct request {
union {
struct list_head queuelist;
struct llist_node ll_list;
};
union {
struct call_single_data csd;
struct work_struct mq_flush_data;
};
struct request_queue *q;
struct blk_mq_ctx *mq_ctx;
u64 cmd_flags;
enum rq_cmd_type_bits cmd_type;
unsigned long atomic_flags;
int cpu;
/* the following two fields are internal, NEVER access directly */
unsigned int __data_len; /* total data len */
sector_t __sector; /* sector cursor */
struct bio *bio;
struct bio *biotail;
struct hlist_node hash; /* merge hash */
/*
* The rb_node is only used inside the io scheduler, requests
* are pruned when moved to the dispatch queue. So let the
* completion_data share space with the rb_node.
*/
union {
struct rb_node rb_node; /* sort/lookup */
void *completion_data;
};
/*
* Three pointers are available for the IO schedulers, if they need
* more they have to dynamically allocate it. Flush requests are
* never put on the IO scheduler. So let the flush fields share
* space with the elevator data.
*/
union {
struct {
struct io_cq *icq;
void *priv[2];
} elv;
struct {
unsigned int seq;
struct list_head list;
rq_end_io_fn *saved_end_io;
} flush;
};
struct gendisk *rq_disk;
struct hd_struct *part;
unsigned long start_time;
#ifdef CONFIG_BLK_CGROUP
struct request_list *rl; /* rl this rq is alloced from */
unsigned long long start_time_ns;
unsigned long long io_start_time_ns; /* when passed to hardware */
#endif
/* Number of scatter-gather DMA addr+len pairs after
* physical address coalescing is performed.
*/
unsigned short nr_phys_segments;
#if defined(CONFIG_BLK_DEV_INTEGRITY)
unsigned short nr_integrity_segments;
#endif
unsigned short ioprio;
void *special; /* opaque pointer available for LLD use */
char *buffer; /* kaddr of the current segment if available */
int tag;
int errors;
/*
* when request is used as a packet command carrier
*/
unsigned char __cmd[BLK_MAX_CDB];
unsigned char *cmd;
unsigned short cmd_len;
unsigned int extra_len; /* length of alignment and padding */
unsigned int sense_len;
unsigned int resid_len; /* residual count */
void *sense;
unsigned long deadline;
struct list_head timeout_list;
unsigned int timeout;
int retries;
/*
* completion callback.
*/
rq_end_io_fn *end_io;
void *end_io_data;
/* for bidi */
struct request *next_rq;
};
引出这个结构体的目的是为了重点说明该结构体里面有一个重要的变量:
struct bio *bio;
该结构体是linux内核中通用块层的一个核心数据结构,它描述了块设备的I/O操作。它联系了内存缓冲区与块设备。
详细介绍请参考: http://blog.chinaunix.net/uid-13245160-id-84372.html四、驱动
在前面的块设备那一节中,后面讲的关于block_device_operations和请求对列已经输入驱动的范畴,在这里主要谈一下关于驱动的注册和注销以及驱动结构体。
4.1、驱动结构体
struct device_driver {
const char *name;
struct bus_type *bus;
struct module *owner;
const char *mod_name; /* used for built-in modules */
bool suppress_bind_attrs; /* disables bind/unbind via sysfs */
const struct of_device_id *of_match_table;
const struct acpi_device_id *acpi_match_table;
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);
const struct attribute_group **groups;
const struct dev_pm_ops *pm;
struct driver_private *p;
};
name:驱动的名字,如果要和设备匹配,该字段的值一般需要与设备结构体的init_name名称相匹配(其实,取决于总线的match函数)。
bus:注册到哪条总线上
其它请参考:http://www.cnblogs.com/armlinux/archive/2010/09/20/2396909.html
4.2、驱动的注册和注销
int driver_register(struct device_driver *drv)
void driver_unregister(struct device_driver *drv)
五、总线、设备、驱动的关联
前面描述了总线、设备和驱动的一些基本的知识,现在我们来理清一下三者之间的关系。当该驱动或者驱动函数正在操作的设备被移除时,内核会调用驱动函数中的remove函数调用,进行一些设备卸载相应的操作。
该函数定义(参考4.1)。
关于match和probe的执行流程请参考:http://blog.chinaunix.net/xmlrpc.php?r=blog/article&uid=28758680&id=4561036
更多资料请参考:http://blog.chinaunix.net/uid-25014876-id-110295.html