在有的场景中,我们需要将硬件资源(物理地址、地址长度)等信息告诉应用程序,然后将这一片地址mmap到用户空间,这样以后,我们在用户空间就可以直接读写(如*paddr = 1)这个区域了。UIO设备给我们提供了一种简单的方式,因为UIO自己实现了struct file_operations 里面的所有接口包括mmap。这样方便我们做内存映射。
实验:创建uio设备,并在/dev下面创建相应的文件,然后在用户层去映射UIO记录的那片内存,并进行读写操作。
1、ko文件如下:
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <asm/uaccess.h>
#include <linux/slab.h>
#include <linux/device.h>
#include <linux/semaphore.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/io.h>
#include <linux/of_net.h>
#include <linux/netdevice.h>
#include <linux/uio_driver.h>
#include <linux/syscalls.h>
#include <linux/types.h>
#include <linux/proc_fs.h>
#include <linux/kern_levels.h>
#include <linux/syscalls.h>
#include <linux/platform_device.h>
/* define and struct type */
#define MY_ARRY_SIZE (1024)
#define MY_MODULE_NAME ("my_char_name")
struct my_mem_type {
char arry[MY_ARRY_SIZE];
};
/* variable declare */
static unsigned int alloc_mem_size = 0;
static struct my_mem_type *p_mem = NULL;
static struct uio_info *p_my_uio_info = NULL;
static struct cdev *p_my_cdev = NULL;
static struct platform_device *p_plat_dev = NULL;
static struct class *p_char_class = NULL;
static struct device *p_char_dev = NULL;
/* function declare */
static int mmap_open(struct inode *p_node, struct file *p_file);
static ssize_t mmap_read(struct file *p_file, char __user *p_buf, size_t size, loff_t * p_loff);
static ssize_t mmap_write(struct file *p_file, const char __user *p_buf, size_t size, loff_t *p_loff);
static int mmap_release(struct inode *p_node, struct file *p_file);
static int my_uio_dev_create(struct device *p_dev);
static const struct file_operations my_cdev_fops = {
.owner = THIS_MODULE,
.open = mmap_open,
.read = mmap_read,
.write = mmap_write,
.release = mmap_release,
};
/*
static int plat_prob(struct device *p_dev)
{
my_uio_dev_create(p_dev);
return 0;
}
*/
static struct device_driver plat_driver = {
.name = "kpart",
.bus = &platform_bus_type,
.probe = plat_prob,
};
int mmap_release(struct inode *p_node, struct file *p_file)
{
return 0;
}
static int mmap_open(struct inode *p_node, struct file *p_file)
{
return simple_open(p_node, p_file);
}
static ssize_t mmap_read(struct file *p_file, char __user *p_buf, size_t size, loff_t * p_loff)
{
if(copy_to_user(p_buf, p_mem, size)) {
pr_err("copy_to_user error\n");
return 0;
}
return size;
}
static ssize_t mmap_write(struct file *p_file, const char __user *p_buf, size_t size, loff_t *p_loff)
{
if(copy_from_user(p_mem, p_buf, size)) {
pr_err("copy_from_user error\n");
return 0;
}
return size;
}
#define SYSCALL_VL(vl_syscall)\
({ \
long err = 0; \
mm_segment_t old_fs; \
\
old_fs = get_fs(); \
set_fs(KERNEL_DS); \
err = vl_syscall; \
set_fs(old_fs); \
err;\
})
static int my_char_dev_create(void)
{
dev_t devno;
int ret, err;
int dev_minor = 0;
int major;
ret = alloc_chrdev_region(&devno, dev_minor, 1, MY_MODULE_NAME);
if (ret < 0) {
pr_err("alloc_chrdev_region failed\n");
return -1;
}
major = MAJOR(devno);
pr_err("devno = %d\n", devno);
p_my_cdev = cdev_alloc();
if (p_my_cdev == NULL) {
pr_err("p_my_cdev alloc failed!\n");
return -1;
}
cdev_init(p_my_cdev, &my_cdev_fops);
p_my_cdev->owner = THIS_MODULE;
err = cdev_add(p_my_cdev, devno, 1);
if (err < 0) {
pr_err("cdev_add failed\n");
return -1;
}
p_char_class = class_create(THIS_MODULE, MY_MODULE_NAME);
if (p_char_class == NULL) {
pr_err("class_create failed!\n");
return -1;
}
p_char_dev = device_create(p_char_class, NULL, MKDEV(major, dev_minor), NULL, MY_MODULE_NAME);
if (p_char_dev == NULL) {
pr_err("device_create failed!\n");
return -1;
}
SYSCALL_VL ( sys_mknod ( "/dev/my_char_dev", S_IFCHR | S_IRUSR | S_IWUSR,
new_encode_dev ( MKDEV ( major, dev_minor ) ) ) );
return 0;
}
static int my_platform_dev_creat(void)
{
p_plat_dev = platform_device_register_simple("kpart", -1, NULL, 0);
return driver_register(&plat_driver);
}
static int __uio_match(struct device *dev, void *data)
{
return 1;
}
static int my_uio_dev_create(struct device *p_dev)
{
int ret;
char uio_filename[128] = {"/dev/my_uio_dev1"};
struct device *puiodev;
if (p_dev == NULL) {
pr_err("device_create failed!\n");
return -1;
}
p_my_uio_info = kzalloc(sizeof(struct uio_info), GFP_KERNEL);
if(p_my_uio_info == NULL) {
pr_err("kzalloc alloc memert failed!\n");
return -1;
}
p_my_uio_info->name = "my_uio_dev";
p_my_uio_info->version = "0.0.1";
p_my_uio_info->irq = UIO_IRQ_NONE;
p_my_uio_info->mem[0].addr = __pa(p_mem);
p_my_uio_info->mem[0].size = alloc_mem_size;
p_my_uio_info->mem[0].memtype = UIO_MEM_PHYS;
pr_err("uio phy addr : %p\n", p_my_uio_info->mem[0].addr);
ret = uio_register_device(p_dev, p_my_uio_info);
sprintf(uio_filename, "/dev/%s%d", "my_uio_dev", 1);
puiodev = device_find_child(p_dev, NULL, __uio_match);
ret = SYSCALL_VL(sys_mknod(uio_filename, S_IFCHR | S_IRUSR | S_IWUSR, new_encode_dev(puiodev->devt)));
return ret;
}
static int __init my_mmap_uio_init(void)
{
/* init memory */
alloc_mem_size = PAGE_ALIGN(sizeof(struct my_mem_type));
p_mem = kmalloc(alloc_mem_size, GFP_KERNEL);
if(p_mem == NULL) {
pr_err("kmalloc alloc mem failed!\n");
return 0;
}
memset(p_mem, 0x01, alloc_mem_size);
pr_debug("p_mem kernel virtual addr : %p, size : %p\n", p_mem, alloc_mem_size);
// create char devcide
if (my_char_dev_create() < 0) {
pr_err("cdev create failed!\n");
return 0;
}
pr_err("char_dev_create\n");
/* create uio device */
if(my_uio_dev_create(p_char_dev) < 0) {
pr_err("uio dev create failed!\n");
return 0;
}
pr_err("uio_dev_create finish\n");
return 1;
//return my_platform_dev_creat();
}
static void __exit my_mmap_uio_exit(void)
{
if (p_mem) {
kfree(p_mem);
}
}
module_init(my_mmap_uio_init);
module_exit(my_mmap_uio_exit);
MODULE_LICENSE("GPL v2");
用户层代码1:测试字符设备文件,因为在/dev下面动态的创建了设备节点。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
#include <getopt.h>
#include <stdlib.h>
#include <time.h>
#include <sys/time.h>
#define BUF_SIZE 1024
char buf[BUF_SIZE];
int main (void)
{
int fd, i;
unsigned long uiosize = 0;
void *p_viraddr = NULL;
int len = 0;
/* init buf */
for (i = 0; i < BUF_SIZE; i++) {
buf[i] = 'a';
}
fd = open("/dev/my_char_dev", O_RDWR, 0);
if (fd < 0) {
printf("fd get failed\n");
}
if (write(fd, buf, BUF_SIZE) < 0) {
printf("write failed\n");
}
memset(buf, 0, BUF_SIZE);
if (read(fd, buf, BUF_SIZE) < 0) {
printf("read failed\n");
}
for (i = 0; i < BUF_SIZE; i++) {
printf("%c", buf[i]);
}
close(fd);
return 0;
}
用户层代码2: 获取uio信息(记录的内存基地址地址,地址长度),然后将这一片内存映射到用户层,然后直接对这一片内存读写操作(等于直接操作内核态下面申请的地址,跳过了繁琐的ioctrl流程)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
#include <getopt.h>
#include <stdlib.h>
#include <time.h>
#include <sys/time.h>
#define BUF_SIZE 1024
char buf[BUF_SIZE];
int main (void)
{
int fd i;
unsigned long long uiosize = 0; uioaddr = 0;
void *p_viraddr = NULL;
int len = 0;
/* init buf */
for (i = 0; i < BUF_SIZE; i++) {
buf[i] = 'a';
}
fd = open("/sys/class/uio/uio3/maps/map0/addr", O_RDONLY, 0);
if (fd < 0) {
printf("fd addr get failed\n");
}
if (read(fd, buf, 128) < 0) {
printf("fd addr get failed!\n");
}
uioaddr = strtoull(buf, NULL, 16);
printf("uioaddr : 0x%p\n", uioaddr );
close(fd);
fd = open("/sys/class/uio/uio3/maps/map0/size", O_RDONLY, 0);
if (fd < 0) {
printf("fd addr get failed\n");
}
if (read(fd, buf, 128) < 0) {
printf("fd sizeget failed!\n");
}
uiosize = strtoull(buf, NULL, 16);
printf("uiosize : 0x%p\n", uiosize);
close(fd);
fd = open("/dev/my_uio_dev1", O_RDWR, 0);
if (fd < 0) {
printf("fd addr get failed\n");
}
p_viraddr = mmap(NULL, uiosize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (p_viraddr == NULL) {
printf("mmp erroe!\n");
return 0;
}
printf("p_viraddr : 0x%p\n", p_viraddr );
for (i = 0; i < 100; i++) {
((char *)p_viraddr)[i] = 'a';
}
for (i = 0; i < 100; i++) {
printf("%c", ((char *)p_viraddr)[i] );
}
close(fd);
return 0;
}
应用程序1的实验现象:aaaaaaaaaa…(1024个)
应用程序2的实验现象:aaaaaaaaaa…(100个)
注意: 应用程序2中: open(“/sys/class/uio/uio3/maps/map0/size”, O_RDONLY, 0); 和 open(“/sys/class/uio/uio3/maps/map0/addr”, O_RDONLY, 0); 中的uio3, 需要根据自己本地创建的依据,因为我本地已经存在了uio0、uio1、uio2, 所以再次创建的时候创建的uio3
注意:class_create ,可以在/sys/class/ 目录下创建my_char_name文件夹
结合 device_create在my_char_name文件夹下面创建指定的设备驱动文件(什么样的设备文件由主从设备号决定)。
注意: SYSCALL_VL 函数,可以在指定目录下面创建指定设驱动文件(
指定目录和文件名由第一个参数决定,什么样的设备文件由主从设备号决定 ),也可以在脚本中mknod 创建设备节点 ,一般适用于静态创建(主从设备号已知)