1. 最简单的示例
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/platform_device.h>
static void rtc_device_release(struct device *dev)
{
printk("%s: %s released...\n", __func__, dev_name(dev));
}
struct platform_device rtc_dev = {
.name = "rtc-demo",
.id = -1,
.dev = {
.release = rtc_device_release,
},
};
static int __init rtc_device_init(void)
{
return platform_device_register(&rtc_dev);
}
static void __exit rtc_device_exit(void)
{
platform_device_unregister(&rtc_dev);
}
static const struct file_operations rtc_fops = {
.owner = THIS_MODULE,
.read = rtc_read,
.write = rtc_write,
.open = rtc_open,
.unlocked_ioctl = rtc_ioctl,
.release = rtc_release,
};
static int rtc_driver_probe(struct platform_device *dev)
{
irqreturn_t ret = 0;
printk("%s: probe and init hello_device: %s\n", __func__, dev->name);
regs = (rtc_reg_t *)ioremap(RTC_BASE, sizeof(rtc_reg_t));
ret = request_irq(39, rtc_alarm_handler, 0, "rtc0-test", NULL);
if (ret == -1){
printk("request_irq failed!\n");
return -1;
}
ret = alloc_chrdev_region(&rtc_chrdev.devno, 0, 1, "rtc-demo");
if (ret) {
printk("alloc char device number failed!\n");
return ret;
}
printk("RTC devnum:%d minornum:%d\n", MAJOR(rtc_chrdev.devno), \
MINOR(rtc_chrdev.devno));
rtc_chrdev.rtc_cdev = cdev_alloc();
cdev_init(rtc_chrdev.rtc_cdev, &rtc_fops);
ret = cdev_add(rtc_chrdev.rtc_cdev, rtc_chrdev.devno, 1);
if (ret < 0) {
printk("cdev_add failed..\n");
return -1;
}
else {
printk("Register char module: rtc success!\n");
}
rtc_class = class_create(THIS_MODULE, "rtc-class");
if (IS_ERR(rtc_class)) {
printk("class_create failed!\n");
return -1;
}
class_device = device_create(rtc_class, NULL, rtc_dev.devno, NULL, \
"rtc-demo%d", 0);
if (IS_ERR(class_device)) {
printk("device_create failed!\n");
return -1;
}
return 0;
}
static int rtc_driver_remove(struct platform_device *dev)
{
cdev_del(rtc_chrdev.rtc_cdev);
unregister_chrdev_region(rtc_chrdev.devno, 1);
free_irq(39,NULL);
printk("%s: driver remove: %s\n", __func__, dev->name);
return 0;
}
static struct platform_device_id arm_rtc_primcell_driver_ids[] = {
{
.name = "rtc-wit0",
.driver_data = 0,
},
{
.name = "rtc-wit1",
.driver_data = 1,
},
{ }
};
static struct platform_driver rtc_drv = {
.id_table = arm_rtc_primcell_driver_ids,
.probe = rtc_driver_probe,
.remove = rtc_driver_remove,
.driver = {
.name = "rtc-demo",
}
};
static int __init rtc_driver_init(void)
{
return platform_driver_register(&rtc_drv);
}
static void __exit rtc_driver_exit(void)
{
platform_driver_unregister(&rtc_drv);
}
2. match
struct platform_device_id {
char name[PLATFORM_NAME_SIZE];
kernel_ulong_t driver_data;
};
static const struct platform_device_id *
platform_match_id(const struct platform_device_id *id,
struct platform_device *pdev)
{
while (id->name[0]) {
if (strcmp(pdev->name, id->name) == 0) {
pdev->id_entry = id;
return id;
}
id++;
}
return NULL;
}
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);
// 1) 使用名称匹配
/* When driver_override is set, only bind to the matching driver */
if (pdev->driver_override)
return !strcmp(pdev->driver_override, drv->name);
// 2) 使用设备树匹配
/* 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);
}
使用id_table
static struct platform_device_id arm_rtc_primcell_driver_ids[] = {
{
.name = "rtc-wit0",
.driver_data = 0,
},
{
.name = "rtc-wit1",
.driver_data = 1,
},
{ }
};
static struct platform_driver pl031_rtc_drv = {
.id_table = arm_rtc_primcell_driver_ids,
.probe = pl031_driver_probe,
.remove = pl031_driver_remove,
.driver = {
.name = "rtc-demo",
}
};
3. resource
platform_device 定义各种依赖的资源
platform_driver 获取资源,初始化硬件
资源用 struct resource 描述,可以描述 中断,内存等
struct resource {
resource_size_t start;
resource_size_t end;
const char *name;
unsigned long flags;
unsigned long desc;
struct resource *parent, *sibling, *child;
};
driver端使用 platform_get_resource获得资源
// type : 资源类型
// index : 目标资源在同类型资源数组中的下标
struct resource *platform_get_resource(struct platform_device *dev,
unsigned int type, unsigned int num);
设备端定义资源
static struct resource rtc_resource[] = {
[0] = { // 定义虚拟内存资源,用于寄存器物理地址映射
.start = 0x10017000, // 内存起始地址
.end = 0x10017000 + 4 * 8, // 内存结束地址
.flags = IORESOURCE_MEM,
},
[1] = { // 定义中断号
.start = 39,
.end = 39,
.flags = IORESOURCE_IRQ,
},
};
static void rtc_device_release(struct device *dev)
{
printk("%s: %s released...\n", __func__, dev_name(dev));
}
struct platform_device rtc_dev = {
.name = "rtc-wit0",
//.name = "rtc-demo",
.id = -1,
.num_resources = 2,
.resource = rtc_resource,
.dev = {
.release = rtc_device_release,
},
};
驱动端使用
static int rtc_driver_probe(struct platform_device *dev)
{
irqreturn_t ret = 0;
struct resource *res_mem, *res_irq;
int device_irq = -1;
printk("%s: probe and init hello_device: %s\n", __func__, dev->name);
// 获得内存资源
res_mem = platform_get_resource(dev, IORESOURCE_MEM, 0);
regs = (rtc_reg_t *)ioremap(res_mem->start, res_mem->end - res_mem->start);
// 获得中断资源
//res_irq = platform_get_resource(dev, IORESOURCE_IRQ, 0);
device_irq = platform_get_irq(dev, 0);
ret = request_irq(device_irq, rtc_alarm_handler, 0, "rtc0-test", NULL);
if (ret == -1){
printk("request_irq failed!\n");
return -1;
}
ret = alloc_chrdev_region(&rtc_chrdev.devno, 0, 1, "rtc-demo");
if (ret) {
printk("alloc char device number failed!\n");
return ret;
}
printk("RTC devnum:%d minornum:%d\n", MAJOR(rtc_chrdev.devno), \
MINOR(rtc_chrdev.devno));
rtc_chrdev.rtc_cdev = cdev_alloc();
cdev_init(rtc_chrdev.rtc_cdev, &rtc_fops);
ret = cdev_add(rtc_chrdev.rtc_cdev, rtc_chrdev.devno, 1);
if (ret < 0) {
printk("cdev_add failed..\n");
return -1;
}
else {
printk("Register char module: rtc success!\n");
}
rtc_class = class_create(THIS_MODULE, "rtc-class");
if (IS_ERR(rtc_class)) {
printk("class_create failed!\n");
return -1;
}
rtc_device = device_create(rtc_class, NULL,rtc_chrdev.devno, NULL, \
"rtc-demo%d", 0);
if (IS_ERR(rtc_device)) {
printk("device_create failed!\n");
return -1;
}
return 0;
}
4. 使用class抽象接口
中间层抽象出接口
struct rtc_class_operations {
int (*set_time)(struct rtc_time *);
int (*set_alarm)(struct rtc_time *);
};
struct rtc_device {
struct cdev *rtc_cdev;
dev_t devno;
const struct rtc_class_operations *ops;
};
中间层定义ioctl的CMD,并回调具体驱动的ops
#define RTC_IOC_MAGIC 'R'
#define RTC_SET_TIME _IOW(RTC_IOC_MAGIC, 1, struct rtc_time)
#define RTC_SET_ALARM _IOW(RTC_IOC_MAGIC, 2, struct rtc_time)
static long rtc_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
struct rtc_device *pdev = file->private_data;
struct rtc_time __user *buf = (struct rtc_time __user *)arg;
if (_IOC_TYPE(cmd) != RTC_IOC_MAGIC) {
printk("rtc ioctl: invalid cmd!\n");
return -EINVAL;
}
switch (cmd) {
case RTC_SET_TIME:
if (_IOC_DIR(cmd) != _IOC_WRITE) {
printk("rtc: invalid ioclt cmd!\n");
return -EINVAL;
}
if (unlikely(copy_from_user(&tm, buf, sizeof(tm)))) {
printk("rtc ioctl: copy_from_user error!\n");
return -EFAULT;
}
set_rtc_time(pdev, &tm);
break;
case RTC_SET_ALARM:
if (_IOC_DIR(cmd) != _IOC_WRITE) {
printk("rtc ioctl: invalid ioclt cmd!\n");
return -EINVAL;
}
if (unlikely(copy_from_user(&tm, buf, sizeof(tm)))) {
printk("rtc ioctl: copy_from_user error!\n");
return -EFAULT;
}
set_rtc_alarm();
break;
default:
printk("error cmd!\n");
return -1;
}
return 0;
}
具体的驱动在probe时,绑定自己的 ops
static int rtc_driver_probe(struct platform_device *dev)
{
irqreturn_t ret = 0;
struct resource *res_mem;
//struct resource *res_irq;
int device_irq = -1;
printk("%s: probe and init hello_device: %s\n", __func__, dev->name);
res_mem = platform_get_resource(dev, IORESOURCE_MEM, 0);
regs = (rtc_reg_t *)ioremap(res_mem->start, res_mem->end - res_mem->start);
//res_irq = platform_get_resource(dev, IORESOURCE_IRQ, 0);
device_irq = platform_get_irq(dev, 0);
ret = request_irq(device_irq, rtc_alarm_handler, 0, "rtc0-test", NULL);
if (ret == -1){
printk("request_irq failed!\n");
return -1;
}
ret = alloc_chrdev_region(&rtc_dev.devno, 0, 1, "rtc-demo");
if (ret) {
printk("alloc char device number failed!\n");
return ret;
}
printk("RTC devnum:%d minornum:%d\n", MAJOR(rtc_dev.devno), \
MINOR(rtc_dev.devno));
rtc_dev.rtc_cdev = cdev_alloc();
cdev_init(rtc_dev.rtc_cdev, &rtc_fops);
ret = cdev_add(rtc_dev.rtc_cdev, rtc_dev.devno, 1);
if (ret < 0) {
printk("cdev_add failed..\n");
return -1;
}
else {
printk("Register char module: rtc success!\n");
}
rtc_dev.ops = &pl031_rtc_ops; // 绑定
rtc_class = class_create(THIS_MODULE, "rtc-class");
if (IS_ERR(rtc_class)) {
printk("class_create failed!\n");
return -1;
}
class_device = device_create(rtc_class, NULL, rtc_dev.devno, NULL, \
"rtc-demo%d", 0);
if (IS_ERR(class_device)) {
printk("device_create failed!\n");
return -1;
}
return 0;
}
具体驱动实现自己的ops
static int pl031_set_rtc_time(struct rtc_time *t)
{
cur_time = rtc_tm_to_time(t);
regs->RTCLR = cur_time;
return 0;
}
struct rtc_class_operations pl031_rtc_ops = {
.set_time = pl031_set_rtc_time,
};
5. 资源管理
为了避免设备模型编程时内存泄漏,提供了类似于内存池的工具
void *devm_kmalloc(struct device *dev, size_t size, gfp_t gfp)
void devm_kfree(struct device *dev, const void *p)
使用devm_kmalloc分配的内存会记录到一个链表,当调用devm_kfree时,会遍历链表,使用没有释放的内存。
device在release时会调用 devm_kfree,所以即使用户忘记释放内存,device_unregister时也会释放。