2006-02-11 11:25:21 +01:00
|
|
|
/* sunhv.c: Serial driver for SUN4V hypervisor console.
|
|
|
|
*
|
2007-05-16 02:03:54 +02:00
|
|
|
* Copyright (C) 2006, 2007 David S. Miller (davem@davemloft.net)
|
2006-02-11 11:25:21 +01:00
|
|
|
*/
|
|
|
|
|
|
|
|
#include <linux/module.h>
|
|
|
|
#include <linux/kernel.h>
|
|
|
|
#include <linux/errno.h>
|
|
|
|
#include <linux/tty.h>
|
|
|
|
#include <linux/tty_flip.h>
|
|
|
|
#include <linux/major.h>
|
|
|
|
#include <linux/circ_buf.h>
|
|
|
|
#include <linux/serial.h>
|
|
|
|
#include <linux/sysrq.h>
|
|
|
|
#include <linux/console.h>
|
|
|
|
#include <linux/spinlock.h>
|
|
|
|
#include <linux/slab.h>
|
|
|
|
#include <linux/delay.h>
|
|
|
|
#include <linux/init.h>
|
|
|
|
|
|
|
|
#include <asm/hypervisor.h>
|
|
|
|
#include <asm/spitfire.h>
|
2006-06-30 09:00:22 +02:00
|
|
|
#include <asm/prom.h>
|
|
|
|
#include <asm/of_device.h>
|
2006-02-14 05:43:02 +01:00
|
|
|
#include <asm/irq.h>
|
2006-02-11 11:25:21 +01:00
|
|
|
|
|
|
|
#if defined(CONFIG_MAGIC_SYSRQ)
|
|
|
|
#define SUPPORT_SYSRQ
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#include <linux/serial_core.h>
|
|
|
|
|
|
|
|
#include "suncore.h"
|
|
|
|
|
|
|
|
#define CON_BREAK ((long)-1)
|
|
|
|
#define CON_HUP ((long)-2)
|
|
|
|
|
2007-05-16 02:03:54 +02:00
|
|
|
#define IGNORE_BREAK 0x1
|
|
|
|
#define IGNORE_ALL 0x2
|
2006-02-11 11:25:21 +01:00
|
|
|
|
2007-05-16 02:03:54 +02:00
|
|
|
static char *con_write_page;
|
|
|
|
static char *con_read_page;
|
2006-02-11 11:25:21 +01:00
|
|
|
|
2007-05-16 02:03:54 +02:00
|
|
|
static int hung_up = 0;
|
2006-02-11 11:25:21 +01:00
|
|
|
|
2007-05-16 02:03:54 +02:00
|
|
|
static void transmit_chars_putchar(struct uart_port *port, struct circ_buf *xmit)
|
2006-02-11 11:25:21 +01:00
|
|
|
{
|
2007-05-16 02:03:54 +02:00
|
|
|
while (!uart_circ_empty(xmit)) {
|
|
|
|
long status = sun4v_con_putchar(xmit->buf[xmit->tail]);
|
2006-02-11 11:25:21 +01:00
|
|
|
|
2007-05-16 02:03:54 +02:00
|
|
|
if (status != HV_EOK)
|
|
|
|
break;
|
2006-02-11 11:25:21 +01:00
|
|
|
|
2007-05-16 02:03:54 +02:00
|
|
|
xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1);
|
|
|
|
port->icount.tx++;
|
|
|
|
}
|
2006-02-11 11:25:21 +01:00
|
|
|
}
|
|
|
|
|
2007-05-16 02:03:54 +02:00
|
|
|
static void transmit_chars_write(struct uart_port *port, struct circ_buf *xmit)
|
|
|
|
{
|
|
|
|
while (!uart_circ_empty(xmit)) {
|
|
|
|
unsigned long ra = __pa(xmit->buf + xmit->tail);
|
|
|
|
unsigned long len, status, sent;
|
2006-02-11 11:25:21 +01:00
|
|
|
|
2007-05-16 02:03:54 +02:00
|
|
|
len = CIRC_CNT_TO_END(xmit->head, xmit->tail,
|
|
|
|
UART_XMIT_SIZE);
|
|
|
|
status = sun4v_con_write(ra, len, &sent);
|
|
|
|
if (status != HV_EOK)
|
|
|
|
break;
|
|
|
|
xmit->tail = (xmit->tail + sent) & (UART_XMIT_SIZE - 1);
|
|
|
|
port->icount.tx += sent;
|
|
|
|
}
|
|
|
|
}
|
2006-02-11 11:25:21 +01:00
|
|
|
|
2007-05-16 02:03:54 +02:00
|
|
|
static int receive_chars_getchar(struct uart_port *port, struct tty_struct *tty)
|
2006-02-11 11:25:21 +01:00
|
|
|
{
|
|
|
|
int saw_console_brk = 0;
|
|
|
|
int limit = 10000;
|
|
|
|
|
|
|
|
while (limit-- > 0) {
|
|
|
|
long status;
|
2007-05-16 02:03:54 +02:00
|
|
|
long c = sun4v_con_getchar(&status);
|
2006-02-11 11:25:21 +01:00
|
|
|
|
|
|
|
if (status == HV_EWOULDBLOCK)
|
|
|
|
break;
|
|
|
|
|
|
|
|
if (c == CON_BREAK) {
|
2006-02-16 09:42:21 +01:00
|
|
|
if (uart_handle_break(port))
|
|
|
|
continue;
|
2006-02-11 11:25:21 +01:00
|
|
|
saw_console_brk = 1;
|
|
|
|
c = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (c == CON_HUP) {
|
|
|
|
hung_up = 1;
|
|
|
|
uart_handle_dcd_change(port, 0);
|
|
|
|
} else if (hung_up) {
|
|
|
|
hung_up = 0;
|
|
|
|
uart_handle_dcd_change(port, 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (tty == NULL) {
|
IRQ: Maintain regs pointer globally rather than passing to IRQ handlers
Maintain a per-CPU global "struct pt_regs *" variable which can be used instead
of passing regs around manually through all ~1800 interrupt handlers in the
Linux kernel.
The regs pointer is used in few places, but it potentially costs both stack
space and code to pass it around. On the FRV arch, removing the regs parameter
from all the genirq function results in a 20% speed up of the IRQ exit path
(ie: from leaving timer_interrupt() to leaving do_IRQ()).
Where appropriate, an arch may override the generic storage facility and do
something different with the variable. On FRV, for instance, the address is
maintained in GR28 at all times inside the kernel as part of general exception
handling.
Having looked over the code, it appears that the parameter may be handed down
through up to twenty or so layers of functions. Consider a USB character
device attached to a USB hub, attached to a USB controller that posts its
interrupts through a cascaded auxiliary interrupt controller. A character
device driver may want to pass regs to the sysrq handler through the input
layer which adds another few layers of parameter passing.
I've build this code with allyesconfig for x86_64 and i386. I've runtested the
main part of the code on FRV and i386, though I can't test most of the drivers.
I've also done partial conversion for powerpc and MIPS - these at least compile
with minimal configurations.
This will affect all archs. Mostly the changes should be relatively easy.
Take do_IRQ(), store the regs pointer at the beginning, saving the old one:
struct pt_regs *old_regs = set_irq_regs(regs);
And put the old one back at the end:
set_irq_regs(old_regs);
Don't pass regs through to generic_handle_irq() or __do_IRQ().
In timer_interrupt(), this sort of change will be necessary:
- update_process_times(user_mode(regs));
- profile_tick(CPU_PROFILING, regs);
+ update_process_times(user_mode(get_irq_regs()));
+ profile_tick(CPU_PROFILING);
I'd like to move update_process_times()'s use of get_irq_regs() into itself,
except that i386, alone of the archs, uses something other than user_mode().
Some notes on the interrupt handling in the drivers:
(*) input_dev() is now gone entirely. The regs pointer is no longer stored in
the input_dev struct.
(*) finish_unlinks() in drivers/usb/host/ohci-q.c needs checking. It does
something different depending on whether it's been supplied with a regs
pointer or not.
(*) Various IRQ handler function pointers have been moved to type
irq_handler_t.
Signed-Off-By: David Howells <dhowells@redhat.com>
(cherry picked from 1b16e7ac850969f38b375e511e3fa2f474a33867 commit)
2006-10-05 15:55:46 +02:00
|
|
|
uart_handle_sysrq_char(port, c);
|
2006-02-11 11:25:21 +01:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
port->icount.rx++;
|
|
|
|
|
IRQ: Maintain regs pointer globally rather than passing to IRQ handlers
Maintain a per-CPU global "struct pt_regs *" variable which can be used instead
of passing regs around manually through all ~1800 interrupt handlers in the
Linux kernel.
The regs pointer is used in few places, but it potentially costs both stack
space and code to pass it around. On the FRV arch, removing the regs parameter
from all the genirq function results in a 20% speed up of the IRQ exit path
(ie: from leaving timer_interrupt() to leaving do_IRQ()).
Where appropriate, an arch may override the generic storage facility and do
something different with the variable. On FRV, for instance, the address is
maintained in GR28 at all times inside the kernel as part of general exception
handling.
Having looked over the code, it appears that the parameter may be handed down
through up to twenty or so layers of functions. Consider a USB character
device attached to a USB hub, attached to a USB controller that posts its
interrupts through a cascaded auxiliary interrupt controller. A character
device driver may want to pass regs to the sysrq handler through the input
layer which adds another few layers of parameter passing.
I've build this code with allyesconfig for x86_64 and i386. I've runtested the
main part of the code on FRV and i386, though I can't test most of the drivers.
I've also done partial conversion for powerpc and MIPS - these at least compile
with minimal configurations.
This will affect all archs. Mostly the changes should be relatively easy.
Take do_IRQ(), store the regs pointer at the beginning, saving the old one:
struct pt_regs *old_regs = set_irq_regs(regs);
And put the old one back at the end:
set_irq_regs(old_regs);
Don't pass regs through to generic_handle_irq() or __do_IRQ().
In timer_interrupt(), this sort of change will be necessary:
- update_process_times(user_mode(regs));
- profile_tick(CPU_PROFILING, regs);
+ update_process_times(user_mode(get_irq_regs()));
+ profile_tick(CPU_PROFILING);
I'd like to move update_process_times()'s use of get_irq_regs() into itself,
except that i386, alone of the archs, uses something other than user_mode().
Some notes on the interrupt handling in the drivers:
(*) input_dev() is now gone entirely. The regs pointer is no longer stored in
the input_dev struct.
(*) finish_unlinks() in drivers/usb/host/ohci-q.c needs checking. It does
something different depending on whether it's been supplied with a regs
pointer or not.
(*) Various IRQ handler function pointers have been moved to type
irq_handler_t.
Signed-Off-By: David Howells <dhowells@redhat.com>
(cherry picked from 1b16e7ac850969f38b375e511e3fa2f474a33867 commit)
2006-10-05 15:55:46 +02:00
|
|
|
if (uart_handle_sysrq_char(port, c))
|
2006-02-11 11:25:21 +01:00
|
|
|
continue;
|
|
|
|
|
2007-05-16 02:03:54 +02:00
|
|
|
tty_insert_flip_char(tty, c, TTY_NORMAL);
|
|
|
|
}
|
|
|
|
|
|
|
|
return saw_console_brk;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int receive_chars_read(struct uart_port *port, struct tty_struct *tty)
|
|
|
|
{
|
|
|
|
int saw_console_brk = 0;
|
|
|
|
int limit = 10000;
|
|
|
|
|
|
|
|
while (limit-- > 0) {
|
|
|
|
unsigned long ra = __pa(con_read_page);
|
|
|
|
unsigned long bytes_read, i;
|
|
|
|
long stat = sun4v_con_read(ra, PAGE_SIZE, &bytes_read);
|
|
|
|
|
|
|
|
if (stat != HV_EOK) {
|
|
|
|
bytes_read = 0;
|
|
|
|
|
|
|
|
if (stat == CON_BREAK) {
|
|
|
|
if (uart_handle_break(port))
|
|
|
|
continue;
|
|
|
|
saw_console_brk = 1;
|
|
|
|
*con_read_page = 0;
|
|
|
|
bytes_read = 1;
|
|
|
|
} else if (stat == CON_HUP) {
|
|
|
|
hung_up = 1;
|
|
|
|
uart_handle_dcd_change(port, 0);
|
|
|
|
continue;
|
|
|
|
} else {
|
|
|
|
/* HV_EWOULDBLOCK, etc. */
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (hung_up) {
|
|
|
|
hung_up = 0;
|
|
|
|
uart_handle_dcd_change(port, 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < bytes_read; i++)
|
|
|
|
uart_handle_sysrq_char(port, con_read_page[i]);
|
|
|
|
|
|
|
|
if (tty == NULL)
|
2006-02-11 11:25:21 +01:00
|
|
|
continue;
|
|
|
|
|
2007-05-16 02:03:54 +02:00
|
|
|
port->icount.rx += bytes_read;
|
|
|
|
|
|
|
|
tty_insert_flip_string(tty, con_read_page, bytes_read);
|
2006-02-11 11:25:21 +01:00
|
|
|
}
|
|
|
|
|
2007-05-16 02:03:54 +02:00
|
|
|
return saw_console_brk;
|
|
|
|
}
|
|
|
|
|
|
|
|
struct sunhv_ops {
|
|
|
|
void (*transmit_chars)(struct uart_port *port, struct circ_buf *xmit);
|
|
|
|
int (*receive_chars)(struct uart_port *port, struct tty_struct *tty);
|
|
|
|
};
|
|
|
|
|
|
|
|
static struct sunhv_ops bychar_ops = {
|
|
|
|
.transmit_chars = transmit_chars_putchar,
|
|
|
|
.receive_chars = receive_chars_getchar,
|
|
|
|
};
|
|
|
|
|
|
|
|
static struct sunhv_ops bywrite_ops = {
|
|
|
|
.transmit_chars = transmit_chars_write,
|
|
|
|
.receive_chars = receive_chars_read,
|
|
|
|
};
|
|
|
|
|
|
|
|
static struct sunhv_ops *sunhv_ops = &bychar_ops;
|
|
|
|
|
|
|
|
static struct tty_struct *receive_chars(struct uart_port *port)
|
|
|
|
{
|
|
|
|
struct tty_struct *tty = NULL;
|
|
|
|
|
|
|
|
if (port->info != NULL) /* Unopened serial console */
|
2008-07-16 22:54:11 +02:00
|
|
|
tty = port->info->port.tty;
|
2007-05-16 02:03:54 +02:00
|
|
|
|
|
|
|
if (sunhv_ops->receive_chars(port, tty))
|
2006-02-11 11:25:21 +01:00
|
|
|
sun_do_break();
|
|
|
|
|
|
|
|
return tty;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void transmit_chars(struct uart_port *port)
|
|
|
|
{
|
2006-02-16 06:56:49 +01:00
|
|
|
struct circ_buf *xmit;
|
|
|
|
|
|
|
|
if (!port->info)
|
|
|
|
return;
|
2006-02-11 11:25:21 +01:00
|
|
|
|
2006-02-16 06:56:49 +01:00
|
|
|
xmit = &port->info->xmit;
|
2006-02-11 11:25:21 +01:00
|
|
|
if (uart_circ_empty(xmit) || uart_tx_stopped(port))
|
|
|
|
return;
|
|
|
|
|
2007-05-16 02:03:54 +02:00
|
|
|
sunhv_ops->transmit_chars(port, xmit);
|
2006-02-11 11:25:21 +01:00
|
|
|
|
|
|
|
if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
|
|
|
|
uart_write_wakeup(port);
|
|
|
|
}
|
|
|
|
|
IRQ: Maintain regs pointer globally rather than passing to IRQ handlers
Maintain a per-CPU global "struct pt_regs *" variable which can be used instead
of passing regs around manually through all ~1800 interrupt handlers in the
Linux kernel.
The regs pointer is used in few places, but it potentially costs both stack
space and code to pass it around. On the FRV arch, removing the regs parameter
from all the genirq function results in a 20% speed up of the IRQ exit path
(ie: from leaving timer_interrupt() to leaving do_IRQ()).
Where appropriate, an arch may override the generic storage facility and do
something different with the variable. On FRV, for instance, the address is
maintained in GR28 at all times inside the kernel as part of general exception
handling.
Having looked over the code, it appears that the parameter may be handed down
through up to twenty or so layers of functions. Consider a USB character
device attached to a USB hub, attached to a USB controller that posts its
interrupts through a cascaded auxiliary interrupt controller. A character
device driver may want to pass regs to the sysrq handler through the input
layer which adds another few layers of parameter passing.
I've build this code with allyesconfig for x86_64 and i386. I've runtested the
main part of the code on FRV and i386, though I can't test most of the drivers.
I've also done partial conversion for powerpc and MIPS - these at least compile
with minimal configurations.
This will affect all archs. Mostly the changes should be relatively easy.
Take do_IRQ(), store the regs pointer at the beginning, saving the old one:
struct pt_regs *old_regs = set_irq_regs(regs);
And put the old one back at the end:
set_irq_regs(old_regs);
Don't pass regs through to generic_handle_irq() or __do_IRQ().
In timer_interrupt(), this sort of change will be necessary:
- update_process_times(user_mode(regs));
- profile_tick(CPU_PROFILING, regs);
+ update_process_times(user_mode(get_irq_regs()));
+ profile_tick(CPU_PROFILING);
I'd like to move update_process_times()'s use of get_irq_regs() into itself,
except that i386, alone of the archs, uses something other than user_mode().
Some notes on the interrupt handling in the drivers:
(*) input_dev() is now gone entirely. The regs pointer is no longer stored in
the input_dev struct.
(*) finish_unlinks() in drivers/usb/host/ohci-q.c needs checking. It does
something different depending on whether it's been supplied with a regs
pointer or not.
(*) Various IRQ handler function pointers have been moved to type
irq_handler_t.
Signed-Off-By: David Howells <dhowells@redhat.com>
(cherry picked from 1b16e7ac850969f38b375e511e3fa2f474a33867 commit)
2006-10-05 15:55:46 +02:00
|
|
|
static irqreturn_t sunhv_interrupt(int irq, void *dev_id)
|
2006-02-11 11:25:21 +01:00
|
|
|
{
|
|
|
|
struct uart_port *port = dev_id;
|
|
|
|
struct tty_struct *tty;
|
|
|
|
unsigned long flags;
|
|
|
|
|
|
|
|
spin_lock_irqsave(&port->lock, flags);
|
IRQ: Maintain regs pointer globally rather than passing to IRQ handlers
Maintain a per-CPU global "struct pt_regs *" variable which can be used instead
of passing regs around manually through all ~1800 interrupt handlers in the
Linux kernel.
The regs pointer is used in few places, but it potentially costs both stack
space and code to pass it around. On the FRV arch, removing the regs parameter
from all the genirq function results in a 20% speed up of the IRQ exit path
(ie: from leaving timer_interrupt() to leaving do_IRQ()).
Where appropriate, an arch may override the generic storage facility and do
something different with the variable. On FRV, for instance, the address is
maintained in GR28 at all times inside the kernel as part of general exception
handling.
Having looked over the code, it appears that the parameter may be handed down
through up to twenty or so layers of functions. Consider a USB character
device attached to a USB hub, attached to a USB controller that posts its
interrupts through a cascaded auxiliary interrupt controller. A character
device driver may want to pass regs to the sysrq handler through the input
layer which adds another few layers of parameter passing.
I've build this code with allyesconfig for x86_64 and i386. I've runtested the
main part of the code on FRV and i386, though I can't test most of the drivers.
I've also done partial conversion for powerpc and MIPS - these at least compile
with minimal configurations.
This will affect all archs. Mostly the changes should be relatively easy.
Take do_IRQ(), store the regs pointer at the beginning, saving the old one:
struct pt_regs *old_regs = set_irq_regs(regs);
And put the old one back at the end:
set_irq_regs(old_regs);
Don't pass regs through to generic_handle_irq() or __do_IRQ().
In timer_interrupt(), this sort of change will be necessary:
- update_process_times(user_mode(regs));
- profile_tick(CPU_PROFILING, regs);
+ update_process_times(user_mode(get_irq_regs()));
+ profile_tick(CPU_PROFILING);
I'd like to move update_process_times()'s use of get_irq_regs() into itself,
except that i386, alone of the archs, uses something other than user_mode().
Some notes on the interrupt handling in the drivers:
(*) input_dev() is now gone entirely. The regs pointer is no longer stored in
the input_dev struct.
(*) finish_unlinks() in drivers/usb/host/ohci-q.c needs checking. It does
something different depending on whether it's been supplied with a regs
pointer or not.
(*) Various IRQ handler function pointers have been moved to type
irq_handler_t.
Signed-Off-By: David Howells <dhowells@redhat.com>
(cherry picked from 1b16e7ac850969f38b375e511e3fa2f474a33867 commit)
2006-10-05 15:55:46 +02:00
|
|
|
tty = receive_chars(port);
|
2006-02-11 11:25:21 +01:00
|
|
|
transmit_chars(port);
|
|
|
|
spin_unlock_irqrestore(&port->lock, flags);
|
|
|
|
|
|
|
|
if (tty)
|
|
|
|
tty_flip_buffer_push(tty);
|
|
|
|
|
|
|
|
return IRQ_HANDLED;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* port->lock is not held. */
|
|
|
|
static unsigned int sunhv_tx_empty(struct uart_port *port)
|
|
|
|
{
|
|
|
|
/* Transmitter is always empty for us. If the circ buffer
|
|
|
|
* is non-empty or there is an x_char pending, our caller
|
|
|
|
* will do the right thing and ignore what we return here.
|
|
|
|
*/
|
|
|
|
return TIOCSER_TEMT;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* port->lock held by caller. */
|
|
|
|
static void sunhv_set_mctrl(struct uart_port *port, unsigned int mctrl)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* port->lock is held by caller and interrupts are disabled. */
|
|
|
|
static unsigned int sunhv_get_mctrl(struct uart_port *port)
|
|
|
|
{
|
|
|
|
return TIOCM_DSR | TIOCM_CAR | TIOCM_CTS;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* port->lock held by caller. */
|
|
|
|
static void sunhv_stop_tx(struct uart_port *port)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* port->lock held by caller. */
|
|
|
|
static void sunhv_start_tx(struct uart_port *port)
|
|
|
|
{
|
2007-07-17 02:05:11 +02:00
|
|
|
transmit_chars(port);
|
2006-02-11 11:25:21 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/* port->lock is not held. */
|
|
|
|
static void sunhv_send_xchar(struct uart_port *port, char ch)
|
|
|
|
{
|
|
|
|
unsigned long flags;
|
|
|
|
int limit = 10000;
|
|
|
|
|
|
|
|
spin_lock_irqsave(&port->lock, flags);
|
|
|
|
|
|
|
|
while (limit-- > 0) {
|
2007-05-16 02:03:54 +02:00
|
|
|
long status = sun4v_con_putchar(ch);
|
2006-02-11 11:25:21 +01:00
|
|
|
if (status == HV_EOK)
|
|
|
|
break;
|
2007-05-16 02:03:54 +02:00
|
|
|
udelay(1);
|
2006-02-11 11:25:21 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
spin_unlock_irqrestore(&port->lock, flags);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* port->lock held by caller. */
|
|
|
|
static void sunhv_stop_rx(struct uart_port *port)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
/* port->lock held by caller. */
|
|
|
|
static void sunhv_enable_ms(struct uart_port *port)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
/* port->lock is not held. */
|
|
|
|
static void sunhv_break_ctl(struct uart_port *port, int break_state)
|
|
|
|
{
|
|
|
|
if (break_state) {
|
|
|
|
unsigned long flags;
|
2007-05-16 02:03:54 +02:00
|
|
|
int limit = 10000;
|
2006-02-11 11:25:21 +01:00
|
|
|
|
|
|
|
spin_lock_irqsave(&port->lock, flags);
|
|
|
|
|
|
|
|
while (limit-- > 0) {
|
2007-05-16 02:03:54 +02:00
|
|
|
long status = sun4v_con_putchar(CON_BREAK);
|
2006-02-11 11:25:21 +01:00
|
|
|
if (status == HV_EOK)
|
|
|
|
break;
|
2007-05-16 02:03:54 +02:00
|
|
|
udelay(1);
|
2006-02-11 11:25:21 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
spin_unlock_irqrestore(&port->lock, flags);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* port->lock is not held. */
|
|
|
|
static int sunhv_startup(struct uart_port *port)
|
|
|
|
{
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* port->lock is not held. */
|
|
|
|
static void sunhv_shutdown(struct uart_port *port)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
/* port->lock is not held. */
|
2006-12-08 11:38:45 +01:00
|
|
|
static void sunhv_set_termios(struct uart_port *port, struct ktermios *termios,
|
|
|
|
struct ktermios *old)
|
2006-02-11 11:25:21 +01:00
|
|
|
{
|
|
|
|
unsigned int baud = uart_get_baud_rate(port, termios, old, 0, 4000000);
|
|
|
|
unsigned int quot = uart_get_divisor(port, baud);
|
|
|
|
unsigned int iflag, cflag;
|
|
|
|
unsigned long flags;
|
|
|
|
|
|
|
|
spin_lock_irqsave(&port->lock, flags);
|
|
|
|
|
|
|
|
iflag = termios->c_iflag;
|
|
|
|
cflag = termios->c_cflag;
|
|
|
|
|
|
|
|
port->ignore_status_mask = 0;
|
|
|
|
if (iflag & IGNBRK)
|
|
|
|
port->ignore_status_mask |= IGNORE_BREAK;
|
|
|
|
if ((cflag & CREAD) == 0)
|
|
|
|
port->ignore_status_mask |= IGNORE_ALL;
|
|
|
|
|
|
|
|
/* XXX */
|
|
|
|
uart_update_timeout(port, cflag,
|
|
|
|
(port->uartclk / (16 * quot)));
|
|
|
|
|
|
|
|
spin_unlock_irqrestore(&port->lock, flags);
|
|
|
|
}
|
|
|
|
|
|
|
|
static const char *sunhv_type(struct uart_port *port)
|
|
|
|
{
|
|
|
|
return "SUN4V HCONS";
|
|
|
|
}
|
|
|
|
|
|
|
|
static void sunhv_release_port(struct uart_port *port)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
static int sunhv_request_port(struct uart_port *port)
|
|
|
|
{
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void sunhv_config_port(struct uart_port *port, int flags)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
static int sunhv_verify_port(struct uart_port *port, struct serial_struct *ser)
|
|
|
|
{
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct uart_ops sunhv_pops = {
|
|
|
|
.tx_empty = sunhv_tx_empty,
|
|
|
|
.set_mctrl = sunhv_set_mctrl,
|
|
|
|
.get_mctrl = sunhv_get_mctrl,
|
|
|
|
.stop_tx = sunhv_stop_tx,
|
|
|
|
.start_tx = sunhv_start_tx,
|
|
|
|
.send_xchar = sunhv_send_xchar,
|
|
|
|
.stop_rx = sunhv_stop_rx,
|
|
|
|
.enable_ms = sunhv_enable_ms,
|
|
|
|
.break_ctl = sunhv_break_ctl,
|
|
|
|
.startup = sunhv_startup,
|
|
|
|
.shutdown = sunhv_shutdown,
|
|
|
|
.set_termios = sunhv_set_termios,
|
|
|
|
.type = sunhv_type,
|
|
|
|
.release_port = sunhv_release_port,
|
|
|
|
.request_port = sunhv_request_port,
|
|
|
|
.config_port = sunhv_config_port,
|
|
|
|
.verify_port = sunhv_verify_port,
|
|
|
|
};
|
|
|
|
|
|
|
|
static struct uart_driver sunhv_reg = {
|
|
|
|
.owner = THIS_MODULE,
|
2008-05-01 10:14:27 +02:00
|
|
|
.driver_name = "sunhv",
|
2006-02-11 11:25:21 +01:00
|
|
|
.dev_name = "ttyS",
|
|
|
|
.major = TTY_MAJOR,
|
|
|
|
};
|
|
|
|
|
|
|
|
static struct uart_port *sunhv_port;
|
|
|
|
|
2007-05-16 02:03:54 +02:00
|
|
|
/* Copy 's' into the con_write_page, decoding "\n" into
|
|
|
|
* "\r\n" along the way. We have to return two lengths
|
|
|
|
* because the caller needs to know how much to advance
|
|
|
|
* 's' and also how many bytes to output via con_write_page.
|
|
|
|
*/
|
|
|
|
static int fill_con_write_page(const char *s, unsigned int n,
|
|
|
|
unsigned long *page_bytes)
|
|
|
|
{
|
|
|
|
const char *orig_s = s;
|
|
|
|
char *p = con_write_page;
|
|
|
|
int left = PAGE_SIZE;
|
|
|
|
|
|
|
|
while (n--) {
|
|
|
|
if (*s == '\n') {
|
|
|
|
if (left < 2)
|
|
|
|
break;
|
|
|
|
*p++ = '\r';
|
|
|
|
left--;
|
|
|
|
} else if (left < 1)
|
|
|
|
break;
|
|
|
|
*p++ = *s++;
|
|
|
|
left--;
|
|
|
|
}
|
|
|
|
*page_bytes = p - con_write_page;
|
|
|
|
return s - orig_s;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void sunhv_console_write_paged(struct console *con, const char *s, unsigned n)
|
2006-02-11 11:25:21 +01:00
|
|
|
{
|
2007-05-16 02:03:54 +02:00
|
|
|
struct uart_port *port = sunhv_port;
|
2006-02-11 11:25:21 +01:00
|
|
|
unsigned long flags;
|
2007-07-16 08:53:32 +02:00
|
|
|
int locked = 1;
|
|
|
|
|
|
|
|
local_irq_save(flags);
|
|
|
|
if (port->sysrq) {
|
|
|
|
locked = 0;
|
|
|
|
} else if (oops_in_progress) {
|
|
|
|
locked = spin_trylock(&port->lock);
|
|
|
|
} else
|
|
|
|
spin_lock(&port->lock);
|
2006-02-11 11:25:21 +01:00
|
|
|
|
2007-05-16 02:03:54 +02:00
|
|
|
while (n > 0) {
|
|
|
|
unsigned long ra = __pa(con_write_page);
|
|
|
|
unsigned long page_bytes;
|
|
|
|
unsigned int cpy = fill_con_write_page(s, n,
|
|
|
|
&page_bytes);
|
|
|
|
|
|
|
|
n -= cpy;
|
|
|
|
s += cpy;
|
|
|
|
while (page_bytes > 0) {
|
|
|
|
unsigned long written;
|
|
|
|
int limit = 1000000;
|
|
|
|
|
|
|
|
while (limit--) {
|
|
|
|
unsigned long stat;
|
|
|
|
|
|
|
|
stat = sun4v_con_write(ra, page_bytes,
|
|
|
|
&written);
|
|
|
|
if (stat == HV_EOK)
|
|
|
|
break;
|
|
|
|
udelay(1);
|
|
|
|
}
|
|
|
|
if (limit <= 0)
|
|
|
|
break;
|
|
|
|
page_bytes -= written;
|
|
|
|
ra += written;
|
|
|
|
}
|
|
|
|
}
|
2007-07-16 08:53:32 +02:00
|
|
|
|
|
|
|
if (locked)
|
|
|
|
spin_unlock(&port->lock);
|
|
|
|
local_irq_restore(flags);
|
2007-05-16 02:03:54 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
static inline void sunhv_console_putchar(struct uart_port *port, char c)
|
|
|
|
{
|
|
|
|
int limit = 1000000;
|
2006-02-11 11:25:21 +01:00
|
|
|
|
|
|
|
while (limit-- > 0) {
|
2007-05-16 02:03:54 +02:00
|
|
|
long status = sun4v_con_putchar(c);
|
2006-02-11 11:25:21 +01:00
|
|
|
if (status == HV_EOK)
|
|
|
|
break;
|
2007-05-16 02:03:54 +02:00
|
|
|
udelay(1);
|
2006-02-11 11:25:21 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2007-05-16 02:03:54 +02:00
|
|
|
static void sunhv_console_write_bychar(struct console *con, const char *s, unsigned n)
|
2006-02-11 11:25:21 +01:00
|
|
|
{
|
|
|
|
struct uart_port *port = sunhv_port;
|
2007-05-16 02:03:54 +02:00
|
|
|
unsigned long flags;
|
2007-07-16 08:53:32 +02:00
|
|
|
int i, locked = 1;
|
|
|
|
|
|
|
|
local_irq_save(flags);
|
|
|
|
if (port->sysrq) {
|
|
|
|
locked = 0;
|
|
|
|
} else if (oops_in_progress) {
|
|
|
|
locked = spin_trylock(&port->lock);
|
|
|
|
} else
|
|
|
|
spin_lock(&port->lock);
|
2006-02-11 11:25:21 +01:00
|
|
|
|
|
|
|
for (i = 0; i < n; i++) {
|
|
|
|
if (*s == '\n')
|
|
|
|
sunhv_console_putchar(port, '\r');
|
|
|
|
sunhv_console_putchar(port, *s++);
|
|
|
|
}
|
2007-07-16 08:53:32 +02:00
|
|
|
|
|
|
|
if (locked)
|
|
|
|
spin_unlock(&port->lock);
|
|
|
|
local_irq_restore(flags);
|
2006-02-11 11:25:21 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
static struct console sunhv_console = {
|
2006-02-14 06:28:40 +01:00
|
|
|
.name = "ttyHV",
|
2007-05-16 02:03:54 +02:00
|
|
|
.write = sunhv_console_write_bychar,
|
2006-02-11 11:25:21 +01:00
|
|
|
.device = uart_console_device,
|
|
|
|
.flags = CON_PRINTBUFFER,
|
|
|
|
.index = -1,
|
|
|
|
.data = &sunhv_reg,
|
|
|
|
};
|
|
|
|
|
2006-06-30 09:00:22 +02:00
|
|
|
static int __devinit hv_probe(struct of_device *op, const struct of_device_id *match)
|
2006-02-11 11:25:21 +01:00
|
|
|
{
|
|
|
|
struct uart_port *port;
|
2007-05-16 02:03:54 +02:00
|
|
|
unsigned long minor;
|
2006-06-30 09:00:22 +02:00
|
|
|
int err;
|
2006-02-11 11:25:21 +01:00
|
|
|
|
2006-06-30 09:00:22 +02:00
|
|
|
if (op->irqs[0] == 0xffffffff)
|
2006-02-11 11:25:21 +01:00
|
|
|
return -ENODEV;
|
|
|
|
|
2006-06-30 09:00:22 +02:00
|
|
|
port = kzalloc(sizeof(struct uart_port), GFP_KERNEL);
|
2006-02-11 11:25:21 +01:00
|
|
|
if (unlikely(!port))
|
|
|
|
return -ENOMEM;
|
|
|
|
|
2007-05-16 02:03:54 +02:00
|
|
|
minor = 1;
|
|
|
|
if (sun4v_hvapi_register(HV_GRP_CORE, 1, &minor) == 0 &&
|
|
|
|
minor >= 1) {
|
|
|
|
err = -ENOMEM;
|
|
|
|
con_write_page = kzalloc(PAGE_SIZE, GFP_KERNEL);
|
|
|
|
if (!con_write_page)
|
|
|
|
goto out_free_port;
|
|
|
|
|
|
|
|
con_read_page = kzalloc(PAGE_SIZE, GFP_KERNEL);
|
|
|
|
if (!con_read_page)
|
|
|
|
goto out_free_con_write_page;
|
|
|
|
|
|
|
|
sunhv_console.write = sunhv_console_write_paged;
|
|
|
|
sunhv_ops = &bywrite_ops;
|
|
|
|
}
|
|
|
|
|
2006-06-30 09:00:22 +02:00
|
|
|
sunhv_port = port;
|
2006-02-16 06:56:49 +01:00
|
|
|
|
2006-02-11 11:25:21 +01:00
|
|
|
port->line = 0;
|
|
|
|
port->ops = &sunhv_pops;
|
|
|
|
port->type = PORT_SUNHV;
|
|
|
|
port->uartclk = ( 29491200 / 16 ); /* arbitrary */
|
|
|
|
|
2006-02-16 06:56:49 +01:00
|
|
|
port->membase = (unsigned char __iomem *) __pa(port);
|
|
|
|
|
2006-06-30 09:00:22 +02:00
|
|
|
port->irq = op->irqs[0];
|
|
|
|
|
|
|
|
port->dev = &op->dev;
|
2006-02-11 11:25:21 +01:00
|
|
|
|
2007-12-11 12:37:04 +01:00
|
|
|
err = sunserial_register_minors(&sunhv_reg, 1);
|
2006-06-30 09:00:22 +02:00
|
|
|
if (err)
|
2007-05-16 02:03:54 +02:00
|
|
|
goto out_free_con_read_page;
|
2006-02-11 11:25:21 +01:00
|
|
|
|
2007-07-21 01:59:26 +02:00
|
|
|
sunserial_console_match(&sunhv_console, op->node,
|
|
|
|
&sunhv_reg, port->line);
|
2006-02-14 06:28:40 +01:00
|
|
|
|
2006-06-30 09:00:22 +02:00
|
|
|
err = uart_add_one_port(&sunhv_reg, port);
|
|
|
|
if (err)
|
|
|
|
goto out_unregister_driver;
|
2006-02-11 11:25:21 +01:00
|
|
|
|
2006-06-30 09:00:22 +02:00
|
|
|
err = request_irq(port->irq, sunhv_interrupt, 0, "hvcons", port);
|
|
|
|
if (err)
|
|
|
|
goto out_remove_port;
|
2006-02-16 06:56:49 +01:00
|
|
|
|
2006-06-30 09:00:22 +02:00
|
|
|
dev_set_drvdata(&op->dev, port);
|
2006-02-11 11:25:21 +01:00
|
|
|
|
|
|
|
return 0;
|
2006-06-30 09:00:22 +02:00
|
|
|
|
|
|
|
out_remove_port:
|
|
|
|
uart_remove_one_port(&sunhv_reg, port);
|
|
|
|
|
|
|
|
out_unregister_driver:
|
2007-12-11 12:37:04 +01:00
|
|
|
sunserial_unregister_minors(&sunhv_reg, 1);
|
2006-06-30 09:00:22 +02:00
|
|
|
|
2007-05-16 02:03:54 +02:00
|
|
|
out_free_con_read_page:
|
|
|
|
kfree(con_read_page);
|
|
|
|
|
|
|
|
out_free_con_write_page:
|
|
|
|
kfree(con_write_page);
|
|
|
|
|
2006-06-30 09:00:22 +02:00
|
|
|
out_free_port:
|
|
|
|
kfree(port);
|
|
|
|
sunhv_port = NULL;
|
|
|
|
return err;
|
2006-02-11 11:25:21 +01:00
|
|
|
}
|
|
|
|
|
2006-06-30 09:00:22 +02:00
|
|
|
static int __devexit hv_remove(struct of_device *dev)
|
2006-02-11 11:25:21 +01:00
|
|
|
{
|
2006-06-30 09:00:22 +02:00
|
|
|
struct uart_port *port = dev_get_drvdata(&dev->dev);
|
2006-02-11 11:25:21 +01:00
|
|
|
|
2006-02-16 06:56:49 +01:00
|
|
|
free_irq(port->irq, port);
|
|
|
|
|
2006-02-11 11:25:21 +01:00
|
|
|
uart_remove_one_port(&sunhv_reg, port);
|
|
|
|
|
2007-12-11 12:37:04 +01:00
|
|
|
sunserial_unregister_minors(&sunhv_reg, 1);
|
2006-02-11 11:25:21 +01:00
|
|
|
|
2006-06-30 09:00:22 +02:00
|
|
|
kfree(port);
|
2006-02-11 11:25:21 +01:00
|
|
|
sunhv_port = NULL;
|
2006-06-30 09:00:22 +02:00
|
|
|
|
|
|
|
dev_set_drvdata(&dev->dev, NULL);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct of_device_id hv_match[] = {
|
|
|
|
{
|
|
|
|
.name = "console",
|
|
|
|
.compatible = "qcn",
|
|
|
|
},
|
2007-05-14 06:27:37 +02:00
|
|
|
{
|
|
|
|
.name = "console",
|
|
|
|
.compatible = "SUNW,sun4v-console",
|
|
|
|
},
|
2006-06-30 09:00:22 +02:00
|
|
|
{},
|
|
|
|
};
|
|
|
|
MODULE_DEVICE_TABLE(of, hv_match);
|
|
|
|
|
|
|
|
static struct of_platform_driver hv_driver = {
|
|
|
|
.name = "hv",
|
|
|
|
.match_table = hv_match,
|
|
|
|
.probe = hv_probe,
|
|
|
|
.remove = __devexit_p(hv_remove),
|
|
|
|
};
|
|
|
|
|
|
|
|
static int __init sunhv_init(void)
|
|
|
|
{
|
|
|
|
if (tlb_type != hypervisor)
|
|
|
|
return -ENODEV;
|
|
|
|
|
|
|
|
return of_register_driver(&hv_driver, &of_bus_type);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void __exit sunhv_exit(void)
|
|
|
|
{
|
|
|
|
of_unregister_driver(&hv_driver);
|
2006-02-11 11:25:21 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
module_init(sunhv_init);
|
|
|
|
module_exit(sunhv_exit);
|
|
|
|
|
|
|
|
MODULE_AUTHOR("David S. Miller");
|
2006-06-30 09:00:22 +02:00
|
|
|
MODULE_DESCRIPTION("SUN4V Hypervisor console driver");
|
|
|
|
MODULE_VERSION("2.0");
|
2006-02-11 11:25:21 +01:00
|
|
|
MODULE_LICENSE("GPL");
|