Chinaunix首页 | 论坛 | 博客
  • 博客访问: 231059
  • 博文数量: 27
  • 博客积分: 465
  • 博客等级: 下士
  • 技术积分: 1340
  • 用 户 组: 普通用户
  • 注册时间: 2012-08-23 22:33
文章分类

全部博文(27)

文章存档

2014年(1)

2013年(2)

2012年(24)

分类: LINUX

2012-09-03 21:25:07

    首先我们来回归下怎么写一个字符设备驱动程序,下面以linux设备中的混杂设备为例

点击(此处)折叠或打开

  1. static int __init misc_init(void)
  2. {
  3.     int err;

  4. #ifdef CONFIG_PROC_FS
  5.     proc_create("misc", 0, NULL, &misc_proc_fops);
  6. #endif
  7.     misc_class = class_create(THIS_MODULE, "misc");
  8.     err = PTR_ERR(misc_class);
  9.     if (IS_ERR(misc_class))
  10.         goto fail_remove;

  11.     err = -EIO;
  12.     if (register_chrdev(MISC_MAJOR,"misc",&misc_fops))
  13.         goto fail_printk;
  14.     misc_class->devnode = misc_devnode;
  15.     return 0;

  16. fail_printk:
  17.     printk("unable to get major %d for misc devices\n", MISC_MAJOR);
  18.     class_destroy(misc_class);
  19. fail_remove:
  20.     remove_proc_entry("misc", NULL);
  21.     return err;
  22. }
上面的主要步骤为申请一个主次设备号,构建一个file_opertiaon,并用register_chrdev注册,构建类,并可以在类下创建设备节点。现在来看看内核是怎么实现lcd驱动程序呢?

点击(此处)折叠或打开

  1. static int __init
  2. fbmem_init(void)
  3. {
  4.     proc_create("fb", 0, NULL, &fb_proc_fops);//与proc文件系统有关

  5.     if (register_chrdev(FB_MAJOR,"fb",&fb_fops))//注册字符设备
  6.         printk("unable to get major %d for fb devs\n", FB_MAJOR);

  7.     fb_class = class_create(THIS_MODULE, "graphics");//创建类,为以后创建设备节点做准备
  8.     if (IS_ERR(fb_class)) {
  9.         printk(KERN_WARNING "Unable to create fb class; errno = %ld\n", PTR_ERR(fb_class));
  10.         fb_class = NULL;
  11.     }
  12.     return 0;
  13. }
从上面也可以看出,内核对于lcd驱动程序也是满足一个主设备号为29的字符设备,那么用户空间访问通过fb_fops结构体的成员函数会操作到LCD控制器硬件寄存器。下面来看看linux帧缓冲设备驱动的主要结构图
framebuffer机制模仿显卡的功能,将显卡硬件结构抽象为一系列的数据结构,可以通过framebuffer的读写直接对显存进行操作。framebuffer一个字符设备,主设备号,对应于/dev/fb0...32的设备文件。对于用户程序而言,与字符设备并没有什么区别,用户具有把其看成一块内存,既可以读也可以写。那么下面来看看
在这个图中占主导地位的fb_info结构体

点击(此处)折叠或打开

  1. struct fb_info {
  2.     atomic_t count;
  3.     int node;             //子设备号
  4.     int flags;
  5.     struct mutex lock;        //互斥锁
  6.     struct mutex mm_lock;        /* Lock for fb_mmap and smem_* fields */
  7.     struct fb_var_screeninfo var;    //当前缓冲区的可变参数
  8.     struct fb_fix_screeninfo fix;    //固定参数
  9.     struct fb_monspecs monspecs;    //当前显示器标志
  10.     struct work_struct queue;    //帧缓冲事件队列
  11.     struct fb_pixmap pixmap;    //图像硬件mapper
  12.     struct fb_pixmap sprite;    //光标硬件mapper
  13.     struct fb_cmap cmap;        //当前的调色板
  14.     struct list_head modelist; /* mode list */
  15.     struct fb_videomode *mode;    //当前的视频模式

  16. #ifdef CONFIG_FB_BACKLIGHT      
  17.     /* assigned backlight device *//支持lcd背光的设置
  18.     /* set before framebuffer registration,
  19.      remove after unregister */
  20.     struct backlight_device *bl_dev;  

  21.     /* Backlight level curve */
  22.     struct mutex bl_curve_mutex;    
  23.     u8 bl_curve[FB_BACKLIGHT_LEVELS];
  24. #endif
  25. #ifdef CONFIG_FB_DEFERRED_IO
  26.     struct delayed_work deferred_work;
  27.     struct fb_deferred_io *fbdefio;
  28. #endif

  29.     struct fb_ops *fbops;     //帧缓冲操作函数集
  30.     struct device *device;        //父设备
  31.     struct device *dev;        /* This is this fb device */
  32.     int class_flag; /* private sysfs flags */
  33. #ifdef CONFIG_FB_TILEBLITTING
  34.     struct fb_tile_ops *tileops; /* Tile Blitting */
  35. #endif
  36.     char __iomem *screen_base;    //虚拟基地址
  37.     unsigned long screen_size;    /* Amount of ioremapped VRAM or 0 */
  38.     void *pseudo_palette;        /* Fake palette of 16 colors */
  39. #define FBINFO_STATE_RUNNING    0
  40. #define FBINFO_STATE_SUSPENDED    1
  41.     u32 state;            /* Hardware state i.e suspend */
  42.     void *fbcon_par; /* fbcon use-only private area */
  43.     /* From here on everything is device dependent */
  44.     void *par;
  45.     /* we need the PCI or similar aperture base/size not
  46.      smem_start/size as smem_start may just be an object
  47.      allocated inside the aperture so may not actually overlap */
  48.     struct apertures_struct {
  49.         unsigned int count;
  50.         struct aperture {
  51.             resource_size_t base;
  52.             resource_size_t size;
  53.         } ranges[0];
  54.     } *apertures;
  55. };
fb_ops结构体用来实现对帧缓冲的操作,这些函数需要我们自行编写的。

点击(此处)折叠或打开

  1. struct fb_ops {
  2.     /* open/release and usage marking */
  3.     struct module *owner;
  4.     int (*fb_open)(struct fb_info *info, int user);
  5.     int (*fb_release)(struct fb_info *info, int user);//打开和释放

  6.     /* For framebuffers with strange non linear layouts or that do not
  7.      * work with normal memory mapped access
  8.      */
  9.     ssize_t (*fb_read)(struct fb_info *info, char __user *buf,
  10.              size_t count, loff_t *ppos);
  11.     ssize_t (*fb_write)(struct fb_info *info, const char __user *buf,
  12.              size_t count, loff_t *ppos);

  13.     /* checks var and eventually tweaks it to something supported,
  14.      * DO NOT MODIFY PAR *///检测可变参数,并调整到支持的值
  15.     int (*fb_check_var)(struct fb_var_screeninfo *var, struct fb_info *info);

  16.     /* set the video mode according to info->var */
  17.     int (*fb_set_par)(struct fb_info *info);//视频设置模式

  18.     /* set color register */
  19.     int (*fb_setcolreg)(unsigned regno, unsigned red, unsigned green,
  20.              unsigned blue, unsigned transp, struct fb_info *info);//设置color寄存器

  21.     /* set color registers in batch */
  22.     int (*fb_setcmap)(struct fb_cmap *cmap, struct fb_info *info);//批量设置color寄存器,设备颜色表

  23.     /* blank display */
  24.     int (*fb_blank)(int blank, struct fb_info *info);//显示空白

  25.     /* pan display */
  26.     int (*fb_pan_display)(struct fb_var_screeninfo *var, struct fb_info *info);

  27.     /* Draws a rectangle */
  28.     void (*fb_fillrect) (struct fb_info *info, const struct fb_fillrect *rect);//填充矩形
  29.     /* Copy data from area to another */
  30.     void (*fb_copyarea) (struct fb_info *info, const struct fb_copyarea *region);//数据复制
  31.     /* Draws a image to the display */
  32.     void (*fb_imageblit) (struct fb_info *info, const struct fb_image *image);//图形填充

  33.     /* Draws cursor */
  34.     int (*fb_cursor) (struct fb_info *info, struct fb_cursor *cursor);//绘制光标

  35.     /* Rotates the display */
  36.     void (*fb_rotate)(struct fb_info *info, int angle);//旋转显示

  37.     /* wait for blit idle, optional */
  38.     int (*fb_sync)(struct fb_info *info);//等待blit空闲

  39.     /* perform fb specific ioctl (optional) */
  40.     int (*fb_ioctl)(struct fb_info *info, unsigned int cmd,
  41.             unsigned long arg);

  42.     /* Handle 32bit compat ioctl (optional) */
  43.     int (*fb_compat_ioctl)(struct fb_info *info, unsigned cmd,
  44.             unsigned long arg);

  45.     /* perform fb specific mmap */
  46.     int (*fb_mmap)(struct fb_info *info, struct vm_area_struct *vma);

  47.     /* get capability given var */
  48.     void (*fb_get_caps)(struct fb_info *info, struct fb_blit_caps *caps,
  49.              struct fb_var_screeninfo *var);

  50.     /* teardown any resources to do with this framebuffer */
  51.     void (*fb_destroy)(struct fb_info *info);

  52.     /* called at KDB enter and leave time to prepare the console */
  53.     int (*fb_debug_enter)(struct fb_info *info);
  54.     int (*fb_debug_leave)(struct fb_info *info);
  55. };

内核中定义了一个struct fb_info *registered_fb[FB_MAX]来存放屏的相关信息,下面来看看帧缓冲设备fb_info结构体的注册。

点击(此处)折叠或打开

  1. int
  2. register_framebuffer(struct fb_info *fb_info)
  3. {
  4.     int ret;

  5.     mutex_lock(&registration_lock);
  6.     ret = do_register_framebuffer(fb_info);
  7.     mutex_unlock(&registration_lock);

  8.     return ret;
  9. }

点击(此处)折叠或打开

  1. static int do_register_framebuffer(struct fb_info *fb_info)
  2. {
  3.     int i;
  4.     struct fb_event event;
  5.     struct fb_videomode mode;

  6.     if (fb_check_foreignness(fb_info))
  7.         return -ENOSYS;

  8.     do_remove_conflicting_framebuffers(fb_info->apertures, fb_info->fix.id,
  9.                      fb_is_primary_device(fb_info));

  10.     if (num_registered_fb == FB_MAX)
  11.         return -ENXIO;

  12.     num_registered_fb++;
  13.     for (i = 0 ; i < FB_MAX; i++)
  14.         if (!registered_fb[i])
  15.             break;
  16.     fb_info->node = i;
  17.     atomic_set(&fb_info->count, 1);
  18.     mutex_init(&fb_info->lock);
  19.     mutex_init(&fb_info->mm_lock);

  20.     fb_info->dev = device_create(fb_class, fb_info->device,
  21.                  MKDEV(FB_MAJOR, i), NULL, "fb%d", i);//创建设备节点
  22.     if (IS_ERR(fb_info->dev)) {
  23.         /* Not fatal */
  24.         printk(KERN_WARNING "Unable to create device for framebuffer %d; errno = %ld\n", i, PTR_ERR(fb_info->dev));
  25.         fb_info->dev = NULL;
  26.     } else
  27.         fb_init_device(fb_info);//初始化fb的属性文件

  28.     if (fb_info->pixmap.addr == NULL) {
  29.         fb_info->pixmap.addr = kmalloc(FBPIXMAPSIZE, GFP_KERNEL);
  30.         if (fb_info->pixmap.addr) {
  31.             fb_info->pixmap.size = FBPIXMAPSIZE;
  32.             fb_info->pixmap.buf_align = 1;
  33.             fb_info->pixmap.scan_align = 1;
  34.             fb_info->pixmap.access_align = 32;
  35.             fb_info->pixmap.flags = FB_PIXMAP_DEFAULT;
  36.         }
  37.     }    
  38.     fb_info->pixmap.offset = 0;

  39.     if (!fb_info->pixmap.blit_x)
  40.         fb_info->pixmap.blit_x = ~(u32)0;

  41.     if (!fb_info->pixmap.blit_y)
  42.         fb_info->pixmap.blit_y = ~(u32)0;

  43.     if (!fb_info->modelist.prev || !fb_info->modelist.next)
  44.         INIT_LIST_HEAD(&fb_info->modelist);

  45.     fb_var_to_videomode(&mode, &fb_info->var);
  46.     fb_add_videomode(&mode, &fb_info->modelist);
  47.     registered_fb[i] = fb_info;

  48.     event.info = fb_info;
  49.     if (!lock_fb_info(fb_info))
  50.         return -ENODEV;
  51.     fb_notifier_call_chain(FB_EVENT_FB_REGISTERED, &event);
  52.     unlock_fb_info(fb_info);
  53.     return 0;
  54. }
这个函数主要是创建设备节点,初始化fb的属性文件等一些初始化,下面来看看帧缓冲区驱动核心层的层次结构图:
那么从上面的图可以总结出怎么写一个lcd的驱动程序的步骤
1.分配一个fb_info的结构体
2.设置
3.注册register_framebuffer
4.硬件相关的初始化
上面将了一些framebuffer帧缓冲的有关的,那么我们具体看看内核怎么去实现lcd的驱动。

点击(此处)折叠或打开

  1. int __init s3c2410fb_init(void)
  2. {
  3.     int ret = platform_driver_register(&s3c2410fb_driver);

  4.     if (ret == 0)
  5.         ret = platform_driver_register(&s3c2412fb_driver);

  6.     return ret;
  7. }

  8. static void __exit s3c2410fb_cleanup(void)
  9. {
  10.     platform_driver_unregister(&s3c2410fb_driver);
  11.     platform_driver_unregister(&s3c2412fb_driver);
  12. }

  13. module_init(s3c2410fb_init);
  14. module_exit(s3c2410fb_cleanup);
lcd驱动挂在platform总线上,注册了一个s3c2410fb_driver的驱动,由总线驱动设备模型中,当注册一个设备或者驱动的时候,会调用总线的match函数,而platform的match函数是匹配设备和驱动的名字

点击(此处)折叠或打开

  1. static struct platform_driver s3c2410fb_driver = {
  2.     .probe        = s3c2410fb_probe,
  3.     .remove        = __devexit_p(s3c2410fb_remove),
  4.     .suspend    = s3c2410fb_suspend,
  5.     .resume        = s3c2410fb_resume,
  6.     .driver        = {
  7.         .name    = "s3c2410-lcd",
  8.         .owner    = THIS_MODULE,
  9.     },
  10. };

  11. struct platform_device s3c_device_lcd = {
  12.     .name        = "s3c2410-lcd",
  13.     .id        = -1,
  14.     .num_resources    = ARRAY_SIZE(s3c_lcd_resource),
  15.     .resource    = s3c_lcd_resource,
  16.     .dev        = {
  17.         .dma_mask        = &samsung_device_dma_mask,
  18.         .coherent_dma_mask    = DMA_BIT_MASK(32),
  19.     }
  20. };

点击(此处)折叠或打开

  1. static struct resource s3c_lcd_resource[] = {
  2.     [0] = DEFINE_RES_MEM(S3C24XX_PA_LCD, S3C24XX_SZ_LCD),//内存
  3.     [1] = DEFINE_RES_IRQ(IRQ_LCD),//中断号
  4. };
驱动与设备匹配会,会调用probe函数

点击(此处)折叠或打开

  1. static int __devinit s3c24xxfb_probe(struct platform_device *pdev,
  2.                  enum s3c_drv_type drv_type)
  3. {
  4.     struct s3c2410fb_info *info;
  5.     struct s3c2410fb_display *display;
  6.     struct fb_info *fbinfo;
  7.     struct s3c2410fb_mach_info *mach_info;
  8.     struct resource *res;
  9.     int ret;
  10.     int irq;
  11.     int i;
  12.     int size;
  13.     u32 lcdcon1;

  14.     mach_info = pdev->dev.platform_data;
  15.     if (mach_info == NULL) {
  16.         dev_err(&pdev->dev,
  17.             "no platform data for lcd, cannot attach\n");
  18.         return -EINVAL;
  19.     }

  20.     if (mach_info->default_display >= mach_info->num_displays) {
  21.         dev_err(&pdev->dev, "default is %d but only %d displays\n",
  22.             mach_info->default_display, mach_info->num_displays);
  23.         return -EINVAL;
  24.     }

  25.     display = mach_info->displays + mach_info->default_display;

  26.     irq = platform_get_irq(pdev, 0);//获取中断号
  27.     if (irq < 0) {
  28.         dev_err(&pdev->dev, "no irq for device\n");
  29.         return -ENOENT;
  30.     }

  31.     fbinfo = framebuffer_alloc(sizeof(struct s3c2410fb_info), &pdev->dev);//分配一个fb_info结构体
  32.     if (!fbinfo)
  33.         return -ENOMEM;

  34.     platform_set_drvdata(pdev, fbinfo);

  35.     info = fbinfo->par;
  36.     info->dev = &pdev->dev;
  37.     info->drv_type = drv_type;

  38.     res = platform_get_resource(pdev, IORESOURCE_MEM, 0);//获取内存地址
  39.     if (res == NULL) {
  40.         dev_err(&pdev->dev, "failed to get memory registers\n");
  41.         ret = -ENXIO;
  42.         goto dealloc_fb;
  43.     }

  44.     size = resource_size(res);
  45.     info->mem = request_mem_region(res->start, size, pdev->name);
  46.     if (info->mem == NULL) {
  47.         dev_err(&pdev->dev, "failed to get memory region\n");
  48.         ret = -ENOENT;
  49.         goto dealloc_fb;
  50.     }

  51.     info->io = ioremap(res->start, size);//io map
  52.     if (info->io == NULL) {
  53.         dev_err(&pdev->dev, "ioremap() of registers failed\n");
  54.         ret = -ENXIO;
  55.         goto release_mem;
  56.     }

  57.     info->irq_base = info->io + ((drv_type == DRV_S3C2412) ? S3C2412_LCDINTBASE : S3C2410_LCDINTBASE);

  58.     dprintk("devinit\n");

  59.     strcpy(fbinfo->fix.id, driver_name);

  60.     /* Stop the video */
  61.     lcdcon1 = readl(info->io + S3C2410_LCDCON1);
  62.     writel(lcdcon1 & ~S3C2410_LCDCON1_ENVID, info->io + S3C2410_LCDCON1);
  63. //设置fbinfo结构体
  64.     fbinfo->fix.type     = FB_TYPE_PACKED_PIXELS;
  65.     fbinfo->fix.type_aux     = 0;
  66.     fbinfo->fix.xpanstep     = 0;
  67.     fbinfo->fix.ypanstep     = 0;
  68.     fbinfo->fix.ywrapstep     = 0;
  69.     fbinfo->fix.accel     = FB_ACCEL_NONE;

  70.     fbinfo->var.nonstd     = 0;
  71.     fbinfo->var.activate     = FB_ACTIVATE_NOW;
  72.     fbinfo->var.accel_flags = 0;
  73.     fbinfo->var.vmode     = FB_VMODE_NONINTERLACED;

  74.     fbinfo->fbops         = &s3c2410fb_ops;
  75.     fbinfo->flags         = FBINFO_FLAG_DEFAULT;
  76.     fbinfo->pseudo_palette = &info->pseudo_pal;

  77.     for (i = 0; i < 256; i++)
  78.         info->palette_buffer[i] = PALETTE_BUFF_CLEAR;

  79.     ret = request_irq(irq, s3c2410fb_irq, 0, pdev->name, info);
  80.     if (ret) {
  81.         dev_err(&pdev->dev, "cannot get irq %d - err %d\n", irq, ret);
  82.         ret = -EBUSY;
  83.         goto release_regs;
  84.     }

  85.     info->clk = clk_get(NULL, "lcd");
  86.     if (IS_ERR(info->clk)) {
  87.         printk(KERN_ERR "failed to get lcd clock source\n");
  88.         ret = PTR_ERR(info->clk);
  89.         goto release_irq;
  90.     }

  91.     clk_enable(info->clk);
  92.     dprintk("got and enabled clock\n");

  93.     msleep(1);

  94.     info->clk_rate = clk_get_rate(info->clk);

  95.     /* find maximum required memory size for display */
  96.     for (i = 0; i < mach_info->num_displays; i++) {
  97.         unsigned long smem_len = mach_info->displays[i].xres;

  98.         smem_len *= mach_info->displays[i].yres;
  99.         smem_len *= mach_info->displays[i].bpp;
  100.         smem_len >>= 3;
  101.         if (fbinfo->fix.smem_len < smem_len)
  102.             fbinfo->fix.smem_len = smem_len;
  103.     }

  104.     /* Initialize video memory */
  105.     ret = s3c2410fb_map_video_memory(fbinfo);
  106.     if (ret) {
  107.         printk(KERN_ERR "Failed to allocate video RAM: %d\n", ret);
  108.         ret = -ENOMEM;
  109.         goto release_clock;
  110.     }

  111.     dprintk("got video memory\n");

  112.     fbinfo->var.xres = display->xres;
  113.     fbinfo->var.yres = display->yres;
  114.     fbinfo->var.bits_per_pixel = display->bpp;

  115.     s3c2410fb_init_registers(fbinfo);

  116.     s3c2410fb_check_var(&fbinfo->var, fbinfo);

  117.     ret = s3c2410fb_cpufreq_register(info);
  118.     if (ret < 0) {
  119.         dev_err(&pdev->dev, "Failed to register cpufreq\n");
  120.         goto free_video_memory;
  121.     }

  122.     ret = register_framebuffer(fbinfo);
  123.     if (ret < 0) {
  124.         printk(KERN_ERR "Failed to register framebuffer device: %d\n",
  125.             ret);
  126.         goto free_cpufreq;
  127.     }

  128.     /* create device files */
  129.     ret = device_create_file(&pdev->dev, &dev_attr_debug);
  130.     if (ret) {
  131.         printk(KERN_ERR "failed to add debug attribute\n");
  132.     }

  133.     printk(KERN_INFO "fb%d: %s frame buffer device\n",
  134.         fbinfo->node, fbinfo->fix.id);

  135.     return 0;

  136.  free_cpufreq:
  137.     s3c2410fb_cpufreq_deregister(info);
  138. free_video_memory:
  139.     s3c2410fb_unmap_video_memory(fbinfo);
  140. release_clock:
  141.     clk_disable(info->clk);
  142.     clk_put(info->clk);
  143. release_irq:
  144.     free_irq(irq, info);
  145. release_regs:
  146.     iounmap(info->io);
  147. release_mem:
  148.     release_mem_region(res->start, size);
  149. dealloc_fb:
  150.     platform_set_drvdata(pdev, NULL);
  151.     framebuffer_release(fbinfo);
  152.     return ret;
  153. }
上面的一些步骤基本与总结的一个lcd的驱动程序的步骤相同,下面来看看用户空间怎么去访问
app:read()-->kernel调用fbmem.c中的fb_read(),其他的接口类似。

点击(此处)折叠或打开

  1. static ssize_t
  2. fb_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
  3. {
  4.     unsigned long p = *ppos;
  5.     struct fb_info *info = file_fb_info(file);//获取次设备号,根据次设备号从registered_fb数组中拿
  6.     u8 *buffer, *dst;                         //到对应的fb_info
  7.     u8 __iomem *src;
  8.     int c, cnt = 0, err = 0;
  9.     unsigned long total_size;

  10.     if (!info || ! info->screen_base)
  11.         return -ENODEV;

  12.     if (info->state != FBINFO_STATE_RUNNING)
  13.         return -EPERM;

  14.     if (info->fbops->fb_read)//调用read函数
  15.         return info->fbops->fb_read(info, buf, count, ppos);
  16.     
  17.     total_size = info->screen_size;

  18.     if (total_size == 0)
  19.         total_size = info->fix.smem_len;

  20.     if (p >= total_size)
  21.         return 0;

  22.     if (count >= total_size)
  23.         count = total_size;

  24.     if (count + p > total_size)
  25.         count = total_size - p;

  26.     buffer = kmalloc((count > PAGE_SIZE) ? PAGE_SIZE : count,
  27.              GFP_KERNEL);
  28.     if (!buffer)
  29.         return -ENOMEM;

  30.     src = (u8 __iomem *) (info->screen_base + p);//获得帧缓冲的起始地址+偏移量

  31.     if (info->fbops->fb_sync)
  32.         info->fbops->fb_sync(info);
  33.       
  34.     while (count) {                           //拷贝数据
  35.         c = (count > PAGE_SIZE) ? PAGE_SIZE : count;
  36.         dst = buffer;
  37.         fb_memcpy_fromfb(dst, src, c);
  38.         dst += c;
  39.         src += c;

  40.         if (copy_to_user(buf, buffer, c)) {//提交给应用层。
  41.             err = -EFAULT;
  42.             break;
  43.         }
  44.         *ppos += c;
  45.         buf += c;
  46.         cnt += c;
  47.         count -= c;
  48.     }

  49.     kfree(buffer);

  50.     return (err) ? err : cnt;
  51. }


阅读(16472) | 评论(0) | 转发(7) |
给主人留下些什么吧!~~