Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+136
View File
@@ -0,0 +1,136 @@
# SPDX-License-Identifier: GPL-2.0
config HVC_DRIVER
bool
help
Generic "hypervisor virtual console" infrastructure for various
hypervisors (pSeries, iSeries, Xen).
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
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_IUCV
bool "z/VM IUCV Hypervisor console support (VM only)"
depends on S390 && NET
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
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 || ARM64
select HVC_DRIVER
select SERIAL_CORE_CONSOLE
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_DCC_SERIALIZE_SMP
bool "Use DCC only on CPU core 0"
depends on SMP && HVC_DCC
help
This is a DEBUG option to serialize all console input and output to CPU 0.
Some external debuggers, do not handle reads/writes from/to DCC on more
than one CPU core. Each core has its own DCC device registers, so when a
CPU core reads or writes from/to DCC, it only accesses its own DCC device.
Since kernel code can run on any CPU core, every time the kernel wants to
write to the console, it might write to a different DCC.
In SMP mode, external debuggers create multiple windows, and each window
shows the DCC output only from that core's DCC. The result is that
console output is either lost or scattered across windows.
Enable this option only if you are sure that you do not need features like
CPU hotplug to work. For example, during early chipset bringups without
debug serial console support. If unsure, say N.
config HVC_RISCV_SBI
bool "RISC-V SBI console support"
depends on RISCV_SBI && NONPORTABLE
select HVC_DRIVER
help
This enables support for console output via RISC-V SBI calls, which
is normally used only during boot to output printk. This driver
conflicts with real console drivers and should not be enabled on
systems that directly access the console.
If you don't know what do to here, say N.
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.
+13
View File
@@ -0,0 +1,13 @@
# SPDX-License-Identifier: GPL-2.0
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_DCC) += hvc_dcc.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_RISCV_SBI) += hvc_riscv_sbi.o
obj-$(CONFIG_HVCS) += hvcs.o
File diff suppressed because it is too large Load Diff
+113
View File
@@ -0,0 +1,113 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* 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
*/
#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;
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;
unsigned long flags;
u8 outbuf[] __aligned(sizeof(long));
};
/* implemented by a low level driver */
struct hv_ops {
ssize_t (*get_chars)(uint32_t vtermno, u8 *buf, size_t count);
ssize_t (*put_chars)(uint32_t vtermno, const u8 *buf, size_t count);
int (*flush)(uint32_t vtermno, bool wait);
/* 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, bool active);
};
/* 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 void 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
+309
View File
@@ -0,0 +1,309 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2010, 2014, 2022 The Linux Foundation. All rights reserved. */
#include <linux/console.h>
#include <linux/cpu.h>
#include <linux/cpumask.h>
#include <linux/init.h>
#include <linux/kfifo.h>
#include <linux/serial.h>
#include <linux/serial_core.h>
#include <linux/smp.h>
#include <linux/spinlock.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)
#define DCC_INBUF_SIZE 128
#define DCC_OUTBUF_SIZE 1024
/* Lock to serialize access to DCC fifo */
static DEFINE_SPINLOCK(dcc_lock);
static DEFINE_KFIFO(inbuf, u8, DCC_INBUF_SIZE);
static DEFINE_KFIFO(outbuf, u8, DCC_OUTBUF_SIZE);
static void dcc_uart_console_putchar(struct uart_port *port, u8 ch)
{
while (__dcc_getstatus() & DCC_STATUS_TX)
cpu_relax();
__dcc_putchar(ch);
}
static void dcc_early_write(struct console *con, const char *s, unsigned n)
{
struct earlycon_device *dev = con->data;
uart_console_write(&dev->port, s, n, dcc_uart_console_putchar);
}
static int __init dcc_early_console_setup(struct earlycon_device *device,
const char *opt)
{
unsigned int count = 0x4000000;
while (--count && (__dcc_getstatus() & DCC_STATUS_TX))
cpu_relax();
if (__dcc_getstatus() & DCC_STATUS_TX)
return -ENODEV;
device->con->write = dcc_early_write;
return 0;
}
EARLYCON_DECLARE(dcc, dcc_early_console_setup);
static ssize_t hvc_dcc_put_chars(uint32_t vt, const u8 *buf, size_t count)
{
size_t i;
for (i = 0; i < count; i++) {
while (__dcc_getstatus() & DCC_STATUS_TX)
cpu_relax();
__dcc_putchar(buf[i]);
}
return count;
}
static ssize_t hvc_dcc_get_chars(uint32_t vt, u8 *buf, size_t count)
{
size_t i;
for (i = 0; i < count; ++i)
if (__dcc_getstatus() & DCC_STATUS_RX)
buf[i] = __dcc_getchar();
else
break;
return i;
}
/*
* Check if the DCC is enabled. If CONFIG_HVC_DCC_SERIALIZE_SMP is enabled,
* then we assume then this function will be called first on core0. That way,
* dcc_core0_available will be true only if it's available on core0.
*/
static bool hvc_dcc_check(void)
{
unsigned long time = jiffies + (HZ / 10);
static bool dcc_core0_available;
/*
* If we're not on core 0, but we previously confirmed that DCC is
* active, then just return true.
*/
int cpu = get_cpu();
if (IS_ENABLED(CONFIG_HVC_DCC_SERIALIZE_SMP) && cpu && dcc_core0_available) {
put_cpu();
return true;
}
put_cpu();
/* 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)) {
dcc_core0_available = true;
return true;
}
}
return false;
}
/*
* Workqueue function that writes the output FIFO to the DCC on core 0.
*/
static void dcc_put_work(struct work_struct *work)
{
unsigned char ch;
unsigned long irqflags;
spin_lock_irqsave(&dcc_lock, irqflags);
/* While there's data in the output FIFO, write it to the DCC */
while (kfifo_get(&outbuf, &ch))
hvc_dcc_put_chars(0, &ch, 1);
/* While we're at it, check for any input characters */
while (!kfifo_is_full(&inbuf)) {
if (!hvc_dcc_get_chars(0, &ch, 1))
break;
kfifo_put(&inbuf, ch);
}
spin_unlock_irqrestore(&dcc_lock, irqflags);
}
static DECLARE_WORK(dcc_pwork, dcc_put_work);
/*
* Workqueue function that reads characters from DCC and puts them into the
* input FIFO.
*/
static void dcc_get_work(struct work_struct *work)
{
unsigned long irqflags;
u8 ch;
/*
* Read characters from DCC and put them into the input FIFO, as
* long as there is room and we have characters to read.
*/
spin_lock_irqsave(&dcc_lock, irqflags);
while (!kfifo_is_full(&inbuf)) {
if (!hvc_dcc_get_chars(0, &ch, 1))
break;
kfifo_put(&inbuf, ch);
}
spin_unlock_irqrestore(&dcc_lock, irqflags);
}
static DECLARE_WORK(dcc_gwork, dcc_get_work);
/*
* Write characters directly to the DCC if we're on core 0 and the FIFO
* is empty, or write them to the FIFO if we're not.
*/
static ssize_t hvc_dcc0_put_chars(u32 vt, const u8 *buf, size_t count)
{
unsigned long irqflags;
ssize_t len;
if (!IS_ENABLED(CONFIG_HVC_DCC_SERIALIZE_SMP))
return hvc_dcc_put_chars(vt, buf, count);
spin_lock_irqsave(&dcc_lock, irqflags);
if (smp_processor_id() || (!kfifo_is_empty(&outbuf))) {
len = kfifo_in(&outbuf, buf, count);
spin_unlock_irqrestore(&dcc_lock, irqflags);
/*
* We just push data to the output FIFO, so schedule the
* workqueue that will actually write that data to DCC.
* CPU hotplug is disabled in dcc_init so CPU0 cannot be
* offlined after the cpu online check.
*/
if (cpu_online(0))
schedule_work_on(0, &dcc_pwork);
return len;
}
/*
* If we're already on core 0, and the FIFO is empty, then just
* write the data to DCC.
*/
len = hvc_dcc_put_chars(vt, buf, count);
spin_unlock_irqrestore(&dcc_lock, irqflags);
return len;
}
/*
* Read characters directly from the DCC if we're on core 0 and the FIFO
* is empty, or read them from the FIFO if we're not.
*/
static ssize_t hvc_dcc0_get_chars(u32 vt, u8 *buf, size_t count)
{
unsigned long irqflags;
ssize_t len;
if (!IS_ENABLED(CONFIG_HVC_DCC_SERIALIZE_SMP))
return hvc_dcc_get_chars(vt, buf, count);
spin_lock_irqsave(&dcc_lock, irqflags);
if (smp_processor_id() || (!kfifo_is_empty(&inbuf))) {
len = kfifo_out(&inbuf, buf, count);
spin_unlock_irqrestore(&dcc_lock, irqflags);
/*
* If the FIFO was empty, there may be characters in the DCC
* that we haven't read yet. Schedule a workqueue to fill
* the input FIFO, so that the next time this function is
* called, we'll have data. CPU hotplug is disabled in dcc_init
* so CPU0 cannot be offlined after the cpu online check.
*/
if (!len && cpu_online(0))
schedule_work_on(0, &dcc_gwork);
return len;
}
/*
* If we're already on core 0, and the FIFO is empty, then just
* read the data from DCC.
*/
len = hvc_dcc_get_chars(vt, buf, count);
spin_unlock_irqrestore(&dcc_lock, irqflags);
return len;
}
static const struct hv_ops hvc_dcc_get_put_ops = {
.get_chars = hvc_dcc0_get_chars,
.put_chars = hvc_dcc0_put_chars,
};
static int __init hvc_dcc_console_init(void)
{
int ret;
if (!hvc_dcc_check())
return -ENODEV;
/* Returns -1 if error */
ret = hvc_instantiate(0, 0, &hvc_dcc_get_put_ops);
return ret < 0 ? -ENODEV : 0;
}
console_initcall(hvc_dcc_console_init);
static int __init hvc_dcc_init(void)
{
struct hvc_struct *p;
if (!hvc_dcc_check())
return -ENODEV;
if (IS_ENABLED(CONFIG_HVC_DCC_SERIALIZE_SMP)) {
pr_warn("\n");
pr_warn("********************************************************************\n");
pr_warn("** NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE **\n");
pr_warn("** **\n");
pr_warn("** HVC_DCC_SERIALIZE_SMP SUPPORT HAS BEEN ENABLED IN THIS KERNEL **\n");
pr_warn("** **\n");
pr_warn("** This means that this is a DEBUG kernel and unsafe for **\n");
pr_warn("** production use and has important feature like CPU hotplug **\n");
pr_warn("** disabled. **\n");
pr_warn("** **\n");
pr_warn("** If you see this message and you are not debugging the **\n");
pr_warn("** kernel, report this immediately to your vendor! **\n");
pr_warn("** **\n");
pr_warn("** NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE **\n");
pr_warn("********************************************************************\n");
cpu_hotplug_disable();
}
p = hvc_alloc(0, 0, &hvc_dcc_get_put_ops, 128);
return PTR_ERR_OR_ZERO(p);
}
device_initcall(hvc_dcc_init);
+55
View File
@@ -0,0 +1,55 @@
// SPDX-License-Identifier: GPL-2.0
/*
* 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();
/*
* We're safe to always return IRQ_HANDLED as the hvcd thread will
* iterate through each hvc_struct.
*/
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, hp->flags,
"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);
}
File diff suppressed because it is too large Load Diff
+417
View File
@@ -0,0 +1,417 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* opal driver interface to hvc_console.c
*
* Copyright 2011 Benjamin Herrenschmidt <benh@kernel.crashing.org>, IBM Corp.
*/
#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_irq.h>
#include <linux/platform_device.h>
#include <linux/export.h>
#include <linux/interrupt.h>
#include <asm/hvconsole.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 const 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 const struct hv_ops hvc_opal_raw_ops = {
.get_chars = opal_get_chars,
.put_chars = opal_put_chars,
.flush = opal_flush_chars,
.notifier_add = notifier_add_irq,
.notifier_del = notifier_del_irq,
.notifier_hangup = notifier_hangup_irq,
};
static ssize_t hvc_opal_hvsi_get_chars(uint32_t vtermno, u8 *buf, size_t 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 ssize_t hvc_opal_hvsi_put_chars(uint32_t vtermno, const u8 *buf,
size_t 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);
}
static 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,
.flush = opal_flush_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_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, irq, 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 %pOF\n",
dev->dev.of_node);
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_obj(struct hvc_opal_priv);
if (!pv)
return -ENOMEM;
pv->proto = proto;
hvc_opal_privs[termno] = pv;
if (proto == HV_PROTOCOL_HVSI) {
/*
* We want put_chars to be atomic to avoid mangling of
* hvsi packets.
*/
hvsilib_init(&pv->hvsi,
opal_get_chars, opal_put_chars_atomic,
termno, 0);
}
/* Instanciate now to establish a mapping index==vtermno */
hvc_instantiate(termno, termno, ops);
} else {
pr_err("hvc_opal: Device %pOF has duplicate terminal number #%d\n",
dev->dev.of_node, termno);
return -ENXIO;
}
pr_info("hvc%d: %s protocol on %pOF%s\n", termno,
proto == HV_PROTOCOL_RAW ? "raw" : "hvsi",
dev->dev.of_node,
boot ? " (boot console)" : "");
irq = irq_of_parse_and_map(dev->dev.of_node, 0);
if (!irq) {
pr_info("hvc%d: No interrupts property, using OPAL event\n",
termno);
irq = opal_event_request(ilog2(OPAL_EVENT_CONSOLE_INPUT));
}
if (!irq) {
pr_err("hvc_opal: Unable to map interrupt for device %pOF\n",
dev->dev.of_node);
return irq;
}
hp = hvc_alloc(termno, irq, ops, MAX_VIO_PUT_CHARS);
if (IS_ERR(hp))
return PTR_ERR(hp);
/* hvc consoles on powernv may need to share a single irq */
hp->flags = IRQF_SHARED;
dev_set_drvdata(&dev->dev, hp);
return 0;
}
static void hvc_opal_remove(struct platform_device *dev)
{
struct hvc_struct *hp = dev_get_drvdata(&dev->dev);
int termno;
termno = hp->vtermno;
hvc_remove(hp);
if (hvc_opal_privs[termno] != &hvc_opal_boot_priv)
kfree(hvc_opal_privs[termno]);
hvc_opal_privs[termno] = NULL;
}
static struct platform_driver hvc_opal_driver = {
.probe = hvc_opal_probe,
.remove = hvc_opal_remove,
.driver = {
.name = hvc_opal_name,
.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;
}
/* This is needed for the cosole to flush
* when there aren't any interrupts.
*/
opal_flush_console(termno);
} 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)
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;
}
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");
} else {
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 (of_node_name_eq(np, "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_atomic,
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_atomic,
index, 1);
hvsilib_establish(&hvc_opal_boot_priv.hvsi);
}
#endif /* CONFIG_PPC_EARLY_DEBUG_OPAL_HVSI */
@@ -0,0 +1,83 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (C) 2008 David Gibson, IBM Corporation
* Copyright (C) 2012 Regents of the University of California
* Copyright (C) 2017 SiFive
*/
#include <linux/console.h>
#include <linux/err.h>
#include <linux/init.h>
#include <linux/moduleparam.h>
#include <linux/types.h>
#include <asm/sbi.h>
#include "hvc_console.h"
static ssize_t hvc_sbi_tty_put(uint32_t vtermno, const u8 *buf, size_t count)
{
size_t i;
for (i = 0; i < count; i++)
sbi_console_putchar(buf[i]);
return i;
}
static ssize_t hvc_sbi_tty_get(uint32_t vtermno, u8 *buf, size_t count)
{
size_t i;
int c;
for (i = 0; i < count; i++) {
c = sbi_console_getchar();
if (c < 0)
break;
buf[i] = c;
}
return i;
}
static const struct hv_ops hvc_sbi_v01_ops = {
.get_chars = hvc_sbi_tty_get,
.put_chars = hvc_sbi_tty_put,
};
static ssize_t hvc_sbi_dbcn_tty_put(uint32_t vtermno, const u8 *buf, size_t count)
{
return sbi_debug_console_write(buf, count);
}
static ssize_t hvc_sbi_dbcn_tty_get(uint32_t vtermno, u8 *buf, size_t count)
{
return sbi_debug_console_read(buf, count);
}
static const struct hv_ops hvc_sbi_dbcn_ops = {
.put_chars = hvc_sbi_dbcn_tty_put,
.get_chars = hvc_sbi_dbcn_tty_get,
};
static int __init hvc_sbi_init(void)
{
int err;
if (sbi_debug_console_available) {
err = PTR_ERR_OR_ZERO(hvc_alloc(0, 0, &hvc_sbi_dbcn_ops, 256));
if (err)
return err;
hvc_instantiate(0, 0, &hvc_sbi_dbcn_ops);
} else if (IS_ENABLED(CONFIG_RISCV_SBI_V01)) {
err = PTR_ERR_OR_ZERO(hvc_alloc(0, 0, &hvc_sbi_v01_ops, 256));
if (err)
return err;
hvc_instantiate(0, 0, &hvc_sbi_v01_ops);
} else {
return -ENODEV;
}
return 0;
}
device_initcall(hvc_sbi_init);
+111
View File
@@ -0,0 +1,111 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* 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
*/
#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
static 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 ssize_t hvc_rtas_write_console(uint32_t vtermno, const u8 *buf,
size_t count)
{
size_t i;
for (i = 0; i < count; i++) {
if (rtas_call(rtascons_put_char_token, 1, 1, NULL, buf[i]))
break;
}
return i;
}
static ssize_t hvc_rtas_read_console(uint32_t vtermno, u8 *buf, size_t count)
{
size_t i;
int 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);
+83
View File
@@ -0,0 +1,83 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* udbg interface to hvc_console.c
*
* (C) Copyright David Gibson, IBM Corporation 2008.
*/
#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"
static struct hvc_struct *hvc_udbg_dev;
static ssize_t hvc_udbg_put(uint32_t vtermno, const u8 *buf, size_t count)
{
size_t i;
for (i = 0; i < count && udbg_putc; i++)
udbg_putc(buf[i]);
return i;
}
static ssize_t hvc_udbg_get(uint32_t vtermno, u8 *buf, size_t count)
{
size_t i;
int 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);
+475
View File
@@ -0,0 +1,475 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* 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>
*
* 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/of.h>
#include <asm/hvconsole.h>
#include <asm/vio.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 const struct vio_device_id hvc_driver_table[] = {
{"serial", "hvterm1"},
#ifndef HVC_OLD_HVSI
{"serial", "hvterm-protocol"},
#endif
{ "", "" }
};
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;
u8 buf[SIZE_VIO_GET_CHARS];
size_t left;
size_t offset;
};
static struct hvterm_priv *hvterm_privs[MAX_NR_HVC_CONSOLES];
/* For early boot console */
static struct hvterm_priv hvterm_priv0;
static ssize_t hvterm_raw_get_chars(uint32_t vtermno, u8 *buf, size_t count)
{
struct hvterm_priv *pv = hvterm_privs[vtermno];
unsigned long i;
unsigned long flags;
size_t 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;
}
/**
* hvterm_raw_put_chars: send characters to firmware for given vterm adapter
* @vtermno: The virtual terminal number.
* @buf: The characters to send. Because of the underlying hypercall in
* hvc_put_chars(), this buffer must be at least 16 bytes long, even if
* you are sending fewer chars.
* @count: number of chars to send.
*/
static ssize_t hvterm_raw_put_chars(uint32_t vtermno, const u8 *buf,
size_t 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 ssize_t hvterm_hvsi_get_chars(uint32_t vtermno, u8 *buf, size_t count)
{
struct hvterm_priv *pv = hvterm_privs[vtermno];
if (WARN_ON(!pv))
return 0;
return hvsilib_get_chars(&pv->hvsi, buf, count);
}
static ssize_t hvterm_hvsi_put_chars(uint32_t vtermno, const u8 *buf,
size_t 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);
}
static 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;
unsigned char bounce_buffer[16];
if (!hvterm_privs[0])
return;
if (c == '\n')
udbg_hvc_putc('\r');
do {
switch(hvterm_privs[0]->proto) {
case HV_PROTOCOL_RAW:
/*
* hvterm_raw_put_chars requires at least a 16-byte
* buffer, so go via the bounce buffer
*/
bounce_buffer[0] = c;
count = hvterm_raw_put_chars(0, bounce_buffer, 1);
break;
case HV_PROTOCOL_HVSI:
count = hvterm_hvsi_put_chars(0, &c, 1);
break;
}
} while (count == 0 || count == -EAGAIN);
}
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 %pOF\n", vdev->dev.of_node);
return -ENXIO;
}
pr_devel("hvc_vio_probe() device %pOF, using %s protocol\n",
vdev->dev.of_node,
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_obj(struct hvterm_priv);
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 struct vio_driver hvc_vio_driver = {
.id_table = hvc_driver_table,
.probe = hvc_vio_probe,
.name = hvc_driver_name,
.driver = {
.suppress_bind_attrs = true,
},
};
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;
}
device_initcall(hvc_vio_init); /* after drivers/tty/hvc/hvc_console.c */
void __init hvc_vio_init_early(void)
{
const __be32 *termno;
const struct hv_ops *ops;
/* find the boot console from /chosen/stdout */
/* Check if it's a virtual terminal */
if (!of_node_name_prefix(of_stdout, "vty"))
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)
{
/*
* If we're running as a hypervisor then we definitely can't call the
* hypervisor to print debug output (we *are* the hypervisor), so don't
* register if we detect that MSR_HV=1.
*/
if (mfmsr() & MSR_HV)
return;
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)
{
/* See comment above in udbg_init_debug_lpar() */
if (mfmsr() & MSR_HV)
return;
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 */
+794
View File
@@ -0,0 +1,794 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* xen console driver interface to hvc_console.c
*
* (c) 2007 Gerd Hoffmann <kraxel@suse.de>
*/
#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 <linux/serial_core.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;
XENCONS_RING_IDX out_cons;
unsigned int out_cons_same;
struct hvc_struct *hvc;
int irq;
int vtermno;
grant_ref_t gntref;
spinlock_t ring_lock;
};
static LIST_HEAD(xenconsoles);
static DEFINE_SPINLOCK(xencons_lock);
/* ------------------------------------------------------------------ */
static bool xen_console_io = false;
static int __initdata opt_console_io = -1;
static int __init parse_xen_console_io(char *arg)
{
bool val;
int ret;
ret = kstrtobool(arg, &val);
if (ret == 0)
opt_console_io = (int)val;
return ret;
}
early_param("xen_console_io", parse_xen_console_io);
static struct xencons_info *vtermno_to_xencons(int vtermno)
{
struct xencons_info *entry, *ret = NULL;
unsigned long flags;
spin_lock_irqsave(&xencons_lock, flags);
if (list_empty(&xenconsoles)) {
spin_unlock_irqrestore(&xencons_lock, flags);
return NULL;
}
list_for_each_entry(entry, &xenconsoles, list) {
if (entry->vtermno == vtermno) {
ret = entry;
break;
}
}
spin_unlock_irqrestore(&xencons_lock, flags);
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 ssize_t __write_console(struct xencons_info *xencons,
const u8 *data, size_t len)
{
XENCONS_RING_IDX cons, prod;
struct xencons_interface *intf = xencons->intf;
unsigned long flags;
size_t sent = 0;
spin_lock_irqsave(&xencons->ring_lock, flags);
cons = intf->out_cons;
prod = intf->out_prod;
mb(); /* update queue values before going on */
if ((prod - cons) > sizeof(intf->out)) {
spin_unlock_irqrestore(&xencons->ring_lock, flags);
pr_err_once("xencons: Illegal ring page indices");
return -EINVAL;
}
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;
spin_unlock_irqrestore(&xencons->ring_lock, flags);
if (sent)
notify_daemon(xencons);
return sent;
}
static ssize_t domU_write_console(uint32_t vtermno, const u8 *data, size_t len)
{
struct xencons_info *cons = vtermno_to_xencons(vtermno);
size_t ret = len;
if (cons == NULL)
return -EINVAL;
if (cons->intf->connection == XENCONSOLE_DISCONNECTED)
return -ENOTCONN;
/*
* 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) {
ssize_t sent = __write_console(cons, data, len);
if (sent < 0)
return sent;
data += sent;
len -= sent;
if (unlikely(len))
HYPERVISOR_sched_op(SCHEDOP_yield, NULL);
}
return ret;
}
static ssize_t domU_read_console(uint32_t vtermno, u8 *buf, size_t len)
{
struct xencons_interface *intf;
XENCONS_RING_IDX cons, prod;
struct xencons_info *xencons = vtermno_to_xencons(vtermno);
unsigned int eoiflag = 0;
unsigned long flags;
size_t recv = 0;
if (xencons == NULL)
return -EINVAL;
intf = xencons->intf;
spin_lock_irqsave(&xencons->ring_lock, flags);
cons = intf->in_cons;
prod = intf->in_prod;
mb(); /* get pointers before reading ring */
if ((prod - cons) > sizeof(intf->in)) {
spin_unlock_irqrestore(&xencons->ring_lock, flags);
pr_err_once("xencons: Illegal ring page indices");
return -EINVAL;
}
while (cons != prod && recv < len)
buf[recv++] = intf->in[MASK_XENCONS_IDX(cons++, intf->in)];
mb(); /* read ring before consuming */
intf->in_cons = cons;
/*
* When to mark interrupt having been spurious:
* - there was no new data to be read, and
* - the backend did not consume some output bytes, and
* - the previous round with no read data didn't see consumed bytes
* (we might have a race with an interrupt being in flight while
* updating xencons->out_cons, so account for that by allowing one
* round without any visible reason)
*/
if (intf->out_cons != xencons->out_cons) {
xencons->out_cons = intf->out_cons;
xencons->out_cons_same = 0;
}
if (!recv && xencons->out_cons_same++ > 1) {
eoiflag = XEN_EOI_FLAG_SPURIOUS;
}
spin_unlock_irqrestore(&xencons->ring_lock, flags);
if (recv) {
notify_daemon(xencons);
}
xen_irq_lateeoi(xencons->irq, eoiflag);
return recv;
}
static const 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 ssize_t dom0_read_console(uint32_t vtermno, u8 *buf, size_t 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 ssize_t dom0_write_console(uint32_t vtermno, const u8 *str, size_t len)
{
int rc = HYPERVISOR_console_io(CONSOLEIO_write, len, (u8 *)str);
if (rc < 0)
return rc;
return len;
}
static const 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 gfn, flags;
struct xencons_info *info;
if (!xen_hvm_domain())
return -ENODEV;
info = vtermno_to_xencons(HVC_COOKIE);
if (!info) {
info = kzalloc_obj(struct xencons_info);
if (!info)
return -ENOMEM;
spin_lock_init(&info->ring_lock);
} 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 gfn = 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;
gfn = v;
info->intf = memremap(gfn << XEN_PAGE_SHIFT, XEN_PAGE_SIZE, MEMREMAP_WB);
if (info->intf == NULL)
goto err;
info->vtermno = HVC_COOKIE;
spin_lock_irqsave(&xencons_lock, flags);
list_add_tail(&info->list, &xenconsoles);
spin_unlock_irqrestore(&xencons_lock, flags);
return 0;
err:
kfree(info);
return -ENODEV;
}
static int xencons_info_pv_init(struct xencons_info *info, int vtermno)
{
spin_lock_init(&info->ring_lock);
info->evtchn = xen_start_info->console.domU.evtchn;
/* GFN == MFN for PV guest */
info->intf = gfn_to_virt(xen_start_info->console.domU.mfn);
info->vtermno = vtermno;
list_add_tail(&info->list, &xenconsoles);
return 0;
}
static int xen_pv_console_init(void)
{
struct xencons_info *info;
unsigned long flags;
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_obj(struct xencons_info);
if (!info)
return -ENOMEM;
} else if (info->intf != NULL) {
/* already configured */
return 0;
}
spin_lock_irqsave(&xencons_lock, flags);
xencons_info_pv_init(info, HVC_COOKIE);
spin_unlock_irqrestore(&xencons_lock, flags);
return 0;
}
static int xen_initial_domain_console_init(void)
{
struct xencons_info *info;
unsigned long flags;
if (!xen_console_io)
return -ENODEV;
info = vtermno_to_xencons(HVC_COOKIE);
if (!info) {
info = kzalloc_obj(struct xencons_info);
if (!info)
return -ENOMEM;
spin_lock_init(&info->ring_lock);
}
info->irq = bind_virq_to_irq(VIRQ_CONSOLE, 0, false);
info->vtermno = HVC_COOKIE;
spin_lock_irqsave(&xencons_lock, flags);
list_add_tail(&info->list, &xenconsoles);
spin_unlock_irqrestore(&xencons_lock, flags);
return 0;
}
static void xen_console_update_evtchn(struct xencons_info *info)
{
if (xen_hvm_domain()) {
uint64_t v = 0;
int err;
err = hvm_get_parameter(HVM_PARAM_CONSOLE_EVTCHN, &v);
if (!err && v)
info->evtchn = v;
} else
info->evtchn = xen_start_info->console.domU.evtchn;
}
void xen_console_resume(void)
{
struct xencons_info *info = vtermno_to_xencons(HVC_COOKIE);
if (info != NULL && info->irq) {
if (!xen_console_io)
xen_console_update_evtchn(info);
rebind_evtchn_irq(info->evtchn, info->irq);
}
}
#ifdef CONFIG_HVC_XEN_FRONTEND
static void xencons_disconnect_backend(struct xencons_info *info)
{
if (info->hvc != NULL)
hvc_remove(info->hvc);
info->hvc = NULL;
if (info->irq > 0) {
evtchn_put(info->evtchn);
info->irq = 0;
info->evtchn = 0;
}
/* evtchn_put() will also close it so this is only an error path */
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;
}
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)
{
unsigned long flags;
xencons_disconnect_backend(info);
spin_lock_irqsave(&xencons_lock, flags);
list_del(&info->list);
spin_unlock_irqrestore(&xencons_lock, flags);
if (info->xbdev != NULL)
xencons_free(info);
else {
if (xen_hvm_domain())
iounmap(info->intf);
kfree(info);
}
return 0;
}
static void xencons_remove(struct xenbus_device *dev)
{
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;
ret = xenbus_alloc_evtchn(dev, &evtchn);
if (ret)
return ret;
info->evtchn = evtchn;
irq = bind_evtchn_to_irq_lateeoi(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);
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,
virt_to_gfn(info->intf), 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;
unsigned long flags;
devid = dev->nodename[strlen(dev->nodename) - 1] - '0';
if (devid == 0)
return -ENODEV;
info = kzalloc_obj(struct xencons_info);
if (!info)
return -ENOMEM;
spin_lock_init(&info->ring_lock);
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_irqsave(&xencons_lock, flags);
list_add_tail(&info->list, &xenconsoles);
spin_unlock_irqrestore(&xencons_lock, flags);
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, XEN_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;
fallthrough; /* Missed the backend's CLOSING state */
case XenbusStateClosing: {
struct xencons_info *info = dev_get_drvdata(&dev->dev);
/*
* Don't tear down the evtchn and grant ref before the other
* end has disconnected, but do stop userspace from trying
* to use the device before we allow the backend to close.
*/
if (info->hvc) {
hvc_remove(info->hvc);
info->hvc = NULL;
}
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,
.not_essential = true,
};
#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_console_io) {
ops = &dom0_hvc_ops;
r = xen_initial_domain_console_init();
if (r < 0)
goto register_fe;
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)
goto register_fe;
info = vtermno_to_xencons(HVC_COOKIE);
info->irq = bind_evtchn_to_irq_lateeoi(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)) {
unsigned long flags;
r = PTR_ERR(info->hvc);
spin_lock_irqsave(&xencons_lock, flags);
list_del(&info->list);
spin_unlock_irqrestore(&xencons_lock, flags);
if (info->irq)
evtchn_put(info->evtchn);
kfree(info);
return r;
}
r = 0;
register_fe:
#ifdef CONFIG_HVC_XEN_FRONTEND
r = xenbus_register_frontend(&xencons_driver);
#endif
return r;
}
device_initcall(xen_hvc_init);
static int __init xen_cons_init(void)
{
const struct hv_ops *ops;
xen_console_io = opt_console_io >= 0 ? opt_console_io :
xen_initial_domain();
if (!xen_domain())
return 0;
if (xen_console_io)
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_X86
static void xen_hvm_early_write(uint32_t vtermno, const char *str, int len)
{
if (xen_cpuid_base())
outsb(0xe9, str, len);
}
#else
static void xen_hvm_early_write(uint32_t vtermno, const char *str, int len) { }
#endif
#ifdef CONFIG_EARLY_PRINTK
static int __init xenboot_console_setup(struct console *console, char *string)
{
static struct xencons_info xenboot;
if (xen_initial_domain() || !xen_pv_domain())
return 0;
return xencons_info_pv_init(&xenboot, 0);
}
static void xenboot_write_console(struct console *console, const char *string,
unsigned len)
{
unsigned int linelen, off = 0;
const char *pos;
if (dom0_write_console(0, string, len) >= 0)
return;
if (!xen_pv_domain()) {
xen_hvm_early_write(0, string, len);
return;
}
if (domU_write_console(0, "(early) ", 8) < 0)
return;
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,
.setup = xenboot_console_setup,
.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);
if (rc != -ENOSYS || !xen_hvm_domain())
return;
}
xen_hvm_early_write(0, str, len);
}
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);
}
static void xenboot_earlycon_write(struct console *console,
const char *string,
unsigned len)
{
dom0_write_console(0, string, len);
}
static int __init xenboot_earlycon_setup(struct earlycon_device *device,
const char *opt)
{
device->con->write = xenboot_earlycon_write;
return 0;
}
EARLYCON_DECLARE(xenboot, xenboot_earlycon_setup);
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+426
View File
@@ -0,0 +1,426 @@
// SPDX-License-Identifier: GPL-2.0
#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, (u8 *)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;
}
ssize_t hvsilib_get_chars(struct hvsi_priv *pv, u8 *buf, size_t count)
{
unsigned int tries;
size_t 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) {
size_t l = min(count, 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;
}
ssize_t hvsilib_put_chars(struct hvsi_priv *pv, const u8 *buf, size_t count)
{
struct hvsi_data dp;
size_t adjcount = min_t(size_t, count, HVSI_MAX_OUTGOING_DATA);
int rc;
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);
}
tty_kref_put(pv->tty);
pv->tty = NULL;
}
void hvsilib_init(struct hvsi_priv *pv,
ssize_t (*get_chars)(uint32_t termno, u8 *buf, size_t count),
ssize_t (*put_chars)(uint32_t termno, const u8 *buf,
size_t 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;
}