Try   HackMD

Linux Virtual Framebuffer Device Driver

tags: vcam

pull request

經過我繳交 pull request 後,老師這樣改名及 desroyed vcamfb 並不好,改名可能會導致追蹤的錯誤,
給我一點建議 要我看 Linux Virtual Framebuffer Device Driverfbset 要我先關注其中記憶體配置的部分

Linux Virtual Framebuffer Device Driver

virtual_fb.cvfb_initvfb_setup__init 備標註為初始化,

vfb_init

static int __init vfb_init(void)
{
    int ret = 0;

#ifndef MODULE
    char *option = NULL;

    if (fb_get_options("vfb", &option))
        return -ENODEV;
    vfb_setup(option);
#endif

    if (!vfb_enable)
        return -ENXIO;

    ret = platform_driver_register(&vfb_driver);

    if (!ret) {
        vfb_device = platform_device_alloc("vfb", 0);

        if (vfb_device)
            ret = platform_device_add(vfb_device);
        else
            ret = -ENOMEM;

        if (ret) {
            platform_device_put(vfb_device);
            platform_driver_unregister(&vfb_driver);
        }
    }

    return ret;
}

vfb_setup 會設 vfb_enable = 1 platform_driver_registervfb_driver 註冊
platforn_device 其中 probe 的意思是

static struct platform_driver vfb_driver = {
    .probe	= vfb_probe,
    .remove = vfb_remove,
    .driver = {
        .name	= "vfb",
    },
};

struct platform_driver

the probe routine is to detect devices residing on the bus and to create device nodes corresponding to these devices.

檢查 devices 並創立 device

vfb_probe

static int vfb_probe(struct platform_device *dev)
{
    struct fb_info *info;
    unsigned int size = PAGE_ALIGN(videomemorysize);
    int retval = -ENOMEM;

    /*
     * For real video cards we use ioremap.
     */
    if (!(videomemory = vmalloc_32_user(size)))
        return retval;

    info = framebuffer_alloc(sizeof(u32) * 256, &dev->dev);
    if (!info)
        goto err;

    info->screen_base = (char __iomem *)videomemory;
    info->fbops = &vfb_ops;

    if (!fb_find_mode(&info->var, info, mode_option,
              NULL, 0, &vfb_default, 8)){
        fb_err(info, "Unable to find usable video mode.\n");
        retval = -EINVAL;
        goto err1;
    }

    vfb_fix.smem_start = (unsigned long) videomemory;
    vfb_fix.smem_len = videomemorysize;
    info->fix = vfb_fix;
    info->pseudo_palette = info->par;
    info->par = NULL;
    info->flags = FBINFO_FLAG_DEFAULT;

    retval = fb_alloc_cmap(&info->cmap, 256, 0);
    if (retval < 0)
        goto err1;

    retval = register_framebuffer(info);
    if (retval < 0)
        goto err2;
    platform_set_drvdata(dev, info);

    vfb_set_par(info);

    fb_info(info, "Virtual frame buffer device, using %ldK of video memory\n",
        videomemorysize >> 10);
    return 0;
err2:
    fb_dealloc_cmap(&info->cmap);
err1:
    framebuffer_release(info);
err:
    vfree(videomemory);
    return retval;
}

vfb_probe 使用 size = 1024 * 1024 * 1 (1MB)
static void *videomemory = vmalloc_32_user(size) For real video cards we use ioremap. ioremap is map bus memory into CPU space.

void * vmalloc_32_user(unsigned long size)

allocate zeroed virtually contiguous 32bit memory
unsigned long size
allocation size

struct fb_info *info = framebuffer_alloc(sizeof(u32) * 256, &dev->dev) 但不是很清楚為什麼是 1024 bytes

struct platform_device {
	const char	*name;
	int		id;
	bool		id_auto;
	struct device	dev;
	u64		platform_dma_mask;
	struct device_dma_parameters dma_parms;
	u32		num_resources;
	struct resource	*resource;

	const struct platform_device_id	*id_entry;
	char *driver_override; /* Driver name to force a match */

	/* MFD cell pointer */
	struct mfd_cell *mfd_cell;

	/* arch specific additions */
	struct pdev_archdata	archdata;
};

unsigned int size = PAGE_ALIGN(videomemorysize); 其中 unsigned long videomemorysize
fb_alloc_cmap(&info->cmap, 256, 0); cmap is not used
register_framebuffer(info); 註冊一個 framebuffer
platform_set_drvdata(dev, info); 將 info 寫入 dev 中。

#define PAGE_ALIGN(addr) ALIGN(addr, PAGE_SIZE)

to align the pointer to the (next) page boundary

struct fb_info *framebuffer_alloc(size_t size, struct device *dev)

creates a new frame buffer info structure

struct fb_ops

Frame buffer operations

fb_alloc_cmap(struct fb_cmap *cmap, int len, int transp)

  • fb_alloc_cmap - allocate a colormap
  • @cmap: frame buffer colormap structure
  • @len: length of @cmap
  • @transp: boolean, 1 if there is transparency, 0 otherwise
  • @flags: flags for kmalloc memory allocation

register_framebuffer(struct fb_info *fb_info)

registers a frame buffer device

platform_set_drvdata(dev, info);

Driver data, set and get with dev_set_drvdata/dev_get_drvdata

參考資料:
fb_info
fb_ops
fb_alloc_cmap
platform_set_drvdata

    vfb_fix.smem_start = (unsigned long) videomemory;
    vfb_fix.smem_len = videomemorysize;
    info->fix = vfb_fix;

fb_fix_screeninfo

struct fb_fix_screeninfo {
	char id[16];			/* identification string eg "TT Builtin" */
	unsigned long smem_start;	/* Start of frame buffer mem */
					/* (physical address) */
	__u32 smem_len;			/* Length of frame buffer mem */
	__u32 type;			/* see FB_TYPE_*		*/
	__u32 type_aux;			/* Interleave for interleaved Planes */
	__u32 visual;			/* see FB_VISUAL_*		*/ 
	__u16 xpanstep;			/* zero if no hardware panning  */
	__u16 ypanstep;			/* zero if no hardware panning  */
	__u16 ywrapstep;		/* zero if no hardware ywrap    */
	__u32 line_length;		/* length of a line in bytes    */
	unsigned long mmio_start;	/* Start of Memory Mapped I/O   */
					/* (physical address) */
	__u32 mmio_len;			/* Length of Memory Mapped I/O  */
	__u32 accel;			/* Indicate to driver which	*/
					/*  specific chip/card we have	*/
	__u16 capabilities;		/* see FB_CAP_*			*/
	__u16 reserved[2];		/* Reserved for future compatibility */
};

對應到 他所設的 fb_fix_screeninfo
vfb_fix

static struct fb_fix_screeninfo vfb_fix = {
    .id =		"Virtual FB",
    .type =		FB_TYPE_PACKED_PIXELS,
    .visual =	FB_VISUAL_PSEUDOCOLOR,
    .xpanstep =	1,
    .ypanstep =	1,
    .ywrapstep =	1,
    .accel =	FB_ACCEL_NONE,
};

vfb_setup

static int __init vfb_setup(char *options)
{
    char *this_opt;

    vfb_enable = 0;

    if (!options)
        return 1;

    vfb_enable = 1;

    if (!*options)
        return 1;

    while ((this_opt = strsep(&options, ",")) != NULL) {
        if (!*this_opt)
            continue;
        /* Test disable for backwards compatibility */
        if (!strcmp(this_opt, "disable"))
            vfb_enable = 0;
        else
            mode_option = this_opt;
    }
    return 1;
}
#endif  /*  MODULE  */

fb_ops

static struct platform_driver vfb_driver = {
    .probe	= vfb_probe,
    .remove = vfb_remove,
    .driver = {
        .name	= "vfb",
    },
};

其中 framebuffer 操作 fb_ops

static const struct fb_ops vfb_ops = {
/* For framebuffers with strange non linear layouts or that do not
* work with normal memory mapped access
*/
    .fb_read        = fb_sys_read,    
    .fb_write       = fb_sys_write,
    .fb_check_var	= vfb_check_var,    /* checks var and eventually tweaks it to something supported,
	 * DO NOT MODIFY PAR */
    .fb_set_par	= vfb_set_par,    /* set the video mode according to info->var */
    .fb_setcolreg	= vfb_setcolreg,    /* set color register */
    .fb_pan_display	= vfb_pan_display,    /* pan display */
    .fb_fillrect	= sys_fillrect,    /* Draws a rectangle */
    .fb_copyarea	= sys_copyarea,    /* Copy data from area to another */
    .fb_imageblit	= sys_imageblit,    /* Draws a image to the display */
    .fb_mmap	= vfb_mmap,    /* perform fb specific mmap */
};

set the video mode

static int vfb_set_par(struct fb_info *info)
{
    switch (info->var.bits_per_pixel) {
    case 1:
        info->fix.visual = FB_VISUAL_MONO01;
        break;
    case 8:
        info->fix.visual = FB_VISUAL_PSEUDOCOLOR;
        break;
    case 16:
    case 24:
    case 32:
        info->fix.visual = FB_VISUAL_TRUECOLOR;
        break;
    }

    info->fix.line_length = get_line_length(info->var.xres_virtual,
                        info->var.bits_per_pixel);

    return 0;
}

#define FB_VISUAL_MONO01 0 /* Monochr. 1=Black 0=White */
#define FB_VISUAL_PSEUDOCOLOR 3 /* Pseudo color (like atari) */
#define FB_VISUAL_TRUECOLOR 2 /* True color */
注意到 case 24 為 rgb 888 沒對其中 設 flag

set the color

static int vfb_check_var(struct fb_var_screeninfo *var,
             struct fb_info *info)
{
    u_long line_length;

    /*
     *  FB_VMODE_CONUPDATE and FB_VMODE_SMOOTH_XPAN are equal!
     *  as FB_VMODE_SMOOTH_XPAN is only used internally
     */

    if (var->vmode & FB_VMODE_CONUPDATE) {
        var->vmode |= FB_VMODE_YWRAP;
        var->xoffset = info->var.xoffset;
        var->yoffset = info->var.yoffset;
    }

    /*
     *  Some very basic checks
     */
    if (!var->xres)
        var->xres = 1;
    if (!var->yres)
        var->yres = 1;
    if (var->xres > var->xres_virtual)
        var->xres_virtual = var->xres;
    if (var->yres > var->yres_virtual)
        var->yres_virtual = var->yres;
    if (var->bits_per_pixel <= 1)
        var->bits_per_pixel = 1;
    else if (var->bits_per_pixel <= 8)
        var->bits_per_pixel = 8;
    else if (var->bits_per_pixel <= 16)
        var->bits_per_pixel = 16;
    else if (var->bits_per_pixel <= 24)
        var->bits_per_pixel = 24;
    else if (var->bits_per_pixel <= 32)
        var->bits_per_pixel = 32;
    else
        return -EINVAL;

    if (var->xres_virtual < var->xoffset + var->xres)
        var->xres_virtual = var->xoffset + var->xres;
    if (var->yres_virtual < var->yoffset + var->yres)
        var->yres_virtual = var->yoffset + var->yres;

    /*
     *  Memory limit
     */
    line_length =
        get_line_length(var->xres_virtual, var->bits_per_pixel);
    if (line_length * var->yres_virtual > videomemorysize)
        return -ENOMEM;

    /*
     * Now that we checked it we alter var. The reason being is that the video
     * mode passed in might not work but slight changes to it might make it 
     * work. This way we let the user know what is acceptable.
     */
    switch (var->bits_per_pixel) {

    case 24:		/* RGB 888 */
        var->red.offset = 0;
        var->red.length = 8;
        var->green.offset = 8;
        var->green.length = 8;
        var->blue.offset = 16;
        var->blue.length = 8;
        var->transp.offset = 0;
        var->transp.length = 0;
        break;
    case 32:		/* RGBA 8888 */
        var->red.offset = 0;
        var->red.length = 8;
        var->green.offset = 8;
        var->green.length = 8;
        var->blue.offset = 16;
        var->blue.length = 8;
        var->transp.offset = 24;
        var->transp.length = 8;
        break;
    }
    var->red.msb_right = 0;
    var->green.msb_right = 0;
    var->blue.msb_right = 0;
    var->transp.msb_right = 0;

    return 0;
}

get_line_length 為 bytpesperline 的意思 weight * 3 for rgb888

vfb_mmap

static int vfb_mmap(struct fb_info *info,
            struct vm_area_struct *vma)
{
    return remap_vmalloc_range(vma, (void *)info->fix.smem_start, vma->vm_pgoff);
}

int remap_vmalloc_range(struct vm_area_struct *vma, void *addr, unsigned long pgoff)

map vmalloc pages to userspace
This function checks that addr is a valid vmalloc’ed area, and that it is big enough to cover the vma. Will return failure if that criteria isn’t met.

struct vm_area_struct *vma
vma to cover (map full range of vma)

void *addr
vmalloc memory

unsigned long pgoff
number of pages into addr before first page to map

檢查 mmap 的大小。 以大小府和 640 * 480
參考資料:
remap_vmalloc_range

vfb_setcolreg

static int vfb_setcolreg(u_int regno, u_int red, u_int green, u_int blue,
             u_int transp, struct fb_info *info)
{
    if (regno >= 256)	/* no. of hw registers */
        return 1;
    /*
     * Program hardware... do anything you want with transp
     */

    /* grayscale works only partially under directcolor */
    if (info->var.grayscale) {
        /* grayscale = 0.30*R + 0.59*G + 0.11*B */
        red = green = blue =
            (red * 77 + green * 151 + blue * 28) >> 8;
    }

    /* Directcolor:
     *   var->{color}.offset contains start of bitfield
     *   var->{color}.length contains length of bitfield
     *   {hardwarespecific} contains width of RAMDAC
     *   cmap[X] is programmed to (X << red.offset) | (X << green.offset) | (X << blue.offset)
     *   RAMDAC[X] is programmed to (red, green, blue)
     *
     * Pseudocolor:
     *    var->{color}.offset is 0 unless the palette index takes less than
     *                        bits_per_pixel bits and is stored in the upper
     *                        bits of the pixel value
     *    var->{color}.length is set so that 1 << length is the number of available
     *                        palette entries
     *    cmap is not used
     *    RAMDAC[X] is programmed to (red, green, blue)
     *
     * Truecolor:
     *    does not use DAC. Usually 3 are present.
     *    var->{color}.offset contains start of bitfield
     *    var->{color}.length contains length of bitfield
     *    cmap is programmed to (red << red.offset) | (green << green.offset) |
     *                      (blue << blue.offset) | (transp << transp.offset)
     *    RAMDAC does not exist
     */
#define CNVT_TOHW(val,width) ((((val)<<(width))+0x7FFF-(val))>>16)
    switch (info->fix.visual) {
    case FB_VISUAL_TRUECOLOR:
    case FB_VISUAL_PSEUDOCOLOR:
        red = CNVT_TOHW(red, info->var.red.length);
        green = CNVT_TOHW(green, info->var.green.length);
        blue = CNVT_TOHW(blue, info->var.blue.length);
        transp = CNVT_TOHW(transp, info->var.transp.length);
        break;
    case FB_VISUAL_DIRECTCOLOR:
        red = CNVT_TOHW(red, 8);	/* expect 8 bit DAC */
        green = CNVT_TOHW(green, 8);
        blue = CNVT_TOHW(blue, 8);
        /* hey, there is bug in transp handling... */
        transp = CNVT_TOHW(transp, 8);
        break;
    }
#undef CNVT_TOHW
    /* Truecolor has hardware independent palette */
    if (info->fix.visual == FB_VISUAL_TRUECOLOR) {
        u32 v;

        if (regno >= 16)
            return 1;

        v = (red << info->var.red.offset) |
            (green << info->var.green.offset) |
            (blue << info->var.blue.offset) |
            (transp << info->var.transp.offset);
        switch (info->var.bits_per_pixel) {
        case 8:
            break;
        case 16:
            ((u32 *) (info->pseudo_palette))[regno] = v;
            break;
        case 24:
        case 32:
            ((u32 *) (info->pseudo_palette))[regno] = v;
            break;
        }
        return 0;
    }
    return 0;
}

其中 在 CNVT_TOHW 標示出 rgb bit 的位置,但是 rgb 24 可以就不需要,因為 case 24 本來就沒設立 flag
vfb_pan_display

    /*
     *  Pan or Wrap the Display
     *
     *  This call looks only at xoffset, yoffset and the FB_VMODE_YWRAP flag
     */

static int vfb_pan_display(struct fb_var_screeninfo *var,
               struct fb_info *info)
{
    if (var->vmode & FB_VMODE_YWRAP) {
        if (var->yoffset >= info->var.yres_virtual ||
            var->xoffset)
            return -EINVAL;
    } else {
        if (var->xoffset + info->var.xres > info->var.xres_virtual ||
            var->yoffset + info->var.yres > info->var.yres_virtual)
            return -EINVAL;
    }
    info->var.xoffset = var->xoffset;
    info->var.yoffset = var->yoffset;
    if (var->vmode & FB_VMODE_YWRAP)
        info->var.vmode |= FB_VMODE_YWRAP;
    else
        info->var.vmode &= ~FB_VMODE_YWRAP;
    return 0;
}

會設定 xoffset yoffset 也會修改成 vmode == FB_VMODE_YWRAP

fb_ops 的其他操作

sysfillrect.c 為 ops sys_fillrect
syscopyarea.c 為 ops sys_copyarea
sysimgblt.c 為 ops sys_imgbit

fb_draw.h

其中為使用在各 .c 檔的 function 。