1
linux/drivers/usb/misc/sisusbvga/sisusb_con.c
Antonino A. Daplas e400b6ec4e vt/vgacon: Check if screen resize request comes from userspace
Various console drivers are able to resize the screen via the con_resize()
hook.  This hook is also visible in userspace via the TIOCWINSZ, VT_RESIZE and
VT_RESIZEX ioctl's.  One particular utility, SVGATextMode, expects that
con_resize() of the VGA console will always return success even if the
resulting screen is not compatible with the hardware.  However, this
particular behavior of the VGA console, as reported in Kernel Bugzilla Bug
7513, can cause undefined behavior if the user starts with a console size
larger than 80x25.

To work around this problem, add an extra parameter to con_resize().  This
parameter is ignored by drivers except for vgacon.  If this parameter is
non-zero, then the resize request came from a VT_RESIZE or VT_RESIZEX ioctl
and vgacon will always return success.  If this parameter is zero, vgacon will
return -EINVAL if the requested size is not compatible with the hardware.  The
latter is the more correct behavior.

With this change, SVGATextMode should still work correctly while in-kernel and
stty resize calls can expect correct behavior from vgacon.

Signed-off-by: Antonino Daplas <adaplas@gmail.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2007-10-16 09:43:20 -07:00

1566 lines
36 KiB
C

/*
* sisusb - usb kernel driver for SiS315(E) based USB2VGA dongles
*
* VGA text mode console part
*
* Copyright (C) 2005 by Thomas Winischhofer, Vienna, Austria
*
* If distributed as part of the Linux kernel, this code is licensed under the
* terms of the GPL v2.
*
* Otherwise, the following license terms apply:
*
* * Redistribution and use in source and binary forms, with or without
* * modification, are permitted provided that the following conditions
* * are met:
* * 1) Redistributions of source code must retain the above copyright
* * notice, this list of conditions and the following disclaimer.
* * 2) Redistributions in binary form must reproduce the above copyright
* * notice, this list of conditions and the following disclaimer in the
* * documentation and/or other materials provided with the distribution.
* * 3) The name of the author may not be used to endorse or promote products
* * derived from this software without specific psisusbr written permission.
* *
* * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESSED OR
* * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* Author: Thomas Winischhofer <thomas@winischhofer.net>
*
* Portions based on vgacon.c which are
* Created 28 Sep 1997 by Geert Uytterhoeven
* Rewritten by Martin Mares <mj@ucw.cz>, July 1998
* based on code Copyright (C) 1991, 1992 Linus Torvalds
* 1995 Jay Estabrook
*
* A note on using in_atomic() in here: We can't handle console
* calls from non-schedulable context due to our USB-dependend
* nature. For now, this driver just ignores any calls if it
* detects this state.
*
*/
#include <linux/mutex.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/signal.h>
#include <linux/fs.h>
#include <linux/usb.h>
#include <linux/tty.h>
#include <linux/console.h>
#include <linux/string.h>
#include <linux/kd.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/vt_kern.h>
#include <linux/selection.h>
#include <linux/spinlock.h>
#include <linux/kref.h>
#include <linux/ioport.h>
#include <linux/interrupt.h>
#include <linux/vmalloc.h>
#include "sisusb.h"
#include "sisusb_init.h"
#ifdef INCL_SISUSB_CON
#define sisusbcon_writew(val, addr) (*(addr) = (val))
#define sisusbcon_readw(addr) (*(addr))
#define sisusbcon_memmovew(d, s, c) memmove(d, s, c)
#define sisusbcon_memcpyw(d, s, c) memcpy(d, s, c)
/* vc_data -> sisusb conversion table */
static struct sisusb_usb_data *mysisusbs[MAX_NR_CONSOLES];
/* Forward declaration */
static const struct consw sisusb_con;
static inline void
sisusbcon_memsetw(u16 *s, u16 c, unsigned int count)
{
count /= 2;
while (count--)
sisusbcon_writew(c, s++);
}
static inline void
sisusb_initialize(struct sisusb_usb_data *sisusb)
{
/* Reset cursor and start address */
if (sisusb_setidxreg(sisusb, SISCR, 0x0c, 0x00))
return;
if (sisusb_setidxreg(sisusb, SISCR, 0x0d, 0x00))
return;
if (sisusb_setidxreg(sisusb, SISCR, 0x0e, 0x00))
return;
sisusb_setidxreg(sisusb, SISCR, 0x0f, 0x00);
}
static inline void
sisusbcon_set_start_address(struct sisusb_usb_data *sisusb, struct vc_data *c)
{
sisusb->cur_start_addr = (c->vc_visible_origin - sisusb->scrbuf) / 2;
sisusb_setidxreg(sisusb, SISCR, 0x0c, (sisusb->cur_start_addr >> 8));
sisusb_setidxreg(sisusb, SISCR, 0x0d, (sisusb->cur_start_addr & 0xff));
}
void
sisusb_set_cursor(struct sisusb_usb_data *sisusb, unsigned int location)
{
if (sisusb->sisusb_cursor_loc == location)
return;
sisusb->sisusb_cursor_loc = location;
/* Hardware bug: Text cursor appears twice or not at all
* at some positions. Work around it with the cursor skew
* bits.
*/
if ((location & 0x0007) == 0x0007) {
sisusb->bad_cursor_pos = 1;
location--;
if (sisusb_setidxregandor(sisusb, SISCR, 0x0b, 0x1f, 0x20))
return;
} else if (sisusb->bad_cursor_pos) {
if (sisusb_setidxregand(sisusb, SISCR, 0x0b, 0x1f))
return;
sisusb->bad_cursor_pos = 0;
}
if (sisusb_setidxreg(sisusb, SISCR, 0x0e, (location >> 8)))
return;
sisusb_setidxreg(sisusb, SISCR, 0x0f, (location & 0xff));
}
static inline struct sisusb_usb_data *
sisusb_get_sisusb(unsigned short console)
{
return mysisusbs[console];
}
static inline int
sisusb_sisusb_valid(struct sisusb_usb_data *sisusb)
{
if (!sisusb->present || !sisusb->ready || !sisusb->sisusb_dev)
return 0;
return 1;
}
static struct sisusb_usb_data *
sisusb_get_sisusb_lock_and_check(unsigned short console)
{
struct sisusb_usb_data *sisusb;
/* We can't handle console calls in non-schedulable
* context due to our locks and the USB transport.
* So we simply ignore them. This should only affect
* some calls to printk.
*/
if (in_atomic())
return NULL;
if (!(sisusb = sisusb_get_sisusb(console)))
return NULL;
mutex_lock(&sisusb->lock);
if (!sisusb_sisusb_valid(sisusb) ||
!sisusb->havethisconsole[console]) {
mutex_unlock(&sisusb->lock);
return NULL;
}
return sisusb;
}
static int
sisusb_is_inactive(struct vc_data *c, struct sisusb_usb_data *sisusb)
{
if (sisusb->is_gfx ||
sisusb->textmodedestroyed ||
c->vc_mode != KD_TEXT)
return 1;
return 0;
}
/* con_startup console interface routine */
static const char *
sisusbcon_startup(void)
{
return "SISUSBCON";
}
/* con_init console interface routine */
static void
sisusbcon_init(struct vc_data *c, int init)
{
struct sisusb_usb_data *sisusb;
int cols, rows;
/* This is called by take_over_console(),
* ie by us/under our control. It is
* only called after text mode and fonts
* are set up/restored.
*/
if (!(sisusb = sisusb_get_sisusb(c->vc_num)))
return;
mutex_lock(&sisusb->lock);
if (!sisusb_sisusb_valid(sisusb)) {
mutex_unlock(&sisusb->lock);
return;
}
c->vc_can_do_color = 1;
c->vc_complement_mask = 0x7700;
c->vc_hi_font_mask = sisusb->current_font_512 ? 0x0800 : 0;
sisusb->haveconsole = 1;
sisusb->havethisconsole[c->vc_num] = 1;
/* We only support 640x400 */
c->vc_scan_lines = 400;
c->vc_font.height = sisusb->current_font_height;
/* We only support width = 8 */
cols = 80;
rows = c->vc_scan_lines / c->vc_font.height;
/* Increment usage count for our sisusb.
* Doing so saves us from upping/downing
* the disconnect semaphore; we can't
* lose our sisusb until this is undone
* in con_deinit. For all other console
* interface functions, it suffices to
* use sisusb->lock and do a quick check
* of sisusb for device disconnection.
*/
kref_get(&sisusb->kref);
if (!*c->vc_uni_pagedir_loc)
con_set_default_unimap(c);
mutex_unlock(&sisusb->lock);
if (init) {
c->vc_cols = cols;
c->vc_rows = rows;
} else
vc_resize(c, cols, rows);
}
/* con_deinit console interface routine */
static void
sisusbcon_deinit(struct vc_data *c)
{
struct sisusb_usb_data *sisusb;
int i;
/* This is called by take_over_console()
* and others, ie not under our control.
*/
if (!(sisusb = sisusb_get_sisusb(c->vc_num)))
return;
mutex_lock(&sisusb->lock);
/* Clear ourselves in mysisusbs */
mysisusbs[c->vc_num] = NULL;
sisusb->havethisconsole[c->vc_num] = 0;
/* Free our font buffer if all consoles are gone */
if (sisusb->font_backup) {
for(i = 0; i < MAX_NR_CONSOLES; i++) {
if (sisusb->havethisconsole[c->vc_num])
break;
}
if (i == MAX_NR_CONSOLES) {
vfree(sisusb->font_backup);
sisusb->font_backup = NULL;
}
}
mutex_unlock(&sisusb->lock);
/* decrement the usage count on our sisusb */
kref_put(&sisusb->kref, sisusb_delete);
}
/* interface routine */
static u8
sisusbcon_build_attr(struct vc_data *c, u8 color, u8 intensity,
u8 blink, u8 underline, u8 reverse, u8 unused)
{
u8 attr = color;
if (underline)
attr = (attr & 0xf0) | c->vc_ulcolor;
else if (intensity == 0)
attr = (attr & 0xf0) | c->vc_halfcolor;
if (reverse)
attr = ((attr) & 0x88) |
((((attr) >> 4) |
((attr) << 4)) & 0x77);
if (blink)
attr ^= 0x80;
if (intensity == 2)
attr ^= 0x08;
return attr;
}
/* Interface routine */
static void
sisusbcon_invert_region(struct vc_data *vc, u16 *p, int count)
{
/* Invert a region. This is called with a pointer
* to the console's internal screen buffer. So we
* simply do the inversion there and rely on
* a call to putc(s) to update the real screen.
*/
while (count--) {
u16 a = sisusbcon_readw(p);
a = ((a) & 0x88ff) |
(((a) & 0x7000) >> 4) |
(((a) & 0x0700) << 4);
sisusbcon_writew(a, p++);
}
}
#define SISUSB_VADDR(x,y) \
((u16 *)c->vc_origin + \
(y) * sisusb->sisusb_num_columns + \
(x))
#define SISUSB_HADDR(x,y) \
((u16 *)(sisusb->vrambase + (c->vc_origin - sisusb->scrbuf)) + \
(y) * sisusb->sisusb_num_columns + \
(x))
/* Interface routine */
static void
sisusbcon_putc(struct vc_data *c, int ch, int y, int x)
{
struct sisusb_usb_data *sisusb;
ssize_t written;
if (!(sisusb = sisusb_get_sisusb_lock_and_check(c->vc_num)))
return;
/* sisusb->lock is down */
if (sisusb_is_inactive(c, sisusb)) {
mutex_unlock(&sisusb->lock);
return;
}
sisusb_copy_memory(sisusb, (char *)SISUSB_VADDR(x, y),
(long)SISUSB_HADDR(x, y), 2, &written);
mutex_unlock(&sisusb->lock);
}
/* Interface routine */
static void
sisusbcon_putcs(struct vc_data *c, const unsigned short *s,
int count, int y, int x)
{
struct sisusb_usb_data *sisusb;
ssize_t written;
u16 *dest;
int i;
if (!(sisusb = sisusb_get_sisusb_lock_and_check(c->vc_num)))
return;
/* sisusb->lock is down */
/* Need to put the characters into the buffer ourselves,
* because the vt does this AFTER calling us.
*/
dest = SISUSB_VADDR(x, y);
for (i = count; i > 0; i--)
sisusbcon_writew(sisusbcon_readw(s++), dest++);
if (sisusb_is_inactive(c, sisusb)) {
mutex_unlock(&sisusb->lock);
return;
}
sisusb_copy_memory(sisusb, (char *)SISUSB_VADDR(x, y),
(long)SISUSB_HADDR(x, y), count * 2, &written);
mutex_unlock(&sisusb->lock);
}
/* Interface routine */
static void
sisusbcon_clear(struct vc_data *c, int y, int x, int height, int width)
{
struct sisusb_usb_data *sisusb;
u16 eattr = c->vc_video_erase_char;
ssize_t written;
int i, length, cols;
u16 *dest;
if (width <= 0 || height <= 0)
return;
if (!(sisusb = sisusb_get_sisusb_lock_and_check(c->vc_num)))
return;
/* sisusb->lock is down */
/* Need to clear buffer ourselves, because the vt does
* this AFTER calling us.
*/
dest = SISUSB_VADDR(x, y);
cols = sisusb->sisusb_num_columns;
if (width > cols)
width = cols;
if (x == 0 && width >= c->vc_cols) {
sisusbcon_memsetw(dest, eattr, height * cols * 2);
} else {
for (i = height; i > 0; i--, dest += cols)
sisusbcon_memsetw(dest, eattr, width * 2);
}
if (sisusb_is_inactive(c, sisusb)) {
mutex_unlock(&sisusb->lock);
return;
}
length = ((height * cols) - x - (cols - width - x)) * 2;
sisusb_copy_memory(sisusb, (unsigned char *)SISUSB_VADDR(x, y),
(long)SISUSB_HADDR(x, y), length, &written);
mutex_unlock(&sisusb->lock);
}
/* Interface routine */
static void
sisusbcon_bmove(struct vc_data *c, int sy, int sx,
int dy, int dx, int height, int width)
{
struct sisusb_usb_data *sisusb;
ssize_t written;
int cols, length;
if (width <= 0 || height <= 0)
return;
if (!(sisusb = sisusb_get_sisusb_lock_and_check(c->vc_num)))
return;
/* sisusb->lock is down */
cols = sisusb->sisusb_num_columns;
if (sisusb_is_inactive(c, sisusb)) {
mutex_unlock(&sisusb->lock);
return;
}
length = ((height * cols) - dx - (cols - width - dx)) * 2;
sisusb_copy_memory(sisusb, (unsigned char *)SISUSB_VADDR(dx, dy),
(long)SISUSB_HADDR(dx, dy), length, &written);
mutex_unlock(&sisusb->lock);
}
/* interface routine */
static int
sisusbcon_switch(struct vc_data *c)
{
struct sisusb_usb_data *sisusb;
ssize_t written;
int length;
/* Returnvalue 0 means we have fully restored screen,
* and vt doesn't need to call do_update_region().
* Returnvalue != 0 naturally means the opposite.
*/
if (!(sisusb = sisusb_get_sisusb_lock_and_check(c->vc_num)))
return 0;
/* sisusb->lock is down */
/* Don't write to screen if in gfx mode */
if (sisusb_is_inactive(c, sisusb)) {
mutex_unlock(&sisusb->lock);
return 0;
}
/* That really should not happen. It would mean we are
* being called while the vc is using its private buffer
* as origin.
*/
if (c->vc_origin == (unsigned long)c->vc_screenbuf) {
mutex_unlock(&sisusb->lock);
dev_dbg(&sisusb->sisusb_dev->dev, "ASSERT ORIGIN != SCREENBUF!\n");
return 0;
}
/* Check that we don't copy too much */
length = min((int)c->vc_screenbuf_size,
(int)(sisusb->scrbuf + sisusb->scrbuf_size - c->vc_origin));
/* Restore the screen contents */
sisusbcon_memcpyw((u16 *)c->vc_origin, (u16 *)c->vc_screenbuf,
length);
sisusb_copy_memory(sisusb, (unsigned char *)c->vc_origin,
(long)SISUSB_HADDR(0, 0),
length, &written);
mutex_unlock(&sisusb->lock);
return 0;
}
/* interface routine */
static void
sisusbcon_save_screen(struct vc_data *c)
{
struct sisusb_usb_data *sisusb;
int length;
/* Save the current screen contents to vc's private
* buffer.
*/
if (!(sisusb = sisusb_get_sisusb_lock_and_check(c->vc_num)))
return;
/* sisusb->lock is down */
if (sisusb_is_inactive(c, sisusb)) {
mutex_unlock(&sisusb->lock);
return;
}
/* Check that we don't copy too much */
length = min((int)c->vc_screenbuf_size,
(int)(sisusb->scrbuf + sisusb->scrbuf_size - c->vc_origin));
/* Save the screen contents to vc's private buffer */
sisusbcon_memcpyw((u16 *)c->vc_screenbuf, (u16 *)c->vc_origin,
length);
mutex_unlock(&sisusb->lock);
}
/* interface routine */
static int
sisusbcon_set_palette(struct vc_data *c, unsigned char *table)
{
struct sisusb_usb_data *sisusb;
int i, j;
/* Return value not used by vt */
if (!CON_IS_VISIBLE(c))
return -EINVAL;
if (!(sisusb = sisusb_get_sisusb_lock_and_check(c->vc_num)))
return -EINVAL;
/* sisusb->lock is down */
if (sisusb_is_inactive(c, sisusb)) {
mutex_unlock(&sisusb->lock);
return -EINVAL;
}
for (i = j = 0; i < 16; i++) {
if (sisusb_setreg(sisusb, SISCOLIDX, table[i]))
break;
if (sisusb_setreg(sisusb, SISCOLDATA, c->vc_palette[j++] >> 2))
break;
if (sisusb_setreg(sisusb, SISCOLDATA, c->vc_palette[j++] >> 2))
break;
if (sisusb_setreg(sisusb, SISCOLDATA, c->vc_palette[j++] >> 2))
break;
}
mutex_unlock(&sisusb->lock);
return 0;
}
/* interface routine */
static int
sisusbcon_blank(struct vc_data *c, int blank, int mode_switch)
{
struct sisusb_usb_data *sisusb;
u8 sr1, cr17, pmreg, cr63;
ssize_t written;
int ret = 0;
if (!(sisusb = sisusb_get_sisusb_lock_and_check(c->vc_num)))
return 0;
/* sisusb->lock is down */
if (mode_switch)
sisusb->is_gfx = blank ? 1 : 0;
if (sisusb_is_inactive(c, sisusb)) {
mutex_unlock(&sisusb->lock);
return 0;
}
switch (blank) {
case 1: /* Normal blanking: Clear screen */
case -1:
sisusbcon_memsetw((u16 *)c->vc_origin,
c->vc_video_erase_char,
c->vc_screenbuf_size);
sisusb_copy_memory(sisusb,
(unsigned char *)c->vc_origin,
(u32)(sisusb->vrambase +
(c->vc_origin - sisusb->scrbuf)),
c->vc_screenbuf_size, &written);
sisusb->con_blanked = 1;
ret = 1;
break;
default: /* VESA blanking */
switch (blank) {
case 0: /* Unblank */
sr1 = 0x00;
cr17 = 0x80;
pmreg = 0x00;
cr63 = 0x00;
ret = 1;
sisusb->con_blanked = 0;
break;
case VESA_VSYNC_SUSPEND + 1:
sr1 = 0x20;
cr17 = 0x80;
pmreg = 0x80;
cr63 = 0x40;
break;
case VESA_HSYNC_SUSPEND + 1:
sr1 = 0x20;
cr17 = 0x80;
pmreg = 0x40;
cr63 = 0x40;
break;
case VESA_POWERDOWN + 1:
sr1 = 0x20;
cr17 = 0x00;
pmreg = 0xc0;
cr63 = 0x40;
break;
default:
mutex_unlock(&sisusb->lock);
return -EINVAL;
}
sisusb_setidxregandor(sisusb, SISSR, 0x01, ~0x20, sr1);
sisusb_setidxregandor(sisusb, SISCR, 0x17, 0x7f, cr17);
sisusb_setidxregandor(sisusb, SISSR, 0x1f, 0x3f, pmreg);
sisusb_setidxregandor(sisusb, SISCR, 0x63, 0xbf, cr63);
}
mutex_unlock(&sisusb->lock);
return ret;
}
/* interface routine */
static int
sisusbcon_scrolldelta(struct vc_data *c, int lines)
{
struct sisusb_usb_data *sisusb;
int margin = c->vc_size_row * 4;
int ul, we, p, st;
/* The return value does not seem to be used */
if (!(sisusb = sisusb_get_sisusb_lock_and_check(c->vc_num)))
return 0;
/* sisusb->lock is down */
if (sisusb_is_inactive(c, sisusb)) {
mutex_unlock(&sisusb->lock);
return 0;
}
if (!lines) /* Turn scrollback off */
c->vc_visible_origin = c->vc_origin;
else {
if (sisusb->con_rolled_over >
(c->vc_scr_end - sisusb->scrbuf) + margin) {
ul = c->vc_scr_end - sisusb->scrbuf;
we = sisusb->con_rolled_over + c->vc_size_row;
} else {
ul = 0;
we = sisusb->scrbuf_size;
}
p = (c->vc_visible_origin - sisusb->scrbuf - ul + we) % we +
lines * c->vc_size_row;
st = (c->vc_origin - sisusb->scrbuf - ul + we) % we;
if (st < 2 * margin)
margin = 0;
if (p < margin)
p = 0;
if (p > st - margin)
p = st;
c->vc_visible_origin = sisusb->scrbuf + (p + ul) % we;
}
sisusbcon_set_start_address(sisusb, c);
mutex_unlock(&sisusb->lock);
return 1;
}
/* Interface routine */
static void
sisusbcon_cursor(struct vc_data *c, int mode)
{
struct sisusb_usb_data *sisusb;
int from, to, baseline;
if (!(sisusb = sisusb_get_sisusb_lock_and_check(c->vc_num)))
return;
/* sisusb->lock is down */
if (sisusb_is_inactive(c, sisusb)) {
mutex_unlock(&sisusb->lock);
return;
}
if (c->vc_origin != c->vc_visible_origin) {
c->vc_visible_origin = c->vc_origin;
sisusbcon_set_start_address(sisusb, c);
}
if (mode == CM_ERASE) {
sisusb_setidxregor(sisusb, SISCR, 0x0a, 0x20);
sisusb->sisusb_cursor_size_to = -1;
mutex_unlock(&sisusb->lock);
return;
}
sisusb_set_cursor(sisusb, (c->vc_pos - sisusb->scrbuf) / 2);
baseline = c->vc_font.height - (c->vc_font.height < 10 ? 1 : 2);
switch (c->vc_cursor_type & 0x0f) {
case CUR_BLOCK: from = 1;
to = c->vc_font.height;
break;
case CUR_TWO_THIRDS: from = c->vc_font.height / 3;
to = baseline;
break;
case CUR_LOWER_HALF: from = c->vc_font.height / 2;
to = baseline;
break;
case CUR_LOWER_THIRD: from = (c->vc_font.height * 2) / 3;
to = baseline;
break;
case CUR_NONE: from = 31;
to = 30;
break;
default:
case CUR_UNDERLINE: from = baseline - 1;
to = baseline;
break;
}
if (sisusb->sisusb_cursor_size_from != from ||
sisusb->sisusb_cursor_size_to != to) {
sisusb_setidxreg(sisusb, SISCR, 0x0a, from);
sisusb_setidxregandor(sisusb, SISCR, 0x0b, 0xe0, to);
sisusb->sisusb_cursor_size_from = from;
sisusb->sisusb_cursor_size_to = to;
}
mutex_unlock(&sisusb->lock);
}
static int
sisusbcon_scroll_area(struct vc_data *c, struct sisusb_usb_data *sisusb,
int t, int b, int dir, int lines)
{
int cols = sisusb->sisusb_num_columns;
int length = ((b - t) * cols) * 2;
u16 eattr = c->vc_video_erase_char;
ssize_t written;
/* sisusb->lock is down */
/* Scroll an area which does not match the
* visible screen's dimensions. This needs
* to be done separately, as it does not
* use hardware panning.
*/
switch (dir) {
case SM_UP:
sisusbcon_memmovew(SISUSB_VADDR(0, t),
SISUSB_VADDR(0, t + lines),
(b - t - lines) * cols * 2);
sisusbcon_memsetw(SISUSB_VADDR(0, b - lines), eattr,
lines * cols * 2);
break;
case SM_DOWN:
sisusbcon_memmovew(SISUSB_VADDR(0, t + lines),
SISUSB_VADDR(0, t),
(b - t - lines) * cols * 2);
sisusbcon_memsetw(SISUSB_VADDR(0, t), eattr,
lines * cols * 2);
break;
}
sisusb_copy_memory(sisusb, (char *)SISUSB_VADDR(0, t),
(long)SISUSB_HADDR(0, t), length, &written);
mutex_unlock(&sisusb->lock);
return 1;
}
/* Interface routine */
static int
sisusbcon_scroll(struct vc_data *c, int t, int b, int dir, int lines)
{
struct sisusb_usb_data *sisusb;
u16 eattr = c->vc_video_erase_char;
ssize_t written;
int copyall = 0;
unsigned long oldorigin;
unsigned int delta = lines * c->vc_size_row;
u32 originoffset;
/* Returning != 0 means we have done the scrolling successfully.
* Returning 0 makes vt do the scrolling on its own.
* Note that con_scroll is only called if the console is
* visible. In that case, the origin should be our buffer,
* not the vt's private one.
*/
if (!lines)
return 1;
if (!(sisusb = sisusb_get_sisusb_lock_and_check(c->vc_num)))
return 0;
/* sisusb->lock is down */
if (sisusb_is_inactive(c, sisusb)) {
mutex_unlock(&sisusb->lock);
return 0;
}
/* Special case */
if (t || b != c->vc_rows)
return sisusbcon_scroll_area(c, sisusb, t, b, dir, lines);
if (c->vc_origin != c->vc_visible_origin) {
c->vc_visible_origin = c->vc_origin;
sisusbcon_set_start_address(sisusb, c);
}
/* limit amount to maximum realistic size */
if (lines > c->vc_rows)
lines = c->vc_rows;
oldorigin = c->vc_origin;
switch (dir) {
case SM_UP:
if (c->vc_scr_end + delta >=
sisusb->scrbuf + sisusb->scrbuf_size) {
sisusbcon_memcpyw((u16 *)sisusb->scrbuf,
(u16 *)(oldorigin + delta),
c->vc_screenbuf_size - delta);
c->vc_origin = sisusb->scrbuf;
sisusb->con_rolled_over = oldorigin - sisusb->scrbuf;
copyall = 1;
} else
c->vc_origin += delta;
sisusbcon_memsetw(
(u16 *)(c->vc_origin + c->vc_screenbuf_size - delta),
eattr, delta);
break;
case SM_DOWN:
if (oldorigin - delta < sisusb->scrbuf) {
sisusbcon_memmovew((u16 *)(sisusb->scrbuf +
sisusb->scrbuf_size -
c->vc_screenbuf_size +
delta),
(u16 *)oldorigin,
c->vc_screenbuf_size - delta);
c->vc_origin = sisusb->scrbuf +
sisusb->scrbuf_size -
c->vc_screenbuf_size;
sisusb->con_rolled_over = 0;
copyall = 1;
} else
c->vc_origin -= delta;
c->vc_scr_end = c->vc_origin + c->vc_screenbuf_size;
scr_memsetw((u16 *)(c->vc_origin), eattr, delta);
break;
}
originoffset = (u32)(c->vc_origin - sisusb->scrbuf);
if (copyall)
sisusb_copy_memory(sisusb,
(char *)c->vc_origin,
(u32)(sisusb->vrambase + originoffset),
c->vc_screenbuf_size, &written);
else if (dir == SM_UP)
sisusb_copy_memory(sisusb,
(char *)c->vc_origin + c->vc_screenbuf_size - delta,
(u32)sisusb->vrambase + originoffset +
c->vc_screenbuf_size - delta,
delta, &written);
else
sisusb_copy_memory(sisusb,
(char *)c->vc_origin,
(u32)(sisusb->vrambase + originoffset),
delta, &written);
c->vc_scr_end = c->vc_origin + c->vc_screenbuf_size;
c->vc_visible_origin = c->vc_origin;
sisusbcon_set_start_address(sisusb, c);
c->vc_pos = c->vc_pos - oldorigin + c->vc_origin;
mutex_unlock(&sisusb->lock);
return 1;
}
/* Interface routine */
static int
sisusbcon_set_origin(struct vc_data *c)
{
struct sisusb_usb_data *sisusb;
/* Returning != 0 means we were successful.
* Returning 0 will vt make to use its own
* screenbuffer as the origin.
*/
if (!(sisusb = sisusb_get_sisusb_lock_and_check(c->vc_num)))
return 0;
/* sisusb->lock is down */
if (sisusb_is_inactive(c, sisusb) || sisusb->con_blanked) {
mutex_unlock(&sisusb->lock);
return 0;
}
c->vc_origin = c->vc_visible_origin = sisusb->scrbuf;
sisusbcon_set_start_address(sisusb, c);
sisusb->con_rolled_over = 0;
mutex_unlock(&sisusb->lock);
return 1;
}
/* Interface routine */
static int
sisusbcon_resize(struct vc_data *c, unsigned int newcols, unsigned int newrows,
unsigned int user)
{
struct sisusb_usb_data *sisusb;
int fh;
if (!(sisusb = sisusb_get_sisusb_lock_and_check(c->vc_num)))
return -ENODEV;
fh = sisusb->current_font_height;
mutex_unlock(&sisusb->lock);
/* We are quite unflexible as regards resizing. The vt code
* handles sizes where the line length isn't equal the pitch
* quite badly. As regards the rows, our panning tricks only
* work well if the number of rows equals the visible number
* of rows.
*/
if (newcols != 80 || c->vc_scan_lines / fh != newrows)
return -EINVAL;
return 0;
}
int
sisusbcon_do_font_op(struct sisusb_usb_data *sisusb, int set, int slot,
u8 *arg, int cmapsz, int ch512, int dorecalc,
struct vc_data *c, int fh, int uplock)
{
int font_select = 0x00, i, err = 0;
u32 offset = 0;
u8 dummy;
/* sisusb->lock is down */
/*
* The default font is kept in slot 0.
* A user font is loaded in slot 2 (256 ch)
* or 2+3 (512 ch).
*/
if ((slot != 0 && slot != 2) || !fh) {
if (uplock)
mutex_unlock(&sisusb->lock);
return -EINVAL;
}
if (set)
sisusb->font_slot = slot;
/* Default font is always 256 */
if (slot == 0)
ch512 = 0;
else
offset = 4 * cmapsz;
font_select = (slot == 0) ? 0x00 : (ch512 ? 0x0e : 0x0a);
err |= sisusb_setidxreg(sisusb, SISSR, 0x00, 0x01); /* Reset */
err |= sisusb_setidxreg(sisusb, SISSR, 0x02, 0x04); /* Write to plane 2 */
err |= sisusb_setidxreg(sisusb, SISSR, 0x04, 0x07); /* Memory mode a0-bf */
err |= sisusb_setidxreg(sisusb, SISSR, 0x00, 0x03); /* Reset */
if (err)
goto font_op_error;
err |= sisusb_setidxreg(sisusb, SISGR, 0x04, 0x03); /* Select plane read 2 */
err |= sisusb_setidxreg(sisusb, SISGR, 0x05, 0x00); /* Disable odd/even */
err |= sisusb_setidxreg(sisusb, SISGR, 0x06, 0x00); /* Address range a0-bf */
if (err)
goto font_op_error;
if (arg) {
if (set)
for (i = 0; i < cmapsz; i++) {
err |= sisusb_writeb(sisusb,
sisusb->vrambase + offset + i,
arg[i]);
if (err)
break;
}
else
for (i = 0; i < cmapsz; i++) {
err |= sisusb_readb(sisusb,
sisusb->vrambase + offset + i,
&arg[i]);
if (err)
break;
}
/*
* In 512-character mode, the character map is not contiguous if
* we want to remain EGA compatible -- which we do
*/
if (ch512) {
if (set)
for (i = 0; i < cmapsz; i++) {
err |= sisusb_writeb(sisusb,
sisusb->vrambase + offset +
(2 * cmapsz) + i,
arg[cmapsz + i]);
if (err)
break;
}
else
for (i = 0; i < cmapsz; i++) {
err |= sisusb_readb(sisusb,
sisusb->vrambase + offset +
(2 * cmapsz) + i,
&arg[cmapsz + i]);
if (err)
break;
}
}
}
if (err)
goto font_op_error;
err |= sisusb_setidxreg(sisusb, SISSR, 0x00, 0x01); /* Reset */
err |= sisusb_setidxreg(sisusb, SISSR, 0x02, 0x03); /* Write to planes 0+1 */
err |= sisusb_setidxreg(sisusb, SISSR, 0x04, 0x03); /* Memory mode a0-bf */
if (set)
sisusb_setidxreg(sisusb, SISSR, 0x03, font_select);
err |= sisusb_setidxreg(sisusb, SISSR, 0x00, 0x03); /* Reset end */
if (err)
goto font_op_error;
err |= sisusb_setidxreg(sisusb, SISGR, 0x04, 0x00); /* Select plane read 0 */
err |= sisusb_setidxreg(sisusb, SISGR, 0x05, 0x10); /* Enable odd/even */
err |= sisusb_setidxreg(sisusb, SISGR, 0x06, 0x06); /* Address range b8-bf */
if (err)
goto font_op_error;
if ((set) && (ch512 != sisusb->current_font_512)) {
/* Font is shared among all our consoles.
* And so is the hi_font_mask.
*/
for (i = 0; i < MAX_NR_CONSOLES; i++) {
struct vc_data *c = vc_cons[i].d;
if (c && c->vc_sw == &sisusb_con)
c->vc_hi_font_mask = ch512 ? 0x0800 : 0;
}
sisusb->current_font_512 = ch512;
/* color plane enable register:
256-char: enable intensity bit
512-char: disable intensity bit */
sisusb_getreg(sisusb, SISINPSTAT, &dummy);
sisusb_setreg(sisusb, SISAR, 0x12);
sisusb_setreg(sisusb, SISAR, ch512 ? 0x07 : 0x0f);
sisusb_getreg(sisusb, SISINPSTAT, &dummy);
sisusb_setreg(sisusb, SISAR, 0x20);
sisusb_getreg(sisusb, SISINPSTAT, &dummy);
}
if (dorecalc) {
/*
* Adjust the screen to fit a font of a certain height
*/
unsigned char ovr, vde, fsr;
int rows = 0, maxscan = 0;
if (c) {
/* Number of video rows */
rows = c->vc_scan_lines / fh;
/* Scan lines to actually display-1 */
maxscan = rows * fh - 1;
/*printk(KERN_DEBUG "sisusb recalc rows %d maxscan %d fh %d sl %d\n",
rows, maxscan, fh, c->vc_scan_lines);*/
sisusb_getidxreg(sisusb, SISCR, 0x07, &ovr);
vde = maxscan & 0xff;
ovr = (ovr & 0xbd) |
((maxscan & 0x100) >> 7) |
((maxscan & 0x200) >> 3);
sisusb_setidxreg(sisusb, SISCR, 0x07, ovr);
sisusb_setidxreg(sisusb, SISCR, 0x12, vde);
}
sisusb_getidxreg(sisusb, SISCR, 0x09, &fsr);
fsr = (fsr & 0xe0) | (fh - 1);
sisusb_setidxreg(sisusb, SISCR, 0x09, fsr);
sisusb->current_font_height = fh;
sisusb->sisusb_cursor_size_from = -1;
sisusb->sisusb_cursor_size_to = -1;
}
if (uplock)
mutex_unlock(&sisusb->lock);
if (dorecalc && c) {
int i, rows = c->vc_scan_lines / fh;
/* Now adjust our consoles' size */
for (i = 0; i < MAX_NR_CONSOLES; i++) {
struct vc_data *vc = vc_cons[i].d;
if (vc && vc->vc_sw == &sisusb_con) {
if (CON_IS_VISIBLE(vc)) {
vc->vc_sw->con_cursor(vc, CM_DRAW);
}
vc->vc_font.height = fh;
vc_resize(vc, 0, rows);
}
}
}
return 0;
font_op_error:
if (uplock)
mutex_unlock(&sisusb->lock);
return -EIO;
}
/* Interface routine */
static int
sisusbcon_font_set(struct vc_data *c, struct console_font *font,
unsigned flags)
{
struct sisusb_usb_data *sisusb;
unsigned charcount = font->charcount;
if (font->width != 8 || (charcount != 256 && charcount != 512))
return -EINVAL;
if (!(sisusb = sisusb_get_sisusb_lock_and_check(c->vc_num)))
return -ENODEV;
/* sisusb->lock is down */
/* Save the user-provided font into a buffer. This
* is used for restoring text mode after quitting
* from X and for the con_getfont routine.
*/
if (sisusb->font_backup) {
if (sisusb->font_backup_size < charcount) {
vfree(sisusb->font_backup);
sisusb->font_backup = NULL;
}
}
if (!sisusb->font_backup)
sisusb->font_backup = vmalloc(charcount * 32);
if (sisusb->font_backup) {
memcpy(sisusb->font_backup, font->data, charcount * 32);
sisusb->font_backup_size = charcount;
sisusb->font_backup_height = font->height;
sisusb->font_backup_512 = (charcount == 512) ? 1 : 0;
}
/* do_font_op ups sisusb->lock */
return sisusbcon_do_font_op(sisusb, 1, 2, font->data,
8192, (charcount == 512),
(!(flags & KD_FONT_FLAG_DONT_RECALC)) ? 1 : 0,
c, font->height, 1);
}
/* Interface routine */
static int
sisusbcon_font_get(struct vc_data *c, struct console_font *font)
{
struct sisusb_usb_data *sisusb;
if (!(sisusb = sisusb_get_sisusb_lock_and_check(c->vc_num)))
return -ENODEV;
/* sisusb->lock is down */
font->width = 8;
font->height = c->vc_font.height;
font->charcount = 256;
if (!font->data) {
mutex_unlock(&sisusb->lock);
return 0;
}
if (!sisusb->font_backup) {
mutex_unlock(&sisusb->lock);
return -ENODEV;
}
/* Copy 256 chars only, like vgacon */
memcpy(font->data, sisusb->font_backup, 256 * 32);
mutex_unlock(&sisusb->lock);
return 0;
}
/*
* The console `switch' structure for the sisusb console
*/
static const struct consw sisusb_con = {
.owner = THIS_MODULE,
.con_startup = sisusbcon_startup,
.con_init = sisusbcon_init,
.con_deinit = sisusbcon_deinit,
.con_clear = sisusbcon_clear,
.con_putc = sisusbcon_putc,
.con_putcs = sisusbcon_putcs,
.con_cursor = sisusbcon_cursor,
.con_scroll = sisusbcon_scroll,
.con_bmove = sisusbcon_bmove,
.con_switch = sisusbcon_switch,
.con_blank = sisusbcon_blank,
.con_font_set = sisusbcon_font_set,
.con_font_get = sisusbcon_font_get,
.con_set_palette = sisusbcon_set_palette,
.con_scrolldelta = sisusbcon_scrolldelta,
.con_build_attr = sisusbcon_build_attr,
.con_invert_region = sisusbcon_invert_region,
.con_set_origin = sisusbcon_set_origin,
.con_save_screen = sisusbcon_save_screen,
.con_resize = sisusbcon_resize,
};
/* Our very own dummy console driver */
static const char *sisusbdummycon_startup(void)
{
return "SISUSBVGADUMMY";
}
static void sisusbdummycon_init(struct vc_data *vc, int init)
{
vc->vc_can_do_color = 1;
if (init) {
vc->vc_cols = 80;
vc->vc_rows = 25;
} else
vc_resize(vc, 80, 25);
}
static int sisusbdummycon_dummy(void)
{
return 0;
}
#define SISUSBCONDUMMY (void *)sisusbdummycon_dummy
static const struct consw sisusb_dummy_con = {
.owner = THIS_MODULE,
.con_startup = sisusbdummycon_startup,
.con_init = sisusbdummycon_init,
.con_deinit = SISUSBCONDUMMY,
.con_clear = SISUSBCONDUMMY,
.con_putc = SISUSBCONDUMMY,
.con_putcs = SISUSBCONDUMMY,
.con_cursor = SISUSBCONDUMMY,
.con_scroll = SISUSBCONDUMMY,
.con_bmove = SISUSBCONDUMMY,
.con_switch = SISUSBCONDUMMY,
.con_blank = SISUSBCONDUMMY,
.con_font_set = SISUSBCONDUMMY,
.con_font_get = SISUSBCONDUMMY,
.con_font_default = SISUSBCONDUMMY,
.con_font_copy = SISUSBCONDUMMY,
.con_set_palette = SISUSBCONDUMMY,
.con_scrolldelta = SISUSBCONDUMMY,
};
int
sisusb_console_init(struct sisusb_usb_data *sisusb, int first, int last)
{
int i, ret;
mutex_lock(&sisusb->lock);
/* Erm.. that should not happen */
if (sisusb->haveconsole || !sisusb->SiS_Pr) {
mutex_unlock(&sisusb->lock);
return 1;
}
sisusb->con_first = first;
sisusb->con_last = last;
if (first > last ||
first > MAX_NR_CONSOLES ||
last > MAX_NR_CONSOLES) {
mutex_unlock(&sisusb->lock);
return 1;
}
/* If gfxcore not initialized or no consoles given, quit graciously */
if (!sisusb->gfxinit || first < 1 || last < 1) {
mutex_unlock(&sisusb->lock);
return 0;
}
sisusb->sisusb_cursor_loc = -1;
sisusb->sisusb_cursor_size_from = -1;
sisusb->sisusb_cursor_size_to = -1;
/* Set up text mode (and upload default font) */
if (sisusb_reset_text_mode(sisusb, 1)) {
mutex_unlock(&sisusb->lock);
dev_err(&sisusb->sisusb_dev->dev, "Failed to set up text mode\n");
return 1;
}
/* Initialize some gfx registers */
sisusb_initialize(sisusb);
for (i = first - 1; i <= last - 1; i++) {
/* Save sisusb for our interface routines */
mysisusbs[i] = sisusb;
}
/* Initial console setup */
sisusb->sisusb_num_columns = 80;
/* Use a 32K buffer (matches b8000-bffff area) */
sisusb->scrbuf_size = 32 * 1024;
/* Allocate screen buffer */
if (!(sisusb->scrbuf = (unsigned long)vmalloc(sisusb->scrbuf_size))) {
mutex_unlock(&sisusb->lock);
dev_err(&sisusb->sisusb_dev->dev, "Failed to allocate screen buffer\n");
return 1;
}
mutex_unlock(&sisusb->lock);
/* Now grab the desired console(s) */
ret = take_over_console(&sisusb_con, first - 1, last - 1, 0);
if (!ret)
sisusb->haveconsole = 1;
else {
for (i = first - 1; i <= last - 1; i++)
mysisusbs[i] = NULL;
}
return ret;
}
void
sisusb_console_exit(struct sisusb_usb_data *sisusb)
{
int i;
/* This is called if the device is disconnected
* and while disconnect and lock semaphores
* are up. This should be save because we
* can't lose our sisusb any other way but by
* disconnection (and hence, the disconnect
* sema is for protecting all other access
* functions from disconnection, not the
* other way round).
*/
/* Now what do we do in case of disconnection:
* One alternative would be to simply call
* give_up_console(). Nah, not a good idea.
* give_up_console() is obviously buggy as it
* only discards the consw pointer from the
* driver_map, but doesn't adapt vc->vc_sw
* of the affected consoles. Hence, the next
* call to any of the console functions will
* eventually take a trip to oops county.
* Also, give_up_console for some reason
* doesn't decrement our module refcount.
* Instead, we switch our consoles to a private
* dummy console. This, of course, keeps our
* refcount up as well, but it works perfectly.
*/
if (sisusb->haveconsole) {
for (i = 0; i < MAX_NR_CONSOLES; i++)
if (sisusb->havethisconsole[i])
take_over_console(&sisusb_dummy_con, i, i, 0);
/* At this point, con_deinit for all our
* consoles is executed by take_over_console().
*/
sisusb->haveconsole = 0;
}
vfree((void *)sisusb->scrbuf);
sisusb->scrbuf = 0;
vfree(sisusb->font_backup);
sisusb->font_backup = NULL;
}
void __init sisusb_init_concode(void)
{
int i;
for (i = 0; i < MAX_NR_CONSOLES; i++)
mysisusbs[i] = NULL;
}
#endif /* INCL_CON */