* the OS cannot change the speed of the port through this protocol.
*/
-/* TODO:
- * test FSP reset
- * add udbg support for xmon/kdb
- */
-
#undef DEBUG
#include <linux/console.h>
#include <asm/prom.h>
#include <asm/uaccess.h>
#include <asm/vio.h>
+#include <asm/param.h>
#define HVSI_MAJOR 229
#define HVSI_MINOR 128
#define __ALIGNED__ __attribute__((__aligned__(sizeof(long))))
struct hvsi_struct {
- struct work_struct writer;
+ struct delayed_work writer;
+ struct work_struct handshaker;
wait_queue_head_t emptyq; /* woken when outbuf is emptied */
wait_queue_head_t stateq; /* woken when HVSI state changes */
spinlock_t lock;
HVSI_WAIT_FOR_VER_QUERY,
HVSI_OPEN,
HVSI_WAIT_FOR_MCTRL_RESPONSE,
+ HVSI_FSP_DIED,
};
#define HVSI_CONSOLE 0x1
} u;
} __attribute__((packed));
+
+
+static inline int is_console(struct hvsi_struct *hp)
+{
+ return hp->flags & HVSI_CONSOLE;
+}
+
static inline int is_open(struct hvsi_struct *hp)
{
/* if we're waiting for an mctrl then we're already open */
"HVSI_WAIT_FOR_VER_QUERY",
"HVSI_OPEN",
"HVSI_WAIT_FOR_MCTRL_RESPONSE",
+ "HVSI_FSP_DIED",
};
const char *name = state_names[hp->state];
- if (hp->state > (sizeof(state_names)/sizeof(char*)))
+ if (hp->state > ARRAY_SIZE(state_names))
name = "UNKNOWN";
pr_debug("hvsi%i: state = %s\n", hp->index, name);
dump_hex(packet, header->len);
}
-/* can't use hvc_get_chars because that strips CRs */
static int hvsi_read(struct hvsi_struct *hp, char *buf, int count)
{
unsigned long got;
- if (plpar_hcall(H_GET_TERM_CHAR, hp->vtermno, 0, 0, 0, &got,
- (unsigned long *)buf, (unsigned long *)buf+1) == H_Success)
- return got;
- return 0;
+ got = hvc_get_chars(hp->vtermno, buf, count);
+
+ return got;
}
-/*
- * we can't call tty_hangup() directly here because we need to call that
- * outside of our lock
- */
-static struct tty_struct *hvsi_recv_control(struct hvsi_struct *hp,
- uint8_t *packet)
+static void hvsi_recv_control(struct hvsi_struct *hp, uint8_t *packet,
+ struct tty_struct **to_hangup, struct hvsi_struct **to_handshake)
{
- struct tty_struct *to_hangup = NULL;
struct hvsi_control *header = (struct hvsi_control *)packet;
switch (header->verb) {
/* CD went away; no more connection */
pr_debug("hvsi%i: CD dropped\n", hp->index);
hp->mctrl &= TIOCM_CD;
- if (!(hp->tty->flags & CLOCAL))
- to_hangup = hp->tty;
+ /* If userland hasn't done an open(2) yet, hp->tty is NULL. */
+ if (hp->tty && !(hp->tty->flags & CLOCAL))
+ *to_hangup = hp->tty;
}
break;
case VSV_CLOSE_PROTOCOL:
- printk(KERN_DEBUG
- "hvsi%i: service processor closed connection!\n", hp->index);
- __set_state(hp, HVSI_CLOSED);
- to_hangup = hp->tty;
- hp->tty = NULL;
+ pr_debug("hvsi%i: service processor came back\n", hp->index);
+ if (hp->state != HVSI_CLOSED) {
+ *to_handshake = hp;
+ }
break;
default:
printk(KERN_WARNING "hvsi%i: unknown HVSI control packet: ",
dump_packet(packet);
break;
}
-
- return to_hangup;
}
static void hvsi_recv_response(struct hvsi_struct *hp, uint8_t *packet)
switch (hp->state) {
case HVSI_WAIT_FOR_VER_QUERY:
- __set_state(hp, HVSI_OPEN);
hvsi_version_respond(hp, query->seqno);
+ __set_state(hp, HVSI_OPEN);
break;
default:
printk(KERN_ERR "hvsi%i: unexpected query: ", hp->index);
hp->sysrq = 1;
continue;
} else if (hp->sysrq) {
- handle_sysrq(c, NULL, hp->tty);
+ handle_sysrq(c, hp->tty);
hp->sysrq = 0;
continue;
}
* incoming data).
*/
static int hvsi_load_chunk(struct hvsi_struct *hp, struct tty_struct **flip,
- struct tty_struct **hangup)
+ struct tty_struct **hangup, struct hvsi_struct **handshake)
{
uint8_t *packet = hp->inbuf;
int chunklen;
*flip = NULL;
*hangup = NULL;
+ *handshake = NULL;
chunklen = hvsi_read(hp, hp->inbuf_end, HVSI_MAX_READ);
- if (chunklen == 0)
+ if (chunklen == 0) {
+ pr_debug("%s: 0-length read\n", __FUNCTION__);
return 0;
+ }
pr_debug("%s: got %i bytes\n", __FUNCTION__, chunklen);
dbg_dump_hex(hp->inbuf_end, chunklen);
*flip = hvsi_recv_data(hp, packet);
break;
case VS_CONTROL_PACKET_HEADER:
- *hangup = hvsi_recv_control(hp, packet);
+ hvsi_recv_control(hp, packet, hangup, handshake);
break;
case VS_QUERY_RESPONSE_PACKET_HEADER:
hvsi_recv_response(hp, packet);
packet += len_packet(packet);
- if (*hangup) {
- pr_debug("%s: hangup\n", __FUNCTION__);
+ if (*hangup || *handshake) {
+ pr_debug("%s: hangup or handshake\n", __FUNCTION__);
/*
* we need to send the hangup now before receiving any more data.
* If we get "data, hangup, data", we can't deliver the second
* must get all pending data because we only get an irq on empty->non-empty
* transition
*/
-static irqreturn_t hvsi_interrupt(int irq, void *arg, struct pt_regs *regs)
+static irqreturn_t hvsi_interrupt(int irq, void *arg)
{
struct hvsi_struct *hp = (struct hvsi_struct *)arg;
struct tty_struct *flip;
struct tty_struct *hangup;
+ struct hvsi_struct *handshake;
unsigned long flags;
- irqreturn_t handled = IRQ_NONE;
int again = 1;
pr_debug("%s\n", __FUNCTION__);
while (again) {
spin_lock_irqsave(&hp->lock, flags);
- again = hvsi_load_chunk(hp, &flip, &hangup);
- handled = IRQ_HANDLED;
+ again = hvsi_load_chunk(hp, &flip, &hangup, &handshake);
spin_unlock_irqrestore(&hp->lock, flags);
/*
if (hangup) {
tty_hangup(hangup);
}
+
+ if (handshake) {
+ pr_debug("hvsi%i: attempting re-handshake\n", handshake->index);
+ schedule_work(&handshake->handshaker);
+ }
}
spin_lock_irqsave(&hp->lock, flags);
tty_flip_buffer_push(flip);
}
- return handled;
+ return IRQ_HANDLED;
}
/* for boot console, before the irq handler is running */
unsigned long end_jiffies = jiffies + HVSI_TIMEOUT;
for (;;) {
- hvsi_interrupt(hp->virq, (void *)hp, NULL); /* get pending data */
+ hvsi_interrupt(hp->virq, (void *)hp); /* get pending data */
if (hp->state == state)
return 0;
/* wait for irq handler to change our state */
static int wait_for_state(struct hvsi_struct *hp, int state)
{
- unsigned long end_jiffies = jiffies + HVSI_TIMEOUT;
- unsigned long timeout;
int ret = 0;
- DECLARE_WAITQUEUE(myself, current);
- set_current_state(TASK_INTERRUPTIBLE);
- add_wait_queue(&hp->stateq, &myself);
-
- for (;;) {
- set_current_state(TASK_INTERRUPTIBLE);
- if (hp->state == state)
- break;
- timeout = end_jiffies - jiffies;
- if (time_after(jiffies, end_jiffies)) {
- ret = -EIO;
- break;
- }
- schedule_timeout(timeout);
- }
- remove_wait_queue(&hp->stateq, &myself);
- set_current_state(TASK_RUNNING);
+ if (!wait_event_timeout(hp->stateq, (hp->state == state), HVSI_TIMEOUT))
+ ret = -EIO;
return ret;
}
return 0;
}
+static void hvsi_handshaker(struct work_struct *work)
+{
+ struct hvsi_struct *hp =
+ container_of(work, struct hvsi_struct, handshaker);
+
+ if (hvsi_handshake(hp) >= 0)
+ return;
+
+ printk(KERN_ERR "hvsi%i: re-handshaking failed\n", hp->index);
+ if (is_console(hp)) {
+ /*
+ * ttys will re-attempt the handshake via hvsi_open, but
+ * the console will not.
+ */
+ printk(KERN_ERR "hvsi%i: lost console!\n", hp->index);
+ }
+}
+
static int hvsi_put_chars(struct hvsi_struct *hp, const char *buf, int count)
{
struct hvsi_data packet __ALIGNED__;
tty->driver_data = hp;
tty->low_latency = 1; /* avoid throttle/tty_flip_buffer_push race */
+ mb();
+ if (hp->state == HVSI_FSP_DIED)
+ return -EIO;
+
spin_lock_irqsave(&hp->lock, flags);
hp->tty = tty;
hp->count++;
h_vio_signal(hp->vtermno, VIO_IRQ_ENABLE);
spin_unlock_irqrestore(&hp->lock, flags);
- if (hp->flags & HVSI_CONSOLE)
+ if (is_console(hp))
return 0; /* this has already been handshaked as the console */
ret = hvsi_handshake(hp);
/* wait for hvsi_write_worker to empty hp->outbuf */
static void hvsi_flush_output(struct hvsi_struct *hp)
{
- unsigned long end_jiffies = jiffies + HVSI_TIMEOUT;
- unsigned long timeout;
-
- DECLARE_WAITQUEUE(myself, current);
- set_current_state(TASK_UNINTERRUPTIBLE);
- add_wait_queue(&hp->emptyq, &myself);
-
- for (;;) {
- set_current_state(TASK_UNINTERRUPTIBLE);
- if (hp->n_outbuf <= 0)
- break;
- timeout = end_jiffies - jiffies;
- if (time_after(jiffies, end_jiffies))
- break;
- schedule_timeout(timeout);
- }
- remove_wait_queue(&hp->emptyq, &myself);
- set_current_state(TASK_RUNNING);
+ wait_event_timeout(hp->emptyq, (hp->n_outbuf <= 0), HVSI_TIMEOUT);
/* 'writer' could still be pending if it didn't see n_outbuf = 0 yet */
cancel_delayed_work(&hp->writer);
hp->inbuf_end = hp->inbuf; /* discard remaining partial packets */
/* only close down connection if it is not the console */
- if (!(hp->flags & HVSI_CONSOLE)) {
+ if (!is_console(hp)) {
h_vio_signal(hp->vtermno, VIO_IRQ_DISABLE); /* no more irqs */
__set_state(hp, HVSI_CLOSED);
/*
static void hvsi_hangup(struct tty_struct *tty)
{
struct hvsi_struct *hp = tty->driver_data;
+ unsigned long flags;
pr_debug("%s\n", __FUNCTION__);
+ spin_lock_irqsave(&hp->lock, flags);
+
hp->count = 0;
+ hp->n_outbuf = 0;
hp->tty = NULL;
+
+ spin_unlock_irqrestore(&hp->lock, flags);
}
/* called with hp->lock held */
return;
n = hvsi_put_chars(hp, hp->outbuf, hp->n_outbuf);
- if (n != 0) {
- /*
- * either all data was sent or there was an error, and we throw away
- * data on error.
- */
+ if (n > 0) {
+ /* success */
+ pr_debug("%s: wrote %i chars\n", __FUNCTION__, n);
hp->n_outbuf = 0;
+ } else if (n == -EIO) {
+ __set_state(hp, HVSI_FSP_DIED);
+ printk(KERN_ERR "hvsi%i: service processor died\n", hp->index);
}
}
/* hvsi_write_worker will keep rescheduling itself until outbuf is empty */
-static void hvsi_write_worker(void *arg)
+static void hvsi_write_worker(struct work_struct *work)
{
- struct hvsi_struct *hp = (struct hvsi_struct *)arg;
+ struct hvsi_struct *hp =
+ container_of(work, struct hvsi_struct, writer.work);
unsigned long flags;
#ifdef DEBUG
static long start_j = 0;
spin_lock_irqsave(&hp->lock, flags);
+ pr_debug("%s: %i chars in buffer\n", __FUNCTION__, hp->n_outbuf);
+
+ if (!is_open(hp)) {
+ /*
+ * We could have a non-open connection if the service processor died
+ * while we were busily scheduling ourselves. In that case, it could
+ * be minutes before the service processor comes back, so only try
+ * again once a second.
+ */
+ schedule_delayed_work(&hp->writer, HZ);
+ goto out;
+ }
+
hvsi_push(hp);
if (hp->n_outbuf > 0)
schedule_delayed_work(&hp->writer, 10);
start_j = 0;
#endif /* DEBUG */
wake_up_all(&hp->emptyq);
- if (test_bit(TTY_DO_WRITE_WAKEUP, &hp->tty->flags)
- && hp->tty->ldisc.write_wakeup)
- hp->tty->ldisc.write_wakeup(hp->tty);
- wake_up_interruptible(&hp->tty->write_wait);
+ tty_wakeup(hp->tty);
}
+out:
spin_unlock_irqrestore(&hp->lock, flags);
}
return hp->n_outbuf;
}
-static int hvsi_write(struct tty_struct *tty, int from_user,
+static int hvsi_write(struct tty_struct *tty,
const unsigned char *buf, int count)
{
struct hvsi_struct *hp = tty->driver_data;
const char *source = buf;
- char *kbuf = NULL;
unsigned long flags;
int total = 0;
int origcount = count;
- if (from_user) {
- kbuf = kmalloc(count, GFP_KERNEL);
- if (kbuf == NULL)
- return -ENOMEM;
- if (copy_from_user(kbuf, buf, count)) {
- kfree(kbuf);
- return -EFAULT;
- }
- source = kbuf;
- }
-
spin_lock_irqsave(&hp->lock, flags);
+ pr_debug("%s: %i chars in buffer\n", __FUNCTION__, hp->n_outbuf);
+
if (!is_open(hp)) {
/* we're either closing or not yet open; don't accept data */
pr_debug("%s: not open\n", __FUNCTION__);
out:
spin_unlock_irqrestore(&hp->lock, flags);
- if (from_user)
- kfree(kbuf);
-
if (total != origcount)
pr_debug("%s: wanted %i, only wrote %i\n", __FUNCTION__, origcount,
total);
}
-static struct tty_operations hvsi_ops = {
+static const struct tty_operations hvsi_ops = {
.open = hvsi_open,
.close = hvsi_close,
.write = hvsi_write,
return -ENOMEM;
hvsi_driver->owner = THIS_MODULE;
- hvsi_driver->devfs_name = "hvsi/";
hvsi_driver->driver_name = "hvsi";
hvsi_driver->name = "hvsi";
hvsi_driver->major = HVSI_MAJOR;
hvsi_driver->type = TTY_DRIVER_TYPE_SYSTEM;
hvsi_driver->init_termios = tty_std_termios;
hvsi_driver->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL;
+ hvsi_driver->init_termios.c_ispeed = 9600;
+ hvsi_driver->init_termios.c_ospeed = 9600;
hvsi_driver->flags = TTY_DRIVER_REAL_RAW;
tty_set_operations(hvsi_driver, &hvsi_ops);
struct hvsi_struct *hp = &hvsi_ports[i];
int ret = 1;
- ret = request_irq(hp->virq, hvsi_interrupt, SA_INTERRUPT, "hvsi", hp);
+ ret = request_irq(hp->virq, hvsi_interrupt, IRQF_DISABLED, "hvsi", hp);
if (ret)
printk(KERN_ERR "HVSI: couldn't reserve irq 0x%x (error %i)\n",
hp->virq, ret);
if (tty_register_driver(hvsi_driver))
panic("Couldn't register hvsi console driver\n");
- printk(KERN_INFO "HVSI: registered %i devices\n", hvsi_count);
+ printk(KERN_DEBUG "HVSI: registered %i devices\n", hvsi_count);
return 0;
}
vty != NULL;
vty = of_find_compatible_node(vty, "serial", "hvterm-protocol")) {
struct hvsi_struct *hp;
- uint32_t *vtermno;
- uint32_t *irq;
+ const uint32_t *vtermno, *irq;
- vtermno = (uint32_t *)get_property(vty, "reg", NULL);
- irq = (uint32_t *)get_property(vty, "interrupts", NULL);
+ vtermno = get_property(vty, "reg", NULL);
+ irq = get_property(vty, "interrupts", NULL);
if (!vtermno || !irq)
continue;
}
hp = &hvsi_ports[hvsi_count];
- INIT_WORK(&hp->writer, hvsi_write_worker, hp);
+ INIT_DELAYED_WORK(&hp->writer, hvsi_write_worker);
+ INIT_WORK(&hp->handshaker, hvsi_handshaker);
init_waitqueue_head(&hp->emptyq);
init_waitqueue_head(&hp->stateq);
- hp->lock = SPIN_LOCK_UNLOCKED;
+ spin_lock_init(&hp->lock);
hp->index = hvsi_count;
hp->inbuf_end = hp->inbuf;
hp->state = HVSI_CLOSED;
hp->vtermno = *vtermno;
- hp->virq = virt_irq_create_mapping(irq[0]);
+ hp->virq = irq_create_mapping(NULL, irq[0]);
if (hp->virq == NO_IRQ) {
printk(KERN_ERR "%s: couldn't create irq mapping for 0x%x\n",
- __FUNCTION__, hp->virq);
+ __FUNCTION__, irq[0]);
continue;
- } else
- hp->virq = irq_offset_up(hp->virq);
+ }
hvsi_count++;
}