// 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. Load more
// 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;