forked from luck/tmp_suning_uos_patched
d4ead16f50
This patch (as908) adds central protection in usbcore for the prototypical race between opening and unregistering a char device. The spinlock used to protect the minor-numbers array is replaced with an rwsem, which can remain locked across a call to a driver's open() method. This guarantees that open() and deregister() will be mutually exclusive. The private locks currently used in several individual drivers for this purpose are no longer necessary, and the patch removes them. The following USB drivers are affected: usblcd, idmouse, auerswald, legousbtower, sisusbvga/sisusb, ldusb, adutux, iowarrior, and usb-skeleton. As a side effect of this change, usb_deregister_dev() must not be called while holding a lock that is acquired by open(). Unfortunately a number of drivers do this, but luckily the solution is simple: call usb_deregister_dev() before acquiring the lock. In addition to these changes (and their consequent code simplifications), the patch fixes a use-after-free bug in adutux and a race between open() and release() in iowarrior. Signed-off-by: Alan Stern <stern@rowland.harvard.edu> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
1615 lines
37 KiB
C
1615 lines
37 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/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 */
|
|
|
|
/* Don't need to put the character into buffer ourselves,
|
|
* because the vt does this BEFORE calling us.
|
|
*/
|
|
#if 0
|
|
sisusbcon_writew(ch, SISUSB_VADDR(x, y));
|
|
#endif
|
|
|
|
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 0
|
|
u16 *src, *dest;
|
|
int i;
|
|
#endif
|
|
|
|
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;
|
|
|
|
/* Don't need to move data outselves, because
|
|
* vt does this BEFORE calling us.
|
|
* This is only used by vt's insert/deletechar.
|
|
*/
|
|
#if 0
|
|
if (sx == 0 && dx == 0 && width >= c->vc_cols && width <= cols) {
|
|
|
|
sisusbcon_memmovew(SISUSB_VADDR(0, dy), SISUSB_VADDR(0, sy),
|
|
height * width * 2);
|
|
|
|
} else if (dy < sy || (dy == sy && dx < sx)) {
|
|
|
|
src = SISUSB_VADDR(sx, sy);
|
|
dest = SISUSB_VADDR(dx, dy);
|
|
|
|
for (i = height; i > 0; i--) {
|
|
sisusbcon_memmovew(dest, src, width * 2);
|
|
src += cols;
|
|
dest += cols;
|
|
}
|
|
|
|
} else {
|
|
|
|
src = SISUSB_VADDR(sx, sy + height - 1);
|
|
dest = SISUSB_VADDR(dx, dy + height - 1);
|
|
|
|
for (i = height; i > 0; i--) {
|
|
sisusbcon_memmovew(dest, src, width * 2);
|
|
src -= cols;
|
|
dest -= cols;
|
|
}
|
|
|
|
}
|
|
#endif
|
|
|
|
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);
|
|
printk(KERN_DEBUG "sisusb: 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)
|
|
{
|
|
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, minor = sisusb->minor;
|
|
|
|
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);
|
|
printk(KERN_ERR
|
|
"sisusbvga[%d]: Failed to set up text mode\n",
|
|
minor);
|
|
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);
|
|
printk(KERN_ERR
|
|
"sisusbvga[%d]: Failed to allocate screen buffer\n",
|
|
minor);
|
|
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 */
|
|
|
|
|
|
|