SylixOS字符设备驱动开发(十三)

gewenbin
gewenbin
gewenbin
188
文章
15
评论
2020年12月17日23:21:40 评论 1,158

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_addrmap_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);
}
gewenbin
  • 本文由 发表于 2020年12月17日23:21:40
  • 转载请务必保留本文链接:http://www.databusworld.cn/8957.html
匿名

发表评论

匿名网友 填写信息

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: