Fedora kernel-2.6.17-1.2142_FC4 patched with stable patch-2.6.17.4-vs2.0.2-rc26.diff
[linux-2.6.git] / arch / ppc / 4xx_io / serial_sicc.c
index f0728f3..98b25fa 100644 (file)
@@ -1,6 +1,4 @@
 /*
- *  arch/ppc/4xx_io/serial_sicc.c
- *
  *  Driver for IBM STB3xxx SICC serial port
  *
  *  Based on drivers/char/serial_amba.c, by ARM Ltd.
 #include <linux/mm.h>
 #include <linux/slab.h>
 #include <linux/init.h>
+#include <linux/capability.h>
 #include <linux/circ_buf.h>
 #include <linux/serial.h>
 #include <linux/console.h>
 #include <linux/sysrq.h>
+#include <linux/bitops.h>
 
 #include <asm/system.h>
 #include <asm/io.h>
 #include <asm/irq.h>
 #include <asm/uaccess.h>
-#include <asm/bitops.h>
 #include <asm/serial.h>
 
 
@@ -214,7 +213,6 @@ static struct tty_driver *siccnormal_driver;
  * memory if large numbers of serial ports are open.
  */
 static u_char *tmp_buf;
-static DECLARE_MUTEX(tmp_buf_sem);
 
 #define HIGH_BITS_OFFSET    ((sizeof(long)-sizeof(int))*8)
 
@@ -264,6 +262,7 @@ struct SICC_state {
     unsigned int        flags;
     int         count;
     struct SICC_info    *info;
+    spinlock_t         sicc_lock;
 };
 
 #define SICC_XMIT_SIZE 1024
@@ -385,9 +384,10 @@ static void siccuart_stop(struct tty_struct *tty)
     struct SICC_info *info = tty->driver_data;
     unsigned long flags;
 
-    save_flags(flags); cli();
+    /* disable interrupts while stopping serial port interrupts */
+    spin_lock_irqsave(&info->state->sicc_lock,flags);
     siccuart_disable_tx_interrupt(info);
-    restore_flags(flags);
+    spin_unlock_irqrestore(&info->state->sicc_lock,flags);
 }
 
 static void siccuart_start(struct tty_struct *tty)
@@ -395,11 +395,12 @@ static void siccuart_start(struct tty_struct *tty)
     struct SICC_info *info = tty->driver_data;
     unsigned long flags;
 
-    save_flags(flags); cli();
+    /* disable interrupts while starting serial port interrupts */
+    spin_lock_irqsave(&info->state->sicc_lock,flags);
     if (info->xmit.head != info->xmit.tail
         && info->xmit.buf)
         siccuart_enable_tx_interrupt(info);
-    restore_flags(flags);
+    spin_unlock_irqrestore(&info->state->sicc_lock,flags);
 }
 
 
@@ -604,7 +605,8 @@ static int siccuart_startup(struct SICC_info *info)
        return -ENOMEM;
     }
 
-    save_flags(flags); cli();
+    /* lock access to info while doing setup */
+    spin_lock_irqsave(&info->state->sicc_lock,flags);
 
     if (info->xmit.buf)
         free_page(page);
@@ -688,12 +690,12 @@ static int siccuart_startup(struct SICC_info *info)
     siccuart_enable_rx_interrupt(info);
 
     info->flags |= ASYNC_INITIALIZED;
-    restore_flags(flags);
+    spin_unlock_irqrestore(&info->state->sicc_lock,flags);
     return 0;
 
 
 errout:
-    restore_flags(flags);
+    spin_unlock_irqrestore(&info->state->sicc_lock,flags);
     return retval;
 }
 
@@ -708,7 +710,8 @@ static void siccuart_shutdown(struct SICC_info *info)
     if (!(info->flags & ASYNC_INITIALIZED))
         return;
 
-    save_flags(flags); cli(); /* Disable interrupts */
+    /* lock while shutting down port */
+    spin_lock_irqsave(&info->state->sicc_lock,flags); /* Disable interrupts */
 
     /*
      * clear delta_msr_wait queue to avoid mem leaks: we may free the irq
@@ -746,7 +749,7 @@ static void siccuart_shutdown(struct SICC_info *info)
 
     info->flags &= ~ASYNC_INITIALIZED;
 
-    restore_flags(flags);
+    spin_unlock_irqrestore(&info->state->sicc_lock,flags);
 }
 
 
@@ -868,8 +871,8 @@ static void siccuart_change_speed(struct SICC_info *info, struct termios *old_te
             info->ignore_status_mask |=  _LSR_OE_MASK;
     }
 
-    /* first, disable everything */
-    save_flags(flags); cli();
+    /* disable interrupts while reading and clearing registers */
+    spin_lock_irqsave(&info->state->sicc_lock,flags);
 
     old_rcr = readb(info->port->uart_base + BL_SICC_RCR);
     old_tcr = readb(info->port->uart_base + BL_SICC_TxCR);
@@ -881,7 +884,7 @@ static void siccuart_change_speed(struct SICC_info *info, struct termios *old_te
     /*RLBtrace (&ppc403Chan0, 0x2000000c, 0, 0);*/
 
 
-    restore_flags(flags);
+    spin_unlock_irqrestore(&info->state->sicc_lock,flags);
 
 
     /* Set baud rate */
@@ -909,12 +912,13 @@ static void siccuart_put_char(struct tty_struct *tty, u_char ch)
     if (!tty || !info->xmit.buf)
         return;
 
-    save_flags(flags); cli();
+    /* lock info->xmit while adding character to tx buffer */
+    spin_lock_irqsave(&info->state->sicc_lock,flags);
     if (CIRC_SPACE(info->xmit.head, info->xmit.tail, SICC_XMIT_SIZE) != 0) {
         info->xmit.buf[info->xmit.head] = ch;
         info->xmit.head = (info->xmit.head + 1) & (SICC_XMIT_SIZE - 1);
     }
-    restore_flags(flags);
+    spin_unlock_irqrestore(&info->state->sicc_lock,flags);
 }
 
 static void siccuart_flush_chars(struct tty_struct *tty)
@@ -928,12 +932,13 @@ static void siccuart_flush_chars(struct tty_struct *tty)
         || !info->xmit.buf)
         return;
 
-    save_flags(flags); cli();
+    /* disable interrupts while transmitting characters */
+    spin_lock_irqsave(&info->state->sicc_lock,flags);
     siccuart_enable_tx_interrupt(info);
-    restore_flags(flags);
+    spin_unlock_irqrestore(&info->state->sicc_lock,flags);
 }
 
-static int siccuart_write(struct tty_struct *tty, int from_user,
+static int siccuart_write(struct tty_struct *tty,
               const u_char * buf, int count)
 {
     struct SICC_info *info = tty->driver_data;
@@ -943,63 +948,28 @@ static int siccuart_write(struct tty_struct *tty, int from_user,
     if (!tty || !info->xmit.buf || !tmp_buf)
         return 0;
 
-    save_flags(flags);
-    if (from_user) {
-        down(&tmp_buf_sem);
-        while (1) {
-            int c1;
-            c = CIRC_SPACE_TO_END(info->xmit.head,
-                          info->xmit.tail,
-                          SICC_XMIT_SIZE);
-            if (count < c)
-                c = count;
-            if (c <= 0)
-                break;
-
-            c -= copy_from_user(tmp_buf, buf, c);
-            if (!c) {
-                if (!ret)
-                    ret = -EFAULT;
-                break;
-            }
-            cli();
-            c1 = CIRC_SPACE_TO_END(info->xmit.head,
-                           info->xmit.tail,
-                           SICC_XMIT_SIZE);
-            if (c1 < c)
-                c = c1;
-            memcpy(info->xmit.buf + info->xmit.head, tmp_buf, c);
-            info->xmit.head = (info->xmit.head + c) &
-                      (SICC_XMIT_SIZE - 1);
-            restore_flags(flags);
-            buf += c;
-            count -= c;
-            ret += c;
-        }
-        up(&tmp_buf_sem);
-    } else {
-        cli();
-        while (1) {
-            c = CIRC_SPACE_TO_END(info->xmit.head,
-                          info->xmit.tail,
-                          SICC_XMIT_SIZE);
-            if (count < c)
-                c = count;
-            if (c <= 0)
-                break;
-            memcpy(info->xmit.buf + info->xmit.head, buf, c);
-            info->xmit.head = (info->xmit.head + c) &
-                      (SICC_XMIT_SIZE - 1);
-            buf += c;
-            count -= c;
-            ret += c;
-        }
-        restore_flags(flags);
+    /* lock info->xmit while removing characters from buffer */
+    spin_lock_irqsave(&info->state->sicc_lock,flags);
+    while (1) {
+        c = CIRC_SPACE_TO_END(info->xmit.head,
+                      info->xmit.tail,
+                      SICC_XMIT_SIZE);
+        if (count < c)
+            c = count;
+        if (c <= 0)
+            break;
+        memcpy(info->xmit.buf + info->xmit.head, buf, c);
+        info->xmit.head = (info->xmit.head + c) &
+                  (SICC_XMIT_SIZE - 1);
+        buf += c;
+        count -= c;
+        ret += c;
     }
     if (info->xmit.head != info->xmit.tail
         && !tty->stopped
         && !tty->hw_stopped)
         siccuart_enable_tx_interrupt(info);
+    spin_unlock_irqrestore(&info->state->sicc_lock,flags);
     return ret;
 }
 
@@ -1023,9 +993,10 @@ static void siccuart_flush_buffer(struct tty_struct *tty)
     unsigned long flags;
 
     pr_debug("siccuart_flush_buffer(%d) called\n", tty->index);
-    save_flags(flags); cli();
+    /* lock info->xmit while zeroing buffer counts */
+    spin_lock_irqsave(&info->state->sicc_lock,flags);
     info->xmit.head = info->xmit.tail = 0;
-    restore_flags(flags);
+    spin_unlock_irqrestore(&info->state->sicc_lock,flags);
     wake_up_interruptible(&tty->write_wait);
     if ((tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) &&
         tty->ldisc.write_wakeup)
@@ -1054,10 +1025,11 @@ static void siccuart_throttle(struct tty_struct *tty)
         siccuart_send_xchar(tty, STOP_CHAR(tty));
 
     if (tty->termios->c_cflag & CRTSCTS) {
-        save_flags(flags); cli();
+        /* disable interrupts while setting modem control lines */
+        spin_lock_irqsave(&info->state->sicc_lock,flags);
         info->mctrl &= ~TIOCM_RTS;
         info->port->set_mctrl(info->port, info->mctrl);
-        restore_flags(flags);
+        spin_unlock_irqrestore(&info->state->sicc_lock,flags);
     }
 }
 
@@ -1074,10 +1046,11 @@ static void siccuart_unthrottle(struct tty_struct *tty)
     }
 
     if (tty->termios->c_cflag & CRTSCTS) {
-        save_flags(flags); cli();
+        /* disable interrupts while setting modem control lines */
+        spin_lock_irqsave(&info->state->sicc_lock,flags);
         info->mctrl |= TIOCM_RTS;
         info->port->set_mctrl(info->port, info->mctrl);
-        restore_flags(flags);
+        spin_unlock_irqrestore(&info->state->sicc_lock,flags);
     }
 }
 
@@ -1170,8 +1143,8 @@ static int set_serial_info(struct SICC_info *info,
     info->flags = ((state->flags & ~ASYNC_INTERNAL_FLAGS) |
                (info->flags & ASYNC_INTERNAL_FLAGS));
     state->custom_divisor = new_serial.custom_divisor;
-    state->close_delay = new_serial.close_delay * HZ / 100;
-    state->closing_wait = new_serial.closing_wait * HZ / 100;
+    state->close_delay = msecs_to_jiffies(10 * new_serial.close_delay);
+    state->closing_wait = msecs_to_jiffies(10 * new_serial.closing_wait);
     info->tty->low_latency = (info->flags & ASYNC_LOW_LATENCY) ? 1 : 0;
     port->fifosize = new_serial.xmit_fifo_size;
 
@@ -1216,9 +1189,10 @@ static int get_lsr_info(struct SICC_info *info, unsigned int *value)
     unsigned int result, status;
     unsigned long flags;
 
-    save_flags(flags); cli();
+    /* disable interrupts while reading status from port */
+    spin_lock_irqsave(&info->state->sicc_lock,flags);
     status = readb(info->port->uart_base +  BL_SICC_LSR);
-    restore_flags(flags);
+    spin_unlock_irqrestore(&info->state->sicc_lock,flags);
     result = status & _LSR_TSR_EMPTY ? TIOCSER_TEMT : 0;
 
     /*
@@ -1269,10 +1243,11 @@ static int set_modem_info(struct SICC_info *info, unsigned int cmd,
     default:
         return -EINVAL;
     }
-    save_flags(flags); cli();
+    /* disable interrupts while setting modem control lines */
+    spin_lock_irqsave(&info->state->sicc_lock,flags);
     if (old != info->mctrl)
         info->port->set_mctrl(info->port, info->mctrl);
-    restore_flags(flags);
+    spin_unlock_irqrestore(&info->state->sicc_lock,flags);
     return 0;
 }
 
@@ -1283,14 +1258,15 @@ static void siccuart_break_ctl(struct tty_struct *tty, int break_state)
     unsigned int lcr_h;
 
 
-    save_flags(flags); cli();
+    /* disable interrupts while setting break state */
+    spin_lock_irqsave(&info->state->sicc_lock,flags);
     lcr_h = readb(info->port + BL_SICC_LSR);
     if (break_state == -1)
         lcr_h |=  _LSR_LB_MASK;
     else
         lcr_h &= ~_LSR_LB_MASK;
     writeb(lcr_h, info->port + BL_SICC_LSRS);
-    restore_flags(flags);
+    spin_unlock_irqrestore(&info->state->sicc_lock,flags);
 }
 
 static int siccuart_ioctl(struct tty_struct *tty, struct file *file,
@@ -1338,9 +1314,10 @@ static int siccuart_ioctl(struct tty_struct *tty, struct file *file,
          *     RI where only 0->1 is counted.
          */
         case TIOCGICOUNT:
-            save_flags(flags); cli();
+            /* disable interrupts while getting interrupt count */
+            spin_lock_irqsave(&info->state->sicc_lock,flags);
             cnow = info->state->icount;
-            restore_flags(flags);
+            spin_unlock_irqrestore(&info->state->sicc_lock,flags);
             icount.cts = cnow.cts;
             icount.dsr = cnow.dsr;
             icount.rng = cnow.rng;
@@ -1377,22 +1354,24 @@ static void siccuart_set_termios(struct tty_struct *tty, struct termios *old_ter
     /* Handle transition to B0 status */
     if ((old_termios->c_cflag & CBAUD) &&
         !(cflag & CBAUD)) {
-        save_flags(flags); cli();
+        /* disable interrupts while setting break state */
+        spin_lock_irqsave(&info->state->sicc_lock,flags);
         info->mctrl &= ~(TIOCM_RTS | TIOCM_DTR);
         info->port->set_mctrl(info->port, info->mctrl);
-        restore_flags(flags);
+        spin_unlock_irqrestore(&info->state->sicc_lock,flags);
     }
 
     /* Handle transition away from B0 status */
     if (!(old_termios->c_cflag & CBAUD) &&
         (cflag & CBAUD)) {
-        save_flags(flags); cli();
+        /* disable interrupts while setting break state */
+        spin_lock_irqsave(&info->state->sicc_lock,flags);
         info->mctrl |= TIOCM_DTR;
         if (!(cflag & CRTSCTS) ||
             !test_bit(TTY_THROTTLED, &tty->flags))
             info->mctrl |= TIOCM_RTS;
         info->port->set_mctrl(info->port, info->mctrl);
-        restore_flags(flags);
+        spin_unlock_irqrestore(&info->state->sicc_lock,flags);
     }
 
     /* Handle turning off CRTSCTS */
@@ -1428,11 +1407,11 @@ static void siccuart_close(struct tty_struct *tty, struct file *filp)
 
     //pr_debug("siccuart_close() called\n");
 
-    save_flags(flags); cli();
+    /* lock tty->driver_data while closing port */
+    spin_lock_irqsave(&info->state->sicc_lock,flags);
 
     if (tty_hung_up_p(filp)) {
-        restore_flags(flags);
-        return;
+        goto quick_close;
     }
 
     if ((tty->count == 1) && (state->count != 1)) {
@@ -1451,11 +1430,10 @@ static void siccuart_close(struct tty_struct *tty, struct file *filp)
         state->count = 0;
     }
     if (state->count) {
-        restore_flags(flags);
-        return;
+        goto quick_close;
     }
     info->flags |= ASYNC_CLOSING;
-    restore_flags(flags);
+    spin_unlock_irqrestore(&info->state->sicc_lock,flags);
     /*
      * Now we wait for the transmit buffer to clear; and we notify
      * the line discipline to only process XON/XOFF characters.
@@ -1485,14 +1463,17 @@ static void siccuart_close(struct tty_struct *tty, struct file *filp)
     info->event = 0;
     info->tty = NULL;
     if (info->blocked_open) {
-        if (info->state->close_delay) {
-            set_current_state(TASK_INTERRUPTIBLE);
-            schedule_timeout(info->state->close_delay);
-        }
+        if (info->state->close_delay)
+            schedule_timeout_interruptible(info->state->close_delay);
         wake_up_interruptible(&info->open_wait);
     }
     info->flags &= ~(ASYNC_NORMAL_ACTIVE|ASYNC_CLOSING);
     wake_up_interruptible(&info->close_wait);
+    return;
+
+quick_close:
+    spin_unlock_irqrestore(&info->state->sicc_lock,flags);
+    return;
 }
 
 static void siccuart_wait_until_sent(struct tty_struct *tty, int timeout)
@@ -1511,7 +1492,7 @@ static void siccuart_wait_until_sent(struct tty_struct *tty, int timeout)
      * Note: we have to use pretty tight timings here to satisfy
      * the NIST-PCTS.
      */
-    char_time = (info->timeout - HZ/50) / info->port->fifosize;
+    char_time = (info->timeout - msecs_to_jiffies(20)) / info->port->fifosize;
     char_time = char_time / 5;
     if (char_time == 0)
         char_time = 1;
@@ -1536,8 +1517,7 @@ static void siccuart_wait_until_sent(struct tty_struct *tty, int timeout)
            tty->index, jiffies,
            expire, char_time);
     while ((readb(info->port->uart_base + BL_SICC_LSR) & _LSR_TX_ALL) != _LSR_TX_ALL) {
-        set_current_state(TASK_INTERRUPTIBLE);
-        schedule_timeout(char_time);
+        schedule_timeout_interruptible(char_time);
         if (signal_pending(current))
             break;
         if (timeout && time_after(jiffies, expire))
@@ -1604,20 +1584,22 @@ static int block_til_ready(struct tty_struct *tty, struct file *filp,
      */
     retval = 0;
     add_wait_queue(&info->open_wait, &wait);
-    save_flags(flags); cli();
+    /* lock while decrementing state->count */
+    spin_lock_irqsave(&info->state->sicc_lock,flags);
     if (!tty_hung_up_p(filp)) {
         extra_count = 1;
         state->count--;
     }
-    restore_flags(flags);
+    spin_unlock_irqrestore(&info->state->sicc_lock,flags);
     info->blocked_open++;
     while (1) {
-        save_flags(flags); cli();
+        /* disable interrupts while setting modem control lines */
+        spin_lock_irqsave(&info->state->sicc_lock,flags);
         if (tty->termios->c_cflag & CBAUD) {
             info->mctrl = TIOCM_DTR | TIOCM_RTS;
             info->port->set_mctrl(info->port, info->mctrl);
         }
-        restore_flags(flags);
+        spin_unlock_irqrestore(&info->state->sicc_lock,flags);
         set_current_state(TASK_INTERRUPTIBLE);
         if (tty_hung_up_p(filp) ||
             !(info->flags & ASYNC_INITIALIZED)) {
@@ -1655,9 +1637,8 @@ static struct SICC_info *siccuart_get(int line)
     state->count++;
     if (state->info)
         return state->info;
-    info = kmalloc(sizeof(struct SICC_info), GFP_KERNEL);
+    info = kzalloc(sizeof(struct SICC_info), GFP_KERNEL);
     if (info) {
-        memset(info, 0, sizeof(struct SICC_info));
         init_waitqueue_head(&info->open_wait);
         init_waitqueue_head(&info->close_wait);
         init_waitqueue_head(&info->delta_msr_wait);
@@ -1786,8 +1767,9 @@ int __init siccuart_init(void)
     for (i = 0; i < SERIAL_SICC_NR; i++) {
         struct SICC_state *state = sicc_state + i;
         state->line     = i;
-        state->close_delay  = 5 * HZ / 10;
+        state->close_delay  = msecs_to_jiffies(500);
         state->closing_wait = 30 * HZ;
+       spin_lock_init(&state->sicc_lock);
     }