X-Git-Url: http://git.onelab.eu/?a=blobdiff_plain;f=drivers%2Fchar%2Fhvsi.c;h=d7806834fc171ad07f08c90f0e5b20a30c000987;hb=97bf2856c6014879bd04983a3e9dfcdac1e7fe85;hp=595079c07be541edff08da5b86286dabd4f420e6;hpb=a2c21200f1c81b08cb55e417b68150bba439b646;p=linux-2.6.git diff --git a/drivers/char/hvsi.c b/drivers/char/hvsi.c index 595079c07..d7806834f 100644 --- a/drivers/char/hvsi.c +++ b/drivers/char/hvsi.c @@ -29,11 +29,6 @@ * 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 @@ -54,6 +49,7 @@ #include #include #include +#include #define HVSI_MAJOR 229 #define HVSI_MINOR 128 @@ -73,7 +69,8 @@ #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; @@ -109,6 +106,7 @@ enum HVSI_PROTOCOL_STATE { HVSI_WAIT_FOR_VER_QUERY, HVSI_OPEN, HVSI_WAIT_FOR_MCTRL_RESPONSE, + HVSI_FSP_DIED, }; #define HVSI_CONSOLE 0x1 @@ -172,6 +170,13 @@ struct hvsi_query_response { } 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 */ @@ -188,10 +193,11 @@ static inline void print_state(struct hvsi_struct *hp) "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); @@ -285,25 +291,18 @@ static void dump_packet(uint8_t *packet) 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) { @@ -312,16 +311,16 @@ static struct tty_struct *hvsi_recv_control(struct hvsi_struct *hp, /* 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: ", @@ -329,8 +328,6 @@ static struct tty_struct *hvsi_recv_control(struct hvsi_struct *hp, dump_packet(packet); break; } - - return to_hangup; } static void hvsi_recv_response(struct hvsi_struct *hp, uint8_t *packet) @@ -388,8 +385,8 @@ static void hvsi_recv_query(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); @@ -409,7 +406,7 @@ static void hvsi_insert_chars(struct hvsi_struct *hp, const char *buf, int len) hp->sysrq = 1; continue; } else if (hp->sysrq) { - handle_sysrq(c, NULL, hp->tty); + handle_sysrq(c, hp->tty); hp->sysrq = 0; continue; } @@ -467,17 +464,20 @@ static struct tty_struct *hvsi_recv_data(struct hvsi_struct *hp, * 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); @@ -509,7 +509,7 @@ static int hvsi_load_chunk(struct hvsi_struct *hp, struct tty_struct **flip, *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); @@ -526,8 +526,8 @@ static int hvsi_load_chunk(struct hvsi_struct *hp, struct tty_struct **flip, 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 @@ -555,21 +555,20 @@ static void hvsi_send_overflow(struct hvsi_struct *hp) * 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); /* @@ -587,6 +586,11 @@ static irqreturn_t hvsi_interrupt(int irq, void *arg, struct pt_regs *regs) 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); @@ -603,7 +607,7 @@ static irqreturn_t hvsi_interrupt(int irq, void *arg, struct pt_regs *regs) tty_flip_buffer_push(flip); } - return handled; + return IRQ_HANDLED; } /* for boot console, before the irq handler is running */ @@ -612,7 +616,7 @@ static int __init poll_for_state(struct hvsi_struct *hp, int state) 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; @@ -626,27 +630,10 @@ static int __init poll_for_state(struct hvsi_struct *hp, int state) /* 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; } @@ -757,6 +744,24 @@ static int hvsi_handshake(struct hvsi_struct *hp) 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__; @@ -808,6 +813,10 @@ static int hvsi_open(struct tty_struct *tty, struct file *filp) 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++; @@ -815,7 +824,7 @@ static int hvsi_open(struct tty_struct *tty, struct file *filp) 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); @@ -842,24 +851,7 @@ static int hvsi_open(struct tty_struct *tty, struct file *filp) /* 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); @@ -889,7 +881,7 @@ static void hvsi_close(struct tty_struct *tty, struct file *filp) 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); /* @@ -927,11 +919,17 @@ static void hvsi_close(struct tty_struct *tty, struct file *filp) 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 */ @@ -943,19 +941,21 @@ static void hvsi_push(struct hvsi_struct *hp) 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; @@ -966,6 +966,19 @@ static void hvsi_write_worker(void *arg) 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); @@ -976,12 +989,10 @@ static void hvsi_write_worker(void *arg) 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); } @@ -999,29 +1010,19 @@ static int hvsi_chars_in_buffer(struct tty_struct *tty) 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__); @@ -1057,9 +1058,6 @@ static int hvsi_write(struct tty_struct *tty, int from_user, 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); @@ -1134,7 +1132,7 @@ static int hvsi_tiocmset(struct tty_struct *tty, struct file *file, } -static struct tty_operations hvsi_ops = { +static const struct tty_operations hvsi_ops = { .open = hvsi_open, .close = hvsi_close, .write = hvsi_write, @@ -1156,7 +1154,6 @@ static int __init hvsi_init(void) 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; @@ -1164,6 +1161,8 @@ static int __init hvsi_init(void) 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); @@ -1171,7 +1170,7 @@ static int __init hvsi_init(void) 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); @@ -1181,7 +1180,7 @@ static int __init hvsi_init(void) 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; } @@ -1279,11 +1278,10 @@ static int __init hvsi_console_init(void) 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; @@ -1293,21 +1291,21 @@ static int __init hvsi_console_init(void) } 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++; }