X-Git-Url: http://git.onelab.eu/?a=blobdiff_plain;f=drivers%2Fchar%2Fhvsi.c;h=07d7e212bb18751906efdcf04aa6830ce129b3d2;hb=6a77f38946aaee1cd85eeec6cf4229b204c15071;hp=595079c07be541edff08da5b86286dabd4f420e6;hpb=87fc8d1bb10cd459024a742c6a10961fefcef18f;p=linux-2.6.git diff --git a/drivers/char/hvsi.c b/drivers/char/hvsi.c index 595079c07..07d7e212b 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 @@ -74,6 +70,7 @@ struct hvsi_struct { struct work_struct 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,6 +193,7 @@ 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]; @@ -296,14 +302,9 @@ static int hvsi_read(struct hvsi_struct *hp, char *buf, int count) return 0; } -/* - * 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) { @@ -313,15 +314,14 @@ static struct tty_struct *hvsi_recv_control(struct hvsi_struct *hp, pr_debug("hvsi%i: CD dropped\n", hp->index); hp->mctrl &= TIOCM_CD; if (!(hp->tty->flags & CLOCAL)) - to_hangup = hp->tty; + *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 +329,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 +386,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); @@ -467,17 +465,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 +510,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 +527,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 @@ -560,16 +561,15 @@ static irqreturn_t hvsi_interrupt(int irq, void *arg, struct pt_regs *regs) 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 +587,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 +608,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 */ @@ -757,6 +762,23 @@ static int hvsi_handshake(struct hvsi_struct *hp) return 0; } +static void hvsi_handshaker(void *arg) +{ + struct hvsi_struct *hp = (struct hvsi_struct *)arg; + + 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 +830,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 +841,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); @@ -889,7 +915,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 +953,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,12 +975,13 @@ 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); } } @@ -966,6 +999,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); @@ -982,6 +1028,7 @@ static void hvsi_write_worker(void *arg) wake_up_interruptible(&hp->tty->write_wait); } +out: spin_unlock_irqrestore(&hp->lock, flags); } @@ -999,29 +1046,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 +1094,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); @@ -1294,9 +1328,10 @@ static int __init hvsi_console_init(void) hp = &hvsi_ports[hvsi_count]; INIT_WORK(&hp->writer, hvsi_write_worker, hp); + INIT_WORK(&hp->handshaker, hvsi_handshaker, hp); 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;