mirror of
https://github.com/AetherDroid/android_kernel_samsung_on5xelte.git
synced 2025-09-08 17:18:05 -04:00
Fixed MTP to work with TWRP
This commit is contained in:
commit
f6dfaef42e
50820 changed files with 20846062 additions and 0 deletions
124
drivers/tty/hvc/Kconfig
Normal file
124
drivers/tty/hvc/Kconfig
Normal file
|
@ -0,0 +1,124 @@
|
|||
if TTY
|
||||
|
||||
config HVC_DRIVER
|
||||
bool
|
||||
help
|
||||
Generic "hypervisor virtual console" infrastructure for various
|
||||
hypervisors (pSeries, iSeries, Xen, lguest).
|
||||
It will automatically be selected if one of the back-end console drivers
|
||||
is selected.
|
||||
|
||||
config HVC_IRQ
|
||||
bool
|
||||
|
||||
config HVC_CONSOLE
|
||||
bool "pSeries Hypervisor Virtual Console support"
|
||||
depends on PPC_PSERIES
|
||||
select HVC_DRIVER
|
||||
select HVC_IRQ
|
||||
help
|
||||
pSeries machines when partitioned support a hypervisor virtual
|
||||
console. This driver allows each pSeries partition to have a console
|
||||
which is accessed via the HMC.
|
||||
|
||||
config HVC_OLD_HVSI
|
||||
bool "Old driver for pSeries serial port (/dev/hvsi*)"
|
||||
depends on HVC_CONSOLE
|
||||
default n
|
||||
|
||||
config HVC_OPAL
|
||||
bool "OPAL Console support"
|
||||
depends on PPC_POWERNV
|
||||
select HVC_DRIVER
|
||||
select HVC_IRQ
|
||||
default y
|
||||
help
|
||||
PowerNV machines running under OPAL need that driver to get a console
|
||||
|
||||
config HVC_RTAS
|
||||
bool "IBM RTAS Console support"
|
||||
depends on PPC_RTAS
|
||||
select HVC_DRIVER
|
||||
help
|
||||
IBM Console device driver which makes use of RTAS
|
||||
|
||||
config HVC_BEAT
|
||||
bool "Toshiba's Beat Hypervisor Console support"
|
||||
depends on PPC_CELLEB
|
||||
select HVC_DRIVER
|
||||
help
|
||||
Toshiba's Cell Reference Set Beat Console device driver
|
||||
|
||||
config HVC_IUCV
|
||||
bool "z/VM IUCV Hypervisor console support (VM only)"
|
||||
depends on S390
|
||||
select HVC_DRIVER
|
||||
select IUCV
|
||||
default y
|
||||
help
|
||||
This driver provides a Hypervisor console (HVC) back-end to access
|
||||
a Linux (console) terminal via a z/VM IUCV communication path.
|
||||
|
||||
config HVC_XEN
|
||||
bool "Xen Hypervisor Console support"
|
||||
depends on XEN
|
||||
select HVC_DRIVER
|
||||
select HVC_IRQ
|
||||
default y
|
||||
help
|
||||
Xen virtual console device driver
|
||||
|
||||
config HVC_XEN_FRONTEND
|
||||
bool "Xen Hypervisor Multiple Consoles support"
|
||||
depends on HVC_XEN
|
||||
select XEN_XENBUS_FRONTEND
|
||||
default y
|
||||
help
|
||||
Xen driver for secondary virtual consoles
|
||||
|
||||
config HVC_UDBG
|
||||
bool "udbg based fake hypervisor console"
|
||||
depends on PPC
|
||||
select HVC_DRIVER
|
||||
default n
|
||||
help
|
||||
This is meant to be used during HW bring up or debugging when
|
||||
no other console mechanism exist but udbg, to get you a quick
|
||||
console for userspace. Do NOT enable in production kernels.
|
||||
|
||||
config HVC_DCC
|
||||
bool "ARM JTAG DCC console"
|
||||
depends on ARM
|
||||
select HVC_DRIVER
|
||||
help
|
||||
This console uses the JTAG DCC on ARM to create a console under the HVC
|
||||
driver. This console is used through a JTAG only on ARM. If you don't have
|
||||
a JTAG then you probably don't want this option.
|
||||
|
||||
config HVC_BFIN_JTAG
|
||||
bool "Blackfin JTAG console"
|
||||
depends on BLACKFIN
|
||||
select HVC_DRIVER
|
||||
help
|
||||
This console uses the Blackfin JTAG to create a console under the
|
||||
the HVC driver. If you don't have JTAG, then you probably don't
|
||||
want this option.
|
||||
|
||||
config HVCS
|
||||
tristate "IBM Hypervisor Virtual Console Server support"
|
||||
depends on PPC_PSERIES && HVC_CONSOLE
|
||||
help
|
||||
Partitionable IBM Power5 ppc64 machines allow hosting of
|
||||
firmware virtual consoles from one Linux partition by
|
||||
another Linux partition. This driver allows console data
|
||||
from Linux partitions to be accessed through TTY device
|
||||
interfaces in the device tree of a Linux partition running
|
||||
this driver.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called hvcs. Additionally, this module
|
||||
will depend on arch specific APIs exported from hvcserver.ko
|
||||
which will also be compiled when this driver is built as a
|
||||
module.
|
||||
|
||||
endif # TTY
|
14
drivers/tty/hvc/Makefile
Normal file
14
drivers/tty/hvc/Makefile
Normal file
|
@ -0,0 +1,14 @@
|
|||
obj-$(CONFIG_HVC_CONSOLE) += hvc_vio.o hvsi_lib.o
|
||||
obj-$(CONFIG_HVC_OPAL) += hvc_opal.o hvsi_lib.o
|
||||
obj-$(CONFIG_HVC_OLD_HVSI) += hvsi.o
|
||||
obj-$(CONFIG_HVC_RTAS) += hvc_rtas.o
|
||||
obj-$(CONFIG_HVC_TILE) += hvc_tile.o
|
||||
obj-$(CONFIG_HVC_DCC) += hvc_dcc.o
|
||||
obj-$(CONFIG_HVC_BEAT) += hvc_beat.o
|
||||
obj-$(CONFIG_HVC_DRIVER) += hvc_console.o
|
||||
obj-$(CONFIG_HVC_IRQ) += hvc_irq.o
|
||||
obj-$(CONFIG_HVC_XEN) += hvc_xen.o
|
||||
obj-$(CONFIG_HVC_IUCV) += hvc_iucv.o
|
||||
obj-$(CONFIG_HVC_UDBG) += hvc_udbg.o
|
||||
obj-$(CONFIG_HVC_BFIN_JTAG) += hvc_bfin_jtag.o
|
||||
obj-$(CONFIG_HVCS) += hvcs.o
|
134
drivers/tty/hvc/hvc_beat.c
Normal file
134
drivers/tty/hvc/hvc_beat.c
Normal file
|
@ -0,0 +1,134 @@
|
|||
/*
|
||||
* Beat hypervisor console driver
|
||||
*
|
||||
* (C) Copyright 2006 TOSHIBA CORPORATION
|
||||
*
|
||||
* This code is based on drivers/char/hvc_rtas.c:
|
||||
* (C) Copyright IBM Corporation 2001-2005
|
||||
* (C) Copyright Red Hat, Inc. 2005
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/console.h>
|
||||
#include <asm/prom.h>
|
||||
#include <asm/hvconsole.h>
|
||||
#include <asm/firmware.h>
|
||||
|
||||
#include "hvc_console.h"
|
||||
|
||||
extern int64_t beat_get_term_char(uint64_t, uint64_t *, uint64_t *, uint64_t *);
|
||||
extern int64_t beat_put_term_char(uint64_t, uint64_t, uint64_t, uint64_t);
|
||||
|
||||
struct hvc_struct *hvc_beat_dev = NULL;
|
||||
|
||||
/* bug: only one queue is available regardless of vtermno */
|
||||
static int hvc_beat_get_chars(uint32_t vtermno, char *buf, int cnt)
|
||||
{
|
||||
static unsigned char q[sizeof(unsigned long) * 2]
|
||||
__attribute__((aligned(sizeof(unsigned long))));
|
||||
static int qlen = 0;
|
||||
u64 got;
|
||||
|
||||
again:
|
||||
if (qlen) {
|
||||
if (qlen > cnt) {
|
||||
memcpy(buf, q, cnt);
|
||||
qlen -= cnt;
|
||||
memmove(q + cnt, q, qlen);
|
||||
return cnt;
|
||||
} else { /* qlen <= cnt */
|
||||
int r;
|
||||
|
||||
memcpy(buf, q, qlen);
|
||||
r = qlen;
|
||||
qlen = 0;
|
||||
return r;
|
||||
}
|
||||
}
|
||||
if (beat_get_term_char(vtermno, &got,
|
||||
((u64 *)q), ((u64 *)q) + 1) == 0) {
|
||||
qlen = got;
|
||||
goto again;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int hvc_beat_put_chars(uint32_t vtermno, const char *buf, int cnt)
|
||||
{
|
||||
unsigned long kb[2];
|
||||
int rest, nlen;
|
||||
|
||||
for (rest = cnt; rest > 0; rest -= nlen) {
|
||||
nlen = (rest > 16) ? 16 : rest;
|
||||
memcpy(kb, buf, nlen);
|
||||
beat_put_term_char(vtermno, nlen, kb[0], kb[1]);
|
||||
buf += nlen;
|
||||
}
|
||||
return cnt;
|
||||
}
|
||||
|
||||
static const struct hv_ops hvc_beat_get_put_ops = {
|
||||
.get_chars = hvc_beat_get_chars,
|
||||
.put_chars = hvc_beat_put_chars,
|
||||
};
|
||||
|
||||
static int hvc_beat_useit = 1;
|
||||
|
||||
static int hvc_beat_config(char *p)
|
||||
{
|
||||
hvc_beat_useit = simple_strtoul(p, NULL, 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __init hvc_beat_console_init(void)
|
||||
{
|
||||
if (hvc_beat_useit && of_machine_is_compatible("Beat")) {
|
||||
hvc_instantiate(0, 0, &hvc_beat_get_put_ops);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* temp */
|
||||
static int __init hvc_beat_init(void)
|
||||
{
|
||||
struct hvc_struct *hp;
|
||||
|
||||
if (!firmware_has_feature(FW_FEATURE_BEAT))
|
||||
return -ENODEV;
|
||||
|
||||
hp = hvc_alloc(0, 0, &hvc_beat_get_put_ops, 16);
|
||||
if (IS_ERR(hp))
|
||||
return PTR_ERR(hp);
|
||||
hvc_beat_dev = hp;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void __exit hvc_beat_exit(void)
|
||||
{
|
||||
if (hvc_beat_dev)
|
||||
hvc_remove(hvc_beat_dev);
|
||||
}
|
||||
|
||||
module_init(hvc_beat_init);
|
||||
module_exit(hvc_beat_exit);
|
||||
|
||||
__setup("hvc_beat=", hvc_beat_config);
|
||||
|
||||
console_initcall(hvc_beat_console_init);
|
105
drivers/tty/hvc/hvc_bfin_jtag.c
Normal file
105
drivers/tty/hvc/hvc_bfin_jtag.c
Normal file
|
@ -0,0 +1,105 @@
|
|||
/*
|
||||
* Console via Blackfin JTAG Communication
|
||||
*
|
||||
* Copyright 2008-2011 Analog Devices Inc.
|
||||
*
|
||||
* Enter bugs at http://blackfin.uclinux.org/
|
||||
*
|
||||
* Licensed under the GPL-2 or later.
|
||||
*/
|
||||
|
||||
#include <linux/console.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/moduleparam.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
#include "hvc_console.h"
|
||||
|
||||
/* See the Debug/Emulation chapter in the HRM */
|
||||
#define EMUDOF 0x00000001 /* EMUDAT_OUT full & valid */
|
||||
#define EMUDIF 0x00000002 /* EMUDAT_IN full & valid */
|
||||
#define EMUDOOVF 0x00000004 /* EMUDAT_OUT overflow */
|
||||
#define EMUDIOVF 0x00000008 /* EMUDAT_IN overflow */
|
||||
|
||||
/* Helper functions to glue the register API to simple C operations */
|
||||
static inline uint32_t bfin_write_emudat(uint32_t emudat)
|
||||
{
|
||||
__asm__ __volatile__("emudat = %0;" : : "d"(emudat));
|
||||
return emudat;
|
||||
}
|
||||
|
||||
static inline uint32_t bfin_read_emudat(void)
|
||||
{
|
||||
uint32_t emudat;
|
||||
__asm__ __volatile__("%0 = emudat;" : "=d"(emudat));
|
||||
return emudat;
|
||||
}
|
||||
|
||||
/* Send data to the host */
|
||||
static int hvc_bfin_put_chars(uint32_t vt, const char *buf, int count)
|
||||
{
|
||||
static uint32_t outbound_len;
|
||||
uint32_t emudat;
|
||||
int ret;
|
||||
|
||||
if (bfin_read_DBGSTAT() & EMUDOF)
|
||||
return 0;
|
||||
|
||||
if (!outbound_len) {
|
||||
outbound_len = count;
|
||||
bfin_write_emudat(outbound_len);
|
||||
return 0;
|
||||
}
|
||||
|
||||
ret = min(outbound_len, (uint32_t)4);
|
||||
memcpy(&emudat, buf, ret);
|
||||
bfin_write_emudat(emudat);
|
||||
outbound_len -= ret;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Receive data from the host */
|
||||
static int hvc_bfin_get_chars(uint32_t vt, char *buf, int count)
|
||||
{
|
||||
static uint32_t inbound_len;
|
||||
uint32_t emudat;
|
||||
int ret;
|
||||
|
||||
if (!(bfin_read_DBGSTAT() & EMUDIF))
|
||||
return 0;
|
||||
emudat = bfin_read_emudat();
|
||||
|
||||
if (!inbound_len) {
|
||||
inbound_len = emudat;
|
||||
return 0;
|
||||
}
|
||||
|
||||
ret = min(inbound_len, (uint32_t)4);
|
||||
memcpy(buf, &emudat, ret);
|
||||
inbound_len -= ret;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Glue the HVC layers to the Blackfin layers */
|
||||
static const struct hv_ops hvc_bfin_get_put_ops = {
|
||||
.get_chars = hvc_bfin_get_chars,
|
||||
.put_chars = hvc_bfin_put_chars,
|
||||
};
|
||||
|
||||
static int __init hvc_bfin_console_init(void)
|
||||
{
|
||||
hvc_instantiate(0, 0, &hvc_bfin_get_put_ops);
|
||||
return 0;
|
||||
}
|
||||
console_initcall(hvc_bfin_console_init);
|
||||
|
||||
static int __init hvc_bfin_init(void)
|
||||
{
|
||||
hvc_alloc(0, 0, &hvc_bfin_get_put_ops, 128);
|
||||
return 0;
|
||||
}
|
||||
device_initcall(hvc_bfin_init);
|
1022
drivers/tty/hvc/hvc_console.c
Normal file
1022
drivers/tty/hvc/hvc_console.c
Normal file
File diff suppressed because it is too large
Load diff
124
drivers/tty/hvc/hvc_console.h
Normal file
124
drivers/tty/hvc/hvc_console.h
Normal file
|
@ -0,0 +1,124 @@
|
|||
/*
|
||||
* hvc_console.h
|
||||
* Copyright (C) 2005 IBM Corporation
|
||||
*
|
||||
* Author(s):
|
||||
* Ryan S. Arnold <rsa@us.ibm.com>
|
||||
*
|
||||
* hvc_console header information:
|
||||
* moved here from arch/powerpc/include/asm/hvconsole.h
|
||||
* and drivers/char/hvc_console.c
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#ifndef HVC_CONSOLE_H
|
||||
#define HVC_CONSOLE_H
|
||||
#include <linux/kref.h>
|
||||
#include <linux/tty.h>
|
||||
#include <linux/spinlock.h>
|
||||
|
||||
/*
|
||||
* This is the max number of console adapters that can/will be found as
|
||||
* console devices on first stage console init. Any number beyond this range
|
||||
* can't be used as a console device but is still a valid tty device.
|
||||
*/
|
||||
#define MAX_NR_HVC_CONSOLES 16
|
||||
|
||||
/*
|
||||
* The Linux TTY code does not support dynamic addition of tty derived devices
|
||||
* so we need to know how many tty devices we might need when space is allocated
|
||||
* for the tty device. Since this driver supports hotplug of vty adapters we
|
||||
* need to make sure we have enough allocated.
|
||||
*/
|
||||
#define HVC_ALLOC_TTY_ADAPTERS 8
|
||||
|
||||
struct hvc_struct {
|
||||
struct tty_port port;
|
||||
spinlock_t lock;
|
||||
int index;
|
||||
int do_wakeup;
|
||||
char *outbuf;
|
||||
int outbuf_size;
|
||||
int n_outbuf;
|
||||
uint32_t vtermno;
|
||||
const struct hv_ops *ops;
|
||||
int irq_requested;
|
||||
int data;
|
||||
struct winsize ws;
|
||||
struct work_struct tty_resize;
|
||||
struct list_head next;
|
||||
};
|
||||
|
||||
/* implemented by a low level driver */
|
||||
struct hv_ops {
|
||||
int (*get_chars)(uint32_t vtermno, char *buf, int count);
|
||||
int (*put_chars)(uint32_t vtermno, const char *buf, int count);
|
||||
|
||||
/* Callbacks for notification. Called in open, close and hangup */
|
||||
int (*notifier_add)(struct hvc_struct *hp, int irq);
|
||||
void (*notifier_del)(struct hvc_struct *hp, int irq);
|
||||
void (*notifier_hangup)(struct hvc_struct *hp, int irq);
|
||||
|
||||
/* tiocmget/set implementation */
|
||||
int (*tiocmget)(struct hvc_struct *hp);
|
||||
int (*tiocmset)(struct hvc_struct *hp, unsigned int set, unsigned int clear);
|
||||
|
||||
/* Callbacks to handle tty ports */
|
||||
void (*dtr_rts)(struct hvc_struct *hp, int raise);
|
||||
};
|
||||
|
||||
/* Register a vterm and a slot index for use as a console (console_init) */
|
||||
extern int hvc_instantiate(uint32_t vtermno, int index,
|
||||
const struct hv_ops *ops);
|
||||
|
||||
/* register a vterm for hvc tty operation (module_init or hotplug add) */
|
||||
extern struct hvc_struct * hvc_alloc(uint32_t vtermno, int data,
|
||||
const struct hv_ops *ops, int outbuf_size);
|
||||
/* remove a vterm from hvc tty operation (module_exit or hotplug remove) */
|
||||
extern int hvc_remove(struct hvc_struct *hp);
|
||||
|
||||
/* data available */
|
||||
int hvc_poll(struct hvc_struct *hp);
|
||||
void hvc_kick(void);
|
||||
|
||||
/* Resize hvc tty terminal window */
|
||||
extern void __hvc_resize(struct hvc_struct *hp, struct winsize ws);
|
||||
|
||||
static inline void hvc_resize(struct hvc_struct *hp, struct winsize ws)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&hp->lock, flags);
|
||||
__hvc_resize(hp, ws);
|
||||
spin_unlock_irqrestore(&hp->lock, flags);
|
||||
}
|
||||
|
||||
/* default notifier for irq based notification */
|
||||
extern int notifier_add_irq(struct hvc_struct *hp, int data);
|
||||
extern void notifier_del_irq(struct hvc_struct *hp, int data);
|
||||
extern void notifier_hangup_irq(struct hvc_struct *hp, int data);
|
||||
|
||||
|
||||
#if defined(CONFIG_XMON) && defined(CONFIG_SMP)
|
||||
#include <asm/xmon.h>
|
||||
#else
|
||||
static inline int cpus_are_in_xmon(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // HVC_CONSOLE_H
|
89
drivers/tty/hvc/hvc_dcc.c
Normal file
89
drivers/tty/hvc/hvc_dcc.c
Normal file
|
@ -0,0 +1,89 @@
|
|||
/* Copyright (c) 2010, 2014 The Linux Foundation. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 and
|
||||
* only version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#include <linux/init.h>
|
||||
|
||||
#include <asm/dcc.h>
|
||||
#include <asm/processor.h>
|
||||
|
||||
#include "hvc_console.h"
|
||||
|
||||
/* DCC Status Bits */
|
||||
#define DCC_STATUS_RX (1 << 30)
|
||||
#define DCC_STATUS_TX (1 << 29)
|
||||
|
||||
static int hvc_dcc_put_chars(uint32_t vt, const char *buf, int count)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < count; i++) {
|
||||
while (__dcc_getstatus() & DCC_STATUS_TX)
|
||||
cpu_relax();
|
||||
|
||||
__dcc_putchar(buf[i]);
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static int hvc_dcc_get_chars(uint32_t vt, char *buf, int count)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < count; ++i)
|
||||
if (__dcc_getstatus() & DCC_STATUS_RX)
|
||||
buf[i] = __dcc_getchar();
|
||||
else
|
||||
break;
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
static bool hvc_dcc_check(void)
|
||||
{
|
||||
unsigned long time = jiffies + (HZ / 10);
|
||||
|
||||
/* Write a test character to check if it is handled */
|
||||
__dcc_putchar('\n');
|
||||
|
||||
while (time_is_after_jiffies(time)) {
|
||||
if (!(__dcc_getstatus() & DCC_STATUS_TX))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static const struct hv_ops hvc_dcc_get_put_ops = {
|
||||
.get_chars = hvc_dcc_get_chars,
|
||||
.put_chars = hvc_dcc_put_chars,
|
||||
};
|
||||
|
||||
static int __init hvc_dcc_console_init(void)
|
||||
{
|
||||
if (!hvc_dcc_check())
|
||||
return -ENODEV;
|
||||
|
||||
hvc_instantiate(0, 0, &hvc_dcc_get_put_ops);
|
||||
return 0;
|
||||
}
|
||||
console_initcall(hvc_dcc_console_init);
|
||||
|
||||
static int __init hvc_dcc_init(void)
|
||||
{
|
||||
if (!hvc_dcc_check())
|
||||
return -ENODEV;
|
||||
|
||||
hvc_alloc(0, 0, &hvc_dcc_get_put_ops, 128);
|
||||
return 0;
|
||||
}
|
||||
device_initcall(hvc_dcc_init);
|
49
drivers/tty/hvc/hvc_irq.c
Normal file
49
drivers/tty/hvc/hvc_irq.c
Normal file
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* Copyright IBM Corp. 2001,2008
|
||||
*
|
||||
* This file contains the IRQ specific code for hvc_console
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/interrupt.h>
|
||||
|
||||
#include "hvc_console.h"
|
||||
|
||||
static irqreturn_t hvc_handle_interrupt(int irq, void *dev_instance)
|
||||
{
|
||||
/* if hvc_poll request a repoll, then kick the hvcd thread */
|
||||
if (hvc_poll(dev_instance))
|
||||
hvc_kick();
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
/*
|
||||
* For IRQ based systems these callbacks can be used
|
||||
*/
|
||||
int notifier_add_irq(struct hvc_struct *hp, int irq)
|
||||
{
|
||||
int rc;
|
||||
|
||||
if (!irq) {
|
||||
hp->irq_requested = 0;
|
||||
return 0;
|
||||
}
|
||||
rc = request_irq(irq, hvc_handle_interrupt, 0,
|
||||
"hvc_console", hp);
|
||||
if (!rc)
|
||||
hp->irq_requested = 1;
|
||||
return rc;
|
||||
}
|
||||
|
||||
void notifier_del_irq(struct hvc_struct *hp, int irq)
|
||||
{
|
||||
if (!hp->irq_requested)
|
||||
return;
|
||||
free_irq(irq, hp);
|
||||
hp->irq_requested = 0;
|
||||
}
|
||||
|
||||
void notifier_hangup_irq(struct hvc_struct *hp, int irq)
|
||||
{
|
||||
notifier_del_irq(hp, irq);
|
||||
}
|
1461
drivers/tty/hvc/hvc_iucv.c
Normal file
1461
drivers/tty/hvc/hvc_iucv.c
Normal file
File diff suppressed because it is too large
Load diff
430
drivers/tty/hvc/hvc_opal.c
Normal file
430
drivers/tty/hvc/hvc_opal.c
Normal file
|
@ -0,0 +1,430 @@
|
|||
/*
|
||||
* opal driver interface to hvc_console.c
|
||||
*
|
||||
* Copyright 2011 Benjamin Herrenschmidt <benh@kernel.crashing.org>, IBM Corp.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#undef DEBUG
|
||||
|
||||
#include <linux/types.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/console.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/export.h>
|
||||
|
||||
#include <asm/hvconsole.h>
|
||||
#include <asm/prom.h>
|
||||
#include <asm/firmware.h>
|
||||
#include <asm/hvsi.h>
|
||||
#include <asm/udbg.h>
|
||||
#include <asm/opal.h>
|
||||
|
||||
#include "hvc_console.h"
|
||||
|
||||
static const char hvc_opal_name[] = "hvc_opal";
|
||||
|
||||
static struct of_device_id hvc_opal_match[] = {
|
||||
{ .name = "serial", .compatible = "ibm,opal-console-raw" },
|
||||
{ .name = "serial", .compatible = "ibm,opal-console-hvsi" },
|
||||
{ },
|
||||
};
|
||||
|
||||
typedef enum hv_protocol {
|
||||
HV_PROTOCOL_RAW,
|
||||
HV_PROTOCOL_HVSI
|
||||
} hv_protocol_t;
|
||||
|
||||
struct hvc_opal_priv {
|
||||
hv_protocol_t proto; /* Raw data or HVSI packets */
|
||||
struct hvsi_priv hvsi; /* HVSI specific data */
|
||||
};
|
||||
static struct hvc_opal_priv *hvc_opal_privs[MAX_NR_HVC_CONSOLES];
|
||||
|
||||
/* For early boot console */
|
||||
static struct hvc_opal_priv hvc_opal_boot_priv;
|
||||
static u32 hvc_opal_boot_termno;
|
||||
static bool hvc_opal_event_registered;
|
||||
|
||||
static const struct hv_ops hvc_opal_raw_ops = {
|
||||
.get_chars = opal_get_chars,
|
||||
.put_chars = opal_put_chars,
|
||||
.notifier_add = notifier_add_irq,
|
||||
.notifier_del = notifier_del_irq,
|
||||
.notifier_hangup = notifier_hangup_irq,
|
||||
};
|
||||
|
||||
static int hvc_opal_hvsi_get_chars(uint32_t vtermno, char *buf, int count)
|
||||
{
|
||||
struct hvc_opal_priv *pv = hvc_opal_privs[vtermno];
|
||||
|
||||
if (WARN_ON(!pv))
|
||||
return -ENODEV;
|
||||
|
||||
return hvsilib_get_chars(&pv->hvsi, buf, count);
|
||||
}
|
||||
|
||||
static int hvc_opal_hvsi_put_chars(uint32_t vtermno, const char *buf, int count)
|
||||
{
|
||||
struct hvc_opal_priv *pv = hvc_opal_privs[vtermno];
|
||||
|
||||
if (WARN_ON(!pv))
|
||||
return -ENODEV;
|
||||
|
||||
return hvsilib_put_chars(&pv->hvsi, buf, count);
|
||||
}
|
||||
|
||||
static int hvc_opal_hvsi_open(struct hvc_struct *hp, int data)
|
||||
{
|
||||
struct hvc_opal_priv *pv = hvc_opal_privs[hp->vtermno];
|
||||
int rc;
|
||||
|
||||
pr_devel("HVSI@%x: do open !\n", hp->vtermno);
|
||||
|
||||
rc = notifier_add_irq(hp, data);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
return hvsilib_open(&pv->hvsi, hp);
|
||||
}
|
||||
|
||||
static void hvc_opal_hvsi_close(struct hvc_struct *hp, int data)
|
||||
{
|
||||
struct hvc_opal_priv *pv = hvc_opal_privs[hp->vtermno];
|
||||
|
||||
pr_devel("HVSI@%x: do close !\n", hp->vtermno);
|
||||
|
||||
hvsilib_close(&pv->hvsi, hp);
|
||||
|
||||
notifier_del_irq(hp, data);
|
||||
}
|
||||
|
||||
void hvc_opal_hvsi_hangup(struct hvc_struct *hp, int data)
|
||||
{
|
||||
struct hvc_opal_priv *pv = hvc_opal_privs[hp->vtermno];
|
||||
|
||||
pr_devel("HVSI@%x: do hangup !\n", hp->vtermno);
|
||||
|
||||
hvsilib_close(&pv->hvsi, hp);
|
||||
|
||||
notifier_hangup_irq(hp, data);
|
||||
}
|
||||
|
||||
static int hvc_opal_hvsi_tiocmget(struct hvc_struct *hp)
|
||||
{
|
||||
struct hvc_opal_priv *pv = hvc_opal_privs[hp->vtermno];
|
||||
|
||||
if (!pv)
|
||||
return -EINVAL;
|
||||
return pv->hvsi.mctrl;
|
||||
}
|
||||
|
||||
static int hvc_opal_hvsi_tiocmset(struct hvc_struct *hp, unsigned int set,
|
||||
unsigned int clear)
|
||||
{
|
||||
struct hvc_opal_priv *pv = hvc_opal_privs[hp->vtermno];
|
||||
|
||||
pr_devel("HVSI@%x: Set modem control, set=%x,clr=%x\n",
|
||||
hp->vtermno, set, clear);
|
||||
|
||||
if (set & TIOCM_DTR)
|
||||
hvsilib_write_mctrl(&pv->hvsi, 1);
|
||||
else if (clear & TIOCM_DTR)
|
||||
hvsilib_write_mctrl(&pv->hvsi, 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct hv_ops hvc_opal_hvsi_ops = {
|
||||
.get_chars = hvc_opal_hvsi_get_chars,
|
||||
.put_chars = hvc_opal_hvsi_put_chars,
|
||||
.notifier_add = hvc_opal_hvsi_open,
|
||||
.notifier_del = hvc_opal_hvsi_close,
|
||||
.notifier_hangup = hvc_opal_hvsi_hangup,
|
||||
.tiocmget = hvc_opal_hvsi_tiocmget,
|
||||
.tiocmset = hvc_opal_hvsi_tiocmset,
|
||||
};
|
||||
|
||||
static int hvc_opal_console_event(struct notifier_block *nb,
|
||||
unsigned long events, void *change)
|
||||
{
|
||||
if (events & OPAL_EVENT_CONSOLE_INPUT)
|
||||
hvc_kick();
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct notifier_block hvc_opal_console_nb = {
|
||||
.notifier_call = hvc_opal_console_event,
|
||||
};
|
||||
|
||||
static int hvc_opal_probe(struct platform_device *dev)
|
||||
{
|
||||
const struct hv_ops *ops;
|
||||
struct hvc_struct *hp;
|
||||
struct hvc_opal_priv *pv;
|
||||
hv_protocol_t proto;
|
||||
unsigned int termno, boot = 0;
|
||||
const __be32 *reg;
|
||||
|
||||
|
||||
if (of_device_is_compatible(dev->dev.of_node, "ibm,opal-console-raw")) {
|
||||
proto = HV_PROTOCOL_RAW;
|
||||
ops = &hvc_opal_raw_ops;
|
||||
} else if (of_device_is_compatible(dev->dev.of_node,
|
||||
"ibm,opal-console-hvsi")) {
|
||||
proto = HV_PROTOCOL_HVSI;
|
||||
ops = &hvc_opal_hvsi_ops;
|
||||
} else {
|
||||
pr_err("hvc_opal: Unknown protocol for %s\n",
|
||||
dev->dev.of_node->full_name);
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
reg = of_get_property(dev->dev.of_node, "reg", NULL);
|
||||
termno = reg ? be32_to_cpup(reg) : 0;
|
||||
|
||||
/* Is it our boot one ? */
|
||||
if (hvc_opal_privs[termno] == &hvc_opal_boot_priv) {
|
||||
pv = hvc_opal_privs[termno];
|
||||
boot = 1;
|
||||
} else if (hvc_opal_privs[termno] == NULL) {
|
||||
pv = kzalloc(sizeof(struct hvc_opal_priv), GFP_KERNEL);
|
||||
if (!pv)
|
||||
return -ENOMEM;
|
||||
pv->proto = proto;
|
||||
hvc_opal_privs[termno] = pv;
|
||||
if (proto == HV_PROTOCOL_HVSI)
|
||||
hvsilib_init(&pv->hvsi, opal_get_chars, opal_put_chars,
|
||||
termno, 0);
|
||||
|
||||
/* Instanciate now to establish a mapping index==vtermno */
|
||||
hvc_instantiate(termno, termno, ops);
|
||||
} else {
|
||||
pr_err("hvc_opal: Device %s has duplicate terminal number #%d\n",
|
||||
dev->dev.of_node->full_name, termno);
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
pr_info("hvc%d: %s protocol on %s%s\n", termno,
|
||||
proto == HV_PROTOCOL_RAW ? "raw" : "hvsi",
|
||||
dev->dev.of_node->full_name,
|
||||
boot ? " (boot console)" : "");
|
||||
|
||||
/* We don't do IRQ ... */
|
||||
hp = hvc_alloc(termno, 0, ops, MAX_VIO_PUT_CHARS);
|
||||
if (IS_ERR(hp))
|
||||
return PTR_ERR(hp);
|
||||
dev_set_drvdata(&dev->dev, hp);
|
||||
|
||||
/* ... but we use OPAL event to kick the console */
|
||||
if (!hvc_opal_event_registered) {
|
||||
opal_notifier_register(&hvc_opal_console_nb);
|
||||
hvc_opal_event_registered = true;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int hvc_opal_remove(struct platform_device *dev)
|
||||
{
|
||||
struct hvc_struct *hp = dev_get_drvdata(&dev->dev);
|
||||
int rc, termno;
|
||||
|
||||
termno = hp->vtermno;
|
||||
rc = hvc_remove(hp);
|
||||
if (rc == 0) {
|
||||
if (hvc_opal_privs[termno] != &hvc_opal_boot_priv)
|
||||
kfree(hvc_opal_privs[termno]);
|
||||
hvc_opal_privs[termno] = NULL;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
static struct platform_driver hvc_opal_driver = {
|
||||
.probe = hvc_opal_probe,
|
||||
.remove = hvc_opal_remove,
|
||||
.driver = {
|
||||
.name = hvc_opal_name,
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = hvc_opal_match,
|
||||
}
|
||||
};
|
||||
|
||||
static int __init hvc_opal_init(void)
|
||||
{
|
||||
if (!firmware_has_feature(FW_FEATURE_OPAL))
|
||||
return -ENODEV;
|
||||
|
||||
/* Register as a vio device to receive callbacks */
|
||||
return platform_driver_register(&hvc_opal_driver);
|
||||
}
|
||||
device_initcall(hvc_opal_init);
|
||||
|
||||
static void udbg_opal_putc(char c)
|
||||
{
|
||||
unsigned int termno = hvc_opal_boot_termno;
|
||||
int count = -1;
|
||||
|
||||
if (c == '\n')
|
||||
udbg_opal_putc('\r');
|
||||
|
||||
do {
|
||||
switch(hvc_opal_boot_priv.proto) {
|
||||
case HV_PROTOCOL_RAW:
|
||||
count = opal_put_chars(termno, &c, 1);
|
||||
break;
|
||||
case HV_PROTOCOL_HVSI:
|
||||
count = hvc_opal_hvsi_put_chars(termno, &c, 1);
|
||||
break;
|
||||
}
|
||||
} while(count == 0 || count == -EAGAIN);
|
||||
}
|
||||
|
||||
static int udbg_opal_getc_poll(void)
|
||||
{
|
||||
unsigned int termno = hvc_opal_boot_termno;
|
||||
int rc = 0;
|
||||
char c;
|
||||
|
||||
switch(hvc_opal_boot_priv.proto) {
|
||||
case HV_PROTOCOL_RAW:
|
||||
rc = opal_get_chars(termno, &c, 1);
|
||||
break;
|
||||
case HV_PROTOCOL_HVSI:
|
||||
rc = hvc_opal_hvsi_get_chars(termno, &c, 1);
|
||||
break;
|
||||
}
|
||||
if (!rc)
|
||||
return -1;
|
||||
return c;
|
||||
}
|
||||
|
||||
static int udbg_opal_getc(void)
|
||||
{
|
||||
int ch;
|
||||
for (;;) {
|
||||
ch = udbg_opal_getc_poll();
|
||||
if (ch == -1) {
|
||||
/* This shouldn't be needed...but... */
|
||||
volatile unsigned long delay;
|
||||
for (delay=0; delay < 2000000; delay++)
|
||||
;
|
||||
} else {
|
||||
return ch;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void udbg_init_opal_common(void)
|
||||
{
|
||||
udbg_putc = udbg_opal_putc;
|
||||
udbg_getc = udbg_opal_getc;
|
||||
udbg_getc_poll = udbg_opal_getc_poll;
|
||||
tb_ticks_per_usec = 0x200; /* Make udelay not suck */
|
||||
}
|
||||
|
||||
void __init hvc_opal_init_early(void)
|
||||
{
|
||||
struct device_node *stdout_node = of_node_get(of_stdout);
|
||||
const __be32 *termno;
|
||||
const struct hv_ops *ops;
|
||||
u32 index;
|
||||
|
||||
/* If the console wasn't in /chosen, try /ibm,opal */
|
||||
if (!stdout_node) {
|
||||
struct device_node *opal, *np;
|
||||
|
||||
/* Current OPAL takeover doesn't provide the stdout
|
||||
* path, so we hard wire it
|
||||
*/
|
||||
opal = of_find_node_by_path("/ibm,opal/consoles");
|
||||
if (opal)
|
||||
pr_devel("hvc_opal: Found consoles in new location\n");
|
||||
if (!opal) {
|
||||
opal = of_find_node_by_path("/ibm,opal");
|
||||
if (opal)
|
||||
pr_devel("hvc_opal: "
|
||||
"Found consoles in old location\n");
|
||||
}
|
||||
if (!opal)
|
||||
return;
|
||||
for_each_child_of_node(opal, np) {
|
||||
if (!strcmp(np->name, "serial")) {
|
||||
stdout_node = np;
|
||||
break;
|
||||
}
|
||||
}
|
||||
of_node_put(opal);
|
||||
}
|
||||
if (!stdout_node)
|
||||
return;
|
||||
termno = of_get_property(stdout_node, "reg", NULL);
|
||||
index = termno ? be32_to_cpup(termno) : 0;
|
||||
if (index >= MAX_NR_HVC_CONSOLES)
|
||||
return;
|
||||
hvc_opal_privs[index] = &hvc_opal_boot_priv;
|
||||
|
||||
/* Check the protocol */
|
||||
if (of_device_is_compatible(stdout_node, "ibm,opal-console-raw")) {
|
||||
hvc_opal_boot_priv.proto = HV_PROTOCOL_RAW;
|
||||
ops = &hvc_opal_raw_ops;
|
||||
pr_devel("hvc_opal: Found RAW console\n");
|
||||
}
|
||||
else if (of_device_is_compatible(stdout_node,"ibm,opal-console-hvsi")) {
|
||||
hvc_opal_boot_priv.proto = HV_PROTOCOL_HVSI;
|
||||
ops = &hvc_opal_hvsi_ops;
|
||||
hvsilib_init(&hvc_opal_boot_priv.hvsi, opal_get_chars,
|
||||
opal_put_chars, index, 1);
|
||||
/* HVSI, perform the handshake now */
|
||||
hvsilib_establish(&hvc_opal_boot_priv.hvsi);
|
||||
pr_devel("hvc_opal: Found HVSI console\n");
|
||||
} else
|
||||
goto out;
|
||||
hvc_opal_boot_termno = index;
|
||||
udbg_init_opal_common();
|
||||
add_preferred_console("hvc", index, NULL);
|
||||
hvc_instantiate(index, index, ops);
|
||||
out:
|
||||
of_node_put(stdout_node);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PPC_EARLY_DEBUG_OPAL_RAW
|
||||
void __init udbg_init_debug_opal_raw(void)
|
||||
{
|
||||
u32 index = CONFIG_PPC_EARLY_DEBUG_OPAL_VTERMNO;
|
||||
hvc_opal_privs[index] = &hvc_opal_boot_priv;
|
||||
hvc_opal_boot_priv.proto = HV_PROTOCOL_RAW;
|
||||
hvc_opal_boot_termno = index;
|
||||
udbg_init_opal_common();
|
||||
}
|
||||
#endif /* CONFIG_PPC_EARLY_DEBUG_OPAL_RAW */
|
||||
|
||||
#ifdef CONFIG_PPC_EARLY_DEBUG_OPAL_HVSI
|
||||
void __init udbg_init_debug_opal_hvsi(void)
|
||||
{
|
||||
u32 index = CONFIG_PPC_EARLY_DEBUG_OPAL_VTERMNO;
|
||||
hvc_opal_privs[index] = &hvc_opal_boot_priv;
|
||||
hvc_opal_boot_termno = index;
|
||||
udbg_init_opal_common();
|
||||
hvsilib_init(&hvc_opal_boot_priv.hvsi, opal_get_chars, opal_put_chars,
|
||||
index, 1);
|
||||
hvsilib_establish(&hvc_opal_boot_priv.hvsi);
|
||||
}
|
||||
#endif /* CONFIG_PPC_EARLY_DEBUG_OPAL_HVSI */
|
123
drivers/tty/hvc/hvc_rtas.c
Normal file
123
drivers/tty/hvc/hvc_rtas.c
Normal file
|
@ -0,0 +1,123 @@
|
|||
/*
|
||||
* IBM RTAS driver interface to hvc_console.c
|
||||
*
|
||||
* (C) Copyright IBM Corporation 2001-2005
|
||||
* (C) Copyright Red Hat, Inc. 2005
|
||||
*
|
||||
* Author(s): Maximino Augilar <IBM STI Design Center>
|
||||
* : Ryan S. Arnold <rsa@us.ibm.com>
|
||||
* : Utz Bacher <utz.bacher@de.ibm.com>
|
||||
* : David Woodhouse <dwmw2@infradead.org>
|
||||
*
|
||||
* inspired by drivers/char/hvc_console.c
|
||||
* written by Anton Blanchard and Paul Mackerras
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include <linux/console.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/moduleparam.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
#include <asm/irq.h>
|
||||
#include <asm/rtas.h>
|
||||
#include "hvc_console.h"
|
||||
|
||||
#define hvc_rtas_cookie 0x67781e15
|
||||
struct hvc_struct *hvc_rtas_dev;
|
||||
|
||||
static int rtascons_put_char_token = RTAS_UNKNOWN_SERVICE;
|
||||
static int rtascons_get_char_token = RTAS_UNKNOWN_SERVICE;
|
||||
|
||||
static inline int hvc_rtas_write_console(uint32_t vtermno, const char *buf,
|
||||
int count)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < count; i++) {
|
||||
if (rtas_call(rtascons_put_char_token, 1, 1, NULL, buf[i]))
|
||||
break;
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
static int hvc_rtas_read_console(uint32_t vtermno, char *buf, int count)
|
||||
{
|
||||
int i, c;
|
||||
|
||||
for (i = 0; i < count; i++) {
|
||||
if (rtas_call(rtascons_get_char_token, 0, 2, &c))
|
||||
break;
|
||||
|
||||
buf[i] = c;
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
static const struct hv_ops hvc_rtas_get_put_ops = {
|
||||
.get_chars = hvc_rtas_read_console,
|
||||
.put_chars = hvc_rtas_write_console,
|
||||
};
|
||||
|
||||
static int __init hvc_rtas_init(void)
|
||||
{
|
||||
struct hvc_struct *hp;
|
||||
|
||||
if (rtascons_put_char_token == RTAS_UNKNOWN_SERVICE)
|
||||
rtascons_put_char_token = rtas_token("put-term-char");
|
||||
if (rtascons_put_char_token == RTAS_UNKNOWN_SERVICE)
|
||||
return -EIO;
|
||||
|
||||
if (rtascons_get_char_token == RTAS_UNKNOWN_SERVICE)
|
||||
rtascons_get_char_token = rtas_token("get-term-char");
|
||||
if (rtascons_get_char_token == RTAS_UNKNOWN_SERVICE)
|
||||
return -EIO;
|
||||
|
||||
BUG_ON(hvc_rtas_dev);
|
||||
|
||||
/* Allocate an hvc_struct for the console device we instantiated
|
||||
* earlier. Save off hp so that we can return it on exit */
|
||||
hp = hvc_alloc(hvc_rtas_cookie, 0, &hvc_rtas_get_put_ops, 16);
|
||||
if (IS_ERR(hp))
|
||||
return PTR_ERR(hp);
|
||||
|
||||
hvc_rtas_dev = hp;
|
||||
|
||||
return 0;
|
||||
}
|
||||
device_initcall(hvc_rtas_init);
|
||||
|
||||
/* This will happen prior to module init. There is no tty at this time? */
|
||||
static int __init hvc_rtas_console_init(void)
|
||||
{
|
||||
rtascons_put_char_token = rtas_token("put-term-char");
|
||||
if (rtascons_put_char_token == RTAS_UNKNOWN_SERVICE)
|
||||
return -EIO;
|
||||
|
||||
rtascons_get_char_token = rtas_token("get-term-char");
|
||||
if (rtascons_get_char_token == RTAS_UNKNOWN_SERVICE)
|
||||
return -EIO;
|
||||
|
||||
hvc_instantiate(hvc_rtas_cookie, 0, &hvc_rtas_get_put_ops);
|
||||
add_preferred_console("hvc", 0, NULL);
|
||||
|
||||
return 0;
|
||||
}
|
||||
console_initcall(hvc_rtas_console_init);
|
205
drivers/tty/hvc/hvc_tile.c
Normal file
205
drivers/tty/hvc/hvc_tile.c
Normal file
|
@ -0,0 +1,205 @@
|
|||
/*
|
||||
* Copyright 2010 Tilera Corporation. All Rights Reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, version 2.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or
|
||||
* NON INFRINGEMENT. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* Tilera TILE Processor hypervisor console
|
||||
*/
|
||||
|
||||
#include <linux/console.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/moduleparam.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
#include <asm/setup.h>
|
||||
#include <arch/sim_def.h>
|
||||
|
||||
#include <hv/hypervisor.h>
|
||||
|
||||
#include "hvc_console.h"
|
||||
|
||||
static int use_sim_console;
|
||||
static int __init sim_console(char *str)
|
||||
{
|
||||
use_sim_console = 1;
|
||||
return 0;
|
||||
}
|
||||
early_param("sim_console", sim_console);
|
||||
|
||||
int tile_console_write(const char *buf, int count)
|
||||
{
|
||||
if (unlikely(use_sim_console)) {
|
||||
int i;
|
||||
for (i = 0; i < count; ++i)
|
||||
__insn_mtspr(SPR_SIM_CONTROL, SIM_CONTROL_PUTC |
|
||||
(buf[i] << _SIM_CONTROL_OPERATOR_BITS));
|
||||
__insn_mtspr(SPR_SIM_CONTROL, SIM_CONTROL_PUTC |
|
||||
(SIM_PUTC_FLUSH_BINARY <<
|
||||
_SIM_CONTROL_OPERATOR_BITS));
|
||||
return 0;
|
||||
} else {
|
||||
return hv_console_write((HV_VirtAddr)buf, count);
|
||||
}
|
||||
}
|
||||
|
||||
static int hvc_tile_put_chars(uint32_t vt, const char *buf, int count)
|
||||
{
|
||||
return tile_console_write(buf, count);
|
||||
}
|
||||
|
||||
static int hvc_tile_get_chars(uint32_t vt, char *buf, int count)
|
||||
{
|
||||
int i, c;
|
||||
|
||||
for (i = 0; i < count; ++i) {
|
||||
c = hv_console_read_if_ready();
|
||||
if (c < 0)
|
||||
break;
|
||||
buf[i] = c;
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
#ifdef __tilegx__
|
||||
/*
|
||||
* IRQ based callbacks.
|
||||
*/
|
||||
static int hvc_tile_notifier_add_irq(struct hvc_struct *hp, int irq)
|
||||
{
|
||||
int rc;
|
||||
int cpu = raw_smp_processor_id(); /* Choose an arbitrary cpu */
|
||||
HV_Coord coord = { .x = cpu_x(cpu), .y = cpu_y(cpu) };
|
||||
|
||||
rc = notifier_add_irq(hp, irq);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
/*
|
||||
* Request that the hypervisor start sending us interrupts.
|
||||
* If the hypervisor returns an error, we still return 0, so that
|
||||
* we can fall back to polling.
|
||||
*/
|
||||
if (hv_console_set_ipi(KERNEL_PL, irq, coord) < 0)
|
||||
notifier_del_irq(hp, irq);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void hvc_tile_notifier_del_irq(struct hvc_struct *hp, int irq)
|
||||
{
|
||||
HV_Coord coord = { 0, 0 };
|
||||
|
||||
/* Tell the hypervisor to stop sending us interrupts. */
|
||||
hv_console_set_ipi(KERNEL_PL, -1, coord);
|
||||
|
||||
notifier_del_irq(hp, irq);
|
||||
}
|
||||
|
||||
static void hvc_tile_notifier_hangup_irq(struct hvc_struct *hp, int irq)
|
||||
{
|
||||
hvc_tile_notifier_del_irq(hp, irq);
|
||||
}
|
||||
#endif
|
||||
|
||||
static const struct hv_ops hvc_tile_get_put_ops = {
|
||||
.get_chars = hvc_tile_get_chars,
|
||||
.put_chars = hvc_tile_put_chars,
|
||||
#ifdef __tilegx__
|
||||
.notifier_add = hvc_tile_notifier_add_irq,
|
||||
.notifier_del = hvc_tile_notifier_del_irq,
|
||||
.notifier_hangup = hvc_tile_notifier_hangup_irq,
|
||||
#endif
|
||||
};
|
||||
|
||||
|
||||
#ifdef __tilegx__
|
||||
static int hvc_tile_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct hvc_struct *hp;
|
||||
int tile_hvc_irq;
|
||||
|
||||
/* Create our IRQ and register it. */
|
||||
tile_hvc_irq = irq_alloc_hwirq(-1);
|
||||
if (!tile_hvc_irq)
|
||||
return -ENXIO;
|
||||
|
||||
tile_irq_activate(tile_hvc_irq, TILE_IRQ_PERCPU);
|
||||
hp = hvc_alloc(0, tile_hvc_irq, &hvc_tile_get_put_ops, 128);
|
||||
if (IS_ERR(hp)) {
|
||||
irq_free_hwirq(tile_hvc_irq);
|
||||
return PTR_ERR(hp);
|
||||
}
|
||||
dev_set_drvdata(&pdev->dev, hp);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int hvc_tile_remove(struct platform_device *pdev)
|
||||
{
|
||||
int rc;
|
||||
struct hvc_struct *hp = dev_get_drvdata(&pdev->dev);
|
||||
|
||||
rc = hvc_remove(hp);
|
||||
if (rc == 0)
|
||||
irq_free_hwirq(hp->data);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static void hvc_tile_shutdown(struct platform_device *pdev)
|
||||
{
|
||||
struct hvc_struct *hp = dev_get_drvdata(&pdev->dev);
|
||||
|
||||
hvc_tile_notifier_del_irq(hp, hp->data);
|
||||
}
|
||||
|
||||
static struct platform_device hvc_tile_pdev = {
|
||||
.name = "hvc-tile",
|
||||
.id = 0,
|
||||
};
|
||||
|
||||
static struct platform_driver hvc_tile_driver = {
|
||||
.probe = hvc_tile_probe,
|
||||
.remove = hvc_tile_remove,
|
||||
.shutdown = hvc_tile_shutdown,
|
||||
.driver = {
|
||||
.name = "hvc-tile",
|
||||
.owner = THIS_MODULE,
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
||||
static int __init hvc_tile_console_init(void)
|
||||
{
|
||||
hvc_instantiate(0, 0, &hvc_tile_get_put_ops);
|
||||
add_preferred_console("hvc", 0, NULL);
|
||||
return 0;
|
||||
}
|
||||
console_initcall(hvc_tile_console_init);
|
||||
|
||||
static int __init hvc_tile_init(void)
|
||||
{
|
||||
#ifndef __tilegx__
|
||||
struct hvc_struct *hp;
|
||||
hp = hvc_alloc(0, 0, &hvc_tile_get_put_ops, 128);
|
||||
return PTR_ERR_OR_ZERO(hp);
|
||||
#else
|
||||
platform_device_register(&hvc_tile_pdev);
|
||||
return platform_driver_register(&hvc_tile_driver);
|
||||
#endif
|
||||
}
|
||||
device_initcall(hvc_tile_init);
|
95
drivers/tty/hvc/hvc_udbg.c
Normal file
95
drivers/tty/hvc/hvc_udbg.c
Normal file
|
@ -0,0 +1,95 @@
|
|||
/*
|
||||
* udbg interface to hvc_console.c
|
||||
*
|
||||
* (C) Copyright David Gibson, IBM Corporation 2008.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include <linux/console.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/moduleparam.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/irq.h>
|
||||
|
||||
#include <asm/udbg.h>
|
||||
|
||||
#include "hvc_console.h"
|
||||
|
||||
struct hvc_struct *hvc_udbg_dev;
|
||||
|
||||
static int hvc_udbg_put(uint32_t vtermno, const char *buf, int count)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < count && udbg_putc; i++)
|
||||
udbg_putc(buf[i]);
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
static int hvc_udbg_get(uint32_t vtermno, char *buf, int count)
|
||||
{
|
||||
int i, c;
|
||||
|
||||
if (!udbg_getc_poll)
|
||||
return 0;
|
||||
|
||||
for (i = 0; i < count; i++) {
|
||||
if ((c = udbg_getc_poll()) == -1)
|
||||
break;
|
||||
buf[i] = c;
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
static const struct hv_ops hvc_udbg_ops = {
|
||||
.get_chars = hvc_udbg_get,
|
||||
.put_chars = hvc_udbg_put,
|
||||
};
|
||||
|
||||
static int __init hvc_udbg_init(void)
|
||||
{
|
||||
struct hvc_struct *hp;
|
||||
|
||||
if (!udbg_putc)
|
||||
return -ENODEV;
|
||||
|
||||
BUG_ON(hvc_udbg_dev);
|
||||
|
||||
hp = hvc_alloc(0, 0, &hvc_udbg_ops, 16);
|
||||
if (IS_ERR(hp))
|
||||
return PTR_ERR(hp);
|
||||
|
||||
hvc_udbg_dev = hp;
|
||||
|
||||
return 0;
|
||||
}
|
||||
device_initcall(hvc_udbg_init);
|
||||
|
||||
static int __init hvc_udbg_console_init(void)
|
||||
{
|
||||
if (!udbg_putc)
|
||||
return -ENODEV;
|
||||
|
||||
hvc_instantiate(0, 0, &hvc_udbg_ops);
|
||||
add_preferred_console("hvc", 0, NULL);
|
||||
|
||||
return 0;
|
||||
}
|
||||
console_initcall(hvc_udbg_console_init);
|
490
drivers/tty/hvc/hvc_vio.c
Normal file
490
drivers/tty/hvc/hvc_vio.c
Normal file
|
@ -0,0 +1,490 @@
|
|||
/*
|
||||
* vio driver interface to hvc_console.c
|
||||
*
|
||||
* This code was moved here to allow the remaining code to be reused as a
|
||||
* generic polling mode with semi-reliable transport driver core to the
|
||||
* console and tty subsystems.
|
||||
*
|
||||
*
|
||||
* Copyright (C) 2001 Anton Blanchard <anton@au.ibm.com>, IBM
|
||||
* Copyright (C) 2001 Paul Mackerras <paulus@au.ibm.com>, IBM
|
||||
* Copyright (C) 2004 Benjamin Herrenschmidt <benh@kernel.crashing.org>, IBM Corp.
|
||||
* Copyright (C) 2004 IBM Corporation
|
||||
*
|
||||
* Additional Author(s):
|
||||
* Ryan S. Arnold <rsa@us.ibm.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
* TODO:
|
||||
*
|
||||
* - handle error in sending hvsi protocol packets
|
||||
* - retry nego on subsequent sends ?
|
||||
*/
|
||||
|
||||
#undef DEBUG
|
||||
|
||||
#include <linux/types.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/console.h>
|
||||
#include <linux/module.h>
|
||||
|
||||
#include <asm/hvconsole.h>
|
||||
#include <asm/vio.h>
|
||||
#include <asm/prom.h>
|
||||
#include <asm/hvsi.h>
|
||||
#include <asm/udbg.h>
|
||||
#include <asm/machdep.h>
|
||||
|
||||
#include "hvc_console.h"
|
||||
|
||||
static const char hvc_driver_name[] = "hvc_console";
|
||||
|
||||
static struct vio_device_id hvc_driver_table[] = {
|
||||
{"serial", "hvterm1"},
|
||||
#ifndef HVC_OLD_HVSI
|
||||
{"serial", "hvterm-protocol"},
|
||||
#endif
|
||||
{ "", "" }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(vio, hvc_driver_table);
|
||||
|
||||
typedef enum hv_protocol {
|
||||
HV_PROTOCOL_RAW,
|
||||
HV_PROTOCOL_HVSI
|
||||
} hv_protocol_t;
|
||||
|
||||
struct hvterm_priv {
|
||||
u32 termno; /* HV term number */
|
||||
hv_protocol_t proto; /* Raw data or HVSI packets */
|
||||
struct hvsi_priv hvsi; /* HVSI specific data */
|
||||
spinlock_t buf_lock;
|
||||
char buf[SIZE_VIO_GET_CHARS];
|
||||
int left;
|
||||
int offset;
|
||||
};
|
||||
static struct hvterm_priv *hvterm_privs[MAX_NR_HVC_CONSOLES];
|
||||
/* For early boot console */
|
||||
static struct hvterm_priv hvterm_priv0;
|
||||
|
||||
static int hvterm_raw_get_chars(uint32_t vtermno, char *buf, int count)
|
||||
{
|
||||
struct hvterm_priv *pv = hvterm_privs[vtermno];
|
||||
unsigned long i;
|
||||
unsigned long flags;
|
||||
int got;
|
||||
|
||||
if (WARN_ON(!pv))
|
||||
return 0;
|
||||
|
||||
spin_lock_irqsave(&pv->buf_lock, flags);
|
||||
|
||||
if (pv->left == 0) {
|
||||
pv->offset = 0;
|
||||
pv->left = hvc_get_chars(pv->termno, pv->buf, count);
|
||||
|
||||
/*
|
||||
* Work around a HV bug where it gives us a null
|
||||
* after every \r. -- paulus
|
||||
*/
|
||||
for (i = 1; i < pv->left; ++i) {
|
||||
if (pv->buf[i] == 0 && pv->buf[i-1] == '\r') {
|
||||
--pv->left;
|
||||
if (i < pv->left) {
|
||||
memmove(&pv->buf[i], &pv->buf[i+1],
|
||||
pv->left - i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
got = min(count, pv->left);
|
||||
memcpy(buf, &pv->buf[pv->offset], got);
|
||||
pv->offset += got;
|
||||
pv->left -= got;
|
||||
|
||||
spin_unlock_irqrestore(&pv->buf_lock, flags);
|
||||
|
||||
return got;
|
||||
}
|
||||
|
||||
static int hvterm_raw_put_chars(uint32_t vtermno, const char *buf, int count)
|
||||
{
|
||||
struct hvterm_priv *pv = hvterm_privs[vtermno];
|
||||
|
||||
if (WARN_ON(!pv))
|
||||
return 0;
|
||||
|
||||
return hvc_put_chars(pv->termno, buf, count);
|
||||
}
|
||||
|
||||
static const struct hv_ops hvterm_raw_ops = {
|
||||
.get_chars = hvterm_raw_get_chars,
|
||||
.put_chars = hvterm_raw_put_chars,
|
||||
.notifier_add = notifier_add_irq,
|
||||
.notifier_del = notifier_del_irq,
|
||||
.notifier_hangup = notifier_hangup_irq,
|
||||
};
|
||||
|
||||
static int hvterm_hvsi_get_chars(uint32_t vtermno, char *buf, int count)
|
||||
{
|
||||
struct hvterm_priv *pv = hvterm_privs[vtermno];
|
||||
|
||||
if (WARN_ON(!pv))
|
||||
return 0;
|
||||
|
||||
return hvsilib_get_chars(&pv->hvsi, buf, count);
|
||||
}
|
||||
|
||||
static int hvterm_hvsi_put_chars(uint32_t vtermno, const char *buf, int count)
|
||||
{
|
||||
struct hvterm_priv *pv = hvterm_privs[vtermno];
|
||||
|
||||
if (WARN_ON(!pv))
|
||||
return 0;
|
||||
|
||||
return hvsilib_put_chars(&pv->hvsi, buf, count);
|
||||
}
|
||||
|
||||
static int hvterm_hvsi_open(struct hvc_struct *hp, int data)
|
||||
{
|
||||
struct hvterm_priv *pv = hvterm_privs[hp->vtermno];
|
||||
int rc;
|
||||
|
||||
pr_devel("HVSI@%x: open !\n", pv->termno);
|
||||
|
||||
rc = notifier_add_irq(hp, data);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
return hvsilib_open(&pv->hvsi, hp);
|
||||
}
|
||||
|
||||
static void hvterm_hvsi_close(struct hvc_struct *hp, int data)
|
||||
{
|
||||
struct hvterm_priv *pv = hvterm_privs[hp->vtermno];
|
||||
|
||||
pr_devel("HVSI@%x: do close !\n", pv->termno);
|
||||
|
||||
hvsilib_close(&pv->hvsi, hp);
|
||||
|
||||
notifier_del_irq(hp, data);
|
||||
}
|
||||
|
||||
void hvterm_hvsi_hangup(struct hvc_struct *hp, int data)
|
||||
{
|
||||
struct hvterm_priv *pv = hvterm_privs[hp->vtermno];
|
||||
|
||||
pr_devel("HVSI@%x: do hangup !\n", pv->termno);
|
||||
|
||||
hvsilib_close(&pv->hvsi, hp);
|
||||
|
||||
notifier_hangup_irq(hp, data);
|
||||
}
|
||||
|
||||
static int hvterm_hvsi_tiocmget(struct hvc_struct *hp)
|
||||
{
|
||||
struct hvterm_priv *pv = hvterm_privs[hp->vtermno];
|
||||
|
||||
if (!pv)
|
||||
return -EINVAL;
|
||||
return pv->hvsi.mctrl;
|
||||
}
|
||||
|
||||
static int hvterm_hvsi_tiocmset(struct hvc_struct *hp, unsigned int set,
|
||||
unsigned int clear)
|
||||
{
|
||||
struct hvterm_priv *pv = hvterm_privs[hp->vtermno];
|
||||
|
||||
pr_devel("HVSI@%x: Set modem control, set=%x,clr=%x\n",
|
||||
pv->termno, set, clear);
|
||||
|
||||
if (set & TIOCM_DTR)
|
||||
hvsilib_write_mctrl(&pv->hvsi, 1);
|
||||
else if (clear & TIOCM_DTR)
|
||||
hvsilib_write_mctrl(&pv->hvsi, 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct hv_ops hvterm_hvsi_ops = {
|
||||
.get_chars = hvterm_hvsi_get_chars,
|
||||
.put_chars = hvterm_hvsi_put_chars,
|
||||
.notifier_add = hvterm_hvsi_open,
|
||||
.notifier_del = hvterm_hvsi_close,
|
||||
.notifier_hangup = hvterm_hvsi_hangup,
|
||||
.tiocmget = hvterm_hvsi_tiocmget,
|
||||
.tiocmset = hvterm_hvsi_tiocmset,
|
||||
};
|
||||
|
||||
static void udbg_hvc_putc(char c)
|
||||
{
|
||||
int count = -1;
|
||||
|
||||
if (!hvterm_privs[0])
|
||||
return;
|
||||
|
||||
if (c == '\n')
|
||||
udbg_hvc_putc('\r');
|
||||
|
||||
do {
|
||||
switch(hvterm_privs[0]->proto) {
|
||||
case HV_PROTOCOL_RAW:
|
||||
count = hvterm_raw_put_chars(0, &c, 1);
|
||||
break;
|
||||
case HV_PROTOCOL_HVSI:
|
||||
count = hvterm_hvsi_put_chars(0, &c, 1);
|
||||
break;
|
||||
}
|
||||
} while(count == 0);
|
||||
}
|
||||
|
||||
static int udbg_hvc_getc_poll(void)
|
||||
{
|
||||
int rc = 0;
|
||||
char c;
|
||||
|
||||
if (!hvterm_privs[0])
|
||||
return -1;
|
||||
|
||||
switch(hvterm_privs[0]->proto) {
|
||||
case HV_PROTOCOL_RAW:
|
||||
rc = hvterm_raw_get_chars(0, &c, 1);
|
||||
break;
|
||||
case HV_PROTOCOL_HVSI:
|
||||
rc = hvterm_hvsi_get_chars(0, &c, 1);
|
||||
break;
|
||||
}
|
||||
if (!rc)
|
||||
return -1;
|
||||
return c;
|
||||
}
|
||||
|
||||
static int udbg_hvc_getc(void)
|
||||
{
|
||||
int ch;
|
||||
|
||||
if (!hvterm_privs[0])
|
||||
return -1;
|
||||
|
||||
for (;;) {
|
||||
ch = udbg_hvc_getc_poll();
|
||||
if (ch == -1) {
|
||||
/* This shouldn't be needed...but... */
|
||||
volatile unsigned long delay;
|
||||
for (delay=0; delay < 2000000; delay++)
|
||||
;
|
||||
} else {
|
||||
return ch;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int hvc_vio_probe(struct vio_dev *vdev,
|
||||
const struct vio_device_id *id)
|
||||
{
|
||||
const struct hv_ops *ops;
|
||||
struct hvc_struct *hp;
|
||||
struct hvterm_priv *pv;
|
||||
hv_protocol_t proto;
|
||||
int i, termno = -1;
|
||||
|
||||
/* probed with invalid parameters. */
|
||||
if (!vdev || !id)
|
||||
return -EPERM;
|
||||
|
||||
if (of_device_is_compatible(vdev->dev.of_node, "hvterm1")) {
|
||||
proto = HV_PROTOCOL_RAW;
|
||||
ops = &hvterm_raw_ops;
|
||||
} else if (of_device_is_compatible(vdev->dev.of_node, "hvterm-protocol")) {
|
||||
proto = HV_PROTOCOL_HVSI;
|
||||
ops = &hvterm_hvsi_ops;
|
||||
} else {
|
||||
pr_err("hvc_vio: Unknown protocol for %s\n", vdev->dev.of_node->full_name);
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
pr_devel("hvc_vio_probe() device %s, using %s protocol\n",
|
||||
vdev->dev.of_node->full_name,
|
||||
proto == HV_PROTOCOL_RAW ? "raw" : "hvsi");
|
||||
|
||||
/* Is it our boot one ? */
|
||||
if (hvterm_privs[0] == &hvterm_priv0 &&
|
||||
vdev->unit_address == hvterm_priv0.termno) {
|
||||
pv = hvterm_privs[0];
|
||||
termno = 0;
|
||||
pr_devel("->boot console, using termno 0\n");
|
||||
}
|
||||
/* nope, allocate a new one */
|
||||
else {
|
||||
for (i = 0; i < MAX_NR_HVC_CONSOLES && termno < 0; i++)
|
||||
if (!hvterm_privs[i])
|
||||
termno = i;
|
||||
pr_devel("->non-boot console, using termno %d\n", termno);
|
||||
if (termno < 0)
|
||||
return -ENODEV;
|
||||
pv = kzalloc(sizeof(struct hvterm_priv), GFP_KERNEL);
|
||||
if (!pv)
|
||||
return -ENOMEM;
|
||||
pv->termno = vdev->unit_address;
|
||||
pv->proto = proto;
|
||||
spin_lock_init(&pv->buf_lock);
|
||||
hvterm_privs[termno] = pv;
|
||||
hvsilib_init(&pv->hvsi, hvc_get_chars, hvc_put_chars,
|
||||
pv->termno, 0);
|
||||
}
|
||||
|
||||
hp = hvc_alloc(termno, vdev->irq, ops, MAX_VIO_PUT_CHARS);
|
||||
if (IS_ERR(hp))
|
||||
return PTR_ERR(hp);
|
||||
dev_set_drvdata(&vdev->dev, hp);
|
||||
|
||||
/* register udbg if it's not there already for console 0 */
|
||||
if (hp->index == 0 && !udbg_putc) {
|
||||
udbg_putc = udbg_hvc_putc;
|
||||
udbg_getc = udbg_hvc_getc;
|
||||
udbg_getc_poll = udbg_hvc_getc_poll;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int hvc_vio_remove(struct vio_dev *vdev)
|
||||
{
|
||||
struct hvc_struct *hp = dev_get_drvdata(&vdev->dev);
|
||||
int rc, termno;
|
||||
|
||||
termno = hp->vtermno;
|
||||
rc = hvc_remove(hp);
|
||||
if (rc == 0) {
|
||||
if (hvterm_privs[termno] != &hvterm_priv0)
|
||||
kfree(hvterm_privs[termno]);
|
||||
hvterm_privs[termno] = NULL;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
static struct vio_driver hvc_vio_driver = {
|
||||
.id_table = hvc_driver_table,
|
||||
.probe = hvc_vio_probe,
|
||||
.remove = hvc_vio_remove,
|
||||
.name = hvc_driver_name,
|
||||
};
|
||||
|
||||
static int __init hvc_vio_init(void)
|
||||
{
|
||||
int rc;
|
||||
|
||||
/* Register as a vio device to receive callbacks */
|
||||
rc = vio_register_driver(&hvc_vio_driver);
|
||||
|
||||
return rc;
|
||||
}
|
||||
module_init(hvc_vio_init); /* after drivers/char/hvc_console.c */
|
||||
|
||||
static void __exit hvc_vio_exit(void)
|
||||
{
|
||||
vio_unregister_driver(&hvc_vio_driver);
|
||||
}
|
||||
module_exit(hvc_vio_exit);
|
||||
|
||||
void __init hvc_vio_init_early(void)
|
||||
{
|
||||
const __be32 *termno;
|
||||
const char *name;
|
||||
const struct hv_ops *ops;
|
||||
|
||||
/* find the boot console from /chosen/stdout */
|
||||
if (!of_stdout)
|
||||
return;
|
||||
name = of_get_property(of_stdout, "name", NULL);
|
||||
if (!name) {
|
||||
printk(KERN_WARNING "stdout node missing 'name' property!\n");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Check if it's a virtual terminal */
|
||||
if (strncmp(name, "vty", 3) != 0)
|
||||
return;
|
||||
termno = of_get_property(of_stdout, "reg", NULL);
|
||||
if (termno == NULL)
|
||||
return;
|
||||
hvterm_priv0.termno = of_read_number(termno, 1);
|
||||
spin_lock_init(&hvterm_priv0.buf_lock);
|
||||
hvterm_privs[0] = &hvterm_priv0;
|
||||
|
||||
/* Check the protocol */
|
||||
if (of_device_is_compatible(of_stdout, "hvterm1")) {
|
||||
hvterm_priv0.proto = HV_PROTOCOL_RAW;
|
||||
ops = &hvterm_raw_ops;
|
||||
}
|
||||
else if (of_device_is_compatible(of_stdout, "hvterm-protocol")) {
|
||||
hvterm_priv0.proto = HV_PROTOCOL_HVSI;
|
||||
ops = &hvterm_hvsi_ops;
|
||||
hvsilib_init(&hvterm_priv0.hvsi, hvc_get_chars, hvc_put_chars,
|
||||
hvterm_priv0.termno, 1);
|
||||
/* HVSI, perform the handshake now */
|
||||
hvsilib_establish(&hvterm_priv0.hvsi);
|
||||
} else
|
||||
return;
|
||||
udbg_putc = udbg_hvc_putc;
|
||||
udbg_getc = udbg_hvc_getc;
|
||||
udbg_getc_poll = udbg_hvc_getc_poll;
|
||||
#ifdef HVC_OLD_HVSI
|
||||
/* When using the old HVSI driver don't register the HVC
|
||||
* backend for HVSI, only do udbg
|
||||
*/
|
||||
if (hvterm_priv0.proto == HV_PROTOCOL_HVSI)
|
||||
return;
|
||||
#endif
|
||||
/* Check whether the user has requested a different console. */
|
||||
if (!strstr(boot_command_line, "console="))
|
||||
add_preferred_console("hvc", 0, NULL);
|
||||
hvc_instantiate(0, 0, ops);
|
||||
}
|
||||
|
||||
/* call this from early_init() for a working debug console on
|
||||
* vterm capable LPAR machines
|
||||
*/
|
||||
#ifdef CONFIG_PPC_EARLY_DEBUG_LPAR
|
||||
void __init udbg_init_debug_lpar(void)
|
||||
{
|
||||
hvterm_privs[0] = &hvterm_priv0;
|
||||
hvterm_priv0.termno = 0;
|
||||
hvterm_priv0.proto = HV_PROTOCOL_RAW;
|
||||
spin_lock_init(&hvterm_priv0.buf_lock);
|
||||
udbg_putc = udbg_hvc_putc;
|
||||
udbg_getc = udbg_hvc_getc;
|
||||
udbg_getc_poll = udbg_hvc_getc_poll;
|
||||
}
|
||||
#endif /* CONFIG_PPC_EARLY_DEBUG_LPAR */
|
||||
|
||||
#ifdef CONFIG_PPC_EARLY_DEBUG_LPAR_HVSI
|
||||
void __init udbg_init_debug_lpar_hvsi(void)
|
||||
{
|
||||
hvterm_privs[0] = &hvterm_priv0;
|
||||
hvterm_priv0.termno = CONFIG_PPC_EARLY_DEBUG_HVSI_VTERMNO;
|
||||
hvterm_priv0.proto = HV_PROTOCOL_HVSI;
|
||||
spin_lock_init(&hvterm_priv0.buf_lock);
|
||||
udbg_putc = udbg_hvc_putc;
|
||||
udbg_getc = udbg_hvc_getc;
|
||||
udbg_getc_poll = udbg_hvc_getc_poll;
|
||||
hvsilib_init(&hvterm_priv0.hvsi, hvc_get_chars, hvc_put_chars,
|
||||
hvterm_priv0.termno, 1);
|
||||
hvsilib_establish(&hvterm_priv0.hvsi);
|
||||
}
|
||||
#endif /* CONFIG_PPC_EARLY_DEBUG_LPAR_HVSI */
|
654
drivers/tty/hvc/hvc_xen.c
Normal file
654
drivers/tty/hvc/hvc_xen.c
Normal file
|
@ -0,0 +1,654 @@
|
|||
/*
|
||||
* xen console driver interface to hvc_console.c
|
||||
*
|
||||
* (c) 2007 Gerd Hoffmann <kraxel@suse.de>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include <linux/console.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/list.h>
|
||||
|
||||
#include <asm/io.h>
|
||||
#include <asm/xen/hypervisor.h>
|
||||
|
||||
#include <xen/xen.h>
|
||||
#include <xen/interface/xen.h>
|
||||
#include <xen/hvm.h>
|
||||
#include <xen/grant_table.h>
|
||||
#include <xen/page.h>
|
||||
#include <xen/events.h>
|
||||
#include <xen/interface/io/console.h>
|
||||
#include <xen/interface/sched.h>
|
||||
#include <xen/hvc-console.h>
|
||||
#include <xen/xenbus.h>
|
||||
|
||||
#include "hvc_console.h"
|
||||
|
||||
#define HVC_COOKIE 0x58656e /* "Xen" in hex */
|
||||
|
||||
struct xencons_info {
|
||||
struct list_head list;
|
||||
struct xenbus_device *xbdev;
|
||||
struct xencons_interface *intf;
|
||||
unsigned int evtchn;
|
||||
struct hvc_struct *hvc;
|
||||
int irq;
|
||||
int vtermno;
|
||||
grant_ref_t gntref;
|
||||
};
|
||||
|
||||
static LIST_HEAD(xenconsoles);
|
||||
static DEFINE_SPINLOCK(xencons_lock);
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
static struct xencons_info *vtermno_to_xencons(int vtermno)
|
||||
{
|
||||
struct xencons_info *entry, *n, *ret = NULL;
|
||||
|
||||
if (list_empty(&xenconsoles))
|
||||
return NULL;
|
||||
|
||||
list_for_each_entry_safe(entry, n, &xenconsoles, list) {
|
||||
if (entry->vtermno == vtermno) {
|
||||
ret = entry;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static inline int xenbus_devid_to_vtermno(int devid)
|
||||
{
|
||||
return devid + HVC_COOKIE;
|
||||
}
|
||||
|
||||
static inline void notify_daemon(struct xencons_info *cons)
|
||||
{
|
||||
/* Use evtchn: this is called early, before irq is set up. */
|
||||
notify_remote_via_evtchn(cons->evtchn);
|
||||
}
|
||||
|
||||
static int __write_console(struct xencons_info *xencons,
|
||||
const char *data, int len)
|
||||
{
|
||||
XENCONS_RING_IDX cons, prod;
|
||||
struct xencons_interface *intf = xencons->intf;
|
||||
int sent = 0;
|
||||
|
||||
cons = intf->out_cons;
|
||||
prod = intf->out_prod;
|
||||
mb(); /* update queue values before going on */
|
||||
BUG_ON((prod - cons) > sizeof(intf->out));
|
||||
|
||||
while ((sent < len) && ((prod - cons) < sizeof(intf->out)))
|
||||
intf->out[MASK_XENCONS_IDX(prod++, intf->out)] = data[sent++];
|
||||
|
||||
wmb(); /* write ring before updating pointer */
|
||||
intf->out_prod = prod;
|
||||
|
||||
if (sent)
|
||||
notify_daemon(xencons);
|
||||
return sent;
|
||||
}
|
||||
|
||||
static int domU_write_console(uint32_t vtermno, const char *data, int len)
|
||||
{
|
||||
int ret = len;
|
||||
struct xencons_info *cons = vtermno_to_xencons(vtermno);
|
||||
if (cons == NULL)
|
||||
return -EINVAL;
|
||||
|
||||
/*
|
||||
* Make sure the whole buffer is emitted, polling if
|
||||
* necessary. We don't ever want to rely on the hvc daemon
|
||||
* because the most interesting console output is when the
|
||||
* kernel is crippled.
|
||||
*/
|
||||
while (len) {
|
||||
int sent = __write_console(cons, data, len);
|
||||
|
||||
data += sent;
|
||||
len -= sent;
|
||||
|
||||
if (unlikely(len))
|
||||
HYPERVISOR_sched_op(SCHEDOP_yield, NULL);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int domU_read_console(uint32_t vtermno, char *buf, int len)
|
||||
{
|
||||
struct xencons_interface *intf;
|
||||
XENCONS_RING_IDX cons, prod;
|
||||
int recv = 0;
|
||||
struct xencons_info *xencons = vtermno_to_xencons(vtermno);
|
||||
if (xencons == NULL)
|
||||
return -EINVAL;
|
||||
intf = xencons->intf;
|
||||
|
||||
cons = intf->in_cons;
|
||||
prod = intf->in_prod;
|
||||
mb(); /* get pointers before reading ring */
|
||||
BUG_ON((prod - cons) > sizeof(intf->in));
|
||||
|
||||
while (cons != prod && recv < len)
|
||||
buf[recv++] = intf->in[MASK_XENCONS_IDX(cons++, intf->in)];
|
||||
|
||||
mb(); /* read ring before consuming */
|
||||
intf->in_cons = cons;
|
||||
|
||||
notify_daemon(xencons);
|
||||
return recv;
|
||||
}
|
||||
|
||||
static struct hv_ops domU_hvc_ops = {
|
||||
.get_chars = domU_read_console,
|
||||
.put_chars = domU_write_console,
|
||||
.notifier_add = notifier_add_irq,
|
||||
.notifier_del = notifier_del_irq,
|
||||
.notifier_hangup = notifier_hangup_irq,
|
||||
};
|
||||
|
||||
static int dom0_read_console(uint32_t vtermno, char *buf, int len)
|
||||
{
|
||||
return HYPERVISOR_console_io(CONSOLEIO_read, len, buf);
|
||||
}
|
||||
|
||||
/*
|
||||
* Either for a dom0 to write to the system console, or a domU with a
|
||||
* debug version of Xen
|
||||
*/
|
||||
static int dom0_write_console(uint32_t vtermno, const char *str, int len)
|
||||
{
|
||||
int rc = HYPERVISOR_console_io(CONSOLEIO_write, len, (char *)str);
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
static struct hv_ops dom0_hvc_ops = {
|
||||
.get_chars = dom0_read_console,
|
||||
.put_chars = dom0_write_console,
|
||||
.notifier_add = notifier_add_irq,
|
||||
.notifier_del = notifier_del_irq,
|
||||
.notifier_hangup = notifier_hangup_irq,
|
||||
};
|
||||
|
||||
static int xen_hvm_console_init(void)
|
||||
{
|
||||
int r;
|
||||
uint64_t v = 0;
|
||||
unsigned long mfn;
|
||||
struct xencons_info *info;
|
||||
|
||||
if (!xen_hvm_domain())
|
||||
return -ENODEV;
|
||||
|
||||
info = vtermno_to_xencons(HVC_COOKIE);
|
||||
if (!info) {
|
||||
info = kzalloc(sizeof(struct xencons_info), GFP_KERNEL);
|
||||
if (!info)
|
||||
return -ENOMEM;
|
||||
} else if (info->intf != NULL) {
|
||||
/* already configured */
|
||||
return 0;
|
||||
}
|
||||
/*
|
||||
* If the toolstack (or the hypervisor) hasn't set these values, the
|
||||
* default value is 0. Even though mfn = 0 and evtchn = 0 are
|
||||
* theoretically correct values, in practice they never are and they
|
||||
* mean that a legacy toolstack hasn't initialized the pv console correctly.
|
||||
*/
|
||||
r = hvm_get_parameter(HVM_PARAM_CONSOLE_EVTCHN, &v);
|
||||
if (r < 0 || v == 0)
|
||||
goto err;
|
||||
info->evtchn = v;
|
||||
v = 0;
|
||||
r = hvm_get_parameter(HVM_PARAM_CONSOLE_PFN, &v);
|
||||
if (r < 0 || v == 0)
|
||||
goto err;
|
||||
mfn = v;
|
||||
info->intf = xen_remap(mfn << PAGE_SHIFT, PAGE_SIZE);
|
||||
if (info->intf == NULL)
|
||||
goto err;
|
||||
info->vtermno = HVC_COOKIE;
|
||||
|
||||
spin_lock(&xencons_lock);
|
||||
list_add_tail(&info->list, &xenconsoles);
|
||||
spin_unlock(&xencons_lock);
|
||||
|
||||
return 0;
|
||||
err:
|
||||
kfree(info);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
static int xen_pv_console_init(void)
|
||||
{
|
||||
struct xencons_info *info;
|
||||
|
||||
if (!xen_pv_domain())
|
||||
return -ENODEV;
|
||||
|
||||
if (!xen_start_info->console.domU.evtchn)
|
||||
return -ENODEV;
|
||||
|
||||
info = vtermno_to_xencons(HVC_COOKIE);
|
||||
if (!info) {
|
||||
info = kzalloc(sizeof(struct xencons_info), GFP_KERNEL);
|
||||
if (!info)
|
||||
return -ENOMEM;
|
||||
} else if (info->intf != NULL) {
|
||||
/* already configured */
|
||||
return 0;
|
||||
}
|
||||
info->evtchn = xen_start_info->console.domU.evtchn;
|
||||
info->intf = mfn_to_virt(xen_start_info->console.domU.mfn);
|
||||
info->vtermno = HVC_COOKIE;
|
||||
|
||||
spin_lock(&xencons_lock);
|
||||
list_add_tail(&info->list, &xenconsoles);
|
||||
spin_unlock(&xencons_lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int xen_initial_domain_console_init(void)
|
||||
{
|
||||
struct xencons_info *info;
|
||||
|
||||
if (!xen_initial_domain())
|
||||
return -ENODEV;
|
||||
|
||||
info = vtermno_to_xencons(HVC_COOKIE);
|
||||
if (!info) {
|
||||
info = kzalloc(sizeof(struct xencons_info), GFP_KERNEL);
|
||||
if (!info)
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
info->irq = bind_virq_to_irq(VIRQ_CONSOLE, 0);
|
||||
info->vtermno = HVC_COOKIE;
|
||||
|
||||
spin_lock(&xencons_lock);
|
||||
list_add_tail(&info->list, &xenconsoles);
|
||||
spin_unlock(&xencons_lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void xen_console_resume(void)
|
||||
{
|
||||
struct xencons_info *info = vtermno_to_xencons(HVC_COOKIE);
|
||||
if (info != NULL && info->irq)
|
||||
rebind_evtchn_irq(info->evtchn, info->irq);
|
||||
}
|
||||
|
||||
static void xencons_disconnect_backend(struct xencons_info *info)
|
||||
{
|
||||
if (info->irq > 0)
|
||||
unbind_from_irqhandler(info->irq, NULL);
|
||||
info->irq = 0;
|
||||
if (info->evtchn > 0)
|
||||
xenbus_free_evtchn(info->xbdev, info->evtchn);
|
||||
info->evtchn = 0;
|
||||
if (info->gntref > 0)
|
||||
gnttab_free_grant_references(info->gntref);
|
||||
info->gntref = 0;
|
||||
if (info->hvc != NULL)
|
||||
hvc_remove(info->hvc);
|
||||
info->hvc = NULL;
|
||||
}
|
||||
|
||||
static void xencons_free(struct xencons_info *info)
|
||||
{
|
||||
free_page((unsigned long)info->intf);
|
||||
info->intf = NULL;
|
||||
info->vtermno = 0;
|
||||
kfree(info);
|
||||
}
|
||||
|
||||
static int xen_console_remove(struct xencons_info *info)
|
||||
{
|
||||
xencons_disconnect_backend(info);
|
||||
spin_lock(&xencons_lock);
|
||||
list_del(&info->list);
|
||||
spin_unlock(&xencons_lock);
|
||||
if (info->xbdev != NULL)
|
||||
xencons_free(info);
|
||||
else {
|
||||
if (xen_hvm_domain())
|
||||
iounmap(info->intf);
|
||||
kfree(info);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_HVC_XEN_FRONTEND
|
||||
static int xencons_remove(struct xenbus_device *dev)
|
||||
{
|
||||
return xen_console_remove(dev_get_drvdata(&dev->dev));
|
||||
}
|
||||
|
||||
static int xencons_connect_backend(struct xenbus_device *dev,
|
||||
struct xencons_info *info)
|
||||
{
|
||||
int ret, evtchn, devid, ref, irq;
|
||||
struct xenbus_transaction xbt;
|
||||
grant_ref_t gref_head;
|
||||
unsigned long mfn;
|
||||
|
||||
ret = xenbus_alloc_evtchn(dev, &evtchn);
|
||||
if (ret)
|
||||
return ret;
|
||||
info->evtchn = evtchn;
|
||||
irq = bind_evtchn_to_irq(evtchn);
|
||||
if (irq < 0)
|
||||
return irq;
|
||||
info->irq = irq;
|
||||
devid = dev->nodename[strlen(dev->nodename) - 1] - '0';
|
||||
info->hvc = hvc_alloc(xenbus_devid_to_vtermno(devid),
|
||||
irq, &domU_hvc_ops, 256);
|
||||
if (IS_ERR(info->hvc))
|
||||
return PTR_ERR(info->hvc);
|
||||
if (xen_pv_domain())
|
||||
mfn = virt_to_mfn(info->intf);
|
||||
else
|
||||
mfn = __pa(info->intf) >> PAGE_SHIFT;
|
||||
ret = gnttab_alloc_grant_references(1, &gref_head);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
info->gntref = gref_head;
|
||||
ref = gnttab_claim_grant_reference(&gref_head);
|
||||
if (ref < 0)
|
||||
return ref;
|
||||
gnttab_grant_foreign_access_ref(ref, info->xbdev->otherend_id,
|
||||
mfn, 0);
|
||||
|
||||
again:
|
||||
ret = xenbus_transaction_start(&xbt);
|
||||
if (ret) {
|
||||
xenbus_dev_fatal(dev, ret, "starting transaction");
|
||||
return ret;
|
||||
}
|
||||
ret = xenbus_printf(xbt, dev->nodename, "ring-ref", "%d", ref);
|
||||
if (ret)
|
||||
goto error_xenbus;
|
||||
ret = xenbus_printf(xbt, dev->nodename, "port", "%u",
|
||||
evtchn);
|
||||
if (ret)
|
||||
goto error_xenbus;
|
||||
ret = xenbus_transaction_end(xbt, 0);
|
||||
if (ret) {
|
||||
if (ret == -EAGAIN)
|
||||
goto again;
|
||||
xenbus_dev_fatal(dev, ret, "completing transaction");
|
||||
return ret;
|
||||
}
|
||||
|
||||
xenbus_switch_state(dev, XenbusStateInitialised);
|
||||
return 0;
|
||||
|
||||
error_xenbus:
|
||||
xenbus_transaction_end(xbt, 1);
|
||||
xenbus_dev_fatal(dev, ret, "writing xenstore");
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int xencons_probe(struct xenbus_device *dev,
|
||||
const struct xenbus_device_id *id)
|
||||
{
|
||||
int ret, devid;
|
||||
struct xencons_info *info;
|
||||
|
||||
devid = dev->nodename[strlen(dev->nodename) - 1] - '0';
|
||||
if (devid == 0)
|
||||
return -ENODEV;
|
||||
|
||||
info = kzalloc(sizeof(struct xencons_info), GFP_KERNEL);
|
||||
if (!info)
|
||||
return -ENOMEM;
|
||||
dev_set_drvdata(&dev->dev, info);
|
||||
info->xbdev = dev;
|
||||
info->vtermno = xenbus_devid_to_vtermno(devid);
|
||||
info->intf = (void *)__get_free_page(GFP_KERNEL | __GFP_ZERO);
|
||||
if (!info->intf)
|
||||
goto error_nomem;
|
||||
|
||||
ret = xencons_connect_backend(dev, info);
|
||||
if (ret < 0)
|
||||
goto error;
|
||||
spin_lock(&xencons_lock);
|
||||
list_add_tail(&info->list, &xenconsoles);
|
||||
spin_unlock(&xencons_lock);
|
||||
|
||||
return 0;
|
||||
|
||||
error_nomem:
|
||||
ret = -ENOMEM;
|
||||
xenbus_dev_fatal(dev, ret, "allocating device memory");
|
||||
error:
|
||||
xencons_disconnect_backend(info);
|
||||
xencons_free(info);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int xencons_resume(struct xenbus_device *dev)
|
||||
{
|
||||
struct xencons_info *info = dev_get_drvdata(&dev->dev);
|
||||
|
||||
xencons_disconnect_backend(info);
|
||||
memset(info->intf, 0, PAGE_SIZE);
|
||||
return xencons_connect_backend(dev, info);
|
||||
}
|
||||
|
||||
static void xencons_backend_changed(struct xenbus_device *dev,
|
||||
enum xenbus_state backend_state)
|
||||
{
|
||||
switch (backend_state) {
|
||||
case XenbusStateReconfiguring:
|
||||
case XenbusStateReconfigured:
|
||||
case XenbusStateInitialising:
|
||||
case XenbusStateInitialised:
|
||||
case XenbusStateUnknown:
|
||||
break;
|
||||
|
||||
case XenbusStateInitWait:
|
||||
break;
|
||||
|
||||
case XenbusStateConnected:
|
||||
xenbus_switch_state(dev, XenbusStateConnected);
|
||||
break;
|
||||
|
||||
case XenbusStateClosed:
|
||||
if (dev->state == XenbusStateClosed)
|
||||
break;
|
||||
/* Missed the backend's CLOSING state -- fallthrough */
|
||||
case XenbusStateClosing:
|
||||
xenbus_frontend_closed(dev);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static const struct xenbus_device_id xencons_ids[] = {
|
||||
{ "console" },
|
||||
{ "" }
|
||||
};
|
||||
|
||||
static struct xenbus_driver xencons_driver = {
|
||||
.name = "xenconsole",
|
||||
.ids = xencons_ids,
|
||||
.probe = xencons_probe,
|
||||
.remove = xencons_remove,
|
||||
.resume = xencons_resume,
|
||||
.otherend_changed = xencons_backend_changed,
|
||||
};
|
||||
#endif /* CONFIG_HVC_XEN_FRONTEND */
|
||||
|
||||
static int __init xen_hvc_init(void)
|
||||
{
|
||||
int r;
|
||||
struct xencons_info *info;
|
||||
const struct hv_ops *ops;
|
||||
|
||||
if (!xen_domain())
|
||||
return -ENODEV;
|
||||
|
||||
if (xen_initial_domain()) {
|
||||
ops = &dom0_hvc_ops;
|
||||
r = xen_initial_domain_console_init();
|
||||
if (r < 0)
|
||||
return r;
|
||||
info = vtermno_to_xencons(HVC_COOKIE);
|
||||
} else {
|
||||
ops = &domU_hvc_ops;
|
||||
if (xen_hvm_domain())
|
||||
r = xen_hvm_console_init();
|
||||
else
|
||||
r = xen_pv_console_init();
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
info = vtermno_to_xencons(HVC_COOKIE);
|
||||
info->irq = bind_evtchn_to_irq(info->evtchn);
|
||||
}
|
||||
if (info->irq < 0)
|
||||
info->irq = 0; /* NO_IRQ */
|
||||
else
|
||||
irq_set_noprobe(info->irq);
|
||||
|
||||
info->hvc = hvc_alloc(HVC_COOKIE, info->irq, ops, 256);
|
||||
if (IS_ERR(info->hvc)) {
|
||||
r = PTR_ERR(info->hvc);
|
||||
spin_lock(&xencons_lock);
|
||||
list_del(&info->list);
|
||||
spin_unlock(&xencons_lock);
|
||||
if (info->irq)
|
||||
unbind_from_irqhandler(info->irq, NULL);
|
||||
kfree(info);
|
||||
return r;
|
||||
}
|
||||
|
||||
r = 0;
|
||||
#ifdef CONFIG_HVC_XEN_FRONTEND
|
||||
r = xenbus_register_frontend(&xencons_driver);
|
||||
#endif
|
||||
return r;
|
||||
}
|
||||
device_initcall(xen_hvc_init);
|
||||
|
||||
static int xen_cons_init(void)
|
||||
{
|
||||
const struct hv_ops *ops;
|
||||
|
||||
if (!xen_domain())
|
||||
return 0;
|
||||
|
||||
if (xen_initial_domain())
|
||||
ops = &dom0_hvc_ops;
|
||||
else {
|
||||
int r;
|
||||
ops = &domU_hvc_ops;
|
||||
|
||||
if (xen_hvm_domain())
|
||||
r = xen_hvm_console_init();
|
||||
else
|
||||
r = xen_pv_console_init();
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
hvc_instantiate(HVC_COOKIE, 0, ops);
|
||||
return 0;
|
||||
}
|
||||
console_initcall(xen_cons_init);
|
||||
|
||||
#ifdef CONFIG_EARLY_PRINTK
|
||||
static void xenboot_write_console(struct console *console, const char *string,
|
||||
unsigned len)
|
||||
{
|
||||
unsigned int linelen, off = 0;
|
||||
const char *pos;
|
||||
|
||||
if (!xen_pv_domain())
|
||||
return;
|
||||
|
||||
dom0_write_console(0, string, len);
|
||||
|
||||
if (xen_initial_domain())
|
||||
return;
|
||||
|
||||
domU_write_console(0, "(early) ", 8);
|
||||
while (off < len && NULL != (pos = strchr(string+off, '\n'))) {
|
||||
linelen = pos-string+off;
|
||||
if (off + linelen > len)
|
||||
break;
|
||||
domU_write_console(0, string+off, linelen);
|
||||
domU_write_console(0, "\r\n", 2);
|
||||
off += linelen + 1;
|
||||
}
|
||||
if (off < len)
|
||||
domU_write_console(0, string+off, len-off);
|
||||
}
|
||||
|
||||
struct console xenboot_console = {
|
||||
.name = "xenboot",
|
||||
.write = xenboot_write_console,
|
||||
.flags = CON_PRINTBUFFER | CON_BOOT | CON_ANYTIME,
|
||||
.index = -1,
|
||||
};
|
||||
#endif /* CONFIG_EARLY_PRINTK */
|
||||
|
||||
void xen_raw_console_write(const char *str)
|
||||
{
|
||||
ssize_t len = strlen(str);
|
||||
int rc = 0;
|
||||
|
||||
if (xen_domain()) {
|
||||
rc = dom0_write_console(0, str, len);
|
||||
#ifdef CONFIG_X86
|
||||
if (rc == -ENOSYS && xen_hvm_domain())
|
||||
goto outb_print;
|
||||
|
||||
} else if (xen_cpuid_base()) {
|
||||
int i;
|
||||
outb_print:
|
||||
for (i = 0; i < len; i++)
|
||||
outb(str[i], 0xe9);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void xen_raw_printk(const char *fmt, ...)
|
||||
{
|
||||
static char buf[512];
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, fmt);
|
||||
vsnprintf(buf, sizeof(buf), fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
xen_raw_console_write(buf);
|
||||
}
|
1616
drivers/tty/hvc/hvcs.c
Normal file
1616
drivers/tty/hvc/hvcs.c
Normal file
File diff suppressed because it is too large
Load diff
1221
drivers/tty/hvc/hvsi.c
Normal file
1221
drivers/tty/hvc/hvsi.c
Normal file
File diff suppressed because it is too large
Load diff
424
drivers/tty/hvc/hvsi_lib.c
Normal file
424
drivers/tty/hvc/hvsi_lib.c
Normal file
|
@ -0,0 +1,424 @@
|
|||
#include <linux/types.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/console.h>
|
||||
#include <asm/hvsi.h>
|
||||
|
||||
#include "hvc_console.h"
|
||||
|
||||
static int hvsi_send_packet(struct hvsi_priv *pv, struct hvsi_header *packet)
|
||||
{
|
||||
packet->seqno = cpu_to_be16(atomic_inc_return(&pv->seqno));
|
||||
|
||||
/* Assumes that always succeeds, works in practice */
|
||||
return pv->put_chars(pv->termno, (char *)packet, packet->len);
|
||||
}
|
||||
|
||||
static void hvsi_start_handshake(struct hvsi_priv *pv)
|
||||
{
|
||||
struct hvsi_query q;
|
||||
|
||||
/* Reset state */
|
||||
pv->established = 0;
|
||||
atomic_set(&pv->seqno, 0);
|
||||
|
||||
pr_devel("HVSI@%x: Handshaking started\n", pv->termno);
|
||||
|
||||
/* Send version query */
|
||||
q.hdr.type = VS_QUERY_PACKET_HEADER;
|
||||
q.hdr.len = sizeof(struct hvsi_query);
|
||||
q.verb = cpu_to_be16(VSV_SEND_VERSION_NUMBER);
|
||||
hvsi_send_packet(pv, &q.hdr);
|
||||
}
|
||||
|
||||
static int hvsi_send_close(struct hvsi_priv *pv)
|
||||
{
|
||||
struct hvsi_control ctrl;
|
||||
|
||||
pv->established = 0;
|
||||
|
||||
ctrl.hdr.type = VS_CONTROL_PACKET_HEADER;
|
||||
ctrl.hdr.len = sizeof(struct hvsi_control);
|
||||
ctrl.verb = cpu_to_be16(VSV_CLOSE_PROTOCOL);
|
||||
return hvsi_send_packet(pv, &ctrl.hdr);
|
||||
}
|
||||
|
||||
static void hvsi_cd_change(struct hvsi_priv *pv, int cd)
|
||||
{
|
||||
if (cd)
|
||||
pv->mctrl |= TIOCM_CD;
|
||||
else {
|
||||
pv->mctrl &= ~TIOCM_CD;
|
||||
|
||||
/* We copy the existing hvsi driver semantics
|
||||
* here which are to trigger a hangup when
|
||||
* we get a carrier loss.
|
||||
* Closing our connection to the server will
|
||||
* do just that.
|
||||
*/
|
||||
if (!pv->is_console && pv->opened) {
|
||||
pr_devel("HVSI@%x Carrier lost, hanging up !\n",
|
||||
pv->termno);
|
||||
hvsi_send_close(pv);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void hvsi_got_control(struct hvsi_priv *pv)
|
||||
{
|
||||
struct hvsi_control *pkt = (struct hvsi_control *)pv->inbuf;
|
||||
|
||||
switch (be16_to_cpu(pkt->verb)) {
|
||||
case VSV_CLOSE_PROTOCOL:
|
||||
/* We restart the handshaking */
|
||||
hvsi_start_handshake(pv);
|
||||
break;
|
||||
case VSV_MODEM_CTL_UPDATE:
|
||||
/* Transition of carrier detect */
|
||||
hvsi_cd_change(pv, be32_to_cpu(pkt->word) & HVSI_TSCD);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void hvsi_got_query(struct hvsi_priv *pv)
|
||||
{
|
||||
struct hvsi_query *pkt = (struct hvsi_query *)pv->inbuf;
|
||||
struct hvsi_query_response r;
|
||||
|
||||
/* We only handle version queries */
|
||||
if (be16_to_cpu(pkt->verb) != VSV_SEND_VERSION_NUMBER)
|
||||
return;
|
||||
|
||||
pr_devel("HVSI@%x: Got version query, sending response...\n",
|
||||
pv->termno);
|
||||
|
||||
/* Send version response */
|
||||
r.hdr.type = VS_QUERY_RESPONSE_PACKET_HEADER;
|
||||
r.hdr.len = sizeof(struct hvsi_query_response);
|
||||
r.verb = cpu_to_be16(VSV_SEND_VERSION_NUMBER);
|
||||
r.u.version = HVSI_VERSION;
|
||||
r.query_seqno = pkt->hdr.seqno;
|
||||
hvsi_send_packet(pv, &r.hdr);
|
||||
|
||||
/* Assume protocol is open now */
|
||||
pv->established = 1;
|
||||
}
|
||||
|
||||
static void hvsi_got_response(struct hvsi_priv *pv)
|
||||
{
|
||||
struct hvsi_query_response *r =
|
||||
(struct hvsi_query_response *)pv->inbuf;
|
||||
|
||||
switch(r->verb) {
|
||||
case VSV_SEND_MODEM_CTL_STATUS:
|
||||
hvsi_cd_change(pv, be32_to_cpu(r->u.mctrl_word) & HVSI_TSCD);
|
||||
pv->mctrl_update = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static int hvsi_check_packet(struct hvsi_priv *pv)
|
||||
{
|
||||
u8 len, type;
|
||||
|
||||
/* Check header validity. If it's invalid, we ditch
|
||||
* the whole buffer and hope we eventually resync
|
||||
*/
|
||||
if (pv->inbuf[0] < 0xfc) {
|
||||
pv->inbuf_len = pv->inbuf_pktlen = 0;
|
||||
return 0;
|
||||
}
|
||||
type = pv->inbuf[0];
|
||||
len = pv->inbuf[1];
|
||||
|
||||
/* Packet incomplete ? */
|
||||
if (pv->inbuf_len < len)
|
||||
return 0;
|
||||
|
||||
pr_devel("HVSI@%x: Got packet type %x len %d bytes:\n",
|
||||
pv->termno, type, len);
|
||||
|
||||
/* We have a packet, yay ! Handle it */
|
||||
switch(type) {
|
||||
case VS_DATA_PACKET_HEADER:
|
||||
pv->inbuf_pktlen = len - 4;
|
||||
pv->inbuf_cur = 4;
|
||||
return 1;
|
||||
case VS_CONTROL_PACKET_HEADER:
|
||||
hvsi_got_control(pv);
|
||||
break;
|
||||
case VS_QUERY_PACKET_HEADER:
|
||||
hvsi_got_query(pv);
|
||||
break;
|
||||
case VS_QUERY_RESPONSE_PACKET_HEADER:
|
||||
hvsi_got_response(pv);
|
||||
break;
|
||||
}
|
||||
|
||||
/* Swallow packet and retry */
|
||||
pv->inbuf_len -= len;
|
||||
memmove(pv->inbuf, &pv->inbuf[len], pv->inbuf_len);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int hvsi_get_packet(struct hvsi_priv *pv)
|
||||
{
|
||||
/* If we have room in the buffer, ask HV for more */
|
||||
if (pv->inbuf_len < HVSI_INBUF_SIZE)
|
||||
pv->inbuf_len += pv->get_chars(pv->termno,
|
||||
&pv->inbuf[pv->inbuf_len],
|
||||
HVSI_INBUF_SIZE - pv->inbuf_len);
|
||||
/*
|
||||
* If we have at least 4 bytes in the buffer, check for
|
||||
* a full packet and retry
|
||||
*/
|
||||
if (pv->inbuf_len >= 4)
|
||||
return hvsi_check_packet(pv);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int hvsilib_get_chars(struct hvsi_priv *pv, char *buf, int count)
|
||||
{
|
||||
unsigned int tries, read = 0;
|
||||
|
||||
if (WARN_ON(!pv))
|
||||
return -ENXIO;
|
||||
|
||||
/* If we aren't open, don't do anything in order to avoid races
|
||||
* with connection establishment. The hvc core will call this
|
||||
* before we have returned from notifier_add(), and we need to
|
||||
* avoid multiple users playing with the receive buffer
|
||||
*/
|
||||
if (!pv->opened)
|
||||
return 0;
|
||||
|
||||
/* We try twice, once with what data we have and once more
|
||||
* after we try to fetch some more from the hypervisor
|
||||
*/
|
||||
for (tries = 1; count && tries < 2; tries++) {
|
||||
/* Consume existing data packet */
|
||||
if (pv->inbuf_pktlen) {
|
||||
unsigned int l = min(count, (int)pv->inbuf_pktlen);
|
||||
memcpy(&buf[read], &pv->inbuf[pv->inbuf_cur], l);
|
||||
pv->inbuf_cur += l;
|
||||
pv->inbuf_pktlen -= l;
|
||||
count -= l;
|
||||
read += l;
|
||||
}
|
||||
if (count == 0)
|
||||
break;
|
||||
|
||||
/* Data packet fully consumed, move down remaning data */
|
||||
if (pv->inbuf_cur) {
|
||||
pv->inbuf_len -= pv->inbuf_cur;
|
||||
memmove(pv->inbuf, &pv->inbuf[pv->inbuf_cur],
|
||||
pv->inbuf_len);
|
||||
pv->inbuf_cur = 0;
|
||||
}
|
||||
|
||||
/* Try to get another packet */
|
||||
if (hvsi_get_packet(pv))
|
||||
tries--;
|
||||
}
|
||||
if (!pv->established) {
|
||||
pr_devel("HVSI@%x: returning -EPIPE\n", pv->termno);
|
||||
return -EPIPE;
|
||||
}
|
||||
return read;
|
||||
}
|
||||
|
||||
int hvsilib_put_chars(struct hvsi_priv *pv, const char *buf, int count)
|
||||
{
|
||||
struct hvsi_data dp;
|
||||
int rc, adjcount = min(count, HVSI_MAX_OUTGOING_DATA);
|
||||
|
||||
if (WARN_ON(!pv))
|
||||
return -ENODEV;
|
||||
|
||||
dp.hdr.type = VS_DATA_PACKET_HEADER;
|
||||
dp.hdr.len = adjcount + sizeof(struct hvsi_header);
|
||||
memcpy(dp.data, buf, adjcount);
|
||||
rc = hvsi_send_packet(pv, &dp.hdr);
|
||||
if (rc <= 0)
|
||||
return rc;
|
||||
return adjcount;
|
||||
}
|
||||
|
||||
static void maybe_msleep(unsigned long ms)
|
||||
{
|
||||
/* During early boot, IRQs are disabled, use mdelay */
|
||||
if (irqs_disabled())
|
||||
mdelay(ms);
|
||||
else
|
||||
msleep(ms);
|
||||
}
|
||||
|
||||
int hvsilib_read_mctrl(struct hvsi_priv *pv)
|
||||
{
|
||||
struct hvsi_query q;
|
||||
int rc, timeout;
|
||||
|
||||
pr_devel("HVSI@%x: Querying modem control status...\n",
|
||||
pv->termno);
|
||||
|
||||
pv->mctrl_update = 0;
|
||||
q.hdr.type = VS_QUERY_PACKET_HEADER;
|
||||
q.hdr.len = sizeof(struct hvsi_query);
|
||||
q.verb = cpu_to_be16(VSV_SEND_MODEM_CTL_STATUS);
|
||||
rc = hvsi_send_packet(pv, &q.hdr);
|
||||
if (rc <= 0) {
|
||||
pr_devel("HVSI@%x: Error %d...\n", pv->termno, rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Try for up to 200ms */
|
||||
for (timeout = 0; timeout < 20; timeout++) {
|
||||
if (!pv->established)
|
||||
return -ENXIO;
|
||||
if (pv->mctrl_update)
|
||||
return 0;
|
||||
if (!hvsi_get_packet(pv))
|
||||
maybe_msleep(10);
|
||||
}
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
int hvsilib_write_mctrl(struct hvsi_priv *pv, int dtr)
|
||||
{
|
||||
struct hvsi_control ctrl;
|
||||
unsigned short mctrl;
|
||||
|
||||
mctrl = pv->mctrl;
|
||||
if (dtr)
|
||||
mctrl |= TIOCM_DTR;
|
||||
else
|
||||
mctrl &= ~TIOCM_DTR;
|
||||
if (mctrl == pv->mctrl)
|
||||
return 0;
|
||||
pv->mctrl = mctrl;
|
||||
|
||||
pr_devel("HVSI@%x: %s DTR...\n", pv->termno,
|
||||
dtr ? "Setting" : "Clearing");
|
||||
|
||||
ctrl.hdr.type = VS_CONTROL_PACKET_HEADER,
|
||||
ctrl.hdr.len = sizeof(struct hvsi_control);
|
||||
ctrl.verb = cpu_to_be16(VSV_SET_MODEM_CTL);
|
||||
ctrl.mask = cpu_to_be32(HVSI_TSDTR);
|
||||
ctrl.word = cpu_to_be32(dtr ? HVSI_TSDTR : 0);
|
||||
return hvsi_send_packet(pv, &ctrl.hdr);
|
||||
}
|
||||
|
||||
void hvsilib_establish(struct hvsi_priv *pv)
|
||||
{
|
||||
int timeout;
|
||||
|
||||
pr_devel("HVSI@%x: Establishing...\n", pv->termno);
|
||||
|
||||
/* Try for up to 200ms, there can be a packet to
|
||||
* start the process waiting for us...
|
||||
*/
|
||||
for (timeout = 0; timeout < 20; timeout++) {
|
||||
if (pv->established)
|
||||
goto established;
|
||||
if (!hvsi_get_packet(pv))
|
||||
maybe_msleep(10);
|
||||
}
|
||||
|
||||
/* Failed, send a close connection packet just
|
||||
* in case
|
||||
*/
|
||||
pr_devel("HVSI@%x: ... sending close\n", pv->termno);
|
||||
|
||||
hvsi_send_close(pv);
|
||||
|
||||
/* Then restart handshake */
|
||||
|
||||
pr_devel("HVSI@%x: ... restarting handshake\n", pv->termno);
|
||||
|
||||
hvsi_start_handshake(pv);
|
||||
|
||||
pr_devel("HVSI@%x: ... waiting handshake\n", pv->termno);
|
||||
|
||||
/* Try for up to 400ms */
|
||||
for (timeout = 0; timeout < 40; timeout++) {
|
||||
if (pv->established)
|
||||
goto established;
|
||||
if (!hvsi_get_packet(pv))
|
||||
maybe_msleep(10);
|
||||
}
|
||||
|
||||
if (!pv->established) {
|
||||
pr_devel("HVSI@%x: Timeout handshaking, giving up !\n",
|
||||
pv->termno);
|
||||
return;
|
||||
}
|
||||
established:
|
||||
/* Query modem control lines */
|
||||
|
||||
pr_devel("HVSI@%x: ... established, reading mctrl\n", pv->termno);
|
||||
|
||||
hvsilib_read_mctrl(pv);
|
||||
|
||||
/* Set our own DTR */
|
||||
|
||||
pr_devel("HVSI@%x: ... setting mctrl\n", pv->termno);
|
||||
|
||||
hvsilib_write_mctrl(pv, 1);
|
||||
|
||||
/* Set the opened flag so reads are allowed */
|
||||
wmb();
|
||||
pv->opened = 1;
|
||||
}
|
||||
|
||||
int hvsilib_open(struct hvsi_priv *pv, struct hvc_struct *hp)
|
||||
{
|
||||
pr_devel("HVSI@%x: open !\n", pv->termno);
|
||||
|
||||
/* Keep track of the tty data structure */
|
||||
pv->tty = tty_port_tty_get(&hp->port);
|
||||
|
||||
hvsilib_establish(pv);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void hvsilib_close(struct hvsi_priv *pv, struct hvc_struct *hp)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
pr_devel("HVSI@%x: close !\n", pv->termno);
|
||||
|
||||
if (!pv->is_console) {
|
||||
pr_devel("HVSI@%x: Not a console, tearing down\n",
|
||||
pv->termno);
|
||||
|
||||
/* Clear opened, synchronize with khvcd */
|
||||
spin_lock_irqsave(&hp->lock, flags);
|
||||
pv->opened = 0;
|
||||
spin_unlock_irqrestore(&hp->lock, flags);
|
||||
|
||||
/* Clear our own DTR */
|
||||
if (!pv->tty || (pv->tty->termios.c_cflag & HUPCL))
|
||||
hvsilib_write_mctrl(pv, 0);
|
||||
|
||||
/* Tear down the connection */
|
||||
hvsi_send_close(pv);
|
||||
}
|
||||
|
||||
if (pv->tty)
|
||||
tty_kref_put(pv->tty);
|
||||
pv->tty = NULL;
|
||||
}
|
||||
|
||||
void hvsilib_init(struct hvsi_priv *pv,
|
||||
int (*get_chars)(uint32_t termno, char *buf, int count),
|
||||
int (*put_chars)(uint32_t termno, const char *buf,
|
||||
int count),
|
||||
int termno, int is_console)
|
||||
{
|
||||
memset(pv, 0, sizeof(*pv));
|
||||
pv->get_chars = get_chars;
|
||||
pv->put_chars = put_chars;
|
||||
pv->termno = termno;
|
||||
pv->is_console = is_console;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue