最近公司裁员,本人所在的部门全部被lay off了。 呵呵,闲来无事,一边找工作,一边学习linux driver 相关的知识。最近在学习ARM AMBA 总线设备驱动的相关知识,在网上搜索了一下相关的文章,并不是很多,而且有的文章描述的并不是很清楚,无奈之下,只好硬着头皮,自己HACK一下源码了。以下是本人的一些总结,如有不妥之处,还望各位大虾们多多指教。
1:总线
总线是处理器与一个或多个设备之间的通道,在设备模型中,所有的设备都是通过总线相连。在Linux设备模型中,用bus_type结构表示总线,它定义在<linux/device.h>中:
struct bus_type {
const char * name;
struct subsystem subsys;
struct kset drivers;
struct kset devices;
struct klist klist_devices;
struct klist klist_drivers;
struct blocking_notifier_head bus_notifier;
struct bus_attribute * bus_attrs;
struct device_attribute * dev_attrs;
struct driver_attribute * drv_attrs;
int (*match)(struct device * dev, struct device_driver * drv);
int (*uevent)(struct device *dev, char **envp,
int num_envp, char *buffer, int buffer_size);
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 (*suspend_late)(struct device * dev, pm_message_t state);
int (*resume_early)(struct device * dev);
int (*resume)(struct device * dev);
};
我们注意到,一个总线下面包含了两个kset,分别代表了总线的驱动程序和插入总线的所有设备。
2: AMBA总线的声明与注册
在<driver/amba/bus.c>中有对AMBA总线的声明,如下:
/*
* Primecells are part of the Advanced Microcontroller Bus Architecture,
* so we call the bus "amba".
*/
static struct bus_type amba_bustype = {
.name = "amba",
.match = amba_match,
.uevent = amba_uevent,
.suspend = amba_suspend,
.resume = amba_resume,
};
注意,只有非常少的bus_type成员需要初始化,他们中大部分是由设备模型中心来控制。
对于新的总线,我们必须调用bus_register进行注册:
static int __init amba_init(void)
{
return bus_register(&amba_bustype);
}
如果注册成功,新的总线子系统就会被添加到系统中,可以在sysfs的sys/bus目录下看到它,然后我们就可以向这个总线添加设备了。
3:AMBA总线设备和驱动程序的加载
以上AMBA总线层的驱动程序(这些方法在后面将会有所介绍)在内核中已经写好了,我们要做的工作是如何将自己的AMBA设备以及驱动程序加载到AMBA总线中去。下面,将以AMBA-PL011这个串口设备为例,解释驱动和设备是如何被添加的。
首先我们来看看如何声明一个AMBA总线设备
AMBA设备结构定义在文件<linux/amba/bus.h>中,如下;
struct amba_device {
struct device dev;
struct resource res;
u64 dma_mask;
unsigned int periphid;
unsigned int irq[AMBA_NR_IRQS];
};
例如,我们定义一个串口设备UART0:
static struct amba_device uart0_device = { /
.dev = { /
.coherent_dma_mask = ~0, /
.bus_id = dev:f1, /
.platform_data = NULL, /
}, /
.res = { / //端口资源
.start = UART0_BASE, / //设备起始的物理地址
.end = (UART0_BASE) + SZ_4K - 1,/
.flags = IORESOURCE_MEM, /
}, /
.dma_mask = ~0, /
.irq = UART0_IRQ, / //设备的中断号
}
在Linux系统中每一个设备都用一个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 */
unsigned is_registered:1;
struct device_attribute uevent_attr;
struct device_attribute *devt_attr;
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 */
void *firmware_data; /* Firmware specific data (e.g. ACPI,
BIOS data),reserved for device core*/
struct dev_pm_info power;
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 */
/* class_device migration path */
struct list_head node;
struct class *class; /* optional*/
dev_t devt; /* dev_t, creates the sysfs "dev" */
struct attribute_group **groups; /* optional groups */
void (*release)(struct device * dev);
};
上面的数据结构的成员很多,其中几个成员需要了解一下:
struct device *parent: 该设备所属的设备,如果为NULL,则表示是顶层设备;
char bus_id[BUS_ID_SIZE]: 在总线上唯一表示该设备的字符串;
struct bus_type *bus: 表示该设备连接到了何种类型的总线上;
struct device_driver: 管理该设备的驱动程序。
在定义了一AMBA设备之后,我们需要将此设备注册到总线上,调用如下函数:
int amba_device_register(struct amba_device *dev, struct resource *parent)
此函数定义在<driver/amba/bus.c>中,下面对这个函数做一些详细分析,看看设备是如何挂载到总线上面去的,以上面的设备为例,我们调用接口函数如下:
amba_device_register(&uart0_device, &iomem_resource);
其中,iomem_resource是一IO内存资源,定义如下:
struct resource iomem_resource = {
.name = "PCI mem",
.start = 0,
.end = -1,
.flags = IORESOURCE_MEM,
};
amba_device_register具体分析如下:
/**
* amba_device_register - register an AMBA device
* @dev: AMBA device to register
* @parent: parent memory resource
*
* Setup the AMBA device, reading the cell ID if present.
* Claim the resource, and register the AMBA device with
* the Linux device manager.
*/
int amba_device_register(struct amba_device *dev, struct resource *parent)
{
u32 pid, cid;
void __iomem *tmp;
int i, ret;
dev->dev.release = amba_device_release;
dev->dev.bus = &amba_bustype;//设备挂载的总线为AMBA
dev->dev.dma_mask = &dev->dma_mask;
dev->res.name = dev->dev.bus_id;
if (!dev->dev.coherent_dma_mask && dev->dma_mask)
dev_warn(&dev->dev, "coherent dma mask is unset/n");
ret = request_resource(parent, &dev->res); //检查设备的IO资源是否已经被占用
if (ret == 0) { //下面的ioremap主要是将物理地址空间转换成虚拟地址
tmp = ioremap(dev->res.start, SZ_4K);
if (!tmp) {
ret = -ENOMEM;
goto out;
}
for (pid = 0, i = 0; i < 4; i++) //读取设备的PeriphID
pid |= (readl(tmp + 0xfe0 + 4 * i) & 255) << (i * 8);
for (cid = 0, i = 0; i < 4; i++)//读取设备的PCellID
cid |= (readl(tmp + 0xff0 + 4 * i) & 255) << (i * 8);
//注意,上面的偏址在ARM Periph IP内都是固定的,不相信的话可以查看ARM公司提供的相关外设的IP sepc。
iounmap(tmp);
if (cid == 0xb105f00d)
dev->periphid = pid;
if (dev->periphid)//如果设备的ID不为NULL,就注册设备,否则退出。
//注意,如果不是标准的 ARM Periph IP,而你又想将此设备挂载到AMBA总线上的话,你必须为自己的设备提供一个PeriphID, 并在定义设备的时候初始化这个成员变量。
ret = device_register(&dev->dev); //将设备注册到系统中
else
ret = -ENODEV;
if (ret == 0) {
device_create_file(&dev->dev, &dev_attr_id);
if (dev->irq[0] != NO_IRQ)
device_create_file(&dev->dev, &dev_attr_irq0);
if (dev->irq[1] != NO_IRQ)
device_create_file(&dev->dev, &dev_attr_irq1);
device_create_file(&dev->dev, &dev_attr_resource);
} else {
out:
release_resource(&dev->res);
printfs("error at amba-register/n");
}
}
return ret;
}
设备成功注册后,酒后添加到上面提到的总线设备集合中。在设备注册的过程中,系统要搜索总线上注册的驱动,看看有没有匹配的,如果设备还没有绑定驱动程序,则为其绑定一个。
具体的调用过程如下:
device_register-->>>device_add-->>>bus_attach_device-->>>device_attach-->>>__device_attach
/**
* device_attach - try to attach device to a driver.
* @dev: device.
*
* Walk the list of drivers that the bus has and call
* driver_probe_device() for each pair. If a compatible
* pair is found, break out and return.
*
* Returns 1 if the device was bound to a driver;
* 0 if no matching device was found; error code otherwise.
*
* When called for a USB interface, @dev->parent->sem must be held.
*/
int device_attach(struct device * dev)
{
int ret = 0;
down(&dev->sem);
if (dev->driver) { //如果设备已经有驱动,绑定它
ret = device_bind_driver(dev);
if (ret == 0)
ret = 1;
} else //如果没有,遍历总线上的驱动链表,查找是否有匹配的驱动
ret = bus_for_each_drv(dev->bus, NULL, dev, __device_attach);
up(&dev->sem);
return ret;
};
/**
* driver_probe_device - attempt to bind device & driver together
* @drv: driver to bind a device to
* @dev: device to try to bind to the driver
*
* First, we call the bus's match function, if one present, which should
* compare the device IDs the driver supports with the device IDs of the
* device. Note we don't do this ourselves because we don't know the
* format of the ID structures, nor what is to be considered a match and
* what is not.
*
* This function returns 1 if a match is found, an error if one occurs
* (that is not -ENODEV or -ENXIO), and 0 otherwise.
*
* This function must be called with @dev->sem held. When called for a
* USB interface, @dev->parent->sem must be held as well.
*/
int driver_probe_device(struct device_driver * drv, struct device * dev)
{
struct stupid_thread_structure *data;
struct task_struct *probe_task;
int ret = 0;
if (!device_is_registered(dev))
return -ENODEV;
if (drv->bus->match && !drv->bus->match(dev, drv))//比较设备的ID与驱动的ID是否相等
goto done;
pr_debug("%s: Matched Device %s with Driver %s/n",
drv->bus->name, dev->bus_id, drv->name);
data = kmalloc(sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
data->drv = drv;
data->dev = dev;
printfs("track driver probe/n");
if (drv->multithread_probe) {
probe_task = kthread_run(really_probe, data,
"probe-%s", dev->bus_id);
if (IS_ERR(probe_task))
ret = really_probe(data);
} else
ret = really_probe(data); //如果找到了匹配的驱动,调用驱动的probe方法初始化设备
done:
//printfs("track driver probe: go to done/n");
return ret;
}
转载 : http://blog.csdn/wind_beneath_wings/article/details/3349966
更多推荐
ARM AMBA 总线设备驱动总结
发布评论