从零开始之驱动发开、linux驱动(二十五、framebuffer 子系统框架)

一、概念 

Framebuffer,也叫帧缓冲,其内容对应于屏幕上的界面显示,可以将其简单理解为屏幕上显示内容对应的缓存,修改Framebuffer中的内容,即表示修改屏幕上的内容,所以,直接操作Framebuffer可以直接从显示器上观察到效果。

但Framebuffer并不是屏幕内容的直接的像素表示。Framebuffer实际上包含了几个不同作用的缓存,比如颜色缓存、深度缓存等,具体不详细说明。大家只需要知道,这几个缓存的共同作用下,形成了最终在屏幕上显示的图像。

Framebuffer本质上是一段内存,或者称作显存。

Framebuffer是一个逻辑上的概念,并非在显存或者是内存上有一块固定的物理区域叫Framebuffer。实际上,物理是显存或者内存,只要是在GPU能够访问的空间范围内(GPU的物理地址空间),任意分配一段内存(或显存),都可以作为Framebuffer使用,只需要在分配后将该内存区域信息,设置到显卡相关的寄存器中即可。这个其实跟DMA区域的概念是类似的。

二、linux中的framebuffer子系统

1.相关数据结构

 

下面这个结构体是fremebuffer设备所有信息的一个集合,包含fb设备的全部信息,包括设备的设置参数、状态以及对底层硬件操作的函数指针等等

1.fb_info 

include/linux/fb.h

struct fb_info {
	atomic_t count;            /* 打开计数用 */
	int node;                  /* 存放该fb在fb数组中的下标,也可以理解为次设备信息 */
	int flags;                    /* 一些标志信息 */
	struct mutex lock;		/* Lock for open/release/ioctl funcs */
	struct mutex mm_lock;		/* Lock for fb_mmap and smem_* fields */
	struct fb_var_screeninfo var;	/* Current var 可变的参数信息 */
	struct fb_fix_screeninfo fix;	/* Current fix 固定的参数信息 */
	struct fb_monspecs monspecs;	/* Current Monitor specs 显示器标准 */
	struct work_struct queue;	/* Framebuffer event queue 等待队列节点  */
	struct fb_pixmap pixmap;	/* Image hardware mapper 图像硬件映射 */
	struct fb_pixmap sprite;	/* Cursor hardware mapper 光标硬件 */
	struct fb_cmap cmap;		/* Current cmap  当前颜色表*/
	struct list_head modelist;      /* mode list 模式列表 */
	struct fb_videomode *mode;	/* current mode 当前的显示模式 */

#ifdef CONFIG_FB_BACKLIGHT
	/* assigned backlight device */
	/* set before framebuffer registration, 
	   remove after unregister */
	struct backlight_device *bl_dev;   //对应的背光设备

	/* Backlight level curve */
	struct mutex bl_curve_mutex;	
	u8 bl_curve[FB_BACKLIGHT_LEVELS];  //背光调整
#endif
#ifdef CONFIG_FB_DEFERRED_IO
	struct delayed_work deferred_work;
	struct fb_deferred_io *fbdefio;
#endif

	struct fb_ops *fbops;           // 对底层硬件设备操作的函数指针
	struct device *device;		/* This is the parent 父设备节点 */
	struct device *dev;		/* This is this fb device  当前的帧缓冲设备 */
	int class_flag;                    /* private sysfs flags */
#ifdef CONFIG_FB_TILEBLITTING
	struct fb_tile_ops *tileops;    /* Tile Blitting */
#endif
	char __iomem *screen_base;	/* Virtual address 虚拟地址 */
	unsigned long screen_size;	/* Amount of ioremapped VRAM or 0 LCD IO映射的虚拟内存大小  */ 
	void *pseudo_palette;		/* Fake palette of 16 colors 伪16色 颜色表(调测板)  */ 
#define FBINFO_STATE_RUNNING	0
#define FBINFO_STATE_SUSPENDED	1
	u32 state;			/* Hardware state i.e suspend 挂起或复位的状态 */
	void *fbcon_par;                /* fbcon use-only private area */
	/* From here on everything is device dependent */
	void *par;
	/* we need the PCI or similar aperture base/size not
	   smem_start/size as smem_start may just be an object
	   allocated inside the aperture so may not actually overlap */
	struct apertures_struct {
		unsigned int count;
		struct aperture {
			resource_size_t base;
			resource_size_t size;
		} ranges[0];
	} *apertures;

	bool skip_vt_switch; /* no VT switch on suspend/resume required */
};

2.fb_var_screeninfo 

struct fb_var_screeninfo {
	__u32 xres;			/* visible resolution	可视区域,一行有多少个像素点	*/
	__u32 yres;                /* 可视区域,一列有多少个像素点  */
	__u32 xres_virtual;		/* virtual resolution	虚拟区域,一行有多少个像素点,简单的意思就是内存中定义的区间是比较大的	*/
	__u32 yres_virtual;        /* 虚拟区域,一列有多少个像素点 */
	__u32 xoffset;			/* offset from virtual to visible 虚拟到可见屏幕之间的行偏移  */
	__u32 yoffset;			/* resolution	虚拟到可见屏幕之间的列偏移		*/

	__u32 bits_per_pixel;		/* guess what	每个像素的 bit 数		*/
	__u32 grayscale;		/* 0 = color, 1 = grayscale,等于零就成黑白 (灰度)	*/
					/* >1 = FOURCC			*/
    // 通过 pixel per bpp 来设定 red green 和 blue 的位置; pixel per bpp 可以通过 ioctl 设定
	struct fb_bitfield red;		/* bitfield in fb mem if true color, */
	struct fb_bitfield green;	/* else only length is significant */
	struct fb_bitfield blue;
	struct fb_bitfield transp;	/* transparency			*/	

	__u32 nonstd;			/* != 0 Non standard pixel format */

	__u32 activate;			/* see FB_ACTIVATE_*		*/

	__u32 height;			/* height of picture in mm    */
	__u32 width;			/* width of picture in mm     */

	__u32 accel_flags;		/* (OBSOLETE) see fb_info.flags */

	/* Timing: All values in pixclocks, except pixclock (of course)
       时序,这些部分就是显示器的显示方法了,和具体的液晶显示屏有关,在驱动中一般放在 具体液晶屏的配置文件 
     */
	__u32 pixclock;			/* pixel clock in ps (pico seconds)像素时钟  */
	__u32 left_margin;		/* time from sync to picture行切换,从同步到绘图之间的延迟	*/
	__u32 right_margin;		/* time from picture to sync行切换,从绘图到同步之间的延迟	*/
	__u32 upper_margin;		/* time from sync to picture帧切换,从同步到绘图之间的延迟	*/
	__u32 lower_margin;    //帧切换,从绘图到同步之间的延迟
	__u32 hsync_len;		/* length of horizontal sync水平同步的长度	*/
	__u32 vsync_len;		/* length of vertical sync垂直同步的长度	*/
	__u32 sync;			/* see FB_SYNC_*		*/
	__u32 vmode;			/* see FB_VMODE_*		*/
	__u32 rotate;			/* angle we rotate counter clockwise */
	__u32 colorspace;		/* colorspace for FOURCC-based modes */
	__u32 reserved[4];		/* Reserved for future compatibility */
};

3.fb_fix_screeninfo 

struct fb_fix_screeninfo {
	char id[16];			/* identification string eg "TT Builtin" 字符串形式的标示符  */
	unsigned long smem_start;	/* Start of frame buffer mem fb缓存的开始位置,这里是物理地址 */
					/* (physical address) */
	__u32 smem_len;			/* Length of frame buffer mem  fb缓存的长度 */
	__u32 type;			/* see FB_TYPE_*	看FB_TYPE_	*/
	__u32 type_aux;			/* Interleave for interleaved Planes 分界 */
	__u32 visual;			/* see FB_VISUAL_* 看FB_VISUAL_*		*/ 
	__u16 xpanstep;			/* zero if no hardware panning 如果没有硬件panning就赋值为0 */
	__u16 ypanstep;			/* zero if no hardware panning 如果没有硬件panning就赋值为0 */
	__u16 ywrapstep;		/* zero if no hardware ywrap  如果没有硬件ywrap就赋值为0   */
	__u32 line_length;		/* length of a line in bytes 一行的字节数   */
	unsigned long mmio_start;	/* Start of Memory Mapped I/O 内存映射IO的开始位置  */
					/* (physical address) */
	__u32 mmio_len;			/* Length of Memory Mapped I/O 内存映射IO的长度 */
	__u32 accel;			/* Indicate to driver which	*/
					/*  specific chip/card we have 特殊芯片才需要	*/
	__u16 capabilities;		/* see FB_CAP_*			*/
	__u16 reserved[2];		/* Reserved for future compatibility 保留  */
};

4.fb_ops 


/*
 * Frame buffer operations
 *
 * LOCKING NOTE: those functions must _ALL_ be called with the console
 * semaphore held, this is the only suitable locking mechanism we have
 * in 2.6. Some may be called at interrupt time at this point though.
 *
 * The exception to this is the debug related hooks.  Putting the fb
 * into a debug state (e.g. flipping to the kernel console) and restoring
 * it must be done in a lock-free manner, so low level drivers should
 * keep track of the initial console (if applicable) and may need to
 * perform direct, unlocked hardware writes in these hooks.
 */

struct fb_ops {
	/* open/release and usage marking */
	struct module *owner;
	int (*fb_open)(struct fb_info *info, int user);
	int (*fb_release)(struct fb_info *info, int user);

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

	/* checks var and eventually tweaks it to something supported,
	 * DO NOT MODIFY PAR */
	int (*fb_check_var)(struct fb_var_screeninfo *var, struct fb_info *info);

	/* set the video mode according to info->var */
	int (*fb_set_par)(struct fb_info *info);

	/* set color register */
	int (*fb_setcolreg)(unsigned regno, unsigned red, unsigned green,
			    unsigned blue, unsigned transp, struct fb_info *info);

	/* set color registers in batch */
	int (*fb_setcmap)(struct fb_cmap *cmap, struct fb_info *info);

	/* blank display */
	int (*fb_blank)(int blank, struct fb_info *info);

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

	/* Draws a rectangle */
	void (*fb_fillrect) (struct fb_info *info, const struct fb_fillrect *rect);
	/* Copy data from area to another */
	void (*fb_copyarea) (struct fb_info *info, const struct fb_copyarea *region);
	/* Draws a image to the display */
	void (*fb_imageblit) (struct fb_info *info, const struct fb_image *image);

	/* Draws cursor */
	int (*fb_cursor) (struct fb_info *info, struct fb_cursor *cursor);

	/* Rotates the display */
	void (*fb_rotate)(struct fb_info *info, int angle);

	/* wait for blit idle, optional */
	int (*fb_sync)(struct fb_info *info);

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

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

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

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

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

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

5.fb_pixmap

struct fb_pixmap {
	u8  *addr;		/* pointer to memory	虚拟地址,做映射使用		*/
	u32 size;		/* size of buffer in bytes	大小	*/
	u32 offset;		/* current offset to buffer	偏移	*/
	u32 buf_align;		/* byte alignment of each bitmap	*/
	u32 scan_align;		/* alignment per scanline 扫描线对其		*/
	u32 access_align;	/* alignment per read/write (bits)	*/
	u32 flags;		/* see FB_PIXMAP_*			*/
	u32 blit_x;             /* supported bit block dimensions (1-32)*/
	u32 blit_y;             /* Format: blit_x = 1 << (width - 1)    */
	                        /*         blit_y = 1 << (height - 1)   */
	                        /* if 0, will be set to 0xffffffff (all)*/
	/* access methods */
	void (*writeio)(struct fb_info *info, void __iomem *dst, void *src, unsigned int size);
	void (*readio) (struct fb_info *info, void *dst, void __iomem *src, unsigned int size);
};

 

 

2.framebuffer驱动框架

1.fb子系统的注册

/**
 *	fbmem_init - init frame buffer subsystem
 *
 *	Initialize the frame buffer subsystem.
 *
 *	NOTE: This function is _only_ to be called by drivers/char/mem.c.
 *
 */

static int __init
fbmem_init(void)
{
    /* 创建proc文件系统关于fb相关的操作接口 */
	proc_create("fb", 0, NULL, &fb_proc_fops);

    /* 注册字符设备驱动程序,以及操作接口 */
	if (register_chrdev(FB_MAJOR,"fb",&fb_fops))
		printk("unable to get major %d for fb devs\n", FB_MAJOR);
    /* 创建图像类 */
	fb_class = class_create(THIS_MODULE, "graphics");
	if (IS_ERR(fb_class)) {
		printk(KERN_WARNING "Unable to create fb class; errno = %ld\n", PTR_ERR(fb_class));
		fb_class = NULL;
	}
	return 0;
}

#ifdef MODULE    
/*  如果是编译成模块,则用module_init创建init.text段,用来insmod 安装,同时增加fbmem_exit用来卸载注册的proc文件系统和字符设备号 */            
module_init(fbmem_init);
static void __exit
fbmem_exit(void)
{
	remove_proc_entry("fb", NULL);
	class_destroy(fb_class);
	unregister_chrdev(FB_MAJOR, "fb");
}

module_exit(fbmem_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Framebuffer base");
#else
/* 如果是编译进内核,则要驱动子系统框架要先一步驱动执行创建,驱动执行必要的信息 */
subsys_initcall(fbmem_init);
#endif

唯一要说明的是fb设备还是用老式的统一的注册接口register_chrdev,来注册的fb设备号。所以它里面的次设备号就需要自己来拆分,并分别找到不同fb对应的接口。

2.注册一个fb设备

static DEFINE_MUTEX(registration_lock);    /* 静态全局变量 */


/**
 *	register_framebuffer - registers a frame buffer device
 *	@fb_info: frame buffer info structure
 *
 *	Registers a frame buffer device @fb_info.
 *
 *	Returns negative errno on error, or zero for success.
 *
 */
int
register_framebuffer(struct fb_info *fb_info)
{
	int ret;
    
    /* 上锁,保证该注册一个fb设备时,另一个设备不能被注册 */
	mutex_lock(®istration_lock);
	ret = do_register_framebuffer(fb_info);
	mutex_unlock(®istration_lock);

	return ret;
}


static int do_register_framebuffer(struct fb_info *fb_info)
{
	int i, ret;
	struct fb_event event;
	struct fb_videomode mode;

    /* 判断主机和GPU的字节顺序是否正确 */
	if (fb_check_foreignness(fb_info))
		return -ENOSYS;

    /* 检查将要注册设备的显存的物理地址和已经注册设备的显存物理地址是否重叠,重叠则注册失败 */
	ret = do_remove_conflicting_framebuffers(fb_info->apertures,
						 fb_info->fix.id,
						 fb_is_primary_device(fb_info));
	if (ret)
		return ret;

    /* 是否已经注册满fb设备,默认最大32个 */
	if (num_registered_fb == FB_MAX)
		return -ENXIO;
    
    /* 检查完毕,可以开始注册了 */
	num_registered_fb++;            /* 先把设备数量+1 */
	for (i = 0 ; i < FB_MAX; i++)
		if (!registered_fb[i])        /* 找到未使用的次设备号 */
			break;
	fb_info->node = i;                /* node存放次设备号 */
	atomic_set(&fb_info->count, 1);    /* 初始化计数 */
	mutex_init(&fb_info->lock);        /* 初始化两个锁 */
	mutex_init(&fb_info->mm_lock);

    /* 创建设备信息,前面已经注册了class,这里注册的设备就会在sys的fb_class目录下面 */
	fb_info->dev = device_create(fb_class, fb_info->device,
				     MKDEV(FB_MAJOR, i), NULL, "fb%d", i);     /* 注意这里的名字是fb0 fb1 fb2 ... */
	if (IS_ERR(fb_info->dev)) {
		/* Not fatal */
		printk(KERN_WARNING "Unable to create device for framebuffer %d; errno = %ld\n", i, PTR_ERR(fb_info->dev));
		fb_info->dev = NULL;
	} else
		fb_init_device(fb_info);        /* 在sysfs中注册一些attribute接口show和store */

    /* 如果驱动没实现fb_info中的pixmap,内核会默认使用参数的 */
	if (fb_info->pixmap.addr == NULL) {
		fb_info->pixmap.addr = kmalloc(FBPIXMAPSIZE, GFP_KERNEL);
		if (fb_info->pixmap.addr) {
			fb_info->pixmap.size = FBPIXMAPSIZE;
			fb_info->pixmap.buf_align = 1;
			fb_info->pixmap.scan_align = 1;
			fb_info->pixmap.access_align = 32;
			fb_info->pixmap.flags = FB_PIXMAP_DEFAULT;
		}
	}	
	fb_info->pixmap.offset = 0;        /* 默认没偏移 */

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

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

    /* 如果模式链表,没初始化,则这边要初始化(一个显示屏可以显示多种模式,比如不同的分辨率等等) */
	if (!fb_info->modelist.prev || !fb_info->modelist.next)
		INIT_LIST_HEAD(&fb_info->modelist);

	if (fb_info->skip_vt_switch)    /* 有没有VT开关做暂停和回复 */
		pm_vt_switch_required(fb_info->dev, false);
	else
		pm_vt_switch_required(fb_info->dev, true);

    /* 使用fb_info->var里面的参数,初始化mode */
	fb_var_to_videomode(&mode, &fb_info->var);
	fb_add_videomode(&mode, &fb_info->modelist);    /* 把该模式增加到模式链表中,还可以通过其他方式再增加模式向模式链表中 */
	registered_fb[i] = fb_info;    /* 注册该fb_info到fb总表中,使用的时候用此设备号取到即可 */

	event.info = fb_info;   
	console_lock();     /* 锁定控制台 */
	if (!lock_fb_info(fb_info)) {    /* 检查fb_ops操作接口是否存在,没有则推出,就不执行下面的通知的 */
		console_unlock();
		return -ENODEV;
	}
    
    /* 通知一个fb驱动被注册了 */
	fb_notifier_call_chain(FB_EVENT_FB_REGISTERED, &event);
	unlock_fb_info(fb_info);
	console_unlock();
	return 0;
}

关于上面的注册,我们再分析下面几点:

1.fb设备默认注册32个

/* Definitions of frame buffers						*/

#define FB_MAX			32	/* sufficient for now */

struct fb_info *registered_fb[FB_MAX] __read_mostly;

2.如果要注册多个fb设备,一定要注意不能显存地址重复。(注意这里是物理地址)

3.关于sys中的attribute属性接口,这里先简单说一下,后面章节可能会有这方面的使用举例

/*  这个函数的名字虽然是初始化device,但实际在sys中创建相关接口  */
int fb_init_device(struct fb_info *fb_info)
{
	int i, error = 0;

	dev_set_drvdata(fb_info->dev, fb_info);    /* 把该fb_info绑定的dev里面的私有数据 */

	fb_info->class_flag |= FB_SYSFS_FLAG_ATTR;    /* 给这个设备的类增加属性操作标志 */

	for (i = 0; i < ARRAY_SIZE(device_attrs); i++) {
		error = device_create_file(fb_info->dev, &device_attrs[i]);    /* 在sysfs创建属性文件 */

		if (error)
			break;
	}

	if (error) {
		while (--i >= 0)
			device_remove_file(fb_info->dev, &device_attrs[i]);
		fb_info->class_flag &= ~FB_SYSFS_FLAG_ATTR;
	}

	return 0;
}


/* When cmap is added back in it should be a binary attribute
 * not a text one. Consideration should also be given to converting
 * fbdev to use configfs instead of sysfs */
static struct device_attribute device_attrs[] = {    /* 这里是所有课操作的属性 */
	__ATTR(bits_per_pixel, S_IRUGO|S_IWUSR, show_bpp, store_bpp),    /* 下面我们以这个来分析 */
	__ATTR(blank, S_IRUGO|S_IWUSR, show_blank, store_blank),
	__ATTR(console, S_IRUGO|S_IWUSR, show_console, store_console),
	__ATTR(cursor, S_IRUGO|S_IWUSR, show_cursor, store_cursor),
	__ATTR(mode, S_IRUGO|S_IWUSR, show_mode, store_mode),
	__ATTR(modes, S_IRUGO|S_IWUSR, show_modes, store_modes),
	__ATTR(pan, S_IRUGO|S_IWUSR, show_pan, store_pan),
	__ATTR(virtual_size, S_IRUGO|S_IWUSR, show_virtual, store_virtual),
	__ATTR(name, S_IRUGO, show_name, NULL),
	__ATTR(stride, S_IRUGO, show_stride, NULL),
	__ATTR(rotate, S_IRUGO|S_IWUSR, show_rotate, store_rotate),
	__ATTR(state, S_IRUGO|S_IWUSR, show_fbstate, store_fbstate),
#ifdef CONFIG_FB_BACKLIGHT
	__ATTR(bl_curve, S_IRUGO|S_IWUSR, show_bl_curve, store_bl_curve),
#endif
};

先看一下,一个正常的设备属性格式

/* interface for exporting device attributes */
struct device_attribute {
	struct attribute	attr;        /* 通用的,所有的sys里面可读写的都需要  */
	ssize_t (*show)(struct device *dev, struct device_attribute *attr,
			char *buf);             /* 显示内核空间的接口 */
	ssize_t (*store)(struct device *dev, struct device_attribute *attr,
			 const char *buf, size_t count);    /* 用户空间向内核空间写 */
};

struct attribute {
	const char		*name;    /* 名字 */
	umode_t			mode;     /* 权限 */
#ifdef CONFIG_DEBUG_LOCK_ALLOC
	bool			ignore_lockdep:1;
	struct lock_class_key	*key;
	struct lock_class_key	skey;
#endif
};

下面我们看一下,具体操作的意义

	__ATTR(bits_per_pixel, S_IRUGO|S_IWUSR, show_bpp, store_bpp), 

#define __ATTR(_name, _mode, _show, _store) {				\
	.attr = {.name = __stringify(_name),				\
		 .mode = VERIFY_OCTAL_PERMISSIONS(_mode) },		\
	.show	= _show,						\
	.store	= _store,						\
}

用宏替换后是


{
    .sttr = {.name = "bits_per_pixel",
        .mode = VERIFY_OCTAL_PERMISSIONS(S_IRUGO|S_IWUSR) },    /* 所有人可读,拥有者可写 */
    .show = show_bpp,
    stroe = store_bpp,
    }
}

其中__stringify是转换字符串
#define __stringify_1(x...)	#x
#define __stringify(x...)	__stringify_1(x)

VERIFY_OCTAL_PERMISSIONS是做权限转换的
/* Permissions on a sysfs file: you didn't miss the 0 prefix did you? */
#define VERIFY_OCTAL_PERMISSIONS(perms)					\
	(BUILD_BUG_ON_ZERO((perms) < 0) +				\
	 BUILD_BUG_ON_ZERO((perms) > 0777) +				\
	 /* User perms >= group perms >= other perms */			\
	 BUILD_BUG_ON_ZERO(((perms) >> 6) < (((perms) >> 3) & 7)) +	\
	 BUILD_BUG_ON_ZERO((((perms) >> 3) & 7) < ((perms) & 7)) +	\
	 (perms))



#define S_IRWXUGO	(S_IRWXU|S_IRWXG|S_IRWXO)
#define S_IALLUGO	(S_ISUID|S_ISGID|S_ISVTX|S_IRWXUGO)
#define S_IRUGO		(S_IRUSR|S_IRGRP|S_IROTH)    /* 所有人有可读权限 */
#define S_IWUGO		(S_IWUSR|S_IWGRP|S_IWOTH)
#define S_IXUGO		(S_IXUSR|S_IXGRP|S_IXOTH)


S_IRUSR:用户读  00400
S_IRGRP:用户组读 00040
S_IROTH: 其他读 00004


#define S_IRWXU 00700
#define S_IRUSR 00400
#define S_IWUSR 00200    拥有者可写
#define S_IXUSR 00100

#define S_IRWXG 00070
#define S_IRGRP 00040
#define S_IWGRP 00020
#define S_IXGRP 00010

#define S_IRWXO 00007
#define S_IROTH 00004
#define S_IWOTH 00002
#define S_IXOTH 00001

其中show函数很简单,把当前的bpp参数格式化打印到buf数组里

static ssize_t show_bpp(struct device *device, struct device_attribute *attr,
			char *buf)
{
	struct fb_info *fb_info = dev_get_drvdata(device);    /* 回忆,前面我们把fb_info放到device的私有data了 */
	return snprintf(buf, PAGE_SIZE, "%d\n", fb_info->var.bits_per_pixel);
}

strow则是用户空间改变内核空间的bpp值

static ssize_t store_bpp(struct device *device, struct device_attribute *attr,
			 const char *buf, size_t count)
{
	struct fb_info *fb_info = dev_get_drvdata(device);    /* 回忆,前面我们把fb_info放到device的私有data了 */
	struct fb_var_screeninfo var;
	char ** last = NULL;
	int err;

	var = fb_info->var;
	var.bits_per_pixel = simple_strtoul(buf, last, 0);    /* 字符串转换为整数 */
	if ((err = activate(fb_info, &var)))    /* 写驱动空间的bpp */
		return err;
	return count;
}

static int activate(struct fb_info *fb_info, struct fb_var_screeninfo *var)
{
	int err;

	var->activate |= FB_ACTIVATE_FORCE;
	console_lock();
	fb_info->flags |= FBINFO_MISC_USEREVENT;
	err = fb_set_var(fb_info, var);        /* 设置驱动空间的bpp */
	fb_info->flags &= ~FBINFO_MISC_USEREVENT;
	console_unlock();
	if (err)
		return err;
	return 0;
}

使用属性操作可以很方便的做驱动调试

 

 

3.注销一个驱动

/**
 *	unregister_framebuffer - releases a frame buffer device
 *	@fb_info: frame buffer info structure
 *
 *	Unregisters a frame buffer device @fb_info.
 *
 *	Returns negative errno on error, or zero for success.
 *
 *      This function will also notify the framebuffer console
 *      to release the driver.
 *
 *      This is meant to be called within a driver's module_exit()
 *      function. If this is called outside module_exit(), ensure
 *      that the driver implements fb_open() and fb_release() to
 *      check that no processes are using the device.
 */
int
unregister_framebuffer(struct fb_info *fb_info)
{
	int ret;

	mutex_lock(®istration_lock);
	ret = do_unregister_framebuffer(fb_info);
	mutex_unlock(®istration_lock);

	return ret;
}



static int do_unregister_framebuffer(struct fb_info *fb_info)
{
	struct fb_event event;
	int i, ret = 0;

	i = fb_info->node;    /* 得到次设备号 */
    
    /* 检查次设备信息对不对 */
	if (i < 0 || i >= FB_MAX || registered_fb[i] != fb_info)
		return -EINVAL;

	console_lock();
	if (!lock_fb_info(fb_info)) {    
		console_unlock();    
		return -ENODEV;
	}

	event.info = fb_info;
	ret = fb_notifier_call_chain(FB_EVENT_FB_UNBIND, &event);    /* 通知一个卸载事件,其它就不能用这个了 */
	unlock_fb_info(fb_info);
	console_unlock();

	if (ret)
		return -EINVAL;

	pm_vt_switch_unregister(fb_info->dev);    /* 卸载挂起暂停释放功能 */

	unlink_framebuffer(fb_info);
	if (fb_info->pixmap.addr &&        
	    (fb_info->pixmap.flags & FB_PIXMAP_DEFAULT))    /* 如果是使用默认的pixmap,则是register时申请的内存,这里要释放 */
		kfree(fb_info->pixmap.addr);        
	fb_destroy_modelist(&fb_info->modelist);    /* 删除释放模式链表的所有模式 */
	registered_fb[i] = NULL;        /* 这里卸载要把全局的fb表清掉,后面注册的时候可以用 */
	num_registered_fb--;            /* 总数量减1 */
	fb_cleanup_device(fb_info);    /* 删除属性操作接口 */
	event.info = fb_info;
	console_lock();
	fb_notifier_call_chain(FB_EVENT_FB_UNREGISTERED, &event);    /* 通知卸载 */
	console_unlock();

	/* this may free fb info */
	put_fb_info(fb_info);
	return 0;
}

4.操作接口

因为framebuffer子系统默认默认最多只有32个设备,所以还是用的老的接口注册,即所有的次设备号都是使用的同一套fops操作函数,在注册class时已经添加注册了。

static int __init
fbmem_init(void)
{
	proc_create("fb", 0, NULL, &fb_proc_fops);

	if (register_chrdev(FB_MAJOR,"fb",&fb_fops))    /* 在这里注册的通用的fops */
		printk("unable to get major %d for fb devs\n", FB_MAJOR);

	fb_class = class_create(THIS_MODULE, "graphics");
	if (IS_ERR(fb_class)) {
		printk(KERN_WARNING "Unable to create fb class; errno = %ld\n", PTR_ERR(fb_class));
		fb_class = NULL;
	}
	return 0;
}

static const struct file_operations fb_fops = {
	.owner =	THIS_MODULE,
	.read =		fb_read,
	.write =	fb_write,
	.unlocked_ioctl = fb_ioctl,
#ifdef CONFIG_COMPAT
	.compat_ioctl = fb_compat_ioctl,
#endif
	.mmap =		fb_mmap,
	.open =		fb_open,
	.release =	fb_release,
#ifdef HAVE_ARCH_FB_UNMAPPED_AREA
	.get_unmapped_area = get_fb_unmapped_area,
#endif
#ifdef CONFIG_FB_DEFERRED_IO
	.fsync =	fb_deferred_io_fsync,
#endif
	.llseek =	default_llseek,
};

下面我们以open和write来分析一下大概流程

# define __acquires(x)	__attribute__((context(x,0,1)))
# define __releases(x)	__attribute__((context(x,1,0)))




static int
fb_open(struct inode *inode, struct file *file)
__acquires(&info->lock)            /* 属性修饰,可以不用管 */
__releases(&info->lock)
{
	int fbidx = iminor(inode);        /* 得到次设备号 */
	struct fb_info *info;
	int res = 0;

	info = get_fb_info(fbidx);        /* 由次设备号得到registered_fb数组的fbidx项info */
	if (!info) {
		request_module("fb%d", fbidx);
		info = get_fb_info(fbidx);
		if (!info)
			return -ENODEV;
	}
	if (IS_ERR(info))
		return PTR_ERR(info);

	mutex_lock(&info->lock);
	if (!try_module_get(info->fbops->owner)) {
		res = -ENODEV;
		goto out;
	}
	file->private_data = info;
	if (info->fbops->fb_open) {        /* fops里面的info如果存在,则执行 */
		res = info->fbops->fb_open(info,1);
		if (res)
			module_put(info->fbops->owner);
	}
#ifdef CONFIG_FB_DEFERRED_IO
	if (info->fbdefio)
		fb_deferred_io_open(info, inode, file);
#endif
out:
	mutex_unlock(&info->lock);
	if (res)
		put_fb_info(info);
	return res;
}



static struct fb_info *get_fb_info(unsigned int idx)
{
	struct fb_info *fb_info;

	if (idx >= FB_MAX)
		return ERR_PTR(-ENODEV);

	mutex_lock(®istration_lock);
	fb_info = registered_fb[idx];        /* 如果数组的第idx项存在,则返回,并把打开计数+1 */
	if (fb_info)
		atomic_inc(&fb_info->count);
	mutex_unlock(®istration_lock);

	return fb_info;
}

可以看到系统提供的通用接口的open并没做什么,而是调用真正的注册设备时,注册的fb_info里面的fops里的open函数


static ssize_t
fb_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
	unsigned long p = *ppos;
	struct fb_info *info = file_fb_info(file);    /* 得到次设备号 */
	u8 *buffer, *src;
	u8 __iomem *dst;
	int c, cnt = 0, err = 0;
	unsigned long total_size;

	if (!info || !info->screen_base)        /* 确认设备存在,写显存的基地址存在 */
		return -ENODEV;

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

	if (info->fbops->fb_write)            /* 设备驱动提供了写函数,则调用设备驱动提供的 */
		return info->fbops->fb_write(info, buf, count, ppos);
	
    /* 设备驱动没提供写函数,再执行下面的 */


	total_size = info->screen_size;        /* 显存大小 */

	if (total_size == 0)
		total_size = info->fix.smem_len;    /* 如果没分配显存,则用fb中固定的参数长度 */

	if (p > total_size)    /* p是要写位置相对显存的偏移,这里保证要写的在显存范围内 */
		return -EFBIG;

	if (count > total_size) {
		err = -EFBIG;
		count = total_size;    /* 每次最多刷一屏 */
	}

	if (count + p > total_size) {    /* 结束位置是不是超了 */
		if (!err)
			err = -ENOSPC;

		count = total_size - p;
	}

    /* 申请空间,用来保存用户空间的数据 */
	buffer = kmalloc((count > PAGE_SIZE) ? PAGE_SIZE : count,
			 GFP_KERNEL);
	if (!buffer)
		return -ENOMEM;

	dst = (u8 __iomem *) (info->screen_base + p);    /* 基址+偏移 ->要写的位置 */

	if (info->fbops->fb_sync)
		info->fbops->fb_sync(info);        /* 是否要同步 */

	while (count) {
		c = (count > PAGE_SIZE) ? PAGE_SIZE : count;
		src = buffer;        /* 用户空间数据 */

		if (copy_from_user(src, buf, c)) {    /* 拷贝到内核空间 */
			err = -EFAULT;
			break;
		}

		fb_memcpy_tofb(dst, src, c);        /* 内核空间数据拷贝到显存位置 */
		dst += c;
		src += c;
		*ppos += c;
		buf += c;
		cnt += c;
		count -= c;
	}

	kfree(buffer);        /* 释放零时保存用户数据空间 */

	return (cnt) ? cnt : err;
}

write函数就比较有意思了,如果驱动工程师,写了write的设备驱动,则执行驱动工程师的。否则执行默认的驱动。

而默认的驱动就是把用户空间数据先拷贝到内核空间,之后再把内核数据拷贝到显存里。这里我们可以看到,比较麻烦,拷贝两次,特别是fb数据通常都很多,每秒都要显示好多帧,明显效率不高。

有什么办法解决吗?

有的内核提供 了mmap机制用来解决这个问题。mmap函数的原理和分析我们在后面一节单独分析。

 

总结:

framebuffer子系统(class)提供了一些通用的接口,用来注册/注销设备。同时提供了attribute用来提供应用层对内核中的设备的参数交互。提供了共有的fops用来提供和应用层对设备的操作。

 

 

你可能感兴趣的:(从零开始系列,从零开始学linux驱动)