SylixOS设备操作之mmap
有些外设控制器可能带有DMA功能,这类外设的驱动一般都需要使用物理连续的内存,而且只需要物理内存即可,在驱动层并不需要进行映射访问,这时可以通过下面接口申请物理内存:
PVOID API_VmmPhyAlloc(size_t stSize);
通过此接口返回的地址只表示物理地址,并没有经过映射,所以如果驱动直接访问会崩溃。像显示驱动、摄像头驱动都可能是通过此方式申请物理内存的,那么应用层如何访问驱动里的这些物理内存呢,因为应用层是通过虚拟地址访问的,所以必须先调用mmap 接口将驱动中的物理内存和虚拟内存进行映射,然后才能读写访问:
void *mmap(void *pvAddr, size_t stLen, int iProt, int iFlag, int iFd, off_t off);
mmap 的各个参数解释如下:
- pvAddr:指定固定的虚拟地址进行映射,一般为NULL表示让系统来分配虚拟地址。
- stLen:要映射的空间大小,单位为字节。(系统层面会转换为以页大小为基础单位大小来映射)
- iProt:映射为可读还是可写。
- iFlag:映射标志,比如是否共享等等。
- iFd:要进行映射的文件描述符。
- off:起始映射偏移,一般为0。
此接口成功返回虚拟地址,失败返回MAP_FAILED。
当使用完后,通过munmap 来释放虚拟地址空间:
int munmap(void *pvAddr, size_t stLen);
1. 驱动层mmap实现
首先demo_dev_t 数据结构中需要添加两个成员:phy_addr 、map_flags 分别用来记录驱动中申请的物理内存地址和要映射的属性:
typedef struct _demo_dev { LW_DEV_HDR dev; int flag; LW_SEL_WAKEUPLIST wakeup_list; void *phy_addr; int map_flags; void *priv; } demo_dev_t;
在驱动加载时我们申请4KB大小的物理内存:
demo_dev[0].phy_addr = API_VmmPhyAlloc(4 * 1024); demo_dev[1].phy_addr = API_VmmPhyAlloc(4 * 1024);
不同的设备驱动需要映射的属性不一样,比如显示驱动需要映射内存为非cache属性,摄像头驱动需要映射为cache属性。非cache属性使用LW_VMM_FLAG_DMA 来表示,cache属性用LW_VMM_FLAG_RDWR 来表示:
#if 1 demo_dev[0].map_flags = LW_VMM_FLAG_DMA; demo_dev[1].map_flags = LW_VMM_FLAG_DMA; #else demo_dev[0].map_flags = LW_VMM_FLAG_RDWR; demo_dev[1].map_flags = LW_VMM_FLAG_RDWR; #endif
本例中我们使用非cache属性来演示。
驱动中mmap的原型:
int demo_mmap(demo_dev_t *dev, LW_DEV_MMAP_AREA *vm);
SylixOS系统使用LW_DEV_MMAP_AREA 数据结构来描述要映射的虚拟地址空间信息:
typedef struct { PVOID DMAP_pvAddr; /* 起始地址 */ size_t DMAP_stLen; /* 内存长度 (单位:字节) */ off_t DMAP_offPages; /* 文件映射偏移量 (单位:页面) */ ULONG DMAP_ulFlag; /* 当前虚拟地址 VMM 属性 */ } LW_DEV_MMAP_AREA;
通过此结构可以获得要映射的虚拟空间的起始地址和大小这两个主要信息。然后在mmap中就可以使用API_VmmRemapArea 来建立映射了:
ULONG API_VmmRemapArea(PVOID pvVirtualAddr, PVOID pvPhysicalAddr, size_t stSize, ULONG ulFlag, FUNCPTR pfuncFiller, PVOID pvArg);
此接口的各个参数含义如下:
- pvVirtualAddr:要映射的虚拟起始地址。
- pvPhysicalAddr:要映射的物理起始地址。
- stSize:要映射的大小。
- ulFlag:映射属性。
- pfuncFiller:缺页中断回调函数。
- pvArg:缺页中断回调函数参数。
在此demo驱动中,mmap函数的实现:
static int demo_mmap(demo_dev_t *dev, LW_DEV_MMAP_AREA *vm) { printk("%s.\r\n", __func__); if (API_VmmRemapArea(vm->DMAP_pvAddr, dev->phy_addr, vm->DMAP_stLen, dev->map_flags, LW_NULL, LW_NULL)) { return (PX_ERROR); } return (ERROR_NONE); }
2. 应用层使用mmap
应用层程序中我们先使用mmap映射4KB大小空间,然后通过memset来将这段空间清零,最后使用munmap取消映射:
addr = mmap (NULL, 4 * 1024, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (addr == MAP_FAILED) { printf("mmap file fail.\r\n"); close(fd); return -1; } memset(addr, 0, 4 * 1024); printf("memset ok.\r\n"); sleep(100); munmap(addr, 4 * 1024);
映射标志我们设置为可读写,延时100s是因为我们等会需要在命令行查看一些信息。
3. 实际运行效果
加载驱动,然后不着急运行app,首先在命令行使用virtuals 命令查看当前系统虚拟地址空间使用情况:
[root@sylixos:/apps/app_demo13]# virtuals vmm virtual area show >> vmm virtual program from: 0x10000000, size: 0xc0000000, used: 0% vmm virtual ioremap from: 0xd0001000, size: 0x04000000, used: 0% vmm virtual area usage as follow: VIRTUAL SIZE WRITE CACHE -------- -------- ----- ----- d0001000 1000 true false d0002000 1000 true false d0003000 1000 true false d0004000 1000 true false 10006000 2000 true true 10008000 5000 true true 1000d000 2000 true true 1000f000 1000 true true 10010000 1000 true true 10011000 1000 true true
可以看出虚拟地址空间信息主要包含四部分:起始地址、大小、是否可写、是否为cache属性。这时以后台方式运行app,也就是在app后面跟上 & 这个符号:
[root@sylixos:/apps/app_demo13]# ./app_demo13 & [root@sylixos:/apps/app_demo13]# demo_open . open read/write. file permission: 644. demo_ioctl 2f. demo_ioctl 26. demo_mmap. memset ok.
这时候再使用virtuals 命令查看当前系统虚拟地址空间使用情况:
[root@sylixos:/apps/app_demo13]# virtuals vmm virtual area show >> vmm virtual program from: 0x10000000, size: 0xc0000000, used: 1% vmm virtual ioremap from: 0xd0001000, size: 0x04000000, used: 0% vmm virtual area usage as follow: VIRTUAL SIZE WRITE CACHE -------- -------- ----- ----- d0001000 1000 true false d0002000 1000 true false d0003000 1000 true false d0004000 1000 true false 10001000 1000 true true 10006000 2000 true true 10008000 5000 true true 1000d000 2000 true true 1000f000 1000 true true 10010000 1000 true true 10011000 1000 true true 10012000 1f000 true true 10038000 9000 true true 10041000 5000 true true 10046000 1000 true false 10048000 20000 true true 10068000 2000000 true true 12068000 10000 true true
可以发现最后一行信息就是我们使用mmap时系统分配的虚拟地址空间信息。
附源码
driver_demo13.c源码:
#define __SYLIXOS_KERNEL #include <SylixOS.h> #include <module.h> #include <string.h> #include "driver.h" #include "vir_peripheral_device.h" typedef struct _demo_dev { LW_DEV_HDR dev; int flag; LW_SEL_WAKEUPLIST wakeup_list; void *phy_addr; int map_flags; void *priv; } demo_dev_t; int drv_index; demo_dev_t demo_dev[2]; static long demo_open(LW_DEV_HDR *dev, char *name, int flag, int mode) { demo_dev_t *device = (demo_dev_t *)dev; device->priv = NULL; device->flag = 0; printk("%s %s.\r\n", __func__, name); if (flag & O_NONBLOCK) { printk("open nonblock.\r\n"); device->flag |= O_NONBLOCK; } if ((flag & O_ACCMODE) == O_RDONLY) { printk("open read only.\r\n"); } else if ((flag & O_ACCMODE) == O_WRONLY) { printk("open write only.\r\n"); } else { printk("open read/write.\r\n"); } printk("file permission: %o.\r\n", mode); LW_DEV_INC_USE_COUNT(&device->dev); return (long)device; } static int demo_close(demo_dev_t *dev) { printk("%s.\r\n", __func__); LW_DEV_DEC_USE_COUNT(&dev->dev); return ERROR_NONE; } static int demo_ioctl(demo_dev_t *dev, int cmd, long arg) { PLW_SEL_WAKEUPNODE pselnode; struct stat *pstat; struct data *data; printk("%s %x.\r\n", __func__, cmd); switch (cmd) { case FIOFSTATGET: pstat = (struct stat *)arg; if (!pstat) { return (PX_ERROR); } pstat->st_dev = LW_DEV_MAKE_STDEV(&dev->dev); pstat->st_ino = (ino_t)0; pstat->st_mode = (S_IRWXU | S_IRWXG | S_IRWXO | S_IFCHR); pstat->st_nlink = 1; pstat->st_uid = 0; pstat->st_gid = 0; pstat->st_rdev = 0; pstat->st_size = 0; pstat->st_blksize = 0; pstat->st_blocks = 0; pstat->st_atime = API_RootFsTime(LW_NULL); pstat->st_mtime = API_RootFsTime(LW_NULL); pstat->st_ctime = API_RootFsTime(LW_NULL); break; case FIONBIO: if (*(int *)arg) { dev->flag |= O_NONBLOCK; } else { dev->flag &= ~O_NONBLOCK; } break; case FIOSETFL: if ((int)arg & O_NONBLOCK) { dev->flag |= O_NONBLOCK; } else { dev->flag &= ~O_NONBLOCK; } break; case FIOSELECT: pselnode = (PLW_SEL_WAKEUPNODE)arg; if (SEL_WAKE_UP_TYPE(pselnode) == SELREAD) { if (vir_device_readable()) { SEL_WAKE_UP(pselnode); } else { SEL_WAKE_NODE_ADD(&dev->wakeup_list, pselnode); vir_device_wakeup_register(&dev->wakeup_list); } } else { return (PX_ERROR); } break; case FIOUNSELECT: pselnode = (PLW_SEL_WAKEUPNODE)arg; if (SEL_WAKE_UP_TYPE(pselnode) == SELREAD) { SEL_WAKE_NODE_DELETE(&dev->wakeup_list, pselnode); } else { return (PX_ERROR); } break; case CMD_GET_VERSION: data = (struct data *)arg; data->version = 3; break; default: return (PX_ERROR); } return ERROR_NONE; } static ssize_t demo_read(demo_dev_t *dev, char *buf, size_t size) { int ret = -1; BOOL non_block = FALSE; printk("%s.\r\n", __func__); if (dev->flag & O_NONBLOCK) { non_block = TRUE; } else { non_block = FALSE; } ret = vir_device_read(non_block); if (ret > 0) memcpy(buf, &ret, sizeof(int)); return ret; } static ssize_t demo_write(demo_dev_t *dev, char *buf, size_t size) { char wdata[30] = {0}; int wdata_len = 30; printk("%s.\r\n", __func__); wdata_len = (size < wdata_len) ? size : wdata_len; memcpy(wdata, buf, size); printk("write data %s success.\r\n", wdata); return wdata_len; } static int demo_mmap(demo_dev_t *dev, LW_DEV_MMAP_AREA *vm) { printk("%s.\r\n", __func__); if (API_VmmRemapArea(vm->DMAP_pvAddr, dev->phy_addr, vm->DMAP_stLen, dev->map_flags, LW_NULL, LW_NULL)) { return (PX_ERROR); } return (ERROR_NONE); } static struct file_operations demo_fops = { .owner = THIS_MODULE, .fo_open = demo_open, .fo_close = demo_close, .fo_ioctl = demo_ioctl, .fo_read = demo_read, .fo_write = demo_write, .fo_mmap = demo_mmap, }; int module_init (void) { int ret = 0; drv_index = iosDrvInstallEx(&demo_fops); if (drv_index < 0) { printk("driver install fail.\r\n"); return -1; } DRIVER_LICENSE(drv_index, "GPL->Ver 2.0"); DRIVER_AUTHOR(drv_index, "GeWenBin"); DRIVER_DESCRIPTION(drv_index, "demo driver."); ret = iosDevAdd(&demo_dev[0].dev, "/dev/demo0", drv_index); if (ret != ERROR_NONE) { printk("device add fail.\r\n"); iosDrvRemove(drv_index, TRUE); return -1; } ret = iosDevAdd(&demo_dev[1].dev, "/dev/demo1", drv_index); if (ret != ERROR_NONE) { printk("device1 add fail.\r\n"); iosDevDelete(&demo_dev[0].dev); iosDrvRemove(drv_index, TRUE); return -1; } SEL_WAKE_UP_LIST_INIT(&demo_dev[0].wakeup_list); SEL_WAKE_UP_LIST_INIT(&demo_dev[1].wakeup_list); vir_device_init(); demo_dev[0].phy_addr = API_VmmPhyAlloc(4 * 1024); demo_dev[1].phy_addr = API_VmmPhyAlloc(4 * 1024); #if 1 demo_dev[0].map_flags = LW_VMM_FLAG_DMA; demo_dev[1].map_flags = LW_VMM_FLAG_DMA; #else demo_dev[0].map_flags = LW_VMM_FLAG_RDWR; demo_dev[1].map_flags = LW_VMM_FLAG_RDWR; #endif return 0; } void module_exit (void) { iosDevDelete(&demo_dev[0].dev); iosDevDelete(&demo_dev[1].dev); iosDrvRemove(drv_index, TRUE); if (demo_dev[0].phy_addr) API_VmmPhyFree(demo_dev[0].phy_addr); if (demo_dev[1].phy_addr) API_VmmPhyFree(demo_dev[1].phy_addr); }
app_demo13.c源码:
#include <stdio.h> #include <string.h> #include <sys/mman.h> int main (int argc, char **argv) { int fd; void *addr = NULL; fd = open("/dev/demo0", O_RDWR); if (fd < 0) { printf("open file fail.\r\n"); return -1; } addr = mmap (NULL, 4 * 1024, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (addr == MAP_FAILED) { printf("mmap file fail.\r\n"); close(fd); return -1; } memset(addr, 0, 4 * 1024); printf("memset ok.\r\n"); sleep(100); munmap(addr, 4 * 1024); close(fd); return (0); }
评论