fedora core 6 1.2949 + vserver 2.2.0
[linux-2.6.git] / drivers / char / hvsi.c
index 595079c..d780683 100644 (file)
  * 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>
@@ -54,6 +49,7 @@
 #include <asm/prom.h>
 #include <asm/uaccess.h>
 #include <asm/vio.h>
+#include <asm/param.h>
 
 #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++;
        }