RE: Problem of concurrency in arch/ppc/8260_io/uart.c
flat view
From: Joakim Tjernlund <hidden>
Date: 2003-09-27 13:43:51
quoted
If that freaks out the CPM, put one byte of zero into the buffer and give it a count of 1. Then you just have to hold the lock across the update of the BD ptr, just like everywhere else.That will work, but wont the zero show up on the console? hmm, since the zero count will be very unlikely case anyhow, I can live with that.
OK, this patch does the above. I left my test function(err_copy_from_user) to simulate bad copy_from_user() copies every now and then. Just remove it and change the call in rs_8xx_write when you have tested it. Works great for me, not even jove(emacs like editor) will break it. Jocke Index: arch/ppc/8xx_io/uart.c =================================================================== RCS file: /home/cvsadmin/cvsroot/kernel/linuxppc/arch/ppc/8xx_io/uart.c,v retrieving revision 1.7 diff -u -r1.7 uart.c
--- arch/ppc/8xx_io/uart.c 13 Jun 2003 09:04:43 -0000 1.7
+++ arch/ppc/8xx_io/uart.c 27 Sep 2003 13:29:53 -0000@@ -81,7 +81,11 @@ #define TX_WAKEUP ASYNC_SHARE_IRQ static char *serial_name = "CPM UART driver"; -static char *serial_version = "0.03"; +static char *serial_version = "0.04"; + +/* TX buffer length used by my_console_write. + Assume minumun length until it gets set by this driver */ +static int console_tx_buf_len = 1; static DECLARE_TASK_QUEUE(tq_serial);
@@ -196,6 +200,7 @@ /* The number of buffer descriptors and their sizes. */ +#define EARLY_BUF_SIZE 4 #define RX_NUM_FIFO 4 #define RX_BUF_SIZE 32 #define TX_NUM_FIFO 4
@@ -1068,6 +1073,7 @@ ser_info_t *info = (ser_info_t *)tty->driver_data; volatile cbd_t *bdp; unsigned char *cp; + unsigned long flags; if (serial_paranoia_check(info, tty->device, "rs_put_char")) return;
@@ -1075,7 +1081,16 @@ if (!tty) return; + local_irq_save(flags); bdp = info->tx_cur; + /* Get next BD. + */ + if (bdp->cbd_sc & BD_SC_WRAP) + info->tx_cur = info->tx_bd_base; + else + info->tx_cur = (cbd_t *)bdp + 1; + local_irq_restore(flags); + while (bdp->cbd_sc & BD_SC_READY); cp = info->tx_va_base + ((bdp - info->tx_bd_base) * TX_BUF_SIZE);
@@ -1083,14 +1098,20 @@ bdp->cbd_datlen = 1; bdp->cbd_sc |= BD_SC_READY; - /* Get next BD. - */ - if (bdp->cbd_sc & BD_SC_WRAP) - bdp = info->tx_bd_base; - else - bdp++; +} +int err_copy_from_user(void * to, void* from, int len) +{ + static int err; + int res; + + err = (err+1)%13; - info->tx_cur = (cbd_t *)bdp; + res = copy_from_user(to, from, len); + if(!err && len > 1) + return len-1; /* partial copy */ + if(!err) + return len; /* nothing copied */ + return res; } static int rs_8xx_write(struct tty_struct * tty, int from_user,
@@ -1100,6 +1121,7 @@ ser_info_t *info = (ser_info_t *)tty->driver_data; volatile cbd_t *bdp; unsigned char *cp; + unsigned long flags; #ifdef CONFIG_KGDB_CONSOLE /* Try to let stub handle output. Returns true if it did. */
@@ -1113,44 +1135,47 @@ if (!tty) return 0; - bdp = info->tx_cur; - while (1) { c = MIN(count, TX_BUF_SIZE); if (c <= 0) break; + local_irq_save(flags); + bdp = info->tx_cur; if (bdp->cbd_sc & BD_SC_READY) { info->flags |= TX_WAKEUP; + local_irq_restore(flags); break; } + /* Get next BD. + */ + if (bdp->cbd_sc & BD_SC_WRAP) + info->tx_cur = info->tx_bd_base; + else + info->tx_cur = (cbd_t *)bdp + 1; + local_irq_restore(flags); cp = info->tx_va_base + ((bdp - info->tx_bd_base) * TX_BUF_SIZE); - if (from_user) { - if (copy_from_user((void *)cp, buf, c)) { - if (!ret) - ret = -EFAULT; - break; - } - } else { + if (from_user) + c -= err_copy_from_user((void *)cp, buf, c); + else memcpy((void *)cp, buf, c); + + if (c) { + bdp->cbd_datlen = c; + bdp->cbd_sc |= BD_SC_READY; + } else { + bdp->cbd_datlen = 1;/* Need to TX at least 1 char to keep CPM sane */ + *cp = 0; + bdp->cbd_sc |= BD_SC_READY; + if (!ret) + ret = -EFAULT; + break; } - - bdp->cbd_datlen = c; - bdp->cbd_sc |= BD_SC_READY; - buf += c; count -= c; ret += c; - - /* Get next BD. - */ - if (bdp->cbd_sc & BD_SC_WRAP) - bdp = info->tx_bd_base; - else - bdp++; - info->tx_cur = (cbd_t *)bdp; } return ret; }
@@ -1210,28 +1235,28 @@ { volatile cbd_t *bdp; unsigned char *cp; + unsigned long flags; ser_info_t *info = (ser_info_t *)tty->driver_data; if (serial_paranoia_check(info, tty->device, "rs_send_char")) return; + local_irq_save(flags); bdp = info->tx_cur; + /* Get next BD. + */ + if (bdp->cbd_sc & BD_SC_WRAP) + info->tx_cur = info->tx_bd_base; + else + info->tx_cur = (cbd_t *)bdp + 1; + local_irq_restore(flags); while (bdp->cbd_sc & BD_SC_READY); cp = info->tx_va_base + ((bdp - info->tx_bd_base) * TX_BUF_SIZE); *cp = ch; bdp->cbd_datlen = 1; bdp->cbd_sc |= BD_SC_READY; - - /* Get next BD. - */ - if (bdp->cbd_sc & BD_SC_WRAP) - bdp = info->tx_bd_base; - else - bdp++; - - info->tx_cur = (cbd_t *)bdp; } /*
@@ -1802,7 +1827,7 @@ static void rs_8xx_wait_until_sent(struct tty_struct *tty, int timeout) { ser_info_t *info = (ser_info_t *)tty->driver_data; - unsigned long orig_jiffies, char_time; + unsigned long orig_jiffies, char_time, tst_res; /*int lsr;*/ volatile cbd_t *bdp;
@@ -1837,6 +1862,7 @@ * are empty. */ do { + unsigned long flags; #ifdef SERIAL_DEBUG_RS_WAIT_UNTIL_SENT printk("lsr = %d (jiff=%lu)...", lsr, jiffies); #endif
@@ -1854,12 +1880,15 @@ * is the buffer is available. There are still characters * in the CPM FIFO. */ + local_irq_save(flags); bdp = info->tx_cur; if (bdp == info->tx_bd_base) bdp += (TX_NUM_FIFO-1); else bdp--; - } while (bdp->cbd_sc & BD_SC_READY); + tst_res = !!(bdp->cbd_sc & BD_SC_READY); + local_irq_restore(flags); + } while (tst_res); current->state = TASK_RUNNING; #ifdef SERIAL_DEBUG_RS_WAIT_UNTIL_SENT printk("lsr = %d (jiff=%lu)...done\n", lsr, jiffies);
@@ -2261,10 +2290,11 @@ { struct serial_state *ser; ser_info_t *info; - unsigned i; + unsigned i, c, cr_missing, max_tx_size; volatile cbd_t *bdp, *bdbase; volatile smc_uart_t *up; volatile u_char *cp; + unsigned long flags; ser = rs_table + idx;
@@ -2273,40 +2303,39 @@ * we simply use the single buffer allocated. */ if ((info = (ser_info_t *)ser->info) != NULL) { - bdp = info->tx_cur; bdbase = info->tx_bd_base; - } - else { - /* Pointer to UART in parameter ram. - */ + } else { + /* Pointer to UART in parameter ram. */ up = (smc_uart_t *)&cpmp->cp_dparam[ser->port]; - - /* Get the address of the host memory buffer. - */ - bdp = bdbase = (cbd_t *)&cpmp->cp_dpmem[up->smc_tbase]; + /* Get the address of the host memory buffer.*/ info = &consinfo; + info->tx_bd_base = (cbd_t *)bdbase = (cbd_t *)&cpmp->cp_dpmem[up->smc_tbase]; + info->tx_cur = (cbd_t *)bdbase; } + max_tx_size = console_tx_buf_len; + cr_missing = 0; + while (1){ + c = MIN(max_tx_size, count); + if (c <= 0) + break; - /* - * We need to gracefully shut down the transmitter, disable - * interrupts, then send our bytes out. - */ - - /* - * Now, do each character. This is not as bad as it looks - * since this is a holding FIFO and not a transmitting FIFO. - * We could add the complexity of filling the entire transmit - * buffer, but we would just wait longer between accesses...... - */ - for (i = 0; i < count; i++, s++) { + local_irq_save(flags); + bdp = info->tx_cur; + bdbase = info->tx_bd_base; + if (bdp->cbd_sc & BD_SC_WRAP) + info->tx_cur = (cbd_t *)bdbase; + else + info->tx_cur = (cbd_t *)(bdp+1); + local_irq_restore(flags); + /* Wait for transmitter fifo to empty. * Ready indicates output is ready, and xmt is doing * that, not that it is ready for us to send. */ while (bdp->cbd_sc & BD_SC_READY); - /* Send the character out. + /* Send the characters out. * If the buffer address is in the CPM DPRAM, don't * convert it. */
@@ -2314,55 +2343,32 @@ cp = (u_char *)(bdp->cbd_bufaddr); else cp = info->tx_va_base + ((bdp - info->tx_bd_base) * TX_BUF_SIZE); - *cp = *s; - - bdp->cbd_datlen = 1; - bdp->cbd_sc |= BD_SC_READY; - - if (bdp->cbd_sc & BD_SC_WRAP) - bdp = bdbase; - else - bdp++; - - /* if a LF, also do CR... */ - if (*s == 10) { - while (bdp->cbd_sc & BD_SC_READY); - - /* This 'if' below will never be true, but a few - * people argued with me that it was a "bug by - * inspection" that is was easier to add the code - * than continue the discussion. The only time - * the buffer address is in DPRAM is during early - * use by kgdb/xmon, which format their own packets - * and we never get here. -- Dan - */ - if ((uint)(bdp->cbd_bufaddr) > (uint)IMAP_ADDR) - cp = (u_char *)(bdp->cbd_bufaddr); - else - cp = info->tx_va_base + ((bdp - info->tx_bd_base) * TX_BUF_SIZE); - *cp = 13; - bdp->cbd_datlen = 1; - bdp->cbd_sc |= BD_SC_READY; - if (bdp->cbd_sc & BD_SC_WRAP) { - bdp = bdbase; - } - else { - bdp++; - } + i=1; /* Keeps track of consumed TX buffer space */ + if (cr_missing) { + /* Previus loop didn't have room for the CR, insert it first in this */ + *cp++ = '\r'; + i++; + } + cr_missing = 0; + for (; i <= c; i++) { + if ((*cp++ = *s++) != '\n') + continue; /* Copy bytes until a NewLine is found */ + /* NewLine found, see if there is space in the TX buffer to add a CR */ + if (i < max_tx_size) { + *cp++ = '\r'; /* yes, there is space to add a CR */ + i++; + } else + cr_missing = 1; /* No space in the TX buffer, + rember it so it can be inserted in the next loop */ } - } - - /* - * Finally, Wait for transmitter & holding register to empty - * and restore the IER - */ - while (bdp->cbd_sc & BD_SC_READY); + count -= (c-cr_missing); + bdp->cbd_datlen = i-1; + bdp->cbd_sc |= BD_SC_READY; - if (info) - info->tx_cur = (cbd_t *)bdp; + } + /* while (bdp->cbd_sc & BD_SC_READY); is this really needed? */ } - static void serial_console_write(struct console *c, const char *s, unsigned count) {
@@ -3003,7 +3009,7 @@ } } - + console_tx_buf_len = TX_BUF_SIZE; return 0; }
@@ -3059,7 +3065,7 @@ * memory yet because vm allocator isn't initialized * during this early console init. */ - dp_addr = m8xx_cpm_dpalloc(8); + dp_addr = m8xx_cpm_dpalloc(2*EARLY_BUF_SIZE); mem_addr = (uint)(&cpmp->cp_dpmem[dp_addr]); /* Allocate space for two buffer descriptors in the DP ram.
@@ -3073,10 +3079,10 @@ bdp->cbd_bufaddr = iopa(mem_addr); (bdp+1)->cbd_bufaddr = iopa(mem_addr+4); - consinfo.rx_va_base = mem_addr; - consinfo.rx_bd_base = bdp; - consinfo.tx_va_base = mem_addr + 4; - consinfo.tx_bd_base = bdp+1; + consinfo.rx_va_base = (unsigned char *) mem_addr; + consinfo.rx_bd_base = (cbd_t *) bdp; + consinfo.tx_va_base = (unsigned char *) (mem_addr + EARLY_BUF_SIZE); + consinfo.tx_bd_base = (cbd_t *) (bdp+1); /* For the receive, set empty and wrap. * For transmit, set wrap.
@@ -3177,7 +3183,7 @@ /* Set up the baud rate generator. */ m8xx_cpm_setbrg((ser - rs_table), bd->bi_baudrate); - + console_tx_buf_len = EARLY_BUF_SIZE; return 0; }
** Sent via the linuxppc-embedded mail list. See http://lists.linuxppc.org/