1999 lines · 48.5 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0-only/* * A framebuffer driver for VBE 2.0+ compliant video cards * * (c) 2007 Michal Januszewski <spock@gentoo.org> *     Loosely based upon the vesafb driver. * */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include <linux/init.h>#include <linux/module.h>#include <linux/moduleparam.h>#include <linux/skbuff.h>#include <linux/timer.h>#include <linux/completion.h>#include <linux/connector.h>#include <linux/random.h>#include <linux/platform_device.h>#include <linux/limits.h>#include <linux/fb.h>#include <linux/io.h>#include <linux/mutex.h>#include <linux/slab.h>#include <video/edid.h>#include <video/uvesafb.h>#ifdef CONFIG_X86#include <video/vga.h>#endif#include "edid.h" static struct cb_id uvesafb_cn_id = {	.idx = CN_IDX_V86D,	.val = CN_VAL_V86D_UVESAFB};static char v86d_path[PATH_MAX] = "/sbin/v86d";static char v86d_started;	/* has v86d been started by uvesafb? */ static const struct fb_fix_screeninfo uvesafb_fix = {	.id	= "VESA VGA",	.type	= FB_TYPE_PACKED_PIXELS,	.accel	= FB_ACCEL_NONE,	.visual = FB_VISUAL_TRUECOLOR,}; static int mtrr		= 3;	/* enable mtrr by default */static bool blank	= true;	/* enable blanking by default */static int ypan		= 1;	/* 0: scroll, 1: ypan, 2: ywrap */static bool pmi_setpal	= true; /* use PMI for palette changes */static bool nocrtc;		/* ignore CRTC settings */static bool noedid;		/* don't try DDC transfers */static int vram_remap;		/* set amt. of memory to be used */static int vram_total;		/* set total amount of memory */static u16 maxclk;		/* maximum pixel clock */static u16 maxvf;		/* maximum vertical frequency */static u16 maxhf;		/* maximum horizontal frequency */static u16 vbemode;		/* force use of a specific VBE mode */static char *mode_option;static u8  dac_width	= 6; static struct uvesafb_ktask *uvfb_tasks[UVESAFB_TASKS_MAX];static DEFINE_MUTEX(uvfb_lock); /* * A handler for replies from userspace. * * Make sure each message passes consistency checks and if it does, * find the kernel part of the task struct, copy the registers and * the buffer contents and then complete the task. */static void uvesafb_cn_callback(struct cn_msg *msg, struct netlink_skb_parms *nsp){	struct uvesafb_task *utask;	struct uvesafb_ktask *task; 	if (!capable(CAP_SYS_ADMIN))		return; 	if (msg->seq >= UVESAFB_TASKS_MAX)		return; 	mutex_lock(&uvfb_lock);	task = uvfb_tasks[msg->seq]; 	if (!task || msg->ack != task->ack) {		mutex_unlock(&uvfb_lock);		return;	} 	utask = (struct uvesafb_task *)msg->data; 	/* Sanity checks for the buffer length. */	if (task->t.buf_len < utask->buf_len ||	    utask->buf_len > msg->len - sizeof(*utask)) {		mutex_unlock(&uvfb_lock);		return;	} 	uvfb_tasks[msg->seq] = NULL;	mutex_unlock(&uvfb_lock); 	memcpy(&task->t, utask, sizeof(*utask)); 	if (task->t.buf_len && task->buf)		memcpy(task->buf, utask + 1, task->t.buf_len); 	complete(task->done);	return;} static int uvesafb_helper_start(void){	char *envp[] = {		"HOME=/",		"PATH=/sbin:/bin",		NULL,	}; 	char *argv[] = {		v86d_path,		NULL,	}; 	return call_usermodehelper(v86d_path, argv, envp, UMH_WAIT_PROC);} /* * Execute a uvesafb task. * * Returns 0 if the task is executed successfully. * * A message sent to the userspace consists of the uvesafb_task * struct and (optionally) a buffer. The uvesafb_task struct is * a simplified version of uvesafb_ktask (its kernel counterpart) * containing only the register values, flags and the length of * the buffer. * * Each message is assigned a sequence number (increased linearly) * and a random ack number. The sequence number is used as a key * for the uvfb_tasks array which holds pointers to uvesafb_ktask * structs for all requests. */static int uvesafb_exec(struct uvesafb_ktask *task){	static int seq;	struct cn_msg *m;	int err;	int len = sizeof(task->t) + task->t.buf_len; 	/*	 * Check whether the message isn't longer than the maximum	 * allowed by connector.	 */	if (sizeof(*m) + len > CONNECTOR_MAX_MSG_SIZE) {		pr_warn("message too long (%d), can't execute task\n",			(int)(sizeof(*m) + len));		return -E2BIG;	} 	m = kzalloc(sizeof(*m) + len, GFP_KERNEL);	if (!m)		return -ENOMEM; 	init_completion(task->done); 	memcpy(&m->id, &uvesafb_cn_id, sizeof(m->id));	m->seq = seq;	m->len = len;	m->ack = get_random_u32(); 	/* uvesafb_task structure */	memcpy(m + 1, &task->t, sizeof(task->t)); 	/* Buffer */	memcpy((u8 *)(m + 1) + sizeof(task->t), task->buf, task->t.buf_len); 	/*	 * Save the message ack number so that we can find the kernel	 * part of this task when a reply is received from userspace.	 */	task->ack = m->ack; 	mutex_lock(&uvfb_lock); 	/* If all slots are taken -- bail out. */	if (uvfb_tasks[seq]) {		mutex_unlock(&uvfb_lock);		err = -EBUSY;		goto out;	} 	/* Save a pointer to the kernel part of the task struct. */	uvfb_tasks[seq] = task;	mutex_unlock(&uvfb_lock); 	err = cn_netlink_send(m, 0, 0, GFP_KERNEL);	if (err == -ESRCH) {		/*		 * Try to start the userspace helper if sending		 * the request failed the first time.		 */		err = uvesafb_helper_start();		if (err) {			pr_err("failed to execute %s\n", v86d_path);			pr_err("make sure that the v86d helper is installed and executable\n");		} else {			v86d_started = 1;			err = cn_netlink_send(m, 0, 0, gfp_any());			if (err == -ENOBUFS)				err = 0;		}	} else if (err == -ENOBUFS)		err = 0; 	if (!err && !(task->t.flags & TF_EXIT))		err = !wait_for_completion_timeout(task->done,				msecs_to_jiffies(UVESAFB_TIMEOUT)); 	mutex_lock(&uvfb_lock);	uvfb_tasks[seq] = NULL;	mutex_unlock(&uvfb_lock); 	seq++;	if (seq >= UVESAFB_TASKS_MAX)		seq = 0;out:	kfree(m);	return err;} /* * Free a uvesafb_ktask struct. */static void uvesafb_free(struct uvesafb_ktask *task){	if (task) {		kfree(task->done);		kfree(task);	}} /* * Prepare a uvesafb_ktask struct to be used again. */static void uvesafb_reset(struct uvesafb_ktask *task){	struct completion *cpl = task->done; 	memset(task, 0, sizeof(*task));	task->done = cpl;} /* * Allocate and prepare a uvesafb_ktask struct. */static struct uvesafb_ktask *uvesafb_prep(void){	struct uvesafb_ktask *task; 	task = kzalloc_obj(*task);	if (task) {		task->done = kzalloc_obj(*task->done);		if (!task->done) {			kfree(task);			task = NULL;		}	}	return task;} static void uvesafb_setup_var(struct fb_var_screeninfo *var,		struct fb_info *info, struct vbe_mode_ib *mode){	struct uvesafb_par *par = info->par; 	var->vmode = FB_VMODE_NONINTERLACED;	var->sync = FB_SYNC_VERT_HIGH_ACT; 	var->xres = mode->x_res;	var->yres = mode->y_res;	var->xres_virtual = mode->x_res;	var->yres_virtual = (par->ypan) ?			info->fix.smem_len / mode->bytes_per_scan_line :			mode->y_res;	var->xoffset = 0;	var->yoffset = 0;	var->bits_per_pixel = mode->bits_per_pixel; 	if (var->bits_per_pixel == 15)		var->bits_per_pixel = 16; 	if (var->bits_per_pixel > 8) {		var->red.offset    = mode->red_off;		var->red.length    = mode->red_len;		var->green.offset  = mode->green_off;		var->green.length  = mode->green_len;		var->blue.offset   = mode->blue_off;		var->blue.length   = mode->blue_len;		var->transp.offset = mode->rsvd_off;		var->transp.length = mode->rsvd_len;	} else {		var->red.offset    = 0;		var->green.offset  = 0;		var->blue.offset   = 0;		var->transp.offset = 0; 		var->red.length    = 8;		var->green.length  = 8;		var->blue.length   = 8;		var->transp.length = 0;	}} static int uvesafb_vbe_find_mode(struct uvesafb_par *par,		int xres, int yres, int depth, unsigned char flags){	int i, match = -1, h = 0, d = 0x7fffffff; 	for (i = 0; i < par->vbe_modes_cnt; i++) {		h = abs(par->vbe_modes[i].x_res - xres) +		    abs(par->vbe_modes[i].y_res - yres) +		    abs(depth - par->vbe_modes[i].depth); 		/*		 * We have an exact match in terms of resolution		 * and depth.		 */		if (h == 0)			return i; 		if (h < d || (h == d && par->vbe_modes[i].depth > depth)) {			d = h;			match = i;		}	}	i = 1; 	if (flags & UVESAFB_EXACT_DEPTH &&			par->vbe_modes[match].depth != depth)		i = 0; 	if (flags & UVESAFB_EXACT_RES && d > 24)		i = 0; 	if (i != 0)		return match;	else		return -1;} static u8 *uvesafb_vbe_state_save(struct uvesafb_par *par){	struct uvesafb_ktask *task;	u8 *state;	int err; 	if (!par->vbe_state_size)		return NULL; 	state = kmalloc(par->vbe_state_size, GFP_KERNEL);	if (!state)		return ERR_PTR(-ENOMEM); 	task = uvesafb_prep();	if (!task) {		kfree(state);		return NULL;	} 	task->t.regs.eax = 0x4f04;	task->t.regs.ecx = 0x000f;	task->t.regs.edx = 0x0001;	task->t.flags = TF_BUF_RET | TF_BUF_ESBX;	task->t.buf_len = par->vbe_state_size;	task->buf = state;	err = uvesafb_exec(task); 	if (err || (task->t.regs.eax & 0xffff) != 0x004f) {		pr_warn("VBE get state call failed (eax=0x%x, err=%d)\n",			task->t.regs.eax, err);		kfree(state);		state = NULL;	} 	uvesafb_free(task);	return state;} static void uvesafb_vbe_state_restore(struct uvesafb_par *par, u8 *state_buf){	struct uvesafb_ktask *task;	int err; 	if (!state_buf)		return; 	task = uvesafb_prep();	if (!task)		return; 	task->t.regs.eax = 0x4f04;	task->t.regs.ecx = 0x000f;	task->t.regs.edx = 0x0002;	task->t.buf_len = par->vbe_state_size;	task->t.flags = TF_BUF_ESBX;	task->buf = state_buf; 	err = uvesafb_exec(task);	if (err || (task->t.regs.eax & 0xffff) != 0x004f)		pr_warn("VBE state restore call failed (eax=0x%x, err=%d)\n",			task->t.regs.eax, err); 	uvesafb_free(task);} static int uvesafb_vbe_getinfo(struct uvesafb_ktask *task,			       struct uvesafb_par *par){	int err; 	task->t.regs.eax = 0x4f00;	task->t.flags = TF_VBEIB;	task->t.buf_len = sizeof(struct vbe_ib);	task->buf = &par->vbe_ib;	memcpy(par->vbe_ib.vbe_signature, "VBE2", 4); 	err = uvesafb_exec(task);	if (err || (task->t.regs.eax & 0xffff) != 0x004f) {		pr_err("Getting VBE info block failed (eax=0x%x, err=%d)\n",		       (u32)task->t.regs.eax, err);		return -EINVAL;	} 	if (par->vbe_ib.vbe_version < 0x0200) {		pr_err("Sorry, pre-VBE 2.0 cards are not supported\n");		return -EINVAL;	} 	if (!par->vbe_ib.mode_list_ptr) {		pr_err("Missing mode list!\n");		return -EINVAL;	} 	pr_info(""); 	/*	 * Convert string pointers and the mode list pointer into	 * usable addresses. Print informational messages about the	 * video adapter and its vendor.	 */	if (par->vbe_ib.oem_vendor_name_ptr)		pr_cont("%s, ",			((char *)task->buf) + par->vbe_ib.oem_vendor_name_ptr); 	if (par->vbe_ib.oem_product_name_ptr)		pr_cont("%s, ",			((char *)task->buf) + par->vbe_ib.oem_product_name_ptr); 	if (par->vbe_ib.oem_product_rev_ptr)		pr_cont("%s, ",			((char *)task->buf) + par->vbe_ib.oem_product_rev_ptr); 	if (par->vbe_ib.oem_string_ptr)		pr_cont("OEM: %s, ",			((char *)task->buf) + par->vbe_ib.oem_string_ptr); 	pr_cont("VBE v%d.%d\n",		(par->vbe_ib.vbe_version & 0xff00) >> 8,		par->vbe_ib.vbe_version & 0xff); 	return 0;} static int uvesafb_vbe_getmodes(struct uvesafb_ktask *task,				struct uvesafb_par *par){	int off = 0, err;	u16 *mode; 	par->vbe_modes_cnt = 0; 	/* Count available modes. */	mode = (u16 *) (((u8 *)&par->vbe_ib) + par->vbe_ib.mode_list_ptr);	while (*mode != 0xffff) {		par->vbe_modes_cnt++;		mode++;	} 	par->vbe_modes = kzalloc_objs(struct vbe_mode_ib, par->vbe_modes_cnt);	if (!par->vbe_modes)		return -ENOMEM; 	/* Get info about all available modes. */	mode = (u16 *) (((u8 *)&par->vbe_ib) + par->vbe_ib.mode_list_ptr);	while (*mode != 0xffff) {		struct vbe_mode_ib *mib; 		uvesafb_reset(task);		task->t.regs.eax = 0x4f01;

Lines 1–500 of 1999.

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t