Fixed MTP to work with TWRP

This commit is contained in:
awab228 2018-06-19 23:16:04 +02:00
commit f6dfaef42e
50820 changed files with 20846062 additions and 0 deletions

124
drivers/tty/hvc/Kconfig Normal file
View 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
View 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
View 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);

View 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);

File diff suppressed because it is too large Load diff

View 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
View 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
View 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

File diff suppressed because it is too large Load diff

430
drivers/tty/hvc/hvc_opal.c Normal file
View 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
View 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
View 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);

View 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
View 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
View 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

File diff suppressed because it is too large Load diff

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
View 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;
}