Sharing a buffer between kernel-space and user-space
ION memory allocator 관련 글은 LWN 를 번역한 The Android ION memory allocator 참조.
이번에는 user-space 에서 할당한 메모리를 kernel driver 에서 공유하여 write 를 하고 user에서 write 한 데이터가 잘 들어가 있는지 확인하는 수준(?)으로 진행한다.
% 모바일이나 특정 브라우저에서는 syntex highlight 가 지원이 안되어 textbox 에 소스가 나올 수도 있음.
Scenario
1. User 에서 ion("/dev/ion")을 open ==> alloc ==> share 순으로 진행하여 공유할 ion buffer 를 생성 및 공유 준비
2. User application 에서 공유하려는 kernel driver("/dev/vion_dev")를 open : 여기서 vion_dev 는 내가 만든 device driver 이다.
3. User 에서 만들어진 shared_fd 를 vion_dev driver 로 전달.
4. Kernel driver 에서 받은 shared_fd 를 이용해 import 하여 메모리를 공유할 준비.
5. User 영역에서 특정 string("ion buffer test magic string") 을 vion_dev driver 에 write 를 요청
6. Kernel driver 에서 shared buffer 에 magic string을 write
7. User 에서 mmap 으로 shared buffer 의 내용 확인.
먼저 User application을 살펴 보면,
아래의 ion_XXX interface 들은 https://android.googlesource.com/platform/system/core/+/android-4.1.2_r1/libion/ion.c 를 살펴 보면 내용을 볼 수 있다.
- User-space application
- #include <stdio.h>
- #include <linux/ioctl.h>
- #include <fcntl.h>
- #include <errno.h>
- #include <stdlib.h>
- #include <sys/mman.h>
- #include <string.h>
- #include <linux/ion.h>
- #include <ion/ion.h>
- #include <cutils/log.h>
- #include "ion_test_module.h"
- #define TEST_MODULE_NAME "/dev/vion_dev"
- #define DEFAULT_BUF_SIZE 128
- int share_with_testdrv(int shared_fd)
- {
- int test_fd;
- char buff[DEFAULT_BUF_SIZE] = "ion buffer test magic string";
- int size = 0;
- // TEST_MODULE_NAME : vion_dev 를 open
- test_fd = open(TEST_MODULE_NAME, O_RDWR);
- if (test_fd < 0) {
- printf("Failed to open test driver(vion)\n");
- return 0;
- }
- // register ion client in driver with shared file descriptor
- ioctl(test_fd, VION_REGISTER_ION, &shared_fd);
- // write a string for confirm.
- size = write(test_fd, (char*)buff, DEFAULT_BUF_SIZE - 1);
- if (size > 0) {
- printf("write \"%s\" string to ion test driver complete\n", buff);
- }
- // for sync??
- printf("sleep 1sec...\n");
- sleep(1);
- close(test_fd);
- return size;
- }
- int main(int argc, char ** argv)
- {
- struct ion_handle *ion_hnd;
- size_t size = DEFAULT_BUF_SIZE;
- int ion_fd, shared_fd, test_fd;
- int ret = 0;
- int write_size = 0;
- unsigned char *confirm_vaddr = NULL;
- // open ion device driver
- ion_fd = ion_open();
- if (ion_fd < 0) {
- printf("Failed to open ion device driver\n");
- ret = ion_fd;
- goto out;
- }
- // allocation ion buffer with ION_HEAP_SYSTEM_CONTIG type.
- // ion driver 의 physical address 를 얻어오는 interface(ion_phys()) 는
- // ION_HEAP_SYSTEM 의 memory type은 지원하지 않음.
- ret = ion_alloc(ion_fd, size, 0, ION_HEAP_SYSTEM_CONTIG_MASK,
- ION_FLAG_CACHED | ION_FLAG_CACHED_NEEDS_SYNC,
- &ion_hnd );
- if (ret) {
- printf("ion_alloc failed\n");
- goto close_fd;
- }
- // get shared file descriptor
- ret = ion_share(ion_fd, ion_hnd, &shared_fd);
- if (ret) {
- printf("ion_share failed %d\n", ret);
- goto close_ion;
- }
- // open test driver(vion_dev) and write test string.
- write_size = share_with_testdrv(shared_fd);
- if (write_size <= 0) {
- printf("Failed to share with test drv\n");
- goto close_ion;
- }
- // mmap memory with shared file descriptor.
- confirm_vaddr = (unsigned char*)mmap( NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, shared_fd, 0 );
- if (confirm_vaddr != NULL)
- // check a string which is written in ion test kernel driver.
- printf("confirm_vaddr is not NULL, confirm data string \"%s\"\n", confirm_vaddr);
- else
- printf("mmap failed\n");
- if (confirm_vaddr)
- munmap((void*)confirm_vaddr, size);
- printf("\nion test application terminated.\n");
- close_ion:
- ion_free(ion_fd, ion_hnd);
- close_fd:
- close(ion_fd);
- out:
- return ret;
- }
주석을 참조하고 Android ION 에 대한 내용의 글을 보면 이해가 쉬울 듯 한데..
- Kernel-space driver
- #include <linux/device.h>
- #include <linux/module.h>
- #include <linux/kernel.h>
- #include <linux/init.h>
- #include <linux/cdev.h>
- #include <linux/fs.h>
- #include <asm/uaccess.h>
- #include <linux/export.h>
- #include <asm/io.h>
- #include <linux/ion.h>
- #include "ion_test_module.h"
- #define DEVICE_NAME "vion"
- static int major = 0, minor = 0;
- static struct cdev vion_cdev;
- static struct class *cl;
- static struct ion_client *m_client;
- static struct ion_handle *m_handle;
- static int device_opened = 0;
- extern struct ion_device *lg115x_dev;
- void *kernel_vaddr = NULL;
- static int vion_open(struct inode *inode, struct file *filp)
- {
- #ifdef DEBUG
- printk(KERN_INFO "vion device open\n");
- #endif
- if (device_opened > 0)
- return -EBUSY;
- device_opened++;
- return 0;
- }
- static int vion_release(struct inode *inode, struct file *filp)
- {
- // Already open?
- if (device_opened <= 0)
- return 0;
- device_opened--;
- return 0;
- }
- static long vion_ioctl(struct file *f, unsigned int cmd, unsigned long arg)
- {
- int result = 0;
- switch(cmd) {
- case VION_REGISTER_ION: {
- int ion_register;
- if (copy_from_user(&ion_register,
- (int *)arg,
- sizeof(int))) {
- printk(KERN_ERR "cmd %d : Error get data from ion\n", cmd);
- result = -EFAULT;
- goto exit;
- }
- if (m_client == NULL) {
- // lg115x_dev is exported from driver/gpu/ion/lg115x/lg115x_ion.c
- m_client = ion_client_create(lg115x_dev,
- ION_HEAP_TYPE_SYSTEM_CONTIG,
- "vion");
- }
- if (m_client == NULL) {
- printk(KERN_ERR "Failed to create ion client with lg115x interface\n");
- result = -EFAULT;
- goto exit;
- }
- // get handle by ion_import_dma_buf() call.
- m_handle = ion_import_dma_buf(m_client, ion_register);
- return 0;
- }
- // 아직 필요하진 않지만(ion buffer는 user level에서 free해주니까) register와 pair로
- // 작성됨.
- case VION_UNREGISTER_ION: {
- int ion_register;
- if (copy_from_user(&ion_register,
- (int *)arg,
- sizeof(int))) {
- printk(KERN_ERR "cmd %d : Error get data from ion\n", cmd);
- result = -EFAULT;
- goto exit;
- }
- if (m_client == NULL) {
- printk(KERN_ERR "Failed to free with ion client\n");
- result = -EFAULT;
- goto exit;
- }
- ion_free(m_client, m_handle);
- return 0;
- }
- }
- exit:
- return result;
- }
- size_t get_vaddr_from_ion_buffer(void)
- {
- ion_phys_addr_t phyaddr = 0;
- size_t len = 0;
- int ret_val = 0;
- if (m_client == NULL || m_handle == NULL) {
- printk(KERN_ERR "Can't get client or handle in read operation\n");
- return 0;
- }
- // get physical address and length from handle.
- ret_val = ion_phys(m_client, m_handle, &phyaddr, &len);
- if (ret_val || !phyaddr) {
- printk(KERN_ERR "Error get physical address from ion\n");
- return 0;
- } else {
- printk(KERN_INFO "Read : physaddr 0x%x, len 0x%x\n", (unsigned long)phyaddr, (unsigned int)len);
- }
- // ION_HEAP_TYPE_SYSTEM_CONTIG 은 map 할 필요가 없을 듯.
- // virtual address를 바로 얻어와 copy 하도록 함.
- // kernel_vaddr = ion_map_kernel(m_client, m_handle);
- kernel_vaddr = phys_to_virt(phyaddr);
- printk(KERN_INFO "phys to virt 0x%x\n", kernel_vaddr);
- return len;
- }
- // read 도 작성되었지만 사용할 필요는 없습니다. 테스트 용도로 만들어짐.
- static ssize_t vion_read(struct file *f, char __user *buf,
- size_t len, loff_t *off) {
- size_t size = 0;
- if ((size = get_vaddr_from_ion_buffer()) < 1) {
- printk(KERN_ERR "Need to ready buffer for read\n");
- return 0;
- }
- if (size < len)
- len = size;
- if (copy_to_user((char*)kernel_vaddr, buf, len) < 0)
- return 0;
- return len;
- }
- static ssize_t vion_write(struct file *f, const char __user *buf,
- size_t len, loff_t *off) {
- size_t size = 0;
- #ifdef DEBUG
- printk(KERN_INFO "write data %s\n", buf);
- #endif
- if ((size = get_vaddr_from_ion_buffer()) < 1) {
- printk(KERN_ERR "Need to ready buffer for read\n");
- return 0;
- }
- if (size < len) {
- printk(KERN_ERR "Not enough buffer, set to MAX 0x%x\n", size);
- len = size;
- }
- if (kernel_vaddr != NULL)
- strncpy((char*)kernel_vaddr, buf, len);
- else
- printk(KERN_ERR "virtual address is NULL \n");
- return len;
- }
- static struct file_operations vion_fops = {
- .open = vion_open,
- .release = vion_release,
- .read = vion_read,
- .write = vion_write,
- .unlocked_ioctl = vion_ioctl
- };
- static int __init verify_iondev_init(void)
- {
- int ret_val;
- dev_t dev;
- // Get a number of device node dynamically.
- ret_val = alloc_chrdev_region(&dev, minor, 1, DEVICE_NAME);
- major = MAJOR(dev);
- if (ret_val < 0) {
- printk(KERN_ERR "Error allocating charater device region\n");
- ret_val = -ENODEV;
- return ret_val;
- }
- // Register the character device.
- if ((cl = class_create(THIS_MODULE, "chardrv")) == NULL) {
- printk(KERN_ERR "class_create failed\n");
- unregister_chrdev_region(dev, 1);
- return -1;
- }
- if (device_create(cl, NULL, dev, NULL, "vion_dev") == NULL) {
- printk(KERN_ERR "device_create failed\n");
- class_destroy(cl);
- unregister_chrdev_region(dev, 1);
- return -1;
- }
- cdev_init(&vion_cdev, &vion_fops);
- if (cdev_add(&vion_cdev, dev, 1) == -1) {
- printk(KERN_ERR "cdev_failed failed\n");
- class_destroy(cl);
- unregister_chrdev_region(dev, 1);
- }
- // Initialize ion client
- m_client = NULL;
- #ifdef DEBUG
- printk(KERN_INFO "vion driver : major %d, minor %d\n", MAJOR(dev), MINOR(dev));
- #endif
- return 0;
- }
- static void __exit verify_iondev_exit(void)
- {
- dev_t dev = MKDEV(major, minor);
- device_destory(cl, dev);
- class_destory(cl);
- cdev_del(&vion_cdev);
- unregister_chrdev_region(dev, 1);
- return;
- }
- module_init(verify_iondev_init);
- module_exit(verify_iondev_exit);
참고 할 정도의 code는 되지 않을까? ^^