【demo】platform-based misc driver
Article directory
platform is a virtual bus in Linux, called platform bus, which contains two objects, platform_device and platform_driver, which are used to separate the driver from the device and realize the idea of kernel separation.
A mature character device driver often combines the platform with the character registration program, here demonstrates the use of the platform-based misc driver.
1 platform
The matching priority of the platform bus is: of_match_table > id_table > name
static struct platform_driver xx_driver = {
.probe = xx_probe,
.remove = xx_remove,
.id_table = tbl, ;2
.driver = {
.name = "phoenix", ;3
.of_match_table = NULL, ;1
},
};
in
- of_match_table; mainly used to match with device tree
- id_table; used to match with struct platform_device, which is a matching method before the birth of the device tree
- driver.name; used to match with struct platform_device, only a single match, multi-device use id_table
The following is a multi-device demo of the platform
dev0.c
#include <linux/module.h>
#include <linux/device.h>
#include <linux/platform_device.h>
static struct resource dres[] = {
[0] = {
.name = "dev0",
.start = 0x23010000,
.end = 0x23010000 + 0xffff,
.flags = IORESOURCE_MEM,
},
[1] = {
.name = "dev0",
.start = 19,
.end = 22,
.flags = IORESOURCE_IRQ,
},
};
static void release(struct device *dev)
{
printk("%s %d\n", __func__, __LINE__);
return ;
}
static struct platform_device devc=
{
.name = "phoenix0",
.id = 0,
.dev.release = release,
.num_resources = ARRAY_SIZE(dres),
.resource = dres,
};
static int heo_init(void)
{
printk("%s %d\n", __func__, __LINE__);
return platform_device_register(&devc);
}
static void heo_exit(void)
{
printk("%s %d\n", __func__, __LINE__);
platform_device_unregister(&devc);
return;
}
module_init(heo_init);
module_exit(heo_exit);
MODULE_LICENSE("GPL");
dev1.c
#include <linux/module.h>
#include <linux/device.h>
#include <linux/platform_device.h>
static struct resource dres[] = {
[0] = {
.name = "dev1",
.start = 0x23020000,
.end = 0x23020000 + 0xffff,
.flags = IORESOURCE_MEM,
},
[1] = {
.name = "dev1",
.start = 85,
.end = 88,
.flags = IORESOURCE_IRQ,
},
};
static void release(struct device *dev)
{
printk("%s %d\n", __func__, __LINE__);
return ;
}
static struct platform_device devc=
{
.name = "phoenix1",
.id = 1,
.dev.release = release,
.num_resources = ARRAY_SIZE(dres),
.resource = dres,
};
static int heo_init(void)
{
printk("%s %d\n", __func__, __LINE__);
return platform_device_register(&devc);
}
static void heo_exit(void)
{
printk("%s %d\n", __func__, __LINE__);
platform_device_unregister(&devc);
return;
}
module_init(heo_init);
module_exit(heo_exit);
MODULE_LICENSE("GPL");
driver.c
#include <linux/module.h>
#include <linux/platform_device.h>
static int hello_probe(struct platform_device *pdev)
{
printk("%s %d, match ok, %d, %s\n", __func__, __LINE__, pdev->id, pdev->name);
return 0;
}
static int hello_remove(struct platform_device *pdev)
{
printk("%s %d\n", __func__, __LINE__);
return 0;
}
static struct platform_device_id tbl[] = {
{
"phoenix0"},
{
"phoenix1"},
{
},
};
static struct platform_driver hello_driver = {
.probe = hello_probe,
.remove = hello_remove,
.id_table = tbl,
.driver = {
.name = "phoenix",
},
};
static int hello_init(void)
{
printk("%s %d\n", __func__, __LINE__);
return platform_driver_register(&hello_driver);
}
static void hello_exit(void)
{
printk("%s %d\n", __func__, __LINE__);
platform_driver_unregister(&hello_driver);
return;
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
Makefile
ARCH := arm64
ARMCC := aarch64-linux-gnu-
KDIR := ${HOME}/xxx/kernel
ifneq ($(KERNELRELEASE),)
obj-m:=dev0.o dev1.o driver.o
$(info "2nd")
else
PWD:=$(shell pwd)
all:
$(info "1st")
make -C $(KDIR) M=$(PWD) ARCH=$(ARCH) CROSS_COMPILE=$(ARMCC) modules
.PHONY:clean
clean:
rm -rf *.ko *.o *.symvers *.mod.c *.mod.o *.order .tmp_versions .*.cmd
endif
2 misc
The way of misc registering character devices is much simpler than using alloc_chrdev_region directly, at least it can save a few lines of code.
The driver managed by misc+platform uses the device number to associate the relationship between struct inode and struct miscdevice. The operation function of the device number is as follows.
#include <linux/kdev_t.h>
- (dev_t)–>major device number, minor device number
- MAJOR(dev_t dev);
- MINOR(dev_t dev);
- Major device number, minor device number –>(dev_t)
- MKDEV(int major,int minor);
Association structure
struct miscdevice {
int minor; //次设备号
const char *name;
const struct file_operations *fops;
struct list_head list;
struct device *parent;
struct device *this_device; //当前设备,是device_create的返回值
};
struct inode {
...
atomic_t i_count;
...
dev_t i_rdev; //该成员表示设备文件的inode结构,它包含了真正的设备编号。
loff_t i_size;
...
const struct inode_operations *i_op;
const struct file_operations *i_fop; /* former->i_op->default_file_ops */
...
struct list_head i_devices;
union {
struct pipe_inode_info *i_pipe;
struct block_device *i_bdev;
struct cdev *i_cdev; //该成员表示字符设备的内核的内部结构。
};
...
void *i_private; /* fs or device private pointer */
};
**Note: **Be sure to ensure that struct platform_device
the median id
value struct miscdevice
corresponds to the array subscript.
Compared with the platform program above, only need to change driver.c
driver.c
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/miscdevice.h>
static int dmisc_open(struct inode *inode, struct file *filp)
{
printk("%s %d, pdev %d %d %d\n", __func__, __LINE__, inode->i_rdev, MAJOR(inode->i_rdev), MINOR(inode->i_rdev));
return 0;
}
static int dmisc_release(struct inode *inode, struct file *filp)
{
printk ("%s %d\n", __func__, __LINE__);
return 0;
}
static ssize_t dmisc_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
printk ("%s %d\n", __func__, __LINE__);
return 0;
}
static ssize_t dmisc_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
{
printk ("%s %d\n", __func__, __LINE__);
return 0;
}
static long dmisc_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
printk ("%s %d\n", __func__, __LINE__);
return 0;
}
static struct file_operations drv_ops= {
.owner = THIS_MODULE,
.open = dmisc_open,
.read = dmisc_read,
.write = dmisc_write,
.release = dmisc_release,
.unlocked_ioctl = dmisc_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = dmisc_ioctl,
#endif
};
static struct miscdevice misc[] = {
{
.minor = MISC_DYNAMIC_MINOR,
.name = "phx0",
.fops = &drv_ops,
},
{
.minor = MISC_DYNAMIC_MINOR,
.name = "phx1",
.fops = &drv_ops,
},
};
static int drvheo_probe(struct platform_device *pdev)
{
if (misc_register(&misc[pdev->id]))
return -EFAULT;
printk("%s %d, match ok, pdev %d, %s, misc %s %d %d %d\n", __func__, __LINE__,
pdev->id, pdev->name,
misc[pdev->id].name, MKDEV(MISC_MAJOR, misc[pdev->id].minor), MISC_MAJOR, misc[pdev->id].minor);
return 0;
}
static int drvheo_remove(struct platform_device *pdev)
{
printk("%s %d\n", __func__, __LINE__);
misc_deregister(&misc[pdev->id]);
return 0;
}
static struct platform_device_id tbl[] = {
{
"phoenix0"},
{
"phoenix1"},
{
},
};
static struct platform_driver drvheo_driver = {
.probe = drvheo_probe,
.remove = drvheo_remove,
.id_table = tbl,
.driver = {
.name = "phoenix",
},
};
static int drvheo_init(void)
{
printk("%s %d\n", __func__, __LINE__);
return platform_driver_register(&drvheo_driver);
}
static void drvheo_exit(void)
{
printk("%s %d\n", __func__, __LINE__);
platform_driver_unregister(&drvheo_driver);
return;
}
module_init(drvheo_init);
module_exit(drvheo_exit);
MODULE_LICENSE("GPL");
The printout is as follows:
[root@Hobot msplt] # insmod driver.ko
drvheo_init 102
[root@Hobot msplt] # insmod dev0.ko
heo_init 37
drvheo_probe 69, match ok, pdev 0, phoenix0, misc phx0 10485793 10 33
[root@Hobot msplt] # cat /dev/phx0
dmisc_open 15, pdev 10485793 10 33
dmisc_read 25
dmisc_release 20
[root@Hobot msplt] # insmod dev1.ko
heo_init 37
drvheo_probe 69, match ok, pdev 1, phoenix1, misc phx1 10485792 10 32
[root@Hobot msplt] # cat /dev/phx1
dmisc_open 15, pdev 10485792 10 32
dmisc_read 25
dmisc_release 20
–end