// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
/*
* 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/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"
/* 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)
{
memset16(s, c, count / 2);
}
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;
sisusb = sisusb_get_sisusb(console);
if (!sisusb)
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 do_take_over_console(),
* ie by us/under our control. It is
* only called after text mode and fonts
* are set up/restored.
*/
sisusb = sisusb_get_sisusb(c->vc_num);
if (!sisusb)
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 do_take_over_console()
* and others, ie not under our control.
*/
sisusb = sisusb_get_sisusb(c->vc_num);
if (!sisusb)
return;
mutex_lock(&sisusb->lock);
/* Clear ourselves in mysisusbs */
|