Featured image of post 实现一个杂项设备 mmap ioctl

实现一个杂项设备 mmap ioctl

题目

实现一个杂项设备/dev/uosmisc,并实现它的mmap、ioctl接口。要求: mmap时,写入的数据,在下次映射时能读出来。 ioctl实现命令:UOS_IOC_SETINFO:设置一些信息,UOS_IOC_GETINFO:读取设置的信息。

答案

moudle.c

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/slab.h>
#include <linux/mm.h>
#include <linux/io.h>
#include <linux/device.h>
#include <linux/miscdevice.h>

#define GMEM_ORDER 3
#define GMEM_PAGES 8
#define GMEM_SIZE (GMEM_PAGES*PAGE_SIZE)

#define UOS_IOCTL_NUM 'U'

#define UOS_IOC_SETINFO _IOWR(UOS_IOCTL_NUM, 4, void *)
#define UOS_IOC_GETINFO _IOWR(UOS_IOCTL_NUM, 9, void *)

static int uos_info;

static uint8_t *gmem_buf;
static int gmem_init(void)
{
        gmem_buf = (uint8_t *)__get_free_pages(GFP_KERNEL, GMEM_ORDER);
        if (gmem_buf == NULL)
                return -1;
        return 0;
}


int uos_file_setinfo(unsigned long arg)
{
        int info;
        copy_from_user(&info, (void __user *)arg, sizeof(info));

        uos_info = info;
        printk(KERN_ERR "setinfo uos_info=%d\n", uos_info);

        return 0;
}

unsigned long uos_file_getinfo(unsigned long arg)
{
        int info = uos_info;
        printk(KERN_ERR "getinfo:uos_info=%d\n", info);
        return copy_to_user((void __user *)arg, &info, sizeof(info));
}

static long uos_file_unlocked_ioctl(struct file *file, unsigned int req,
                                        unsigned long arg)
{
        int ret = 0;

        switch(req) {
        case UOS_IOC_SETINFO:
                ret = uos_file_setinfo(arg);
                break;
        case UOS_IOC_GETINFO:
                ret = uos_file_getinfo(arg);
                break;
        }

        return ret;
}

static int uos_file_mmap(struct file *file, struct vm_area_struct *vma)
{
        int ret = 0;

        /*
         * we use reserved mem, gmem_buf, as the mmap phyiscal addr,
         * so we can keep the written contents, and can read data next mapping.
         */
        //get offset from user space
        unsigned long offset = vma->vm_pgoff << PAGE_SHIFT;

        //based on gmem, get the virtual addr
        unsigned long map_offset = (unsigned long)gmem_buf + offset;

        //convert virtual addr to physical page frame
        unsigned long pfn_start = virt_to_phys((void *)map_offset) >> PAGE_SHIFT;

        //get map size
        unsigned long size = vma->vm_end - vma->vm_start;

        if ((size % PAGE_SIZE) != 0)
                size = (size+PAGE_SIZE)/PAGE_SIZE * PAGE_SIZE;

        if (size > GMEM_SIZE)
                size = GMEM_SIZE;

        vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);

        //do memory map
        ret = remap_pfn_range(vma, vma->vm_start, pfn_start, size, vma->vm_page_prot);

        return 0;
}

struct file_operations uos_misc_fops = {
        .owner = THIS_MODULE,
        .unlocked_ioctl = uos_file_unlocked_ioctl,
        .mmap = uos_file_mmap,
};

struct miscdevice uos_misc = {
        .minor = MISC_DYNAMIC_MINOR,
        .name  = "uos-misc",
        .fops  = &uos_misc_fops,
};

static int __init misc_dev_init(void)
{
        int ret;

        ret = gmem_init();
        if (ret)
                return -1;

        ret = misc_register(&uos_misc);
        if (ret) {
                return -1;
        }


        return 0;
}

static void __exit misc_dev_exit(void)
{
        misc_deregister(&uos_misc);
}

module_init(misc_dev_init);
module_exit(misc_dev_exit);

MODULE_LICENSE("GPL");

test.c

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <errno.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/ioctl.h>


#define UOS_IOCTL_NUM 'U'

#define UOS_IOC_SETINFO _IOWR(UOS_IOCTL_NUM, 4, void *)
#define UOS_IOC_GETINFO _IOWR(UOS_IOCTL_NUM, 9, void *)


int main() {
    int fd;
    char *start;
    int info = 10;
    char buf[32];
    int ret = -1;

    fd = open("/dev/uos-misc", O_RDWR);
    if (fd == -1) {
        perror("open");
        exit(EXIT_FAILURE);
    }

    start = mmap(NULL, 32, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
    if (start == MAP_FAILED) {
            close(fd);
            return -1;
    }
    //start[0] = 'a';
    //start[1] = '3';
    //start[2] = 'd';
    //start[3] = '\0';

    printf("%s\n", start);

    ret = ioctl(fd, UOS_IOC_SETINFO, &info);
    if (ret) {
            printf("UOS_IOC_SETINFO failed");
    }

    ret = ioctl(fd, UOS_IOC_GETINFO, &info);
    if (ret) {
            printf("UOS_IOC_GETINFO failed");
    } else {
            printf("UOS_IOC_GETINFO, info=%d\n", info);
    }

    munmap(start, 32);
    close(fd);

    return 0;
}
obj-m := misc_uos.o
misc_uos-objs := module.o

kdir := /lib/modules/$(shell uname -r)/build
cwd := $(shell pwd)

default:
        make -C $(kdir) M=$(cwd) modules

clean:
        make -C $(kdir) M=$(cwd) clean