Linux 设备驱动 //2018.03.22 Dream
目录:
一.字符设备驱动
二.Linux设备驱动并发控制
三.platform总线,设备与驱动
四.i2c体系结构
五.Device tree
附:
为什么要用copy_xx_user 函数?
/****************************************************************************************************************************/
一.字符设备驱动
1.在incude\linux cdev结构体
(1)cdev.h 中定义
struct cdev {
struct kobject kobj; /* kobj对象 */
struct module *owner; /* 所属模块 */
const struct file_operations *ops; /* 文件操作结构体 */
struct list_head list;
dev_t dev; /* 设备号 前12位为主设备号,后20次*/
unsigned int count;
} __randomize_layout;次
(2)//获取设备号宏定义在 kdev.h中
#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))
#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))
#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))
2.设备的注册
(1)void cdev_init(struct cdev *, const struct file_operations *);
//初始化dev结构体,建立file_operations与dev之间的关系
//在linux /dev目录下的字符设备文件
(2)注册和释放设备
int cdev_add(struct cdev *, dev_t, unsigned count);//注册设备
//注册之前需要向系统申请设备号
int register_chrdev_region(dev_t from, unsigned count, const char *name);//静态申请
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,const char *name);//动态申请,系统自动分配一个未被占用的设备号
//释放设备号
unregister_chrdev_region(dev_t from,unsigned count);
//释放设备
void cdev_del(struct cdev *);
3.globalmem虚拟驱动设备 //2018.3.23
4.一般linux中注册的设备都是挂在总线下的,比如i2c,usb,spi等等。对于依附于i2c,usb等总线的设备来说,这自然是挂在所依附的总线下。但是还有一些设备不是依赖于
上述总线,所以linux 为了维护设备,驱动,总线这种结构,虚拟除了platfrom总线,有些字符设备就是挂在次总线下,当然注册该驱动设备的时候,工程师也可以不让这些设备
依附于platfrom总线,就像globalmm虚拟设备一样。。要把他挂在platfrom总线下,至于要套一层platfrom外壳,可以参考globalfifo挂在platfrom下的实例。
/****************************************************************************************************************************/
二.Linux设备驱动并发控制
概念:由于多个执行单元同时对驱动设备的访问,就可能发生竞争态。导致数据读取,写入出现bug。
1.使用原子变量使设备只能被一个进程打开。
(1)初始化原子变量
atomic_t v = ATOMIC_INIT(0);//定义并初始化为0,还有其他操作自行查找
(2)程序中的用法
static int xxx_open(struct inode *inode,struct file *filp)
{
if(!atomic_dec_and_test(&v)){//自减一,并测试是否为0,为0则范围ture。
atomic_inc(&v);//已经被打开,上面减一,加回来。
return -EBUSY;
}
return 0;
}
static int globalmem_release(struct inode *inode,struct file *filp)
{
atomic_inc(&v);//释放设备
return 0;
}
(3)自旋锁
spinlock_t lock;//定义自旋锁
spin_lock_init(&lock);//初始化
...
spin_lock(&lock);//获得自旋锁,立即获得,则立马返回否则一直自旋知道获得锁为止
//or spin_trylock(&lock);//获得与否立即返回。
.../* 临界区*/
spin_unlock(&lcok);//释放锁
自旋锁主要针对SMP(多核并发抢站)和单核但内核可抢占的情况,尽管用了自旋锁可以保护临界区不受别的CPU或者本cpu其他进程抢占问题
但是还可能收到中断的影响所以此时要用到衍生自旋锁:
spin_lock_irq() = spin_lock() + local_irq_disable();
spin_unlock_irq() = spin_unlock() + local_irq_enable();
spin_lock_irqsave() = spin_unlock() + local_irq_save();//与disable的不同之处在与屏蔽中断的时候会保存cpu中断位信息
spin_lock_irqrestron() = spin_unlock() + local_irq_restron();//进行与save相反操作
在多核编程时要在在进程上下文中调用spin_lock_irqsave()/spin_lock_irqrestron(),在中断上下文中调用spin_lock()/
spin_unlock();可以避免多核中断抢占问题。
三.platform总线,设备与驱动 /* 2018.3.28 */
1. platform模型驱动编程,需要实现 platform_device(设备)与 platform_driver(驱动)在platform(虚拟总线)上的注册、匹配,相互绑定,
然后再做为一个普通的字符设备进行相应的应用,总之如果编写的是基于字符设备的platform驱动,在遵循并实现platform总线上驱动与设备的特定
接口的情况下,最核心的还是字符设备的核心结构:cdev、 file_operations(他包含的操作函数接口)、dev_t(设备号)、设备文件(/dev)等,因为
用platform机制编写的字符驱动,它的本质是字符驱动。
(1) struct platform_device /* 设备(硬件部分):中断号,寄存器,DMA等 */
struct platform_device {
const char *name; 名字
int id;
bool id_auto;
struct device dev; 硬件模块必须包含该结构体
u32 num_resources; 资源个数
struct resource *resource; 资源
const struct platform_device_id *id_entry;
/* arch specific additions */
struct pdev_archdata archdata;
};
●整个调用流程 /* 2018.03.29 添加 */
platform_device_register()
platform_device_add
device_add
bus_add_device() & bus_probe_device() /* device_add函数中有两个中要的函数 下面会详细介绍*/
device_initial_probe() /* 实际调用 __device_attach() */
__device_attach
bus_for_each_drv(dev->bus, NULL, &data,__device_attach_driver);
__device_attach_driver()
● device_add() 分析
int device_add(struct device *dev){
error = bus_add_device(dev);/* 将设备加入到管理它的bus总线的设备连表上 创建subsystem链接文件,
链接class下的具体的子系统文件夹 */
bus_probe_device(dev);/* 给设备探测相应的驱动开始寻找设备所对应的驱动-去bus上找dev对应的drv,主要
执行__device_attach,主要进行match,sys_add,执行probe函数和绑定等操作 */
...
}
● bus_add_device(dev) & bus_probe_device(dev) 分析
int bus_add_device(struct device *dev)
{
/* 引用计数加一 */
struct bus_type *bus =bus_get(dev->bus);
if (bus) {
/* 创建相应的属性文件 */
error = device_add_attrs(bus,dev);
/* 在sys/bus/总线类型/devices/dev_name()dev 在devices目录下创建名字为devname(d)
指向sys/devices/相同设备名字的 符号链接*/
error =sysfs_create_link(&bus->p->devices_kset->kobj,
&dev->kobj,dev_name(dev));
/* 在sys/devices/设备名字/目录下创建目录名字为
subsystem 并且指向在sys/bus/总线类型/devices/
de符号链接*/
error =sysfs_create_link(&dev->kobj,
&dev->bus->p->subsys.kobj,"subsystem");
/* 把设备加入到总线的设备链中,这步才是重点*/
klist_add_tail(&dev->p->knode_bus,&bus->p->klist_devices);
}
}
void bus_probe_device(struct device *dev)
{
struct bus_type *bus = dev->bus; /* 获得设备所依附的总线,在初始化时会用*/
if (bus->p->drivers_autoprobe)
device_initial_probe(dev); /* 实际调用__device_attach,*/
...
}
● __device_attach() 分析
static int __device_attach(struct device *dev, bool allow_async)
{
int ret = 0;
device_lock(dev);
if (dev->driver) { /* 先确定device 是否已经有依赖的driver , 有直接绑定*/
if (device_is_bound(dev)) {
ret = 1;
goto out_unlock;
}
ret = device_bind_driver(dev); /* device 和 driver 绑定*/
if (ret == 0)
ret = 1;
else {
dev->driver = NULL;
ret = 0;
}
} else {
...
if (dev->parent)
pm_runtime_get_sync(dev->parent);
ret = bus_for_each_drv(dev->bus, NULL, &data,
__device_attach_driver); /* 没有依赖的driver 在依附的总线上查找 通过 __device_attach_driver */
...
}
● bus_for_each_drv && __device_attach_driver 分析
int bus_for_each_drv(struct bus_type *bus, struct device_driver *start,
void *data, int (*fn)(struct device_driver *, void *))
{
while ((drv = next_driver(&i)) && !error) //-------对于总线中的每个driver调用fn函数进行匹配,fn为__device_attach_driver
error = fn(drv, data);
}
static int __device_attach_driver(struct device_driver *drv, void *_data)
{
...
ret = driver_match_device(drv, dev); /* driver 与 device 是否匹配,函数里就是比较 值是否相等*/
...
return driver_probe_device(drv, dev);/* 匹配的话,调用probe函数*/
}
● driver_probe_device 分析
int driver_probe_device(struct device_driver *drv, struct device *dev)
{
...
if (!device_is_registered(dev)) /* device 是否已经注册到bus里去了,是继续往下*/
return -ENODEV;
...
pm_runtime_barrier(dev);
ret = really_probe(dev, drv); /* 调用probe 函数*/
pm_request_idle(dev);
}
static int really_probe(struct device *dev, struct device_driver *drv)
{
....
dev->driver = drv;//匹配好后,将驱动信息记录到设备内部
if (driver_sysfs_add(dev)) {
printk(KERN_ERR "%s: driver_sysfs_add(%s) failed\n",
__func__, dev_name(dev));
goto probe_failed;
}
if (dev->bus->probe) {//如果总线存在probe函数,则调用总线的probe函数
ret = dev->bus->probe(dev);
if (ret)
goto probe_failed;
} else if (drv->probe) {
ret = drv->probe(dev);//如果总线中没有probe函数,则调用驱动的probe函数
if (ret)
goto probe_failed;
}
driver_bound(dev);//将设备加入到驱动支持的设备链表中,一个设备需要一个驱动,一个驱动支持多个设备
...
}
/****************************************************************************************/
(2) struct platform_driver 软件部分,设备驱动
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 (*resume)(struct platform_device *);
struct device_driver driver;内核里所有的驱动程序必须包含该结构体
const struct platform_device_id *id_table; 八字
};
事实上 platform 驱动只是在字符设备驱动外套一层 platform_driver的外壳,可以看到,模块加载和卸载函数仅仅通过paltform_driver_register()、
paltform_driver_unregister() 函数进行 platform_driver 的注册和注销,而原先用xxx_init()函数注册设备和xxxx_exit()函数注销字符设备的工作已
经被移交到 platform_driver 的 probe() 和 remove() 成员函数中。
●那具体platform驱动的工作过程是什么呢?:
在定义 struct platform_driver xxx_driver时,我们没有定义xxx驱动的porbe()等函数, 只定义了名字等,但是
将xxx_probe()函数地址赋值给了platform_driver 结构体中的 probe函数指针,如下:
struct platform_driver xxx_driver = {
.driver = {
.name = "globalfifo",
.owner = THIS_MODULE,
},
.probe = globalfifo_probe,
.remove = globalfifo_remove,
};
●platform是如何匹配device和driver?
这时就该总线出场了,系统为platform总线定义了一个bus_type 的实例platform_bus_type,其定义如下:
struct bus_type platform_bus_type = {
.name = "platform",
.dev_groups = platform_dev_groups,
.match = platform_match,
.uevent = platform_uevent,
.pm = &platform_dev_pm_ops,
};
设备(或驱动)注册的时候,都会引发总线调用自己的 match函数来寻找目前platform总线是否挂载有与该设备(或驱动)名字匹配的驱动(或设备),如果存在则将
双方绑定。注册时最终会调用下面函数注册,此时就会讲device_driver结构体中其他重要函数指针赋值。
int __platform_driver_register(struct platform_driver *drv,
struct module *owner)
{
drv->driver.owner = owner;
drv->driver.bus = &platform_bus_type;
drv->driver.probe = platform_drv_probe;
drv->driver.remove = platform_drv_remove;
drv->driver.shutdown = platform_drv_shutdown;
return driver_register(&drv->driver);
}
●整个调用流程
platform_driver_register()
driver_register
bus_add_driver
driver_attach
__driver_attach
driver_probe_device
really_probe
if (dev->bus->probe) {/*首先看总线有没有probe函数,若有则调用,而平台总线没有probe*/
ret = dev->bus->probe(dev);
if (ret)
goto probe_failed;
} else if (drv->probe) {/*然后看驱动有没有probe函数,若有则调用,*/
ret = drv->probe(dev);
if (ret)
goto probe_failed;
}
●进函数看看
int bus_add_driver(struct device_driver *drv){
...
if (drv->bus->p->drivers_autoprobe) {
if (driver_allows_async_probing(drv)) {
pr_debug("bus: '%s': probing driver %s asynchronously\n",
drv->bus->name, drv->name);
async_schedule(driver_attach_async, drv);
} else {
error = driver_attach(drv);/* 进入此函数 */
if (error)
goto out_unregister;
}
}
...
}
int driver_attach(struct device_driver *drv)
{
return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);//__driver_attach在bus_for_each_dev()会被调用;
}
int bus_for_each_dev(struct bus_type *bus, struct device *start,
void *data, int (*fn)(struct device *, void *))
{
...
while ((dev = next_device(&i)) && !error) //遍历device链表,
error = fn(dev, data); //调用__driver_attach()函数,判断是否有匹配
...
}
static int __driver_attach(struct device *dev, void *data)
{
struct device_driver *drv = data;
int ret;
ret = driver_match_device(drv, dev);//判断driver 与 device 是否匹配 通过自己总线match,比如platform总线,详情看下面 付
if (ret == 0) {
/* no match */
return 0;
} else if (ret == -EPROBE_DEFER) {
dev_dbg(dev, "Device match requests probe deferral\n");
driver_deferred_probe_add(dev);
} else if (ret < 0) {
dev_dbg(dev, "Bus failed to match device: %d", ret);
return ret;
} /* ret > 0 means positive match */
if (dev->parent) /* Needed for USB */
device_lock(dev->parent);
device_lock(dev);
if (!dev->driver)
driver_probe_device(drv, dev); /* 设备和驱动匹配成功 调用 probe函数*/
device_unlock(dev);
if (dev->parent)
device_unlock(dev->parent);
return 0;
}
附:platform的match
/* 在注册driver和device时,会执行 driver_match_device(drv, dev); 函数,函数里会用 dev/drv->bus->match,
挂在哪条总线上,就会调用xx总线的match 函数 */
struct bus_type platform_bus_type = {
.name = "platform",
.dev_groups = platform_dev_groups,
.match = platform_match, /* match 指向platform_match*/
.uevent = platform_uevent,
.pm = &platform_dev_pm_ops,
};
/* match 过程 最后通过strcmp函数判断*/
static int platform_match(struct device *dev, struct device_driver *drv)
{
struct platform_device *pdev = to_platform_device(dev);
struct platform_driver *pdrv = to_platform_driver(drv);
/* When driver_override is set, only bind to the matching driver */
if (pdev->driver_override)
return !strcmp(pdev->driver_override, drv->name);
/* Attempt an OF style match first */
if (of_driver_match_device(dev, drv))
return 1;
/* Then try ACPI style match */
if (acpi_driver_match_device(dev, drv))
return 1;
/* Then try to match against the id table */
if (pdrv->id_table)
return platform_match_id(pdrv->id_table, pdev) != NULL;
/* fall-back to driver name match */
return (strcmp(pdev->name, drv->name) == 0);=
}
详细参考:
https://blog.csdn.net/zqixiao_09/article/details/50888795
https://www.cnblogs.com/hello2mhb/p/3319131.html
2. 提出一个问题,在s3c24xx的i2c总线驱动模型中,发现没有在任何定义的函数中调用 int platform_device_register(struct platform_device *pdev);函数去注册
platfrom device。这是因为linux4.0的内核把描述设备硬件的信息放在了dtsi中,在arch/arm/boot/dts/ 文件夹下有关于许多平台的设备树文件,s3c24xx赫然在列。
下面我们看一个linux内核旧版本的device例子:
static struct resource s3c_i2c0_resource[] = {
[0] = DEFINE_RES_MEM(S3C_PA_IIC, SZ_4K),
[1] = DEFINE_RES_IRQ(IRQ_IIC),
};
struct platform_device s3c_device_i2c0 = {
.name = "s3c2410-i2c",
.id = 0,
.num_resources = ARRAY_SIZE(s3c_i2c0_resource),
.resource = s3c_i2c0_resource,
};
/****************************************************************************************************************************/
四.i2c体系结构 2018.3.27
i2c体系结构分为i2c核心,i2c总线驱动,i2c设备驱动。
1. i2c_adapter,i2c_algorithm结构体主要来编写适配器驱动,不同的cpu,i2c的适配器驱动不一样。有了控制器驱动才能产生控制cpu产生i2c信号。
i2c_adapter对象实现了一组通过一个i2c控制器发送消息的所有信息, 包括时序, 地址等等, 即封装了i2c控制器的"控制信息"。i2c_algorithm描述一
个i2c主机的发送时序的信息,该类的对象algo是i2c_adapter的一个域,其中的master_xfer()注册的函数最终被设备驱动端的i2c_transfer()回调。
i2c总线的注册,一般不用我们去写,内核中一般都包含了,对大多数cpu的支持。
(1) struct i2c_adapter { //操作cpu内部的i2c,读写相关的寄存器
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 */
const struct i2c_lock_operations *lock_ops;
struct rt_mutex bus_lock;
struct rt_mutex mux_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;
const struct i2c_adapter_quirks *quirks;
struct irq_domain *host_notify_domain;
};
(2) struct i2c_algorithm {
/* If an adapter algorithm can't do I2C-level access, set master_xfer
to NULL. If an adapter algorithm can do SMBus access, set
smbus_xfer. If set to NULL, the SMBus protocol is simulated
using common I2C messages */
/* master_xfer should return the number of messages successfully
processed, or a negative value on error */
int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,
int num);
int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr,
unsigned short flags, char read_write,
u8 command, int size, union i2c_smbus_data *data);
/* To determine what the adapter supports */
u32 (*functionality) (struct i2c_adapter *);
#if IS_ENABLED(CONFIG_I2C_SLAVE)
int (*reg_slave)(struct i2c_client *client);
int (*unreg_slave)(struct i2c_client *client);
#endif
};
3. i2c_driver描述一个挂接在i2c总线上的设备的驱动方法,即i2c设备的驱动,通过i2c_bus_type和设备信息i2c_client中的id_table匹配,匹配成功后通过
clients和i2c_client对象以及i2c_adapter对象相连。
i2c_client描述一个挂接在硬件i2c总线上的设备的设备信息,即i2c设备的设备对象,与i2c_driver对象匹配成功后通过detected和i2c_driver以及
i2c_adapter相连,在控制器驱动与控制器设备匹配成功后被控制器驱动通过i2c_new_device()创建。
(1) struct i2c_driver {
unsigned int class;
/* Notifies the driver that a new bus has appeared. You should avoid
* using this, it will be removed in a near future.
*/
int (*attach_adapter)(struct i2c_adapter *) __deprecated;
/* Standard driver model interfaces */
int (*probe)(struct i2c_client *, const struct i2c_device_id *);
int (*remove)(struct i2c_client *);
/* New driver model interface to aid the seamless removal of the
* current probe()'s, more commonly unused than used second parameter.
*/
int (*probe_new)(struct i2c_client *);
/* driver model interfaces that don't relate to enumeration */
void (*shutdown)(struct i2c_client *);
/* Alert callback, for example for the SMBus alert protocol.
* The format and meaning of the data value depends on the protocol.
* For the SMBus alert protocol, there is a single bit of data passed
* as the alert response's low bit ("event flag").
* For the SMBus Host Notify protocol, the data corresponds to the
* 16-bit payload data reported by the slave device acting as master.
*/
void (*alert)(struct i2c_client *, enum i2c_alert_protocol protocol,
unsigned int data);
/* a ioctl like command that can be used to perform specific functions
* with the device.
*/
int (*command)(struct i2c_client *client, unsigned int cmd, void *arg);
struct device_driver driver;
const struct i2c_device_id *id_table;
/* Device detection callback for automatic device creation */
int (*detect)(struct i2c_client *, struct i2c_board_info *);
const unsigned short *address_list;
struct list_head clients;
};
(2) 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 device dev; /* the device structure */
int irq; /* irq issued by device */
struct list_head detected;
#if IS_ENABLED(CONFIG_I2C_SLAVE)
i2c_slave_cb_t slave_cb; /* callback for slave mode */
#endif
};
4. adapter注册整体流程
以i2c_s3c24xx为例, s3c24xx_i2c_probe(struct platform_device *pdev) 探针函数,在 match 函数匹配成功后调用,在linux4.0中,
cpu的i2c的 device 信息被保存在dtsi中,旧版本保存在板级代码中。i2c_s3c2410.c 此文件就是s3c2410-i2c的驱动文件。match到后,调用probe
开始初始化。最后在系统 /dev 文件夹下 命名为i2c0,i2c1...注册多少个就会显示多少个。
下面分析 s3c24xx_i2c_probe(struct platform_device *pdev) 这个函数:
●one: 分析 s3c24xx_i2c_probe 函数
static int s3c24xx_i2c_probe(struct platform_device *pdev)
{
...
strlcpy(i2c->adap.name, "s3c2410-i2c", sizeof(i2c->adap.name)); // adap.name初始化为s3c2410-i2c,这个在后面会和 i2c设备驱动里
//client->name进行 match ,无误后client->adap 会指向此 adap进行连接
i2c->adap.owner = THIS_MODULE;
i2c->adap.algo = &s3c24xx_i2c_algorithm;
i2c->adap.retries = 2;
i2c->adap.class = I2C_CLASS_DEPRECATED;
i2c->tx_setup = 50; /* adap 初始化 */
ret = s3c24xx_i2c_init(i2c); /* 进去此函数后获取 platfrom data,i2c地址,设置clock */
i2c->adap.nr = i2c->pdata->bus_num; /* 设置总线号 i2c0 1 2 。。*/
i2c->adap.dev.of_node = pdev->dev.of_node;
ret = i2c_add_numbered_adapter(&i2c->adap); /* 通过设置的总线好进行注册 下一步分析此函数*/
//还有一个函数 i2c_add_adapter();动态注册总线号,i2c_add_numbered_adapter函数实质也是调用i2c_add_adapter函数
...
}
●two 分析 i2c_add_adapter
int i2c_add_adapter(struct i2c_adapter *adapter)
{
struct device *dev = &adapter->dev;
int id;
if (dev->of_node) { /* 由于是动态注册,需要获取id */
id = of_alias_get_id(dev->of_node, "i2c");
...
}
mutex_lock(&core_lock);
id = idr_alloc(&i2c_adapter_idr, adapter,
__i2c_first_dynamic_bus_num, 0, GFP_KERNEL); /* 将adapter结构插入到i2c_adapter_idr中 */
mutex_unlock(&core_lock);
adapter->nr = id;
return i2c_register_adapter(adapter); /* 进行注册 */
}
●three 分析 i2c_register_adapter
static int i2c_register_adapter(struct i2c_adapter *adap)
{
...
dev_set_name(&adap->dev, "i2c-%d", adap->nr); /* 设置dev名称,在/dev文件夹下的名字 */
adap->dev.bus = &i2c_bus_type;
adap->dev.type = &i2c_adapter_type;
res = device_register(&adap->dev); /* 初始化,并注册 device,此次调用是注册i2c_adapter内嵌device
也就是把cpu上的i2c控制器信息注册了*/
...
if (adap->nr < __i2c_first_dynamic_bus_num)
i2c_scan_static_board_info(adap); /* 查询板级信息*/
...
}
/* 到这一步后在i2c-adapter文件夹下就能发现 i2c-x 设备 , 也就是说i2c总线驱动部分注册完成,下面注册i2c设备驱动*/
●four 分析 i2c_scan_static_board_info
static void i2c_scan_static_board_info(struct i2c_adapter *adapter)
{
...
list_for_each_entry(devinfo, &__i2c_board_list, list) { /* 遍历__i2c_board_list表,查找i2c设备信息 */
if (devinfo->busnum == adapter->nr
&& !i2c_new_device(adapter, /* 如果指定的设备在 adapter 所对应的i2c上,调用i2c_new_device()函数*/
&devinfo->board_info))
dev_err(&adapter->dev,
"Can't create device at 0x%02x\n",
devinfo->board_info.addr);
}
...
}
●five 分析 i2c_new_device
struct i2c_client *i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info)
{
...
status = i2c_check_addr_busy(adap, i2c_encode_flags_to_addr(client)); /* 检测硬件设备地址是否有效,无效跳到out_err */
if (status)
goto out_err;
client->dev.parent = &client->adapter->dev; /* 新设备client对象初始化 */
client->dev.bus = &i2c_bus_type;
client->dev.type = &i2c_client_type;
client->dev.of_node = info->of_node;
client->dev.fwnode = info->fwnode;
i2c_dev_set_name(adap, client);
status = device_register(&client->dev); /* 注册 i2c device 设备 */
out_err: /* 没有新的设备进来 */
dev_err(&adap->dev,
"Failed to register i2c client %s at 0x%02x (%d)\n",
client->name, client->addr, status);
...
}
解释:这个函数在注册完i2c_adapter 和 内嵌的device 后执行,目的是注册i2c设备,在注册前会判断地址是否有效,若无效的话。不进行client初始化和注册。
整个adapter注册完成。。
●注册过程:
drivers/i2c/busses/i2c-qup.c(这是适配器的驱动)
platform_driver_register
xxx_i2c_probe
i2c_add_numbered_adapter / i2c_add_adapter
i2c_register_adapter
i2c_scan_static_board_info
{
i2c_new_device //arch/arm/mach-msm/board-xxx.c里面定义的i2c设备在此注册(这就是静态注册)
device_register //从这句开始即是设备模型的东西
device_add
bus_probe_device
device_attach
__device_attach /*每注册一个设备都会调用此函数,每注册一个驱动也会调用__driver_attach,先注册设备还是驱
动无硬性规定,不过驱动和设备是相偎相依的*/
driver_match_device /*会根据device的name字段和bus上挂载的drivers链表中每一个driver的id_table的name字段比较,
如果相等即找到了自己的driver*/
driver_probe_device //如果相等调用此函数,不相等返回0。接下来会调用具体设备驱动的probe函数
}
/****************************************************************************************************************************/
五.Device tree 2018.03.28
1. device tree 语法 //参考桌面 DailyWork.odt
(1) label:
这个就是对一个node的别名,可以在其他地方应用,如例1中,pm8941_s3就是一个label,它的定义如下:
pm8941_s3: regulator-s3 {
regulator-min-microvolt = <1800000>;
regulator-max-microvolt = <1800000>;
qcom,init-voltage = <1800000>;
status = "okay";
};
(2) node name
node-name指定了结点的名字,它由1--31个字符组成,可用的字符见下表,一般情况下,结点名应以小写或大写字母开头,
并应该具体通用性,能反应出设备的功能。
(3) unit-address
该项是在描述一个node在总线上的位置,也就是其地址是什么。如果没有这个属性,可以省略不写,并且要确保该node-name在整个设备树的同一层级中是唯一的。
注意:根结点(root node)既没有node-name,也没有unit-address,它由一个”/”表示。并且一个device tree文件中有且只能有一个root node。
(4) property:
这是一个键值对,由属性名(property name)和属性值(value)表示,用来描述一个node的特性,以分号结尾。格式为:
[label:] property-name = value;
属性名(property-name)为由1--31个字符组成的字符串,可用的字符见右边表格:
(与node 那么相比,少了大写字母,多了
"?"和"#"两个特殊符号)
)property:
这是一个键值对,由属性名(property name)和属性值(value)表示,用来描述一个node的特性,以分号结尾。格式为:
[label:] property-name = value;
属性名(property-name)为由1--31个字符组成的字符串,可用的字符见右边表格:
(与node 那么相比,少了大写字母,多了
"?"和"#"两个特殊符号)
(5) 值得一提的是cell这个术语,在Device Tree表示32bit的信息单位。#address-cells = <1> ,当然,可能是一个数组。例如<0x00000000 0x00000000 0x00000000 0x20000000>
value有三种情况:
1)属性值是text string或者string list,用双引号表示。例如device_type = "memory"
2)属性值是32bit unsigned integers,用尖括号表示。例如#size-cells = <1>
3)属性值是binary data,用方括号表示。例如binary-property = [0x01 0x23 0x45 0x67]
详细参考:
https://www.cnblogs.com/wi100sh/category/704112.html 中的device tree 一 二 三
2. Device tree 被platfrom 加载过程分析 2018.03.29
(1) 在/arch/arm/kernel/head.s 中有这样的描述
/* This is normally called from the decompressor code. The requirements
* are: MMU = off, D-cache = off, I-cache = dont care, r0 = 0,
* r1 = machine nr, r2 = atags or dtb pointer. */
r2 = atags or dtb pointer /* r2可能是device tree binary file的指针(bootloader要传递给内核之前要copy到memory中),
也可以能是tag list的指针 */
在ARM的汇编部分的启动代码中(主要是head.S和head-common.S),machine type ID和指向DTB或者atags的指针被保存在变量
__machine_arch_type和__atags_pointer中,这么做是为了后续c代码进行处理。
(2) c 中的处理
● setup_arch 函数
void __init setup_arch(char **cmdline_p)
{
const struct machine_desc *mdesc;
……
mdesc = setup_machine_fdt(__atags_pointer); /* setup_machine_fdt函数的功能就是根据Device Tree
的信息,找到最适合的machine描述符,匹配platform */
if (!mdesc)
mdesc = setup_machine_tags(__atags_pointer, __machine_arch_type); /* fdt 函数找不到,调用tags 函数查找 */
machine_desc = mdesc;
machine_name = mdesc->name;
……
}
● setup_machine_fdt 函数
另附:
/* 为什么要用copy_xx_user 函数? */
答:linux操作系统和驱动程序运行在内核空间,应用程序运行在用户空间,两者不能简单地使用指针传递数据,
因为Linux使用的虚拟内存机制,用户空间的数据可能被换出,当内核空间使用用户空间指针时,对应的数据可能
不在内存中。用户空间的内存映射采用段页式,而内核空间有自己的规则。每个用户空间的应用程序,系统都会分配
4GB的虚拟空间。
关于虚拟内存与物理内存的关系请看:
https://blog.csdn.net/a185531353/article/details/78487430
本次主要的讨论linux运行的内用空间与内核空间进行数据传递(主要是应用在linux的驱动程序中)常用函数copy_to_user
和copy_from_user。下面是对这两个函数进行的详解
1.函数copy_to_user的函数原型
unsigned long copy_to_user(void __user *to, const void *from, unsigned long n)
{
if (likely(access_ok(VERIFY_WRITE, to, n)))
n = __copy_to_user(to, from, n);
return n;
}
参数详解:
参数1( void __user *to): 拷贝内核空间的地址指针
参数2(const void *from): 用户空间的地址指针
参数3(unsigned long n): 内核拷贝到用户空间的字节数
返回值: 成功返回 0,失败是返回还没有拷贝的字节数
2.copy_from_user 函数
unsigned long copy_from_user(void *to, const void __user *from, unsigned long n)
{
if (likely(access_ok(VERIFY_READ, from, n)))
n = __copy_from_user(to, from, n);
else
memset(to, 0, n);
return n;
}
参数详解:
参数1( void __user *to): 拷贝内核空间的地址指针
参数2(const void *from): 用户空间的地址指针
参数3(unsigned long n): 用户空间到内核空间的字节数
返回值: 成功返回 0,失败是返回还没有拷贝的字节数
看一个列子 : 在 msm_sensor_driver_cmd 函数中 void * 是从vendor层传下来的指针 看函数中第二行
static int32_t msm_sensor_driver_cmd(struct msm_sensor_init_t *s_init,
void *arg)
{
int32_t rc = 0;
/* 在这里为什么 能把vendor 层传下来的指针 直接 赋值给 函数中的指针 cfg呢?
在这里其实并没有传递arg的内容,arg是一个指针,存放内容之前要有malloc
分配堆内存,arg 只是代表了这块读内存的首地址只占四个字节,和函数形参的传递
类似,函数传递形参时,这个形参如果是 *类型,那么在传输的时候只是传递此形参
的地址。函数的形参为 *类型 时,主要应用与在此函数中要改变此变量的值,或者
由于要传输的变量太大。 */
struct sensor_init_cfg_data *cfg = (struct sensor_init_cfg_data *)arg;
....
switch (cfg->cfgtype) {
case CFG_SINIT_PROBE:
mutex_lock(&s_init->imutex);
s_init->module_init_status = 0;
rc = msm_sensor_driver_probe(cfg->cfg.setting, // 看一看这个函数
&cfg->probed_info,
cfg->entity_name);
mutex_unlock(&s_init->imutex);
if (rc < 0)
pr_err_ratelimited("%s failed (non-fatal) rc %d", __func__, rc);
break;
.......
}
int32_t msm_sensor_driver_probe(void *setting,
struct msm_sensor_info_t *probed_info, char *entity_name)
{
int32_t rc = 0;
struct msm_sensor_ctrl_t *s_ctrl = NULL;
struct msm_camera_cci_client *cci_client = NULL;
struct msm_camera_sensor_slave_info *slave_info = NULL;
struct msm_camera_slave_info *camera_info = NULL;
unsigned long mount_pos = 0;
uint32_t is_yuv;
/* Validate input parameters */
if (!setting) {
pr_err("failed: slave_info %pK", setting);
return -EINVAL;
}
.......
/* 这里用了 copy_from_user 函数,为什么? */
/* 上一个函数只是赋值首地址,而此处 slave_info 要获取 arg 中 setting 变量的内容,由于linux 内核与用户空间地址的关系
所以,要使用copy_from_user 函数 */
if (copy_from_user(slave_info,(void *)setting, sizeof(*slave_info))) {
pr_err("failed: copy_from_user");
rc = -EFAULT;
goto free_slave_info;
}
}
.......
}
总结:在两个函数中,都有对地址空间的有效性进行了检测。