From ace08c3c4403140e5ce82116c8f2acb38f58f61d Mon Sep 17 00:00:00 2001 From: Jiri Slaby Date: Thu, 11 Jun 2009 12:20:38 +0100 Subject: tty:cyclades, load firmware even on Ze Ze needs firmware to be loaded as well as Zo. Move cyz_load_fw one level upper to achieve that. Signed-off-by: Jiri Slaby Signed-off-by: Alan Cox Signed-off-by: Linus Torvalds --- drivers/char/cyclades.c | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) (limited to 'drivers/char') diff --git a/drivers/char/cyclades.c b/drivers/char/cyclades.c index 1fdb9f657d8..bde24583bcf 100644 --- a/drivers/char/cyclades.c +++ b/drivers/char/cyclades.c @@ -5043,6 +5043,7 @@ static int __devinit cy_pci_probe(struct pci_dev *pdev, nchan = ZE_V1_NPORTS; } else { card_name = "Cyclades-8Zo"; + nchan = 8; #ifdef CY_PCI_DEBUG if (mailbox == ZO_V1) { @@ -5065,15 +5066,11 @@ static int __devinit cy_pci_probe(struct pci_dev *pdev, */ if ((mailbox == ZO_V1) || (mailbox == ZO_V2)) cy_writel(addr2 + ID_ADDRESS, 0L); - - retval = cyz_load_fw(pdev, addr2, addr0, irq); - if (retval) - goto err_unmap; - /* This must be a Cyclades-8Zo/PCI. The extendable - version will have a different device_id and will - be allocated its maximum number of ports. */ - nchan = 8; } + + retval = cyz_load_fw(pdev, addr2, addr0, irq); + if (retval) + goto err_unmap; } if ((cy_next_channel + nchan) > NR_PORTS) { -- cgit v1.2.3 From eca1b592d600bb2b7d24b66e4106de1e751ae510 Mon Sep 17 00:00:00 2001 From: Jiri Slaby Date: Thu, 11 Jun 2009 12:21:30 +0100 Subject: tty: cyclades, don't kill FW Don't reset the PLX chip after FW load, which effectively kills the FW, so that user had to boot manually. Signed-off-by: Jiri Slaby Signed-off-by: Alan Cox Signed-off-by: Linus Torvalds --- drivers/char/cyclades.c | 2 -- 1 file changed, 2 deletions(-) (limited to 'drivers/char') diff --git a/drivers/char/cyclades.c b/drivers/char/cyclades.c index bde24583bcf..6d524391e99 100644 --- a/drivers/char/cyclades.c +++ b/drivers/char/cyclades.c @@ -4932,8 +4932,6 @@ static int __devinit cyz_load_fw(struct pci_dev *pdev, void __iomem *base_addr, cy_writel(&ctl_addr->intr_ctrl_stat, readl(&ctl_addr->intr_ctrl_stat) | 0x00030800UL); - plx_init(pdev, irq, ctl_addr); - return 0; err_rel: release_firmware(fw); -- cgit v1.2.3 From 65a29f60e121ae5116ac1736b50a237bf2db3225 Mon Sep 17 00:00:00 2001 From: Jiri Slaby Date: Thu, 11 Jun 2009 12:22:27 +0100 Subject: tty: cyclades, remove spurious check in ISR No need to check if dev_id is NULL, it never is. Signed-off-by: Jiri Slaby Signed-off-by: Alan Cox Signed-off-by: Linus Torvalds --- drivers/char/cyclades.c | 8 -------- 1 file changed, 8 deletions(-) (limited to 'drivers/char') diff --git a/drivers/char/cyclades.c b/drivers/char/cyclades.c index 6d524391e99..ccf68a9e24b 100644 --- a/drivers/char/cyclades.c +++ b/drivers/char/cyclades.c @@ -1737,14 +1737,6 @@ static irqreturn_t cyz_interrupt(int irq, void *dev_id) { struct cyclades_card *cinfo = dev_id; - if (unlikely(cinfo == NULL)) { -#ifdef CY_DEBUG_INTERRUPTS - printk(KERN_DEBUG "cyz_interrupt: spurious interrupt %d\n", - irq); -#endif - return IRQ_NONE; /* spurious interrupt */ - } - if (unlikely(!ISZLOADED(*cinfo))) { #ifdef CY_DEBUG_INTERRUPTS printk(KERN_DEBUG "cyz_interrupt: board not yet loaded " -- cgit v1.2.3 From fcc8ac1825d3d0fb81f73bc1a80ebc863168bb56 Mon Sep 17 00:00:00 2001 From: Alan Cox Date: Thu, 11 Jun 2009 12:24:17 +0100 Subject: tty: Add carrier processing on close to the tty_port core Some drivers implement this internally, others miss it out. Push the behaviour into the core code as that way everyone will do it consistently. Update the dtr rts method to raise or lower depending upon flags. Having a single method in this style fits most of the implementations more cleanly than two funtions. We need this in place before we tackle the USB side Signed-off-by: Alan Cox Signed-off-by: Linus Torvalds --- drivers/char/epca.c | 4 ++-- drivers/char/isicom.c | 19 +++++++++++++------ drivers/char/istallion.c | 8 ++++---- drivers/char/mxser.c | 12 ++++++++---- drivers/char/pcmcia/synclink_cs.c | 11 +++++++---- drivers/char/rocket.c | 13 +++++++++---- drivers/char/stallion.c | 6 +++--- drivers/char/synclink.c | 9 ++++++--- drivers/char/synclink_gt.c | 9 ++++++--- drivers/char/synclinkmp.c | 9 ++++++--- drivers/char/tty_port.c | 27 +++++++++++++++++++++++---- 11 files changed, 87 insertions(+), 40 deletions(-) (limited to 'drivers/char') diff --git a/drivers/char/epca.c b/drivers/char/epca.c index af7c13ca949..8797b774235 100644 --- a/drivers/char/epca.c +++ b/drivers/char/epca.c @@ -745,7 +745,7 @@ static int epca_carrier_raised(struct tty_port *port) return 0; } -static void epca_raise_dtr_rts(struct tty_port *port) +static void epca_dtr_rts(struct tty_port *port, int onoff) { } @@ -925,7 +925,7 @@ static const struct tty_operations pc_ops = { static const struct tty_port_operations epca_port_ops = { .carrier_raised = epca_carrier_raised, - .raise_dtr_rts = epca_raise_dtr_rts, + .dtr_rts = epca_dtr_rts, }; static int info_open(struct tty_struct *tty, struct file *filp) diff --git a/drivers/char/isicom.c b/drivers/char/isicom.c index a59eac584d1..4d745a89504 100644 --- a/drivers/char/isicom.c +++ b/drivers/char/isicom.c @@ -329,7 +329,7 @@ static inline void drop_rts(struct isi_port *port) /* card->lock MUST NOT be held */ -static void isicom_raise_dtr_rts(struct tty_port *port) +static void isicom_dtr_rts(struct tty_port *port, int on) { struct isi_port *ip = container_of(port, struct isi_port, port); struct isi_board *card = ip->card; @@ -339,10 +339,17 @@ static void isicom_raise_dtr_rts(struct tty_port *port) if (!lock_card(card)) return; - outw(0x8000 | (channel << card->shift_count) | 0x02, base); - outw(0x0f04, base); - InterruptTheCard(base); - ip->status |= (ISI_DTR | ISI_RTS); + if (on) { + outw(0x8000 | (channel << card->shift_count) | 0x02, base); + outw(0x0f04, base); + InterruptTheCard(base); + ip->status |= (ISI_DTR | ISI_RTS); + } else { + outw(0x8000 | (channel << card->shift_count) | 0x02, base); + outw(0x0C04, base); + InterruptTheCard(base); + ip->status &= ~(ISI_DTR | ISI_RTS); + } unlock_card(card); } @@ -1339,7 +1346,7 @@ static const struct tty_operations isicom_ops = { static const struct tty_port_operations isicom_port_ops = { .carrier_raised = isicom_carrier_raised, - .raise_dtr_rts = isicom_raise_dtr_rts, + .dtr_rts = isicom_dtr_rts, }; static int __devinit reset_card(struct pci_dev *pdev, diff --git a/drivers/char/istallion.c b/drivers/char/istallion.c index fff19f7e29d..e18800c400b 100644 --- a/drivers/char/istallion.c +++ b/drivers/char/istallion.c @@ -1140,14 +1140,14 @@ static int stli_carrier_raised(struct tty_port *port) return (portp->sigs & TIOCM_CD) ? 1 : 0; } -static void stli_raise_dtr_rts(struct tty_port *port) +static void stli_dtr_rts(struct tty_port *port, int on) { struct stliport *portp = container_of(port, struct stliport, port); struct stlibrd *brdp = stli_brds[portp->brdnr]; - stli_mkasysigs(&portp->asig, 1, 1); + stli_mkasysigs(&portp->asig, on, on); if (stli_cmdwait(brdp, portp, A_SETSIGNALS, &portp->asig, sizeof(asysigs_t), 0) < 0) - printk(KERN_WARNING "istallion: dtr raise failed.\n"); + printk(KERN_WARNING "istallion: dtr set failed.\n"); } @@ -4417,7 +4417,7 @@ static const struct tty_operations stli_ops = { static const struct tty_port_operations stli_port_ops = { .carrier_raised = stli_carrier_raised, - .raise_dtr_rts = stli_raise_dtr_rts, + .dtr_rts = stli_dtr_rts, }; /*****************************************************************************/ diff --git a/drivers/char/mxser.c b/drivers/char/mxser.c index 13f8871e5b2..9533f43a30b 100644 --- a/drivers/char/mxser.c +++ b/drivers/char/mxser.c @@ -547,14 +547,18 @@ static int mxser_carrier_raised(struct tty_port *port) return (inb(mp->ioaddr + UART_MSR) & UART_MSR_DCD)?1:0; } -static void mxser_raise_dtr_rts(struct tty_port *port) +static void mxser_dtr_rts(struct tty_port *port, int on) { struct mxser_port *mp = container_of(port, struct mxser_port, port); unsigned long flags; spin_lock_irqsave(&mp->slock, flags); - outb(inb(mp->ioaddr + UART_MCR) | - UART_MCR_DTR | UART_MCR_RTS, mp->ioaddr + UART_MCR); + if (on) + outb(inb(mp->ioaddr + UART_MCR) | + UART_MCR_DTR | UART_MCR_RTS, mp->ioaddr + UART_MCR); + else + outb(inb(mp->ioaddr + UART_MCR)&~(UART_MCR_DTR | UART_MCR_RTS), + mp->ioaddr + UART_MCR); spin_unlock_irqrestore(&mp->slock, flags); } @@ -2356,7 +2360,7 @@ static const struct tty_operations mxser_ops = { struct tty_port_operations mxser_port_ops = { .carrier_raised = mxser_carrier_raised, - .raise_dtr_rts = mxser_raise_dtr_rts, + .dtr_rts = mxser_dtr_rts, }; /* diff --git a/drivers/char/pcmcia/synclink_cs.c b/drivers/char/pcmcia/synclink_cs.c index 19d79fc5446..77b36488922 100644 --- a/drivers/char/pcmcia/synclink_cs.c +++ b/drivers/char/pcmcia/synclink_cs.c @@ -383,7 +383,7 @@ static void async_mode(MGSLPC_INFO *info); static void tx_timeout(unsigned long context); static int carrier_raised(struct tty_port *port); -static void raise_dtr_rts(struct tty_port *port); +static void dtr_rts(struct tty_port *port, int onoff); #if SYNCLINK_GENERIC_HDLC #define dev_to_port(D) (dev_to_hdlc(D)->priv) @@ -513,7 +513,7 @@ static void ldisc_receive_buf(struct tty_struct *tty, static const struct tty_port_operations mgslpc_port_ops = { .carrier_raised = carrier_raised, - .raise_dtr_rts = raise_dtr_rts + .dtr_rts = dtr_rts }; static int mgslpc_probe(struct pcmcia_device *link) @@ -2528,13 +2528,16 @@ static int carrier_raised(struct tty_port *port) return 0; } -static void raise_dtr_rts(struct tty_port *port) +static void dtr_rts(struct tty_port *port, int onoff) { MGSLPC_INFO *info = container_of(port, MGSLPC_INFO, port); unsigned long flags; spin_lock_irqsave(&info->lock,flags); - info->serial_signals |= SerialSignal_RTS + SerialSignal_DTR; + if (onoff) + info->serial_signals |= SerialSignal_RTS + SerialSignal_DTR; + else + info->serial_signals &= ~SerialSignal_RTS + SerialSignal_DTR; set_signals(info); spin_unlock_irqrestore(&info->lock,flags); } diff --git a/drivers/char/rocket.c b/drivers/char/rocket.c index f59fc5cea06..7399188049d 100644 --- a/drivers/char/rocket.c +++ b/drivers/char/rocket.c @@ -872,11 +872,16 @@ static int carrier_raised(struct tty_port *port) return (sGetChanStatusLo(&info->channel) & CD_ACT) ? 1 : 0; } -static void raise_dtr_rts(struct tty_port *port) +static void dtr_rts(struct tty_port *port, int on) { struct r_port *info = container_of(port, struct r_port, port); - sSetDTR(&info->channel); - sSetRTS(&info->channel); + if (on) { + sSetDTR(&info->channel); + sSetRTS(&info->channel); + } else { + sClrDTR(&info->channel); + sClrRTS(&info->channel); + } } /* @@ -2250,7 +2255,7 @@ static const struct tty_operations rocket_ops = { static const struct tty_port_operations rocket_port_ops = { .carrier_raised = carrier_raised, - .raise_dtr_rts = raise_dtr_rts, + .dtr_rts = dtr_rts, }; /* diff --git a/drivers/char/stallion.c b/drivers/char/stallion.c index 2ad813a801d..53e504f41b2 100644 --- a/drivers/char/stallion.c +++ b/drivers/char/stallion.c @@ -772,11 +772,11 @@ static int stl_carrier_raised(struct tty_port *port) return (portp->sigs & TIOCM_CD) ? 1 : 0; } -static void stl_raise_dtr_rts(struct tty_port *port) +static void stl_dtr_rts(struct tty_port *port, int on) { struct stlport *portp = container_of(port, struct stlport, port); /* Takes brd_lock internally */ - stl_setsignals(portp, 1, 1); + stl_setsignals(portp, on, on); } /*****************************************************************************/ @@ -2547,7 +2547,7 @@ static const struct tty_operations stl_ops = { static const struct tty_port_operations stl_port_ops = { .carrier_raised = stl_carrier_raised, - .raise_dtr_rts = stl_raise_dtr_rts, + .dtr_rts = stl_dtr_rts, }; /*****************************************************************************/ diff --git a/drivers/char/synclink.c b/drivers/char/synclink.c index afd0b26ca05..afded3a2379 100644 --- a/drivers/char/synclink.c +++ b/drivers/char/synclink.c @@ -3247,13 +3247,16 @@ static int carrier_raised(struct tty_port *port) return (info->serial_signals & SerialSignal_DCD) ? 1 : 0; } -static void raise_dtr_rts(struct tty_port *port) +static void dtr_rts(struct tty_port *port, int on) { struct mgsl_struct *info = container_of(port, struct mgsl_struct, port); unsigned long flags; spin_lock_irqsave(&info->irq_spinlock,flags); - info->serial_signals |= SerialSignal_RTS + SerialSignal_DTR; + if (on) + info->serial_signals |= SerialSignal_RTS + SerialSignal_DTR; + else + info->serial_signals &= ~(SerialSignal_RTS + SerialSignal_DTR); usc_set_serial_signals(info); spin_unlock_irqrestore(&info->irq_spinlock,flags); } @@ -4258,7 +4261,7 @@ static void mgsl_add_device( struct mgsl_struct *info ) static const struct tty_port_operations mgsl_port_ops = { .carrier_raised = carrier_raised, - .raise_dtr_rts = raise_dtr_rts, + .dtr_rts = dtr_rts, }; diff --git a/drivers/char/synclink_gt.c b/drivers/char/synclink_gt.c index 5e256494686..67986ea0d47 100644 --- a/drivers/char/synclink_gt.c +++ b/drivers/char/synclink_gt.c @@ -3099,13 +3099,16 @@ static int carrier_raised(struct tty_port *port) return (info->signals & SerialSignal_DCD) ? 1 : 0; } -static void raise_dtr_rts(struct tty_port *port) +static void dtr_rts(struct tty_port *port, int on) { unsigned long flags; struct slgt_info *info = container_of(port, struct slgt_info, port); spin_lock_irqsave(&info->lock,flags); - info->signals |= SerialSignal_RTS + SerialSignal_DTR; + if (on) + info->signals |= SerialSignal_RTS + SerialSignal_DTR; + else + info->signals &= ~(SerialSignal_RTS + SerialSignal_DTR); set_signals(info); spin_unlock_irqrestore(&info->lock,flags); } @@ -3419,7 +3422,7 @@ static void add_device(struct slgt_info *info) static const struct tty_port_operations slgt_port_ops = { .carrier_raised = carrier_raised, - .raise_dtr_rts = raise_dtr_rts, + .dtr_rts = dtr_rts, }; /* diff --git a/drivers/char/synclinkmp.c b/drivers/char/synclinkmp.c index 26de60efe4b..6f727e3c53a 100644 --- a/drivers/char/synclinkmp.c +++ b/drivers/char/synclinkmp.c @@ -3277,13 +3277,16 @@ static int carrier_raised(struct tty_port *port) return (info->serial_signals & SerialSignal_DCD) ? 1 : 0; } -static void raise_dtr_rts(struct tty_port *port) +static void dtr_rts(struct tty_port *port, int on) { SLMP_INFO *info = container_of(port, SLMP_INFO, port); unsigned long flags; spin_lock_irqsave(&info->lock,flags); - info->serial_signals |= SerialSignal_RTS + SerialSignal_DTR; + if (on) + info->serial_signals |= SerialSignal_RTS + SerialSignal_DTR; + else + info->serial_signals &= ~(SerialSignal_RTS + SerialSignal_DTR); set_signals(info); spin_unlock_irqrestore(&info->lock,flags); } @@ -3746,7 +3749,7 @@ static void add_device(SLMP_INFO *info) static const struct tty_port_operations port_ops = { .carrier_raised = carrier_raised, - .raise_dtr_rts = raise_dtr_rts, + .dtr_rts = dtr_rts, }; /* Allocate and initialize a device instance structure diff --git a/drivers/char/tty_port.c b/drivers/char/tty_port.c index 9b8004c7268..926d4a5593f 100644 --- a/drivers/char/tty_port.c +++ b/drivers/char/tty_port.c @@ -137,7 +137,7 @@ int tty_port_carrier_raised(struct tty_port *port) EXPORT_SYMBOL(tty_port_carrier_raised); /** - * tty_port_raise_dtr_rts - Riase DTR/RTS + * tty_port_raise_dtr_rts - Raise DTR/RTS * @port: tty port * * Wrapper for the DTR/RTS raise logic. For the moment this is used @@ -147,11 +147,27 @@ EXPORT_SYMBOL(tty_port_carrier_raised); void tty_port_raise_dtr_rts(struct tty_port *port) { - if (port->ops->raise_dtr_rts) - port->ops->raise_dtr_rts(port); + if (port->ops->dtr_rts) + port->ops->dtr_rts(port, 1); } EXPORT_SYMBOL(tty_port_raise_dtr_rts); +/** + * tty_port_lower_dtr_rts - Lower DTR/RTS + * @port: tty port + * + * Wrapper for the DTR/RTS raise logic. For the moment this is used + * to hide some internal details. This will eventually become entirely + * internal to the tty port. + */ + +void tty_port_lower_dtr_rts(struct tty_port *port) +{ + if (port->ops->dtr_rts) + port->ops->dtr_rts(port, 0); +} +EXPORT_SYMBOL(tty_port_lower_dtr_rts); + /** * tty_port_block_til_ready - Waiting logic for tty open * @port: the tty port being opened @@ -167,7 +183,7 @@ EXPORT_SYMBOL(tty_port_raise_dtr_rts); * - port flags and counts * * The passed tty_port must implement the carrier_raised method if it can - * do carrier detect and the raise_dtr_rts method if it supports software + * do carrier detect and the dtr_rts method if it supports software * management of these lines. Note that the dtr/rts raise is done each * iteration as a hangup may have previously dropped them while we wait. */ @@ -302,6 +318,9 @@ void tty_port_close_end(struct tty_port *port, struct tty_struct *tty) tty_ldisc_flush(tty); + if (tty->termios->c_cflag & HUPCL) + tty_port_lower_dtr_rts(port); + spin_lock_irqsave(&port->lock, flags); tty->closing = 0; -- cgit v1.2.3 From 1ec739be75a6cb961a46ba0b1982d0edb7f27558 Mon Sep 17 00:00:00 2001 From: Alan Cox Date: Thu, 11 Jun 2009 12:25:25 +0100 Subject: tty: Implement a drain delay in the tty port We need this for devices that cannot flush and wait, but which do not order data and modem events. Without it we will hang up before all the data clears the hardware. Needed for the USB changes. Signed-off-by: Alan Cox Signed-off-by: Linus Torvalds --- drivers/char/tty_port.c | 11 +++++++++++ 1 file changed, 11 insertions(+) (limited to 'drivers/char') diff --git a/drivers/char/tty_port.c b/drivers/char/tty_port.c index 926d4a5593f..4d08b6d27c2 100644 --- a/drivers/char/tty_port.c +++ b/drivers/char/tty_port.c @@ -308,6 +308,17 @@ int tty_port_close_start(struct tty_port *port, struct tty_struct *tty, struct f if (port->flags & ASYNC_INITIALIZED && port->closing_wait != ASYNC_CLOSING_WAIT_NONE) tty_wait_until_sent(tty, port->closing_wait); + if (port->drain_delay) { + unsigned int bps = tty_get_baud_rate(tty); + long timeout; + + if (bps > 1200) + timeout = max_t(long, (HZ * 10 * port->drain_delay) / bps, + HZ / 10); + else + timeout = 2 * HZ; + schedule_timeout_interruptible(timeout); + } return 1; } EXPORT_SYMBOL(tty_port_close_start); -- cgit v1.2.3 From 5ba5a5d21204f61ff655b322a90ed91c75194e4b Mon Sep 17 00:00:00 2001 From: Paul Fulghum Date: Thu, 11 Jun 2009 12:28:37 +0100 Subject: tty: synclink_gt add receive pio mode Add receive programmed IO mode to reduce receive latency when using low data rates. The receive FIFO trigger level of 128 bytes used in DMA mode creates excessive latency when operating at low data rates. PIO mode is selected when user application requests data in blocks of less than 128 bytes. Signed-off-by: Paul Fulghum Signed-off-by: Alan Cox Signed-off-by: Linus Torvalds --- drivers/char/synclink_gt.c | 77 ++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 68 insertions(+), 9 deletions(-) (limited to 'drivers/char') diff --git a/drivers/char/synclink_gt.c b/drivers/char/synclink_gt.c index 67986ea0d47..1386625fc4c 100644 --- a/drivers/char/synclink_gt.c +++ b/drivers/char/synclink_gt.c @@ -214,6 +214,7 @@ struct slgt_desc #define set_desc_next(a,b) (a).next = cpu_to_le32((unsigned int)(b)) #define set_desc_count(a,b)(a).count = cpu_to_le16((unsigned short)(b)) #define set_desc_eof(a,b) (a).status = cpu_to_le16((b) ? (le16_to_cpu((a).status) | BIT0) : (le16_to_cpu((a).status) & ~BIT0)) +#define set_desc_status(a, b) (a).status = cpu_to_le16((unsigned short)(b)) #define desc_count(a) (le16_to_cpu((a).count)) #define desc_status(a) (le16_to_cpu((a).status)) #define desc_complete(a) (le16_to_cpu((a).status) & BIT15) @@ -297,6 +298,7 @@ struct slgt_info { u32 max_frame_size; /* as set by device config */ unsigned int rbuf_fill_level; + unsigned int rx_pio; unsigned int if_mode; unsigned int base_clock; @@ -331,6 +333,8 @@ struct slgt_info { struct slgt_desc *rbufs; unsigned int rbuf_current; unsigned int rbuf_index; + unsigned int rbuf_fill_index; + unsigned short rbuf_fill_count; unsigned int tbuf_count; struct slgt_desc *tbufs; @@ -2110,6 +2114,40 @@ static void ri_change(struct slgt_info *info, unsigned short status) info->pending_bh |= BH_STATUS; } +static void isr_rxdata(struct slgt_info *info) +{ + unsigned int count = info->rbuf_fill_count; + unsigned int i = info->rbuf_fill_index; + unsigned short reg; + + while (rd_reg16(info, SSR) & IRQ_RXDATA) { + reg = rd_reg16(info, RDR); + DBGISR(("isr_rxdata %s RDR=%04X\n", info->device_name, reg)); + if (desc_complete(info->rbufs[i])) { + /* all buffers full */ + rx_stop(info); + info->rx_restart = 1; + continue; + } + info->rbufs[i].buf[count++] = (unsigned char)reg; + /* async mode saves status byte to buffer for each data byte */ + if (info->params.mode == MGSL_MODE_ASYNC) + info->rbufs[i].buf[count++] = (unsigned char)(reg >> 8); + if (count == info->rbuf_fill_level || (reg & BIT10)) { + /* buffer full or end of frame */ + set_desc_count(info->rbufs[i], count); + set_desc_status(info->rbufs[i], BIT15 | (reg >> 8)); + info->rbuf_fill_count = count = 0; + if (++i == info->rbuf_count) + i = 0; + info->pending_bh |= BH_RECEIVE; + } + } + + info->rbuf_fill_index = i; + info->rbuf_fill_count = count; +} + static void isr_serial(struct slgt_info *info) { unsigned short status = rd_reg16(info, SSR); @@ -2125,6 +2163,8 @@ static void isr_serial(struct slgt_info *info) if (info->tx_count) isr_txeom(info, status); } + if (info->rx_pio && (status & IRQ_RXDATA)) + isr_rxdata(info); if ((status & IRQ_RXBREAK) && (status & RXBREAK)) { info->icount.brk++; /* process break detection if tty control allows */ @@ -2141,7 +2181,8 @@ static void isr_serial(struct slgt_info *info) } else { if (status & (IRQ_TXIDLE + IRQ_TXUNDER)) isr_txeom(info, status); - + if (info->rx_pio && (status & IRQ_RXDATA)) + isr_rxdata(info); if (status & IRQ_RXIDLE) { if (status & RXIDLE) info->icount.rxidle++; @@ -2642,6 +2683,10 @@ static int rx_enable(struct slgt_info *info, int enable) return -EINVAL; } info->rbuf_fill_level = rbuf_fill_level; + if (rbuf_fill_level < 128) + info->rx_pio = 1; /* PIO mode */ + else + info->rx_pio = 0; /* DMA mode */ rx_stop(info); /* restart receiver to use new fill level */ } @@ -3844,15 +3889,27 @@ static void rx_start(struct slgt_info *info) rdma_reset(info); reset_rbufs(info); - /* set 1st descriptor address */ - wr_reg32(info, RDDAR, info->rbufs[0].pdesc); - - if (info->params.mode != MGSL_MODE_ASYNC) { - /* enable rx DMA and DMA interrupt */ - wr_reg32(info, RDCSR, (BIT2 + BIT0)); + if (info->rx_pio) { + /* rx request when rx FIFO not empty */ + wr_reg16(info, SCR, (unsigned short)(rd_reg16(info, SCR) & ~BIT14)); + slgt_irq_on(info, IRQ_RXDATA); + if (info->params.mode == MGSL_MODE_ASYNC) { + /* enable saving of rx status */ + wr_reg32(info, RDCSR, BIT6); + } } else { - /* enable saving of rx status, rx DMA and DMA interrupt */ - wr_reg32(info, RDCSR, (BIT6 + BIT2 + BIT0)); + /* rx request when rx FIFO half full */ + wr_reg16(info, SCR, (unsigned short)(rd_reg16(info, SCR) | BIT14)); + /* set 1st descriptor address */ + wr_reg32(info, RDDAR, info->rbufs[0].pdesc); + + if (info->params.mode != MGSL_MODE_ASYNC) { + /* enable rx DMA and DMA interrupt */ + wr_reg32(info, RDCSR, (BIT2 + BIT0)); + } else { + /* enable saving of rx status, rx DMA and DMA interrupt */ + wr_reg32(info, RDCSR, (BIT6 + BIT2 + BIT0)); + } } slgt_irq_on(info, IRQ_RXOVER); @@ -4470,6 +4527,8 @@ static void free_rbufs(struct slgt_info *info, unsigned int i, unsigned int last static void reset_rbufs(struct slgt_info *info) { free_rbufs(info, 0, info->rbuf_count - 1); + info->rbuf_fill_index = 0; + info->rbuf_fill_count = 0; } /* -- cgit v1.2.3 From 97e87f8ebe978e881c7325ba490574bd5500b133 Mon Sep 17 00:00:00 2001 From: Jiri Slaby Date: Thu, 11 Jun 2009 12:29:27 +0100 Subject: tty: cyclades, plx9060 casts cleanup Remove ugly all-over-the-code casts of ctl_addr to 9060 space. Add an union to the cyclades_card structure, which contains a pointer to both 9050 and 9060 spaces. The 9050 space layout is unknown, so let it still as a void __iomem pointer. Signed-off-by: Jiri Slaby Signed-off-by: Alan Cox Signed-off-by: Linus Torvalds --- drivers/char/cyclades.c | 65 +++++++++++++++++++++++++------------------------ 1 file changed, 33 insertions(+), 32 deletions(-) (limited to 'drivers/char') diff --git a/drivers/char/cyclades.c b/drivers/char/cyclades.c index ccf68a9e24b..2cbf74134f1 100644 --- a/drivers/char/cyclades.c +++ b/drivers/char/cyclades.c @@ -666,12 +666,10 @@ static void cy_send_xchar(struct tty_struct *tty, char ch); #define IS_CYC_Z(card) ((card).num_chips == (unsigned int)-1) #define Z_FPGA_CHECK(card) \ - ((readl(&((struct RUNTIME_9060 __iomem *) \ - ((card).ctl_addr))->init_ctrl) & (1<<17)) != 0) + ((readl(&(card).ctl_addr.p9060->init_ctrl) & (1<<17)) != 0) -#define ISZLOADED(card) (((ZO_V1 == readl(&((struct RUNTIME_9060 __iomem *) \ - ((card).ctl_addr))->mail_box_0)) || \ - Z_FPGA_CHECK(card)) && \ +#define ISZLOADED(card) (((ZO_V1 == readl(&(card).ctl_addr.p9060->mail_box_0)) \ + || Z_FPGA_CHECK(card)) && \ (ZFIRM_ID == readl(&((struct FIRM_ID __iomem *) \ ((card).base_addr+ID_ADDRESS))->signature))) @@ -1400,14 +1398,12 @@ cyz_fetch_msg(struct cyclades_card *cinfo, zfw_ctrl = cinfo->base_addr + (readl(&firm_id->zfwctrl_addr) & 0xfffff); board_ctrl = &zfw_ctrl->board_ctrl; - loc_doorbell = readl(&((struct RUNTIME_9060 __iomem *) - (cinfo->ctl_addr))->loc_doorbell); + loc_doorbell = readl(&cinfo->ctl_addr.p9060->loc_doorbell); if (loc_doorbell) { *cmd = (char)(0xff & loc_doorbell); *channel = readl(&board_ctrl->fwcmd_channel); *param = (__u32) readl(&board_ctrl->fwcmd_param); - cy_writel(&((struct RUNTIME_9060 __iomem *)(cinfo->ctl_addr))-> - loc_doorbell, 0xffffffff); + cy_writel(&cinfo->ctl_addr.p9060->loc_doorbell, 0xffffffff); return 1; } return 0; @@ -1431,8 +1427,7 @@ cyz_issue_cmd(struct cyclades_card *cinfo, board_ctrl = &zfw_ctrl->board_ctrl; index = 0; - pci_doorbell = - &((struct RUNTIME_9060 __iomem *)(cinfo->ctl_addr))->pci_doorbell; + pci_doorbell = &cinfo->ctl_addr.p9060->pci_doorbell; while ((readl(pci_doorbell) & 0xff) != 0) { if (index++ == 1000) return (int)(readl(pci_doorbell) & 0xff); @@ -1635,8 +1630,7 @@ static void cyz_handle_cmd(struct cyclades_card *cinfo) zfw_ctrl = cinfo->base_addr + (readl(&firm_id->zfwctrl_addr) & 0xfffff); board_ctrl = &zfw_ctrl->board_ctrl; fw_ver = readl(&board_ctrl->fw_version); - hw_ver = readl(&((struct RUNTIME_9060 __iomem *)(cinfo->ctl_addr))-> - mail_box_0); + hw_ver = readl(&cinfo->ctl_addr.p9060->mail_box_0); while (cyz_fetch_msg(cinfo, &channel, &cmd, ¶m) == 1) { special_count = 0; @@ -2394,8 +2388,8 @@ static int cy_open(struct tty_struct *tty, struct file *filp) struct FIRM_ID __iomem *firm_id = cinfo->base_addr + ID_ADDRESS; if (!ISZLOADED(*cinfo)) { - if (((ZE_V1 == readl(&((struct RUNTIME_9060 __iomem *) - (cinfo->ctl_addr))->mail_box_0)) && + if (((ZE_V1 == readl(&cinfo->ctl_addr.p9060-> + mail_box_0)) && Z_FPGA_CHECK(*cinfo)) && (ZFIRM_HLT == readl( &firm_id->signature))) { @@ -2417,6 +2411,7 @@ static int cy_open(struct tty_struct *tty, struct file *filp) if (!cinfo->intr_enabled) { struct ZFW_CTRL __iomem *zfw_ctrl; struct BOARD_CTRL __iomem *board_ctrl; + u16 intr; zfw_ctrl = cinfo->base_addr + (readl(&firm_id->zfwctrl_addr) & @@ -2425,8 +2420,10 @@ static int cy_open(struct tty_struct *tty, struct file *filp) board_ctrl = &zfw_ctrl->board_ctrl; /* Enable interrupts on the PLX chip */ - cy_writew(cinfo->ctl_addr + 0x68, - readw(cinfo->ctl_addr + 0x68) | 0x0900); + intr = readw(&cinfo->ctl_addr.p9060-> + intr_ctrl_stat) | 0x0900; + cy_writew(&cinfo->ctl_addr.p9060-> + intr_ctrl_stat, intr); /* Enable interrupts on the FW */ retval = cyz_issue_cmd(cinfo, 0, C_CM_IRQ_ENBL, 0L); @@ -4347,8 +4344,7 @@ static int __devinit cy_init_card(struct cyclades_card *cinfo) spin_lock_init(&cinfo->card_lock); if (IS_CYC_Z(*cinfo)) { /* Cyclades-Z */ - mailbox = readl(&((struct RUNTIME_9060 __iomem *) - cinfo->ctl_addr)->mail_box_0); + mailbox = readl(&cinfo->ctl_addr.p9060->mail_box_0); nports = (mailbox == ZE_V1) ? ZE_V1_NPORTS : 8; cinfo->intr_enabled = 0; cinfo->nports = 0; /* Will be correctly set later, after @@ -4613,7 +4609,7 @@ static int __init cy_detect_isa(void) /* set cy_card */ cy_card[j].base_addr = cy_isa_address; - cy_card[j].ctl_addr = NULL; + cy_card[j].ctl_addr.p9050 = NULL; cy_card[j].irq = (int)cy_isa_irq; cy_card[j].bus_index = 0; cy_card[j].first_line = cy_next_channel; @@ -5013,7 +5009,8 @@ static int __devinit cy_pci_probe(struct pci_dev *pdev, } /* Disable interrupts on the PLX before resetting it */ - cy_writew(addr0 + 0x68, readw(addr0 + 0x68) & ~0x0900); + cy_writew(&ctl_addr->intr_ctrl_stat, + readw(&ctl_addr->intr_ctrl_stat) & ~0x0900); plx_init(pdev, irq, addr0); @@ -5109,7 +5106,7 @@ static int __devinit cy_pci_probe(struct pci_dev *pdev, /* set cy_card */ cy_card[card_no].base_addr = addr2; - cy_card[card_no].ctl_addr = addr0; + cy_card[card_no].ctl_addr.p9050 = addr0; cy_card[card_no].irq = irq; cy_card[card_no].bus_index = 1; cy_card[card_no].first_line = cy_next_channel; @@ -5125,17 +5122,20 @@ static int __devinit cy_pci_probe(struct pci_dev *pdev, plx_ver = readb(addr2 + CyPLX_VER) & 0x0f; switch (plx_ver) { case PLX_9050: - cy_writeb(addr0 + 0x4c, 0x43); break; case PLX_9060: case PLX_9080: default: /* Old boards, use PLX_9060 */ - plx_init(pdev, irq, addr0); - cy_writew(addr0 + 0x68, readw(addr0 + 0x68) | 0x0900); + { + struct RUNTIME_9060 __iomem *ctl_addr = addr0; + plx_init(pdev, irq, ctl_addr); + cy_writew(&ctl_addr->intr_ctrl_stat, + readw(&ctl_addr->intr_ctrl_stat) | 0x0900); break; } + } } dev_info(&pdev->dev, "%s/PCI #%d found: %d channels starting from " @@ -5168,17 +5168,18 @@ static void __devexit cy_pci_remove(struct pci_dev *pdev) /* non-Z with old PLX */ if (!IS_CYC_Z(*cinfo) && (readb(cinfo->base_addr + CyPLX_VER) & 0x0f) == PLX_9050) - cy_writeb(cinfo->ctl_addr + 0x4c, 0); + cy_writeb(cinfo->ctl_addr.p9050 + 0x4c, 0); else #ifndef CONFIG_CYZ_INTR if (!IS_CYC_Z(*cinfo)) #endif - cy_writew(cinfo->ctl_addr + 0x68, - readw(cinfo->ctl_addr + 0x68) & ~0x0900); + cy_writew(&cinfo->ctl_addr.p9060->intr_ctrl_stat, + readw(&cinfo->ctl_addr.p9060->intr_ctrl_stat) & + ~0x0900); iounmap(cinfo->base_addr); - if (cinfo->ctl_addr) - iounmap(cinfo->ctl_addr); + if (cinfo->ctl_addr.p9050) + iounmap(cinfo->ctl_addr.p9050); if (cinfo->irq #ifndef CONFIG_CYZ_INTR && !IS_CYC_Z(*cinfo) @@ -5373,8 +5374,8 @@ static void __exit cy_cleanup_module(void) /* clear interrupt */ cy_writeb(card->base_addr + Cy_ClrIntr, 0); iounmap(card->base_addr); - if (card->ctl_addr) - iounmap(card->ctl_addr); + if (card->ctl_addr.p9050) + iounmap(card->ctl_addr.p9050); if (card->irq #ifndef CONFIG_CYZ_INTR && !IS_CYC_Z(*card) -- cgit v1.2.3 From 101b81590d8df0a74c33cf739886247c0a13f4af Mon Sep 17 00:00:00 2001 From: Jiri Slaby Date: Thu, 11 Jun 2009 12:30:10 +0100 Subject: tty: cyclades, cache HW version Store HW version locally to not read it all the time in interrupts and alike. Signed-off-by: Jiri Slaby Signed-off-by: Alan Cox Signed-off-by: Linus Torvalds --- drivers/char/cyclades.c | 32 +++++++++++--------------------- 1 file changed, 11 insertions(+), 21 deletions(-) (limited to 'drivers/char') diff --git a/drivers/char/cyclades.c b/drivers/char/cyclades.c index 2cbf74134f1..cf191cc1c92 100644 --- a/drivers/char/cyclades.c +++ b/drivers/char/cyclades.c @@ -668,8 +668,7 @@ static void cy_send_xchar(struct tty_struct *tty, char ch); #define Z_FPGA_CHECK(card) \ ((readl(&(card).ctl_addr.p9060->init_ctrl) & (1<<17)) != 0) -#define ISZLOADED(card) (((ZO_V1 == readl(&(card).ctl_addr.p9060->mail_box_0)) \ - || Z_FPGA_CHECK(card)) && \ +#define ISZLOADED(card) ((ZO_V1 == (card).hw_ver || Z_FPGA_CHECK(card)) && \ (ZFIRM_ID == readl(&((struct FIRM_ID __iomem *) \ ((card).base_addr+ID_ADDRESS))->signature))) @@ -1393,8 +1392,6 @@ cyz_fetch_msg(struct cyclades_card *cinfo, unsigned long loc_doorbell; firm_id = cinfo->base_addr + ID_ADDRESS; - if (!ISZLOADED(*cinfo)) - return -1; zfw_ctrl = cinfo->base_addr + (readl(&firm_id->zfwctrl_addr) & 0xfffff); board_ctrl = &zfw_ctrl->board_ctrl; @@ -1619,10 +1616,8 @@ static void cyz_handle_cmd(struct cyclades_card *cinfo) static struct BOARD_CTRL __iomem *board_ctrl; static struct CH_CTRL __iomem *ch_ctrl; static struct BUF_CTRL __iomem *buf_ctrl; - __u32 channel; + __u32 channel, param, fw_ver; __u8 cmd; - __u32 param; - __u32 hw_ver, fw_ver; int special_count; int delta_count; @@ -1630,7 +1625,6 @@ static void cyz_handle_cmd(struct cyclades_card *cinfo) zfw_ctrl = cinfo->base_addr + (readl(&firm_id->zfwctrl_addr) & 0xfffff); board_ctrl = &zfw_ctrl->board_ctrl; fw_ver = readl(&board_ctrl->fw_version); - hw_ver = readl(&cinfo->ctl_addr.p9060->mail_box_0); while (cyz_fetch_msg(cinfo, &channel, &cmd, ¶m) == 1) { special_count = 0; @@ -2388,11 +2382,9 @@ static int cy_open(struct tty_struct *tty, struct file *filp) struct FIRM_ID __iomem *firm_id = cinfo->base_addr + ID_ADDRESS; if (!ISZLOADED(*cinfo)) { - if (((ZE_V1 == readl(&cinfo->ctl_addr.p9060-> - mail_box_0)) && - Z_FPGA_CHECK(*cinfo)) && - (ZFIRM_HLT == readl( - &firm_id->signature))) { + if (cinfo->hw_ver == ZE_V1 && Z_FPGA_CHECK(*cinfo) && + readl(&firm_id->signature) == + ZFIRM_HLT) { printk(KERN_ERR "cyc:Cyclades-Z Error: you " "need an external power supply for " "this number of ports.\nFirmware " @@ -4336,7 +4328,6 @@ static void cy_hangup(struct tty_struct *tty) static int __devinit cy_init_card(struct cyclades_card *cinfo) { struct cyclades_port *info; - u32 uninitialized_var(mailbox); unsigned int nports, port; unsigned short chip_number; int uninitialized_var(index); @@ -4344,8 +4335,7 @@ static int __devinit cy_init_card(struct cyclades_card *cinfo) spin_lock_init(&cinfo->card_lock); if (IS_CYC_Z(*cinfo)) { /* Cyclades-Z */ - mailbox = readl(&cinfo->ctl_addr.p9060->mail_box_0); - nports = (mailbox == ZE_V1) ? ZE_V1_NPORTS : 8; + nports = (cinfo->hw_ver == ZE_V1) ? ZE_V1_NPORTS : 8; cinfo->intr_enabled = 0; cinfo->nports = 0; /* Will be correctly set later, after Z FW is loaded */ @@ -4377,7 +4367,7 @@ static int __devinit cy_init_card(struct cyclades_card *cinfo) if (IS_CYC_Z(*cinfo)) { info->type = PORT_STARTECH; - if (mailbox == ZO_V1) + if (cinfo->hw_ver == ZO_V1) info->xmit_fifo_size = CYZ_FIFO_SIZE; else info->xmit_fifo_size = 4 * CYZ_FIFO_SIZE; @@ -4932,7 +4922,7 @@ static int __devinit cy_pci_probe(struct pci_dev *pdev, { void __iomem *addr0 = NULL, *addr2 = NULL; char *card_name = NULL; - u32 mailbox; + u32 uninitialized_var(mailbox); unsigned int device_id, nchan = 0, card_no, i; unsigned char plx_ver; int retval, irq; @@ -5014,7 +5004,7 @@ static int __devinit cy_pci_probe(struct pci_dev *pdev, plx_init(pdev, irq, addr0); - mailbox = (u32)readl(&ctl_addr->mail_box_0); + mailbox = readl(&ctl_addr->mail_box_0); addr2 = ioremap_nocache(pci_resource_start(pdev, 2), mailbox == ZE_V1 ? CyPCI_Ze_win : CyPCI_Zwin); @@ -5026,7 +5016,6 @@ static int __devinit cy_pci_probe(struct pci_dev *pdev, if (mailbox == ZE_V1) { card_name = "Cyclades-Ze"; - readl(&ctl_addr->mail_box_0); nchan = ZE_V1_NPORTS; } else { card_name = "Cyclades-8Zo"; @@ -5089,6 +5078,8 @@ static int __devinit cy_pci_probe(struct pci_dev *pdev, } cy_card[card_no].num_chips = nchan / 4; } else { + cy_card[card_no].hw_ver = mailbox; + cy_card[card_no].num_chips = (unsigned int)-1; #ifdef CONFIG_CYZ_INTR /* allocate IRQ only if board has an IRQ */ if (irq != 0 && irq != 255) { @@ -5101,7 +5092,6 @@ static int __devinit cy_pci_probe(struct pci_dev *pdev, } } #endif /* CONFIG_CYZ_INTR */ - cy_card[card_no].num_chips = (unsigned int)-1; } /* set cy_card */ -- cgit v1.2.3 From 2693f485c22d18474c077f12fd0f797ee8679b33 Mon Sep 17 00:00:00 2001 From: Jiri Slaby Date: Thu, 11 Jun 2009 12:31:06 +0100 Subject: tty: cyclades, convert macros to inlines Remove ugly macros and add inlines instead of them. This improves readability and type checking a much. Signed-off-by: Jiri Slaby Signed-off-by: Alan Cox Signed-off-by: Linus Torvalds --- drivers/char/cyclades.c | 130 ++++++++++++++++++++++++++---------------------- 1 file changed, 71 insertions(+), 59 deletions(-) (limited to 'drivers/char') diff --git a/drivers/char/cyclades.c b/drivers/char/cyclades.c index cf191cc1c92..c9a503f4724 100644 --- a/drivers/char/cyclades.c +++ b/drivers/char/cyclades.c @@ -663,15 +663,6 @@ static void cy_throttle(struct tty_struct *tty); static void cy_send_xchar(struct tty_struct *tty, char ch); -#define IS_CYC_Z(card) ((card).num_chips == (unsigned int)-1) - -#define Z_FPGA_CHECK(card) \ - ((readl(&(card).ctl_addr.p9060->init_ctrl) & (1<<17)) != 0) - -#define ISZLOADED(card) ((ZO_V1 == (card).hw_ver || Z_FPGA_CHECK(card)) && \ - (ZFIRM_ID == readl(&((struct FIRM_ID __iomem *) \ - ((card).base_addr+ID_ADDRESS))->signature))) - #ifndef SERIAL_XMIT_SIZE #define SERIAL_XMIT_SIZE (min(PAGE_SIZE, 4096)) #endif @@ -684,8 +675,6 @@ static void cy_send_xchar(struct tty_struct *tty, char ch); #define DRIVER_VERSION 0x02010203 #define RAM_SIZE 0x80000 -#define Z_FPGA_LOADED(X) ((readl(&(X)->init_ctrl) & (1<<17)) != 0) - enum zblock_type { ZBLOCK_PRG = 0, ZBLOCK_FPGA = 1 @@ -880,6 +869,29 @@ static void cyz_rx_restart(unsigned long); static struct timer_list cyz_rx_full_timer[NR_PORTS]; #endif /* CONFIG_CYZ_INTR */ +static inline bool cy_is_Z(struct cyclades_card *card) +{ + return card->num_chips == (unsigned int)-1; +} + +static inline bool __cyz_fpga_loaded(struct RUNTIME_9060 __iomem *ctl_addr) +{ + return readl(&ctl_addr->init_ctrl) & (1 << 17); +} + +static inline bool cyz_fpga_loaded(struct cyclades_card *card) +{ + return __cyz_fpga_loaded(card->ctl_addr.p9060); +} + +static inline bool cyz_is_loaded(struct cyclades_card *card) +{ + struct FIRM_ID __iomem *fw_id = card->base_addr + ID_ADDRESS; + + return (card->hw_ver == ZO_V1 || cyz_fpga_loaded(card)) && + readl(&fw_id->signature) == ZFIRM_ID; +} + static inline int serial_paranoia_check(struct cyclades_port *info, char *name, const char *routine) { @@ -1417,7 +1429,7 @@ cyz_issue_cmd(struct cyclades_card *cinfo, unsigned int index; firm_id = cinfo->base_addr + ID_ADDRESS; - if (!ISZLOADED(*cinfo)) + if (!cyz_is_loaded(cinfo)) return -1; zfw_ctrl = cinfo->base_addr + (readl(&firm_id->zfwctrl_addr) & 0xfffff); @@ -1725,7 +1737,7 @@ static irqreturn_t cyz_interrupt(int irq, void *dev_id) { struct cyclades_card *cinfo = dev_id; - if (unlikely(!ISZLOADED(*cinfo))) { + if (unlikely(!cyz_is_loaded(cinfo))) { #ifdef CY_DEBUG_INTERRUPTS printk(KERN_DEBUG "cyz_interrupt: board not yet loaded " "(IRQ%d).\n", irq); @@ -1773,9 +1785,9 @@ static void cyz_poll(unsigned long arg) for (card = 0; card < NR_CARDS; card++) { cinfo = &cy_card[card]; - if (!IS_CYC_Z(*cinfo)) + if (!cy_is_Z(cinfo)) continue; - if (!ISZLOADED(*cinfo)) + if (!cyz_is_loaded(cinfo)) continue; firm_id = cinfo->base_addr + ID_ADDRESS; @@ -1854,7 +1866,7 @@ static int startup(struct cyclades_port *info) set_line_char(info); - if (!IS_CYC_Z(*card)) { + if (!cy_is_Z(card)) { chip = channel >> 2; channel &= 0x03; index = card->bus_index; @@ -1911,7 +1923,7 @@ static int startup(struct cyclades_port *info) base_addr = card->base_addr; firm_id = base_addr + ID_ADDRESS; - if (!ISZLOADED(*card)) + if (!cyz_is_loaded(card)) return -ENODEV; zfw_ctrl = card->base_addr + @@ -2006,7 +2018,7 @@ static void start_xmit(struct cyclades_port *info) card = info->card; channel = info->line - card->first_line; - if (!IS_CYC_Z(*card)) { + if (!cy_is_Z(card)) { chip = channel >> 2; channel &= 0x03; index = card->bus_index; @@ -2050,7 +2062,7 @@ static void shutdown(struct cyclades_port *info) card = info->card; channel = info->line - card->first_line; - if (!IS_CYC_Z(*card)) { + if (!cy_is_Z(card)) { chip = channel >> 2; channel &= 0x03; index = card->bus_index; @@ -2106,7 +2118,7 @@ static void shutdown(struct cyclades_port *info) #endif firm_id = base_addr + ID_ADDRESS; - if (!ISZLOADED(*card)) + if (!cyz_is_loaded(card)) return; zfw_ctrl = card->base_addr + @@ -2213,7 +2225,7 @@ block_til_ready(struct tty_struct *tty, struct file *filp, #endif info->port.blocked_open++; - if (!IS_CYC_Z(*cinfo)) { + if (!cy_is_Z(cinfo)) { chip = channel >> 2; channel &= 0x03; index = cinfo->bus_index; @@ -2276,7 +2288,7 @@ block_til_ready(struct tty_struct *tty, struct file *filp, base_addr = cinfo->base_addr; firm_id = base_addr + ID_ADDRESS; - if (!ISZLOADED(*cinfo)) { + if (!cyz_is_loaded(cinfo)) { __set_current_state(TASK_RUNNING); remove_wait_queue(&info->port.open_wait, &wait); return -EINVAL; @@ -2377,12 +2389,12 @@ static int cy_open(struct tty_struct *tty, struct file *filp) treat it as absent from the system. This will make the user pay attention. */ - if (IS_CYC_Z(*info->card)) { + if (cy_is_Z(info->card)) { struct cyclades_card *cinfo = info->card; struct FIRM_ID __iomem *firm_id = cinfo->base_addr + ID_ADDRESS; - if (!ISZLOADED(*cinfo)) { - if (cinfo->hw_ver == ZE_V1 && Z_FPGA_CHECK(*cinfo) && + if (!cyz_is_loaded(cinfo)) { + if (cinfo->hw_ver == ZE_V1 && cyz_fpga_loaded(cinfo) && readl(&firm_id->signature) == ZFIRM_HLT) { printk(KERN_ERR "cyc:Cyclades-Z Error: you " @@ -2537,7 +2549,7 @@ static void cy_wait_until_sent(struct tty_struct *tty, int timeout) #endif card = info->card; channel = (info->line) - (card->first_line); - if (!IS_CYC_Z(*card)) { + if (!cy_is_Z(card)) { chip = channel >> 2; channel &= 0x03; index = card->bus_index; @@ -2582,7 +2594,7 @@ static void cy_flush_buffer(struct tty_struct *tty) info->xmit_cnt = info->xmit_head = info->xmit_tail = 0; spin_unlock_irqrestore(&card->card_lock, flags); - if (IS_CYC_Z(*card)) { /* If it is a Z card, flush the on-board + if (cy_is_Z(card)) { /* If it is a Z card, flush the on-board buffers as well */ spin_lock_irqsave(&card->card_lock, flags); retval = cyz_issue_cmd(card, channel, C_CM_FLUSH_TX, 0L); @@ -2663,7 +2675,7 @@ static void cy_close(struct tty_struct *tty, struct file *filp) spin_lock_irqsave(&card->card_lock, flags); - if (!IS_CYC_Z(*card)) { + if (!cy_is_Z(card)) { int channel = info->line - card->first_line; int index = card->bus_index; void __iomem *base_addr = card->base_addr + @@ -2883,7 +2895,7 @@ static int cy_chars_in_buffer(struct tty_struct *tty) channel = (info->line) - (card->first_line); #ifdef Z_EXT_CHARS_IN_BUFFER - if (!IS_CYC_Z(cy_card[card])) { + if (!cy_is_Z(card)) { #endif /* Z_EXT_CHARS_IN_BUFFER */ #ifdef CY_DEBUG_IO printk(KERN_DEBUG "cyc:cy_chars_in_buffer ttyC%d %d\n", @@ -2996,7 +3008,7 @@ static void set_line_char(struct cyclades_port *info) channel = info->line - card->first_line; chip_number = channel / 4; - if (!IS_CYC_Z(*card)) { + if (!cy_is_Z(card)) { index = card->bus_index; @@ -3221,7 +3233,7 @@ static void set_line_char(struct cyclades_port *info) int retval; firm_id = card->base_addr + ID_ADDRESS; - if (!ISZLOADED(*card)) + if (!cyz_is_loaded(card)) return; zfw_ctrl = card->base_addr + @@ -3438,7 +3450,7 @@ static int get_lsr_info(struct cyclades_port *info, unsigned int __user *value) card = info->card; channel = (info->line) - (card->first_line); - if (!IS_CYC_Z(*card)) { + if (!cy_is_Z(card)) { chip = channel >> 2; channel &= 0x03; index = card->bus_index; @@ -3478,7 +3490,7 @@ static int cy_tiocmget(struct tty_struct *tty, struct file *file) card = info->card; channel = info->line - card->first_line; - if (!IS_CYC_Z(*card)) { + if (!cy_is_Z(card)) { chip = channel >> 2; channel &= 0x03; index = card->bus_index; @@ -3504,7 +3516,7 @@ static int cy_tiocmget(struct tty_struct *tty, struct file *file) } else { base_addr = card->base_addr; firm_id = card->base_addr + ID_ADDRESS; - if (ISZLOADED(*card)) { + if (cyz_is_loaded(card)) { zfw_ctrl = card->base_addr + (readl(&firm_id->zfwctrl_addr) & 0xfffff); board_ctrl = &zfw_ctrl->board_ctrl; @@ -3547,7 +3559,7 @@ cy_tiocmset(struct tty_struct *tty, struct file *file, card = info->card; channel = (info->line) - (card->first_line); - if (!IS_CYC_Z(*card)) { + if (!cy_is_Z(card)) { chip = channel >> 2; channel &= 0x03; index = card->bus_index; @@ -3622,7 +3634,7 @@ cy_tiocmset(struct tty_struct *tty, struct file *file, base_addr = card->base_addr; firm_id = card->base_addr + ID_ADDRESS; - if (ISZLOADED(*card)) { + if (cyz_is_loaded(card)) { zfw_ctrl = card->base_addr + (readl(&firm_id->zfwctrl_addr) & 0xfffff); board_ctrl = &zfw_ctrl->board_ctrl; @@ -3694,7 +3706,7 @@ static int cy_break(struct tty_struct *tty, int break_state) card = info->card; spin_lock_irqsave(&card->card_lock, flags); - if (!IS_CYC_Z(*card)) { + if (!cy_is_Z(card)) { /* Let the transmit ISR take care of this (since it requires stuffing characters into the output stream). */ @@ -3763,7 +3775,7 @@ static int set_threshold(struct cyclades_port *info, unsigned long value) card = info->card; channel = info->line - card->first_line; - if (!IS_CYC_Z(*card)) { + if (!cy_is_Z(card)) { chip = channel >> 2; channel &= 0x03; index = card->bus_index; @@ -3791,7 +3803,7 @@ static int get_threshold(struct cyclades_port *info, card = info->card; channel = info->line - card->first_line; - if (!IS_CYC_Z(*card)) { + if (!cy_is_Z(card)) { chip = channel >> 2; channel &= 0x03; index = card->bus_index; @@ -3825,7 +3837,7 @@ static int set_timeout(struct cyclades_port *info, unsigned long value) card = info->card; channel = info->line - card->first_line; - if (!IS_CYC_Z(*card)) { + if (!cy_is_Z(card)) { chip = channel >> 2; channel &= 0x03; index = card->bus_index; @@ -3848,7 +3860,7 @@ static int get_timeout(struct cyclades_port *info, card = info->card; channel = info->line - card->first_line; - if (!IS_CYC_Z(*card)) { + if (!cy_is_Z(card)) { chip = channel >> 2; channel &= 0x03; index = card->bus_index; @@ -4102,7 +4114,7 @@ static void cy_send_xchar(struct tty_struct *tty, char ch) card = info->card; channel = info->line - card->first_line; - if (IS_CYC_Z(*card)) { + if (cy_is_Z(card)) { if (ch == STOP_CHAR(tty)) cyz_issue_cmd(card, channel, C_CM_SENDXOFF, 0L); else if (ch == START_CHAR(tty)) @@ -4135,7 +4147,7 @@ static void cy_throttle(struct tty_struct *tty) card = info->card; if (I_IXOFF(tty)) { - if (!IS_CYC_Z(*card)) + if (!cy_is_Z(card)) cy_send_xchar(tty, STOP_CHAR(tty)); else info->throttle = 1; @@ -4143,7 +4155,7 @@ static void cy_throttle(struct tty_struct *tty) if (tty->termios->c_cflag & CRTSCTS) { channel = info->line - card->first_line; - if (!IS_CYC_Z(*card)) { + if (!cy_is_Z(card)) { chip = channel >> 2; channel &= 0x03; index = card->bus_index; @@ -4200,7 +4212,7 @@ static void cy_unthrottle(struct tty_struct *tty) if (tty->termios->c_cflag & CRTSCTS) { card = info->card; channel = info->line - card->first_line; - if (!IS_CYC_Z(*card)) { + if (!cy_is_Z(card)) { chip = channel >> 2; channel &= 0x03; index = card->bus_index; @@ -4244,7 +4256,7 @@ static void cy_stop(struct tty_struct *tty) cinfo = info->card; channel = info->line - cinfo->first_line; - if (!IS_CYC_Z(*cinfo)) { + if (!cy_is_Z(cinfo)) { index = cinfo->bus_index; chip = channel >> 2; channel &= 0x03; @@ -4277,7 +4289,7 @@ static void cy_start(struct tty_struct *tty) cinfo = info->card; channel = info->line - cinfo->first_line; index = cinfo->bus_index; - if (!IS_CYC_Z(*cinfo)) { + if (!cy_is_Z(cinfo)) { chip = channel >> 2; channel &= 0x03; base_addr = cinfo->base_addr + (cy_chip_offset[chip] << index); @@ -4334,7 +4346,7 @@ static int __devinit cy_init_card(struct cyclades_card *cinfo) spin_lock_init(&cinfo->card_lock); - if (IS_CYC_Z(*cinfo)) { /* Cyclades-Z */ + if (cy_is_Z(cinfo)) { /* Cyclades-Z */ nports = (cinfo->hw_ver == ZE_V1) ? ZE_V1_NPORTS : 8; cinfo->intr_enabled = 0; cinfo->nports = 0; /* Will be correctly set later, after @@ -4365,7 +4377,7 @@ static int __devinit cy_init_card(struct cyclades_card *cinfo) init_completion(&info->shutdown_wait); init_waitqueue_head(&info->delta_msr_wait); - if (IS_CYC_Z(*cinfo)) { + if (cy_is_Z(cinfo)) { info->type = PORT_STARTECH; if (cinfo->hw_ver == ZO_V1) info->xmit_fifo_size = CYZ_FIFO_SIZE; @@ -4408,7 +4420,7 @@ static int __devinit cy_init_card(struct cyclades_card *cinfo) } #ifndef CONFIG_CYZ_INTR - if (IS_CYC_Z(*cinfo) && !timer_pending(&cyz_timerlist)) { + if (cy_is_Z(cinfo) && !timer_pending(&cyz_timerlist)) { mod_timer(&cyz_timerlist, jiffies + 1); #ifdef CY_PCI_DEBUG printk(KERN_DEBUG "Cyclades-Z polling initialized\n"); @@ -4771,7 +4783,7 @@ static int __devinit cyz_load_fw(struct pci_dev *pdev, void __iomem *base_addr, /* Check whether the firmware is already loaded and running. If positive, skip this board */ - if (Z_FPGA_LOADED(ctl_addr) && readl(&fid->signature) == ZFIRM_ID) { + if (__cyz_fpga_loaded(ctl_addr) && readl(&fid->signature) == ZFIRM_ID) { u32 cntval = readl(base_addr + 0x190); udelay(100); @@ -4790,7 +4802,7 @@ static int __devinit cyz_load_fw(struct pci_dev *pdev, void __iomem *base_addr, mailbox = readl(&ctl_addr->mail_box_0); - if (mailbox == 0 || Z_FPGA_LOADED(ctl_addr)) { + if (mailbox == 0 || __cyz_fpga_loaded(ctl_addr)) { /* stops CPU and set window to beginning of RAM */ cy_writel(&ctl_addr->loc_addr_base, WIN_CREG); cy_writel(&cust->cpu_stop, 0); @@ -4806,7 +4818,7 @@ static int __devinit cyz_load_fw(struct pci_dev *pdev, void __iomem *base_addr, base_addr); if (retval) goto err_rel; - if (!Z_FPGA_LOADED(ctl_addr)) { + if (!__cyz_fpga_loaded(ctl_addr)) { dev_err(&pdev->dev, "fw upload successful, but fw is " "not loaded\n"); goto err_rel; @@ -4865,7 +4877,7 @@ static int __devinit cyz_load_fw(struct pci_dev *pdev, void __iomem *base_addr, "system before loading the new FW to the " "Cyclades-Z.\n"); - if (Z_FPGA_LOADED(ctl_addr)) + if (__cyz_fpga_loaded(ctl_addr)) plx_init(pdev, irq, ctl_addr); retval = -EIO; @@ -4889,7 +4901,7 @@ static int __devinit cyz_load_fw(struct pci_dev *pdev, void __iomem *base_addr, "check the connection between the Z host card and the " "serial expanders.\n"); - if (Z_FPGA_LOADED(ctl_addr)) + if (__cyz_fpga_loaded(ctl_addr)) plx_init(pdev, irq, ctl_addr); dev_info(&pdev->dev, "Null number of ports detected. Board " @@ -5156,12 +5168,12 @@ static void __devexit cy_pci_remove(struct pci_dev *pdev) unsigned int i; /* non-Z with old PLX */ - if (!IS_CYC_Z(*cinfo) && (readb(cinfo->base_addr + CyPLX_VER) & 0x0f) == + if (!cy_is_Z(cinfo) && (readb(cinfo->base_addr + CyPLX_VER) & 0x0f) == PLX_9050) cy_writeb(cinfo->ctl_addr.p9050 + 0x4c, 0); else #ifndef CONFIG_CYZ_INTR - if (!IS_CYC_Z(*cinfo)) + if (!cy_is_Z(cinfo)) #endif cy_writew(&cinfo->ctl_addr.p9060->intr_ctrl_stat, readw(&cinfo->ctl_addr.p9060->intr_ctrl_stat) & @@ -5172,7 +5184,7 @@ static void __devexit cy_pci_remove(struct pci_dev *pdev) iounmap(cinfo->ctl_addr.p9050); if (cinfo->irq #ifndef CONFIG_CYZ_INTR - && !IS_CYC_Z(*cinfo) + && !cy_is_Z(cinfo) #endif /* CONFIG_CYZ_INTR */ ) free_irq(cinfo->irq, cinfo); @@ -5368,7 +5380,7 @@ static void __exit cy_cleanup_module(void) iounmap(card->ctl_addr.p9050); if (card->irq #ifndef CONFIG_CYZ_INTR - && !IS_CYC_Z(*card) + && !cy_is_Z(card) #endif /* CONFIG_CYZ_INTR */ ) free_irq(card->irq, card); -- cgit v1.2.3 From 963118eef9e6706e2b5356309fb0cdd9c9eba81d Mon Sep 17 00:00:00 2001 From: Jiri Slaby Date: Thu, 11 Jun 2009 12:34:27 +0100 Subject: tty: cyclades, fix nports handling Set up ports right after FW load so that we won't allocate maximal (64) ports when we use few. Also remove reading of nports in irq context, since we know it from initialisation now. This also fixes a tty ports unregistration on some fail paths and for Ze which registered 64 and unregistered real port count. Signed-off-by: Jiri Slaby Signed-off-by: Alan Cox Signed-off-by: Linus Torvalds --- drivers/char/cyclades.c | 58 +++++++++++++++---------------------------------- 1 file changed, 17 insertions(+), 41 deletions(-) (limited to 'drivers/char') diff --git a/drivers/char/cyclades.c b/drivers/char/cyclades.c index c9a503f4724..6adbc975966 100644 --- a/drivers/char/cyclades.c +++ b/drivers/char/cyclades.c @@ -604,7 +604,6 @@ #define NR_PORTS 256 -#define ZE_V1_NPORTS 64 #define ZO_V1 0 #define ZO_V2 1 #define ZE_V1 2 @@ -1777,7 +1776,6 @@ static void cyz_poll(unsigned long arg) struct tty_struct *tty; struct FIRM_ID __iomem *firm_id; struct ZFW_CTRL __iomem *zfw_ctrl; - struct BOARD_CTRL __iomem *board_ctrl; struct BUF_CTRL __iomem *buf_ctrl; unsigned long expires = jiffies + HZ; unsigned int port, card; @@ -1793,11 +1791,9 @@ static void cyz_poll(unsigned long arg) firm_id = cinfo->base_addr + ID_ADDRESS; zfw_ctrl = cinfo->base_addr + (readl(&firm_id->zfwctrl_addr) & 0xfffff); - board_ctrl = &(zfw_ctrl->board_ctrl); /* Skip first polling cycle to avoid racing conditions with the FW */ if (!cinfo->intr_enabled) { - cinfo->nports = (int)readl(&board_ctrl->n_channel); cinfo->intr_enabled = 1; continue; } @@ -2413,16 +2409,8 @@ static int cy_open(struct tty_struct *tty, struct file *filp) interrupts should be enabled as soon as the first open happens to one of its ports. */ if (!cinfo->intr_enabled) { - struct ZFW_CTRL __iomem *zfw_ctrl; - struct BOARD_CTRL __iomem *board_ctrl; u16 intr; - zfw_ctrl = cinfo->base_addr + - (readl(&firm_id->zfwctrl_addr) & - 0xfffff); - - board_ctrl = &zfw_ctrl->board_ctrl; - /* Enable interrupts on the PLX chip */ intr = readw(&cinfo->ctl_addr.p9060-> intr_ctrl_stat) | 0x0900; @@ -2435,8 +2423,6 @@ static int cy_open(struct tty_struct *tty, struct file *filp) printk(KERN_ERR "cyc:IRQ enable retval " "was %x\n", retval); } - cinfo->nports = - (int)readl(&board_ctrl->n_channel); cinfo->intr_enabled = 1; } } @@ -4340,30 +4326,20 @@ static void cy_hangup(struct tty_struct *tty) static int __devinit cy_init_card(struct cyclades_card *cinfo) { struct cyclades_port *info; - unsigned int nports, port; + unsigned int port; unsigned short chip_number; - int uninitialized_var(index); spin_lock_init(&cinfo->card_lock); + cinfo->intr_enabled = 0; - if (cy_is_Z(cinfo)) { /* Cyclades-Z */ - nports = (cinfo->hw_ver == ZE_V1) ? ZE_V1_NPORTS : 8; - cinfo->intr_enabled = 0; - cinfo->nports = 0; /* Will be correctly set later, after - Z FW is loaded */ - } else { - index = cinfo->bus_index; - nports = cinfo->nports = CyPORTS_PER_CHIP * cinfo->num_chips; - } - - cinfo->ports = kzalloc(sizeof(*cinfo->ports) * nports, GFP_KERNEL); + cinfo->ports = kcalloc(cinfo->nports, sizeof(*cinfo->ports), + GFP_KERNEL); if (cinfo->ports == NULL) { printk(KERN_ERR "Cyclades: cannot allocate ports\n"); - cinfo->nports = 0; return -ENOMEM; } - for (port = cinfo->first_line; port < cinfo->first_line + nports; + for (port = cinfo->first_line; port < cinfo->first_line + cinfo->nports; port++) { info = &cinfo->ports[port - cinfo->first_line]; tty_port_init(&info->port); @@ -4388,6 +4364,7 @@ static int __devinit cy_init_card(struct cyclades_card *cinfo) cyz_rx_restart, (unsigned long)info); #endif } else { + int index = cinfo->bus_index; info->type = PORT_CIRRUS; info->xmit_fifo_size = CyMAX_CHAR_FIFO; info->cor1 = CyPARITY_NONE | Cy_1_STOP | Cy_8_BITS; @@ -4615,7 +4592,8 @@ static int __init cy_detect_isa(void) cy_card[j].irq = (int)cy_isa_irq; cy_card[j].bus_index = 0; cy_card[j].first_line = cy_next_channel; - cy_card[j].num_chips = cy_isa_nchan / 4; + cy_card[j].num_chips = cy_isa_nchan / CyPORTS_PER_CHIP; + cy_card[j].nports = cy_isa_nchan; if (cy_init_card(&cy_card[j])) { cy_card[j].base_addr = NULL; free_irq(cy_isa_irq, &cy_card[j]); @@ -4771,7 +4749,7 @@ static int __devinit cyz_load_fw(struct pci_dev *pdev, void __iomem *base_addr, struct CUSTOM_REG __iomem *cust = base_addr; struct ZFW_CTRL __iomem *pt_zfwctrl; void __iomem *tmp; - u32 mailbox, status; + u32 mailbox, status, nchan; unsigned int i; int retval; @@ -4892,11 +4870,11 @@ static int __devinit cyz_load_fw(struct pci_dev *pdev, void __iomem *base_addr, base_addr + ID_ADDRESS, readl(&fid->zfwctrl_addr), base_addr + readl(&fid->zfwctrl_addr)); + nchan = readl(&pt_zfwctrl->board_ctrl.n_channel); dev_info(&pdev->dev, "Cyclades-Z FW loaded: version = %x, ports = %u\n", - readl(&pt_zfwctrl->board_ctrl.fw_version), - readl(&pt_zfwctrl->board_ctrl.n_channel)); + readl(&pt_zfwctrl->board_ctrl.fw_version), nchan); - if (readl(&pt_zfwctrl->board_ctrl.n_channel) == 0) { + if (nchan == 0) { dev_warn(&pdev->dev, "no Cyclades-Z ports were found. Please " "check the connection between the Z host card and the " "serial expanders.\n"); @@ -4922,7 +4900,7 @@ static int __devinit cyz_load_fw(struct pci_dev *pdev, void __iomem *base_addr, cy_writel(&ctl_addr->intr_ctrl_stat, readl(&ctl_addr->intr_ctrl_stat) | 0x00030800UL); - return 0; + return nchan; err_rel: release_firmware(fw); err: @@ -5027,12 +5005,8 @@ static int __devinit cy_pci_probe(struct pci_dev *pdev, if (mailbox == ZE_V1) { card_name = "Cyclades-Ze"; - - nchan = ZE_V1_NPORTS; } else { card_name = "Cyclades-8Zo"; - nchan = 8; - #ifdef CY_PCI_DEBUG if (mailbox == ZO_V1) { cy_writel(&ctl_addr->loc_addr_base, WIN_CREG); @@ -5057,8 +5031,9 @@ static int __devinit cy_pci_probe(struct pci_dev *pdev, } retval = cyz_load_fw(pdev, addr2, addr0, irq); - if (retval) + if (retval <= 0) goto err_unmap; + nchan = retval; } if ((cy_next_channel + nchan) > NR_PORTS) { @@ -5088,7 +5063,7 @@ static int __devinit cy_pci_probe(struct pci_dev *pdev, dev_err(&pdev->dev, "could not allocate IRQ\n"); goto err_unmap; } - cy_card[card_no].num_chips = nchan / 4; + cy_card[card_no].num_chips = nchan / CyPORTS_PER_CHIP; } else { cy_card[card_no].hw_ver = mailbox; cy_card[card_no].num_chips = (unsigned int)-1; @@ -5112,6 +5087,7 @@ static int __devinit cy_pci_probe(struct pci_dev *pdev, cy_card[card_no].irq = irq; cy_card[card_no].bus_index = 1; cy_card[card_no].first_line = cy_next_channel; + cy_card[card_no].nports = nchan; retval = cy_init_card(&cy_card[card_no]); if (retval) goto err_null; -- cgit v1.2.3 From b39933fbd304021580800796683b8ddaa3dd0a6a Mon Sep 17 00:00:00 2001 From: Jiri Slaby Date: Thu, 11 Jun 2009 12:35:21 +0100 Subject: tty: cyclades, remove unused variables Signed-off-by: Jiri Slaby Signed-off-by: Alan Cox Signed-off-by: Linus Torvalds --- drivers/char/cyclades.c | 6 ------ 1 file changed, 6 deletions(-) (limited to 'drivers/char') diff --git a/drivers/char/cyclades.c b/drivers/char/cyclades.c index 6adbc975966..45601905174 100644 --- a/drivers/char/cyclades.c +++ b/drivers/char/cyclades.c @@ -2963,7 +2963,6 @@ static void set_line_char(struct cyclades_port *info) void __iomem *base_addr; int chip, channel, index; unsigned cflag, iflag; - unsigned short chip_number; int baud, baud_rate = 0; int i; @@ -2992,7 +2991,6 @@ static void set_line_char(struct cyclades_port *info) card = info->card; channel = info->line - card->first_line; - chip_number = channel / 4; if (!cy_is_Z(card)) { @@ -3212,9 +3210,7 @@ static void set_line_char(struct cyclades_port *info) } else { struct FIRM_ID __iomem *firm_id; struct ZFW_CTRL __iomem *zfw_ctrl; - struct BOARD_CTRL __iomem *board_ctrl; struct CH_CTRL __iomem *ch_ctrl; - struct BUF_CTRL __iomem *buf_ctrl; __u32 sw_flow; int retval; @@ -3224,9 +3220,7 @@ static void set_line_char(struct cyclades_port *info) zfw_ctrl = card->base_addr + (readl(&firm_id->zfwctrl_addr) & 0xfffff); - board_ctrl = &zfw_ctrl->board_ctrl; ch_ctrl = &(zfw_ctrl->ch_ctrl[channel]); - buf_ctrl = &zfw_ctrl->buf_ctrl[channel]; /* baud rate */ baud = tty_get_baud_rate(info->port.tty); -- cgit v1.2.3 From a391ad0f09014856bbc4eeea309593eba977b3b0 Mon Sep 17 00:00:00 2001 From: Jiri Slaby Date: Thu, 11 Jun 2009 12:40:17 +0100 Subject: rocket: fix test_bit parameters Switch from ASYNC_* to ASYNCB_*, because {test,set}_bit expect bit number, not mask. Signed-off-by: Jiri Slaby Signed-off-by: Alan Cox Signed-off-by: Linus Torvalds --- drivers/char/rocket.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'drivers/char') diff --git a/drivers/char/rocket.c b/drivers/char/rocket.c index 7399188049d..63d5b628477 100644 --- a/drivers/char/rocket.c +++ b/drivers/char/rocket.c @@ -939,7 +939,7 @@ static int rp_open(struct tty_struct *tty, struct file *filp) /* * Info->count is now 1; so it's safe to sleep now. */ - if (!test_bit(ASYNC_INITIALIZED, &port->flags)) { + if (!test_bit(ASYNCB_INITIALIZED, &port->flags)) { cp = &info->channel; sSetRxTrigger(cp, TRIG_1); if (sGetChanStatus(cp) & CD_ACT) @@ -963,7 +963,7 @@ static int rp_open(struct tty_struct *tty, struct file *filp) sEnRxFIFO(cp); sEnTransmit(cp); - set_bit(ASYNC_INITIALIZED, &info->port.flags); + set_bit(ASYNCB_INITIALIZED, &info->port.flags); /* * Set up the tty->alt_speed kludge @@ -1646,7 +1646,7 @@ static int rp_write(struct tty_struct *tty, /* Write remaining data into the port's xmit_buf */ while (1) { /* Hung up ? */ - if (!test_bit(ASYNC_NORMAL_ACTIVE, &info->port.flags)) + if (!test_bit(ASYNCB_NORMAL_ACTIVE, &info->port.flags)) goto end; c = min(count, XMIT_BUF_SIZE - info->xmit_cnt - 1); c = min(c, XMIT_BUF_SIZE - info->xmit_head); -- cgit v1.2.3 From c3301a5c04800bcf8afc8a815bf9e570a4e25a08 Mon Sep 17 00:00:00 2001 From: Jiri Slaby Date: Thu, 11 Jun 2009 12:41:05 +0100 Subject: epca: fix test_bit parameters Switch from ASYNC_* to ASYNCB_*, because test_bit expects bit number, not mask. Signed-off-by: Jiri Slaby Signed-off-by: Alan Cox Signed-off-by: Linus Torvalds --- drivers/char/epca.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'drivers/char') diff --git a/drivers/char/epca.c b/drivers/char/epca.c index 8797b774235..710ee933305 100644 --- a/drivers/char/epca.c +++ b/drivers/char/epca.c @@ -1518,7 +1518,7 @@ static void doevent(int crd) if (event & MODEMCHG_IND) { /* A modem signal change has been indicated */ ch->imodem = mstat; - if (test_bit(ASYNC_CHECK_CD, &ch->port.flags)) { + if (test_bit(ASYNCB_CHECK_CD, &ch->port.flags)) { /* We are now receiving dcd */ if (mstat & ch->dcd) wake_up_interruptible(&ch->port.open_wait); @@ -1765,9 +1765,9 @@ static void epcaparam(struct tty_struct *tty, struct channel *ch) * that the driver will wait on carrier detect. */ if (ts->c_cflag & CLOCAL) - clear_bit(ASYNC_CHECK_CD, &ch->port.flags); + clear_bit(ASYNCB_CHECK_CD, &ch->port.flags); else - set_bit(ASYNC_CHECK_CD, &ch->port.flags); + set_bit(ASYNCB_CHECK_CD, &ch->port.flags); mval = ch->m_dtr | ch->m_rts; } /* End CBAUD not detected */ iflag = termios2digi_i(ch, ts->c_iflag); @@ -2244,7 +2244,8 @@ static void do_softint(struct work_struct *work) if (test_and_clear_bit(EPCA_EVENT_HANGUP, &ch->event)) { tty_hangup(tty); wake_up_interruptible(&ch->port.open_wait); - clear_bit(ASYNC_NORMAL_ACTIVE, &ch->port.flags); + clear_bit(ASYNCB_NORMAL_ACTIVE, + &ch->port.flags); } } tty_kref_put(tty); -- cgit v1.2.3 From 38db89799bdf11625a831c5af33938dcb11908b6 Mon Sep 17 00:00:00 2001 From: Alan Cox Date: Thu, 11 Jun 2009 12:44:17 +0100 Subject: tty: throttling race fix The tty throttling code can race due to the lock drops. It takes very high loads but this has been observed and verified by Rob Duncan. The basic problem is that on an SMP box we can go CPU #1 CPU #2 need to throttle ? suppose we should buffer space cleared are we throttled yes ? - unthrottle call throttle method This changeet take the termios lock to protect against this. The termios lock isn't the initial obvious candidate but many implementations of throttle methods already need to poke around their own termios structures (and nobody really locks them against a racing change of flow control). This does mean that anyone who is setting tty->low_latency = 1 and then calling tty_flip_buffer_push from their unthrottle method is going to end up collapsing in a pile of locks. However we've removed all the known bogus users of low_latency = 1 and such use isn't safe anyway for other reasons so catching it would be an improvement. Signed-off-by: Alan Cox Signed-off-by: Linus Torvalds --- drivers/char/tty_ioctl.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) (limited to 'drivers/char') diff --git a/drivers/char/tty_ioctl.c b/drivers/char/tty_ioctl.c index 6f4c7d0a53b..2401dbcbee9 100644 --- a/drivers/char/tty_ioctl.c +++ b/drivers/char/tty_ioctl.c @@ -97,14 +97,19 @@ EXPORT_SYMBOL(tty_driver_flush_buffer); * @tty: terminal * * Indicate that a tty should stop transmitting data down the stack. + * Takes the termios mutex to protect against parallel throttle/unthrottle + * and also to ensure the driver can consistently reference its own + * termios data at this point when implementing software flow control. */ void tty_throttle(struct tty_struct *tty) { + mutex_lock(&tty->termios_mutex); /* check TTY_THROTTLED first so it indicates our state */ if (!test_and_set_bit(TTY_THROTTLED, &tty->flags) && tty->ops->throttle) tty->ops->throttle(tty); + mutex_unlock(&tty->termios_mutex); } EXPORT_SYMBOL(tty_throttle); @@ -113,13 +118,21 @@ EXPORT_SYMBOL(tty_throttle); * @tty: terminal * * Indicate that a tty may continue transmitting data down the stack. + * Takes the termios mutex to protect against parallel throttle/unthrottle + * and also to ensure the driver can consistently reference its own + * termios data at this point when implementing software flow control. + * + * Drivers should however remember that the stack can issue a throttle, + * then change flow control method, then unthrottle. */ void tty_unthrottle(struct tty_struct *tty) { + mutex_lock(&tty->termios_mutex); if (test_and_clear_bit(TTY_THROTTLED, &tty->flags) && tty->ops->unthrottle) tty->ops->unthrottle(tty); + mutex_unlock(&tty->termios_mutex); } EXPORT_SYMBOL(tty_unthrottle); -- cgit v1.2.3 From 620df3c0a5b70656c4de6049825de214f108218e Mon Sep 17 00:00:00 2001 From: Alan Cox Date: Thu, 11 Jun 2009 12:45:53 +0100 Subject: pty: Fix a comment We fixed the globals, so now fix the comment Signed-off-by: Alan Cox Signed-off-by: Linus Torvalds --- drivers/char/pty.c | 1 - 1 file changed, 1 deletion(-) (limited to 'drivers/char') diff --git a/drivers/char/pty.c b/drivers/char/pty.c index 31038a0052a..da2cb8c70c1 100644 --- a/drivers/char/pty.c +++ b/drivers/char/pty.c @@ -30,7 +30,6 @@ #include -/* These are global because they are accessed in tty_io.c */ #ifdef CONFIG_UNIX98_PTYS static struct tty_driver *ptm_driver; static struct tty_driver *pts_driver; -- cgit v1.2.3 From 5f0878acba7db24323f5ba4055ec9a96895bb150 Mon Sep 17 00:00:00 2001 From: Alan Cox Date: Thu, 11 Jun 2009 12:46:41 +0100 Subject: tty: Fix oops when scanning the polling list for kgdb Costantino Leandro found a bug in tty_find_polling_driver and provided a patch that fixed the crash but not the underlying bug. This fixes the underlying bug where the list walk corrupts the values it is using on a match but then reuses them if the open fails. Signed-off-by: Alan Cox Signed-off-by: Linus Torvalds --- drivers/char/tty_io.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) (limited to 'drivers/char') diff --git a/drivers/char/tty_io.c b/drivers/char/tty_io.c index 66b99a2049e..6c817398232 100644 --- a/drivers/char/tty_io.c +++ b/drivers/char/tty_io.c @@ -295,7 +295,7 @@ struct tty_driver *tty_find_polling_driver(char *name, int *line) struct tty_driver *p, *res = NULL; int tty_line = 0; int len; - char *str; + char *str, *stp; for (str = name; *str; str++) if ((*str >= '0' && *str <= '9') || *str == ',') @@ -311,13 +311,14 @@ struct tty_driver *tty_find_polling_driver(char *name, int *line) list_for_each_entry(p, &tty_drivers, tty_drivers) { if (strncmp(name, p->name, len) != 0) continue; - if (*str == ',') - str++; - if (*str == '\0') - str = NULL; + stp = str; + if (*stp == ',') + stp++; + if (*stp == '\0') + stp = NULL; if (tty_line >= 0 && tty_line <= p->num && p->ops && - p->ops->poll_init && !p->ops->poll_init(p, tty_line, str)) { + p->ops->poll_init && !p->ops->poll_init(p, tty_line, stp)) { res = tty_driver_kref_get(p); *line = tty_line; break; -- cgit v1.2.3 From e8b70e7d3e86319a8b2aaabde3866833d92cd80f Mon Sep 17 00:00:00 2001 From: Alan Cox Date: Thu, 11 Jun 2009 12:48:02 +0100 Subject: tty: Extract various bits of ldisc code Before trying to tackle the ldisc bugs the code needs to be a good deal more readable, so do the simple extractions of routines first. Signed-off-by: Alan Cox Signed-off-by: Linus Torvalds --- drivers/char/tty_io.c | 24 +++++++++--- drivers/char/tty_ldisc.c | 97 ++++++++++++++++++++++++++++++++---------------- 2 files changed, 85 insertions(+), 36 deletions(-) (limited to 'drivers/char') diff --git a/drivers/char/tty_io.c b/drivers/char/tty_io.c index 6c817398232..be49d0730bb 100644 --- a/drivers/char/tty_io.c +++ b/drivers/char/tty_io.c @@ -2481,6 +2481,24 @@ static int tty_tiocmset(struct tty_struct *tty, struct file *file, unsigned int return tty->ops->tiocmset(tty, file, set, clear); } +struct tty_struct *tty_pair_get_tty(struct tty_struct *tty) +{ + if (tty->driver->type == TTY_DRIVER_TYPE_PTY && + tty->driver->subtype == PTY_TYPE_MASTER) + tty = tty->link; + return tty; +} +EXPORT_SYMBOL(tty_pair_get_tty); + +struct tty_struct *tty_pair_get_pty(struct tty_struct *tty) +{ + if (tty->driver->type == TTY_DRIVER_TYPE_PTY && + tty->driver->subtype == PTY_TYPE_MASTER) + return tty; + return tty->link; +} +EXPORT_SYMBOL(tty_pair_get_pty); + /* * Split this up, as gcc can choke on it otherwise.. */ @@ -2496,11 +2514,7 @@ long tty_ioctl(struct file *file, unsigned int cmd, unsigned long arg) if (tty_paranoia_check(tty, inode, "tty_ioctl")) return -EINVAL; - real_tty = tty; - if (tty->driver->type == TTY_DRIVER_TYPE_PTY && - tty->driver->subtype == PTY_TYPE_MASTER) - real_tty = tty->link; - + real_tty = tty_pair_get_tty(tty); /* * Factor out some common prep work diff --git a/drivers/char/tty_ldisc.c b/drivers/char/tty_ldisc.c index f78f5b0127a..e3c6416aa86 100644 --- a/drivers/char/tty_ldisc.c +++ b/drivers/char/tty_ldisc.c @@ -443,6 +443,50 @@ static void tty_ldisc_restore(struct tty_struct *tty, struct tty_ldisc *old) } } +/** + * tty_ldisc_halt - shutdown the line discipline + * @tty: tty device + * + * Shut down the line discipline and work queue for this tty device. + * The TTY_LDISC flag being cleared ensures no further references can + * be obtained while the delayed work queue halt ensures that no more + * data is fed to the ldisc. + * + * In order to wait for any existing references to complete see + * tty_ldisc_wait_idle. + */ + +static void tty_ldisc_halt(struct tty_struct *tty) +{ + clear_bit(TTY_LDISC, &tty->flags); + cancel_delayed_work(&tty->buf.work); + /* + * Wait for ->hangup_work and ->buf.work handlers to terminate + */ + flush_scheduled_work(); +} + +/** + * tty_ldisc_wait_idle - wait for the ldisc to become idle + * @tty: tty to wait for + * + * Wait for the line discipline to become idle. The discipline must + * have been halted for this to guarantee it remains idle. + * + */ + +static void tty_ldisc_wait_idle(struct tty_struct *tty) +{ + unsigned long flags; + spin_lock_irqsave(&tty_ldisc_lock, flags); + while (tty->ldisc.refcount) { + spin_unlock_irqrestore(&tty_ldisc_lock, flags); + wait_event(tty_ldisc_wait, tty->ldisc.refcount == 0); + spin_lock_irqsave(&tty_ldisc_lock, flags); + } + spin_unlock_irqrestore(&tty_ldisc_lock, flags); +} + /** * tty_set_ldisc - set line discipline * @tty: the terminal to set @@ -636,6 +680,21 @@ int tty_ldisc_setup(struct tty_struct *tty, struct tty_struct *o_tty) return 0; } +static void tty_ldisc_reinit(struct tty_struct *tty) +{ + struct tty_ldisc ld; + + if (tty->ldisc.ops->close) + (tty->ldisc.ops->close)(tty); + tty_ldisc_put(tty->ldisc.ops); + /* + * Switch the line discipline back + */ + WARN_ON(tty_ldisc_get(N_TTY, &ld)); + tty_ldisc_assign(tty, &ld); + tty_set_termios_ldisc(tty, N_TTY); +} + /** * tty_ldisc_release - release line discipline * @tty: tty being shut down @@ -647,58 +706,34 @@ int tty_ldisc_setup(struct tty_struct *tty, struct tty_struct *o_tty) void tty_ldisc_release(struct tty_struct *tty, struct tty_struct *o_tty) { - unsigned long flags; - struct tty_ldisc ld; + /* * Prevent flush_to_ldisc() from rescheduling the work for later. Then * kill any delayed work. As this is the final close it does not * race with the set_ldisc code path. */ - clear_bit(TTY_LDISC, &tty->flags); - cancel_delayed_work(&tty->buf.work); - /* - * Wait for ->hangup_work and ->buf.work handlers to terminate - */ - - flush_scheduled_work(); + tty_ldisc_halt(tty); /* * Wait for any short term users (we know they are just driver * side waiters as the file is closing so user count on the file * side is zero. */ - spin_lock_irqsave(&tty_ldisc_lock, flags); - while (tty->ldisc.refcount) { - spin_unlock_irqrestore(&tty_ldisc_lock, flags); - wait_event(tty_ldisc_wait, tty->ldisc.refcount == 0); - spin_lock_irqsave(&tty_ldisc_lock, flags); - } - spin_unlock_irqrestore(&tty_ldisc_lock, flags); + + tty_ldisc_wait_idle(tty); + /* * Shutdown the current line discipline, and reset it to N_TTY. * * FIXME: this MUST get fixed for the new reflocking */ - if (tty->ldisc.ops->close) - (tty->ldisc.ops->close)(tty); - tty_ldisc_put(tty->ldisc.ops); - /* - * Switch the line discipline back - */ - WARN_ON(tty_ldisc_get(N_TTY, &ld)); - tty_ldisc_assign(tty, &ld); - tty_set_termios_ldisc(tty, N_TTY); + tty_ldisc_reinit(tty); if (o_tty) { /* FIXME: could o_tty be in setldisc here ? */ clear_bit(TTY_LDISC, &o_tty->flags); - if (o_tty->ldisc.ops->close) - (o_tty->ldisc.ops->close)(o_tty); - tty_ldisc_put(o_tty->ldisc.ops); - WARN_ON(tty_ldisc_get(N_TTY, &ld)); - tty_ldisc_assign(o_tty, &ld); - tty_set_termios_ldisc(o_tty, N_TTY); + tty_ldisc_reinit(o_tty); } } -- cgit v1.2.3 From c65c9bc3efa5589f691276bb9db689119a711222 Mon Sep 17 00:00:00 2001 From: Alan Cox Date: Thu, 11 Jun 2009 12:50:12 +0100 Subject: tty: rewrite the ldisc locking There are several pretty much unfixable races in the old ldisc code, especially with respect to pty behaviour and also to hangup. It's easier to rewrite the code than simply try and patch it up. This patch - splits the ldisc from the tty (so we will be able to refcount it more cleanly later) - introduces a mutex lock for ldisc changing on an active device - fixes the complete mess that hangup caused - implements hopefully correct setldisc/close/hangup locking There are still some problems around pty pairs that have always been there but at least it is now possible to understand the code and fix further problems. This fixes the following known bugs - hang up can leak ldisc references - hang up may not call open/close on ldisc in a matched way - pty/tty pairs can deadlock during an ldisc change - reading the ldisc proc files can cause every ldisc to be loaded and probably a few other of the mysterious ldisc race reports. I'm sure it also adds the odd new one. Signed-off-by: Alan Cox Signed-off-by: Linus Torvalds --- drivers/char/cyclades.c | 2 +- drivers/char/epca.c | 4 +- drivers/char/ip2/i2lib.c | 4 +- drivers/char/ip2/ip2main.c | 4 +- drivers/char/n_hdlc.c | 4 +- drivers/char/pty.c | 10 +- drivers/char/selection.c | 2 +- drivers/char/tty_io.c | 64 +----- drivers/char/tty_ldisc.c | 477 ++++++++++++++++++++++++++++----------------- 9 files changed, 321 insertions(+), 250 deletions(-) (limited to 'drivers/char') diff --git a/drivers/char/cyclades.c b/drivers/char/cyclades.c index 45601905174..f3366d3f06c 100644 --- a/drivers/char/cyclades.c +++ b/drivers/char/cyclades.c @@ -5200,7 +5200,7 @@ static int cyclades_proc_show(struct seq_file *m, void *v) (cur_jifs - info->idle_stats.recv_idle)/ HZ, info->idle_stats.overruns, /* FIXME: double check locking */ - (long)info->port.tty->ldisc.ops->num); + (long)info->port.tty->ldisc->ops->num); else seq_printf(m, "%3d %8lu %10lu %8lu " "%10lu %8lu %9lu %6ld\n", diff --git a/drivers/char/epca.c b/drivers/char/epca.c index 710ee933305..abef1f7d84f 100644 --- a/drivers/char/epca.c +++ b/drivers/char/epca.c @@ -2114,8 +2114,8 @@ static int pc_ioctl(struct tty_struct *tty, struct file *file, tty_wait_until_sent(tty, 0); } else { /* ldisc lock already held in ioctl */ - if (tty->ldisc.ops->flush_buffer) - tty->ldisc.ops->flush_buffer(tty); + if (tty->ldisc->ops->flush_buffer) + tty->ldisc->ops->flush_buffer(tty); } unlock_kernel(); /* Fall Thru */ diff --git a/drivers/char/ip2/i2lib.c b/drivers/char/ip2/i2lib.c index 0061e18aff6..0d10b89218e 100644 --- a/drivers/char/ip2/i2lib.c +++ b/drivers/char/ip2/i2lib.c @@ -868,11 +868,11 @@ i2Input(i2ChanStrPtr pCh) amountToMove = count; } // Move the first block - pCh->pTTY->ldisc.ops->receive_buf( pCh->pTTY, + pCh->pTTY->ldisc->ops->receive_buf( pCh->pTTY, &(pCh->Ibuf[stripIndex]), NULL, amountToMove ); // If we needed to wrap, do the second data move if (count > amountToMove) { - pCh->pTTY->ldisc.ops->receive_buf( pCh->pTTY, + pCh->pTTY->ldisc->ops->receive_buf( pCh->pTTY, pCh->Ibuf, NULL, count - amountToMove ); } // Bump and wrap the stripIndex all at once by the amount of data read. This diff --git a/drivers/char/ip2/ip2main.c b/drivers/char/ip2/ip2main.c index afd9247cf08..517271c762e 100644 --- a/drivers/char/ip2/ip2main.c +++ b/drivers/char/ip2/ip2main.c @@ -1315,8 +1315,8 @@ static inline void isig(int sig, struct tty_struct *tty, int flush) if (tty->pgrp) kill_pgrp(tty->pgrp, sig, 1); if (flush || !L_NOFLSH(tty)) { - if ( tty->ldisc.ops->flush_buffer ) - tty->ldisc.ops->flush_buffer(tty); + if ( tty->ldisc->ops->flush_buffer ) + tty->ldisc->ops->flush_buffer(tty); i2InputFlush( tty->driver_data ); } } diff --git a/drivers/char/n_hdlc.c b/drivers/char/n_hdlc.c index bacb3e2872a..461ece591a5 100644 --- a/drivers/char/n_hdlc.c +++ b/drivers/char/n_hdlc.c @@ -342,8 +342,8 @@ static int n_hdlc_tty_open (struct tty_struct *tty) #endif /* Flush any pending characters in the driver and discipline. */ - if (tty->ldisc.ops->flush_buffer) - tty->ldisc.ops->flush_buffer(tty); + if (tty->ldisc->ops->flush_buffer) + tty->ldisc->ops->flush_buffer(tty); tty_driver_flush_buffer(tty); diff --git a/drivers/char/pty.c b/drivers/char/pty.c index da2cb8c70c1..5acd29e6e04 100644 --- a/drivers/char/pty.c +++ b/drivers/char/pty.c @@ -110,7 +110,7 @@ static int pty_write(struct tty_struct *tty, const unsigned char *buf, c = to->receive_room; if (c > count) c = count; - to->ldisc.ops->receive_buf(to, buf, NULL, c); + to->ldisc->ops->receive_buf(to, buf, NULL, c); return c; } @@ -148,11 +148,11 @@ static int pty_chars_in_buffer(struct tty_struct *tty) int count; /* We should get the line discipline lock for "tty->link" */ - if (!to || !to->ldisc.ops->chars_in_buffer) + if (!to || !to->ldisc->ops->chars_in_buffer) return 0; /* The ldisc must report 0 if no characters available to be read */ - count = to->ldisc.ops->chars_in_buffer(to); + count = to->ldisc->ops->chars_in_buffer(to); if (tty->driver->subtype == PTY_TYPE_SLAVE) return count; @@ -186,8 +186,8 @@ static void pty_flush_buffer(struct tty_struct *tty) if (!to) return; - if (to->ldisc.ops->flush_buffer) - to->ldisc.ops->flush_buffer(to); + if (to->ldisc->ops->flush_buffer) + to->ldisc->ops->flush_buffer(to); if (to->packet) { spin_lock_irqsave(&tty->ctrl_lock, flags); diff --git a/drivers/char/selection.c b/drivers/char/selection.c index cb8ca569896..f97b9e84806 100644 --- a/drivers/char/selection.c +++ b/drivers/char/selection.c @@ -327,7 +327,7 @@ int paste_selection(struct tty_struct *tty) } count = sel_buffer_lth - pasted; count = min(count, tty->receive_room); - tty->ldisc.ops->receive_buf(tty, sel_buffer + pasted, + tty->ldisc->ops->receive_buf(tty, sel_buffer + pasted, NULL, count); pasted += count; } diff --git a/drivers/char/tty_io.c b/drivers/char/tty_io.c index be49d0730bb..2f44b0b241c 100644 --- a/drivers/char/tty_io.c +++ b/drivers/char/tty_io.c @@ -491,22 +491,6 @@ void tty_ldisc_flush(struct tty_struct *tty) EXPORT_SYMBOL_GPL(tty_ldisc_flush); -/** - * tty_reset_termios - reset terminal state - * @tty: tty to reset - * - * Restore a terminal to the driver default state - */ - -static void tty_reset_termios(struct tty_struct *tty) -{ - mutex_lock(&tty->termios_mutex); - *tty->termios = tty->driver->init_termios; - tty->termios->c_ispeed = tty_termios_input_baud_rate(tty->termios); - tty->termios->c_ospeed = tty_termios_baud_rate(tty->termios); - mutex_unlock(&tty->termios_mutex); -} - /** * do_tty_hangup - actual handler for hangup events * @work: tty device @@ -536,7 +520,6 @@ static void do_tty_hangup(struct work_struct *work) struct file *cons_filp = NULL; struct file *filp, *f = NULL; struct task_struct *p; - struct tty_ldisc *ld; int closecount = 0, n; unsigned long flags; int refs = 0; @@ -567,40 +550,8 @@ static void do_tty_hangup(struct work_struct *work) filp->f_op = &hung_up_tty_fops; } file_list_unlock(); - /* - * FIXME! What are the locking issues here? This may me overdoing - * things... This question is especially important now that we've - * removed the irqlock. - */ - ld = tty_ldisc_ref(tty); - if (ld != NULL) { - /* We may have no line discipline at this point */ - if (ld->ops->flush_buffer) - ld->ops->flush_buffer(tty); - tty_driver_flush_buffer(tty); - if ((test_bit(TTY_DO_WRITE_WAKEUP, &tty->flags)) && - ld->ops->write_wakeup) - ld->ops->write_wakeup(tty); - if (ld->ops->hangup) - ld->ops->hangup(tty); - } - /* - * FIXME: Once we trust the LDISC code better we can wait here for - * ldisc completion and fix the driver call race - */ - wake_up_interruptible_poll(&tty->write_wait, POLLOUT); - wake_up_interruptible_poll(&tty->read_wait, POLLIN); - /* - * Shutdown the current line discipline, and reset it to - * N_TTY. - */ - if (tty->driver->flags & TTY_DRIVER_RESET_TERMIOS) - tty_reset_termios(tty); - /* Defer ldisc switch */ - /* tty_deferred_ldisc_switch(N_TTY); - This should get done automatically when the port closes and - tty_release is called */ + tty_ldisc_hangup(tty); read_lock(&tasklist_lock); if (tty->session) { @@ -629,12 +580,15 @@ static void do_tty_hangup(struct work_struct *work) read_unlock(&tasklist_lock); spin_lock_irqsave(&tty->ctrl_lock, flags); - tty->flags = 0; + clear_bit(TTY_THROTTLED, &tty->flags); + clear_bit(TTY_PUSH, &tty->flags); + clear_bit(TTY_DO_WRITE_WAKEUP, &tty->flags); put_pid(tty->session); put_pid(tty->pgrp); tty->session = NULL; tty->pgrp = NULL; tty->ctrl_status = 0; + set_bit(TTY_HUPPED, &tty->flags); spin_unlock_irqrestore(&tty->ctrl_lock, flags); /* Account for the p->signal references we killed */ @@ -660,10 +614,7 @@ static void do_tty_hangup(struct work_struct *work) * can't yet guarantee all that. */ set_bit(TTY_HUPPED, &tty->flags); - if (ld) { - tty_ldisc_enable(tty); - tty_ldisc_deref(ld); - } + tty_ldisc_enable(tty); unlock_kernel(); if (f) fput(f); @@ -2570,7 +2521,7 @@ long tty_ioctl(struct file *file, unsigned int cmd, unsigned long arg) case TIOCGSID: return tiocgsid(tty, real_tty, p); case TIOCGETD: - return put_user(tty->ldisc.ops->num, (int __user *)p); + return put_user(tty->ldisc->ops->num, (int __user *)p); case TIOCSETD: return tiocsetd(tty, p); /* @@ -2785,6 +2736,7 @@ void initialize_tty_struct(struct tty_struct *tty, tty->buf.head = tty->buf.tail = NULL; tty_buffer_init(tty); mutex_init(&tty->termios_mutex); + mutex_init(&tty->ldisc_mutex); init_waitqueue_head(&tty->write_wait); init_waitqueue_head(&tty->read_wait); INIT_WORK(&tty->hangup_work, do_tty_hangup); diff --git a/drivers/char/tty_ldisc.c b/drivers/char/tty_ldisc.c index e3c6416aa86..a58a19a6a5e 100644 --- a/drivers/char/tty_ldisc.c +++ b/drivers/char/tty_ldisc.c @@ -115,19 +115,22 @@ EXPORT_SYMBOL(tty_unregister_ldisc); /** * tty_ldisc_try_get - try and reference an ldisc * @disc: ldisc number - * @ld: tty ldisc structure to complete * * Attempt to open and lock a line discipline into place. Return - * the line discipline refcounted and assigned in ld. On an error - * report the error code back + * the line discipline refcounted or an error. */ -static int tty_ldisc_try_get(int disc, struct tty_ldisc *ld) +static struct tty_ldisc *tty_ldisc_try_get(int disc) { unsigned long flags; + struct tty_ldisc *ld; struct tty_ldisc_ops *ldops; int err = -EINVAL; + ld = kmalloc(sizeof(struct tty_ldisc), GFP_KERNEL); + if (ld == NULL) + return ERR_PTR(-ENOMEM); + spin_lock_irqsave(&tty_ldisc_lock, flags); ld->ops = NULL; ldops = tty_ldiscs[disc]; @@ -140,17 +143,19 @@ static int tty_ldisc_try_get(int disc, struct tty_ldisc *ld) /* lock it */ ldops->refcount++; ld->ops = ldops; + ld->refcount = 0; err = 0; } } spin_unlock_irqrestore(&tty_ldisc_lock, flags); - return err; + if (err) + return ERR_PTR(err); + return ld; } /** * tty_ldisc_get - take a reference to an ldisc * @disc: ldisc number - * @ld: tty line discipline structure to use * * Takes a reference to a line discipline. Deals with refcounts and * module locking counts. Returns NULL if the discipline is not available. @@ -161,44 +166,46 @@ static int tty_ldisc_try_get(int disc, struct tty_ldisc *ld) * takes tty_ldisc_lock to guard against ldisc races */ -static int tty_ldisc_get(int disc, struct tty_ldisc *ld) +static struct tty_ldisc *tty_ldisc_get(int disc) { - int err; + struct tty_ldisc *ld; if (disc < N_TTY || disc >= NR_LDISCS) - return -EINVAL; - err = tty_ldisc_try_get(disc, ld); - if (err < 0) { + return ERR_PTR(-EINVAL); + ld = tty_ldisc_try_get(disc); + if (IS_ERR(ld)) { request_module("tty-ldisc-%d", disc); - err = tty_ldisc_try_get(disc, ld); + ld = tty_ldisc_try_get(disc); } - return err; + return ld; } /** * tty_ldisc_put - drop ldisc reference - * @disc: ldisc number + * @ld: ldisc * * Drop a reference to a line discipline. Manage refcounts and - * module usage counts + * module usage counts. Free the ldisc once the recount hits zero. * * Locking: * takes tty_ldisc_lock to guard against ldisc races */ -static void tty_ldisc_put(struct tty_ldisc_ops *ld) +static void tty_ldisc_put(struct tty_ldisc *ld) { unsigned long flags; - int disc = ld->num; + int disc = ld->ops->num; + struct tty_ldisc_ops *ldo; BUG_ON(disc < N_TTY || disc >= NR_LDISCS); spin_lock_irqsave(&tty_ldisc_lock, flags); - ld = tty_ldiscs[disc]; - BUG_ON(ld->refcount == 0); - ld->refcount--; - module_put(ld->owner); + ldo = tty_ldiscs[disc]; + BUG_ON(ldo->refcount == 0); + ldo->refcount--; + module_put(ldo->owner); spin_unlock_irqrestore(&tty_ldisc_lock, flags); + kfree(ld); } static void * tty_ldiscs_seq_start(struct seq_file *m, loff_t *pos) @@ -219,12 +226,13 @@ static void tty_ldiscs_seq_stop(struct seq_file *m, void *v) static int tty_ldiscs_seq_show(struct seq_file *m, void *v) { int i = *(loff_t *)v; - struct tty_ldisc ld; + struct tty_ldisc *ld; - if (tty_ldisc_get(i, &ld) < 0) + ld = tty_ldisc_try_get(i); + if (IS_ERR(ld)) return 0; - seq_printf(m, "%-10s %2d\n", ld.ops->name ? ld.ops->name : "???", i); - tty_ldisc_put(ld.ops); + seq_printf(m, "%-10s %2d\n", ld->ops->name ? ld->ops->name : "???", i); + tty_ldisc_put(ld); return 0; } @@ -263,8 +271,7 @@ const struct file_operations tty_ldiscs_proc_fops = { static void tty_ldisc_assign(struct tty_struct *tty, struct tty_ldisc *ld) { - ld->refcount = 0; - tty->ldisc = *ld; + tty->ldisc = ld; } /** @@ -286,7 +293,7 @@ static int tty_ldisc_try(struct tty_struct *tty) int ret = 0; spin_lock_irqsave(&tty_ldisc_lock, flags); - ld = &tty->ldisc; + ld = tty->ldisc; if (test_bit(TTY_LDISC, &tty->flags)) { ld->refcount++; ret = 1; @@ -315,8 +322,8 @@ struct tty_ldisc *tty_ldisc_ref_wait(struct tty_struct *tty) { /* wait_event is a macro */ wait_event(tty_ldisc_wait, tty_ldisc_try(tty)); - WARN_ON(tty->ldisc.refcount == 0); - return &tty->ldisc; + WARN_ON(tty->ldisc->refcount == 0); + return tty->ldisc; } EXPORT_SYMBOL_GPL(tty_ldisc_ref_wait); @@ -335,7 +342,7 @@ EXPORT_SYMBOL_GPL(tty_ldisc_ref_wait); struct tty_ldisc *tty_ldisc_ref(struct tty_struct *tty) { if (tty_ldisc_try(tty)) - return &tty->ldisc; + return tty->ldisc; return NULL; } @@ -407,6 +414,39 @@ static void tty_set_termios_ldisc(struct tty_struct *tty, int num) mutex_unlock(&tty->termios_mutex); } +/** + * tty_ldisc_open - open a line discipline + * @tty: tty we are opening the ldisc on + * @ld: discipline to open + * + * A helper opening method. Also a convenient debugging and check + * point. + */ + +static int tty_ldisc_open(struct tty_struct *tty, struct tty_ldisc *ld) +{ + WARN_ON(test_and_set_bit(TTY_LDISC_OPEN, &tty->flags)); + if (ld->ops->open) + return ld->ops->open(tty); + return 0; +} + +/** + * tty_ldisc_close - close a line discipline + * @tty: tty we are opening the ldisc on + * @ld: discipline to close + * + * A helper close method. Also a convenient debugging and check + * point. + */ + +static void tty_ldisc_close(struct tty_struct *tty, struct tty_ldisc *ld) +{ + WARN_ON(!test_bit(TTY_LDISC_OPEN, &tty->flags)); + clear_bit(TTY_LDISC_OPEN, &tty->flags); + if (ld->ops->close) + ld->ops->close(tty); +} /** * tty_ldisc_restore - helper for tty ldisc change @@ -420,31 +460,32 @@ static void tty_set_termios_ldisc(struct tty_struct *tty, int num) static void tty_ldisc_restore(struct tty_struct *tty, struct tty_ldisc *old) { char buf[64]; - struct tty_ldisc new_ldisc; + struct tty_ldisc *new_ldisc; + int r; /* There is an outstanding reference here so this is safe */ - tty_ldisc_get(old->ops->num, old); + old = tty_ldisc_get(old->ops->num); + WARN_ON(IS_ERR(old)); tty_ldisc_assign(tty, old); tty_set_termios_ldisc(tty, old->ops->num); - if (old->ops->open && (old->ops->open(tty) < 0)) { - tty_ldisc_put(old->ops); + if (tty_ldisc_open(tty, old) < 0) { + tty_ldisc_put(old); /* This driver is always present */ - if (tty_ldisc_get(N_TTY, &new_ldisc) < 0) + new_ldisc =tty_ldisc_get(N_TTY); + if (IS_ERR(new_ldisc)) panic("n_tty: get"); - tty_ldisc_assign(tty, &new_ldisc); + tty_ldisc_assign(tty, new_ldisc); tty_set_termios_ldisc(tty, N_TTY); - if (new_ldisc.ops->open) { - int r = new_ldisc.ops->open(tty); - if (r < 0) - panic("Couldn't open N_TTY ldisc for " - "%s --- error %d.", - tty_name(tty, buf), r); - } + r = tty_ldisc_open(tty, new_ldisc); + if (r < 0) + panic("Couldn't open N_TTY ldisc for " + "%s --- error %d.", + tty_name(tty, buf), r); } } /** - * tty_ldisc_halt - shutdown the line discipline + * tty_ldisc_halt - shut down the line discipline * @tty: tty device * * Shut down the line discipline and work queue for this tty device. @@ -456,14 +497,10 @@ static void tty_ldisc_restore(struct tty_struct *tty, struct tty_ldisc *old) * tty_ldisc_wait_idle. */ -static void tty_ldisc_halt(struct tty_struct *tty) +static int tty_ldisc_halt(struct tty_struct *tty) { clear_bit(TTY_LDISC, &tty->flags); - cancel_delayed_work(&tty->buf.work); - /* - * Wait for ->hangup_work and ->buf.work handlers to terminate - */ - flush_scheduled_work(); + return cancel_delayed_work(&tty->buf.work); } /** @@ -473,18 +510,22 @@ static void tty_ldisc_halt(struct tty_struct *tty) * Wait for the line discipline to become idle. The discipline must * have been halted for this to guarantee it remains idle. * + * tty_ldisc_lock protects the ref counts currently. */ -static void tty_ldisc_wait_idle(struct tty_struct *tty) +static int tty_ldisc_wait_idle(struct tty_struct *tty) { unsigned long flags; spin_lock_irqsave(&tty_ldisc_lock, flags); - while (tty->ldisc.refcount) { + while (tty->ldisc->refcount) { spin_unlock_irqrestore(&tty_ldisc_lock, flags); - wait_event(tty_ldisc_wait, tty->ldisc.refcount == 0); + if (wait_event_timeout(tty_ldisc_wait, + tty->ldisc->refcount == 0, 5 * HZ) == 0) + return -EBUSY; spin_lock_irqsave(&tty_ldisc_lock, flags); } spin_unlock_irqrestore(&tty_ldisc_lock, flags); + return 0; } /** @@ -493,38 +534,63 @@ static void tty_ldisc_wait_idle(struct tty_struct *tty) * @ldisc: the line discipline * * Set the discipline of a tty line. Must be called from a process - * context. + * context. The ldisc change logic has to protect itself against any + * overlapping ldisc change (including on the other end of pty pairs), + * the close of one side of a tty/pty pair, and eventually hangup. * - * Locking: takes tty_ldisc_lock. - * called functions take termios_mutex + * Locking: takes tty_ldisc_lock, termios_mutex */ int tty_set_ldisc(struct tty_struct *tty, int ldisc) { int retval; - struct tty_ldisc o_ldisc, new_ldisc; - int work; - unsigned long flags; + struct tty_ldisc *o_ldisc, *new_ldisc; + int work, o_work = 0; struct tty_struct *o_tty; -restart: - /* This is a bit ugly for now but means we can break the 'ldisc - is part of the tty struct' assumption later */ - retval = tty_ldisc_get(ldisc, &new_ldisc); - if (retval) - return retval; + new_ldisc = tty_ldisc_get(ldisc); + if (IS_ERR(new_ldisc)) + return PTR_ERR(new_ldisc); /* - * Problem: What do we do if this blocks ? + * We need to look at the tty locking here for pty/tty pairs + * when both sides try to change in parallel. */ - tty_wait_until_sent(tty, 0); + o_tty = tty->link; /* o_tty is the pty side or NULL */ + - if (tty->ldisc.ops->num == ldisc) { - tty_ldisc_put(new_ldisc.ops); + /* + * Check the no-op case + */ + + if (tty->ldisc->ops->num == ldisc) { + tty_ldisc_put(new_ldisc); return 0; } + /* + * Problem: What do we do if this blocks ? + * We could deadlock here + */ + + tty_wait_until_sent(tty, 0); + + mutex_lock(&tty->ldisc_mutex); + + /* + * We could be midstream of another ldisc change which has + * dropped the lock during processing. If so we need to wait. + */ + + while (test_bit(TTY_LDISC_CHANGING, &tty->flags)) { + mutex_unlock(&tty->ldisc_mutex); + wait_event(tty_ldisc_wait, + test_bit(TTY_LDISC_CHANGING, &tty->flags) == 0); + mutex_lock(&tty->ldisc_mutex); + } + set_bit(TTY_LDISC_CHANGING, &tty->flags); + /* * No more input please, we are switching. The new ldisc * will update this value in the ldisc open function @@ -533,8 +599,6 @@ restart: tty->receive_room = 0; o_ldisc = tty->ldisc; - o_tty = tty->link; - /* * Make sure we don't change while someone holds a * reference to the line discipline. The TTY_LDISC bit @@ -545,108 +609,181 @@ restart: * with a userspace app continually trying to use the tty in * parallel to the change and re-referencing the tty. */ - clear_bit(TTY_LDISC, &tty->flags); - if (o_tty) - clear_bit(TTY_LDISC, &o_tty->flags); - spin_lock_irqsave(&tty_ldisc_lock, flags); - if (tty->ldisc.refcount || (o_tty && o_tty->ldisc.refcount)) { - if (tty->ldisc.refcount) { - /* Free the new ldisc we grabbed. Must drop the lock - first. */ - spin_unlock_irqrestore(&tty_ldisc_lock, flags); - tty_ldisc_put(o_ldisc.ops); - /* - * There are several reasons we may be busy, including - * random momentary I/O traffic. We must therefore - * retry. We could distinguish between blocking ops - * and retries if we made tty_ldisc_wait() smarter. - * That is up for discussion. - */ - if (wait_event_interruptible(tty_ldisc_wait, tty->ldisc.refcount == 0) < 0) - return -ERESTARTSYS; - goto restart; - } - if (o_tty && o_tty->ldisc.refcount) { - spin_unlock_irqrestore(&tty_ldisc_lock, flags); - tty_ldisc_put(o_tty->ldisc.ops); - if (wait_event_interruptible(tty_ldisc_wait, o_tty->ldisc.refcount == 0) < 0) - return -ERESTARTSYS; - goto restart; - } - } - /* - * If the TTY_LDISC bit is set, then we are racing against - * another ldisc change - */ - if (test_bit(TTY_LDISC_CHANGING, &tty->flags)) { - struct tty_ldisc *ld; - spin_unlock_irqrestore(&tty_ldisc_lock, flags); - tty_ldisc_put(new_ldisc.ops); - ld = tty_ldisc_ref_wait(tty); - tty_ldisc_deref(ld); - goto restart; - } - /* - * This flag is used to avoid two parallel ldisc changes. Once - * open and close are fine grained locked this may work better - * as a mutex shared with the open/close/hup paths - */ - set_bit(TTY_LDISC_CHANGING, &tty->flags); + work = tty_ldisc_halt(tty); if (o_tty) - set_bit(TTY_LDISC_CHANGING, &o_tty->flags); - spin_unlock_irqrestore(&tty_ldisc_lock, flags); - - /* - * From this point on we know nobody has an ldisc - * usage reference, nor can they obtain one until - * we say so later on. - */ + o_work = tty_ldisc_halt(o_tty); - work = cancel_delayed_work(&tty->buf.work); /* - * Wait for ->hangup_work and ->buf.work handlers to terminate - * MUST NOT hold locks here. + * Wait for ->hangup_work and ->buf.work handlers to terminate. + * We must drop the mutex here in case a hangup is also in process. */ + + mutex_unlock(&tty->ldisc_mutex); + flush_scheduled_work(); + + /* Let any existing reference holders finish */ + retval = tty_ldisc_wait_idle(tty); + if (retval < 0) { + clear_bit(TTY_LDISC_CHANGING, &tty->flags); + tty_ldisc_put(new_ldisc); + return retval; + } + + mutex_lock(&tty->ldisc_mutex); + if (test_bit(TTY_HUPPED, &tty->flags)) { + /* We were raced by the hangup method. It will have stomped + the ldisc data and closed the ldisc down */ + clear_bit(TTY_LDISC_CHANGING, &tty->flags); + mutex_unlock(&tty->ldisc_mutex); + tty_ldisc_put(new_ldisc); + return -EIO; + } + /* Shutdown the current discipline. */ - if (o_ldisc.ops->close) - (o_ldisc.ops->close)(tty); + tty_ldisc_close(tty, o_ldisc); /* Now set up the new line discipline. */ - tty_ldisc_assign(tty, &new_ldisc); + tty_ldisc_assign(tty, new_ldisc); tty_set_termios_ldisc(tty, ldisc); - if (new_ldisc.ops->open) - retval = (new_ldisc.ops->open)(tty); + + retval = tty_ldisc_open(tty, new_ldisc); if (retval < 0) { - tty_ldisc_put(new_ldisc.ops); - tty_ldisc_restore(tty, &o_ldisc); + /* Back to the old one or N_TTY if we can't */ + tty_ldisc_put(new_ldisc); + tty_ldisc_restore(tty, o_ldisc); } + /* At this point we hold a reference to the new ldisc and a a reference to the old ldisc. If we ended up flipping back to the existing ldisc we have two references to it */ - if (tty->ldisc.ops->num != o_ldisc.ops->num && tty->ops->set_ldisc) + if (tty->ldisc->ops->num != o_ldisc->ops->num && tty->ops->set_ldisc) tty->ops->set_ldisc(tty); - tty_ldisc_put(o_ldisc.ops); + tty_ldisc_put(o_ldisc); /* - * Allow ldisc referencing to occur as soon as the driver - * ldisc callback completes. + * Allow ldisc referencing to occur again */ tty_ldisc_enable(tty); if (o_tty) tty_ldisc_enable(o_tty); - /* Restart it in case no characters kick it off. Safe if + /* Restart the work queue in case no characters kick it off. Safe if already running */ if (work) schedule_delayed_work(&tty->buf.work, 1); + if (o_work) + schedule_delayed_work(&o_tty->buf.work, 1); + mutex_unlock(&tty->ldisc_mutex); return retval; } +/** + * tty_reset_termios - reset terminal state + * @tty: tty to reset + * + * Restore a terminal to the driver default state. + */ + +static void tty_reset_termios(struct tty_struct *tty) +{ + mutex_lock(&tty->termios_mutex); + *tty->termios = tty->driver->init_termios; + tty->termios->c_ispeed = tty_termios_input_baud_rate(tty->termios); + tty->termios->c_ospeed = tty_termios_baud_rate(tty->termios); + mutex_unlock(&tty->termios_mutex); +} + + +/** + * tty_ldisc_reinit - reinitialise the tty ldisc + * @tty: tty to reinit + * + * Switch the tty back to N_TTY line discipline and leave the + * ldisc state closed + */ + +static void tty_ldisc_reinit(struct tty_struct *tty) +{ + struct tty_ldisc *ld; + + tty_ldisc_close(tty, tty->ldisc); + tty_ldisc_put(tty->ldisc); + tty->ldisc = NULL; + /* + * Switch the line discipline back + */ + ld = tty_ldisc_get(N_TTY); + BUG_ON(IS_ERR(ld)); + tty_ldisc_assign(tty, ld); + tty_set_termios_ldisc(tty, N_TTY); +} + +/** + * tty_ldisc_hangup - hangup ldisc reset + * @tty: tty being hung up + * + * Some tty devices reset their termios when they receive a hangup + * event. In that situation we must also switch back to N_TTY properly + * before we reset the termios data. + * + * Locking: We can take the ldisc mutex as the rest of the code is + * careful to allow for this. + * + * In the pty pair case this occurs in the close() path of the + * tty itself so we must be careful about locking rules. + */ + +void tty_ldisc_hangup(struct tty_struct *tty) +{ + struct tty_ldisc *ld; + + /* + * FIXME! What are the locking issues here? This may me overdoing + * things... This question is especially important now that we've + * removed the irqlock. + */ + ld = tty_ldisc_ref(tty); + if (ld != NULL) { + /* We may have no line discipline at this point */ + if (ld->ops->flush_buffer) + ld->ops->flush_buffer(tty); + tty_driver_flush_buffer(tty); + if ((test_bit(TTY_DO_WRITE_WAKEUP, &tty->flags)) && + ld->ops->write_wakeup) + ld->ops->write_wakeup(tty); + if (ld->ops->hangup) + ld->ops->hangup(tty); + tty_ldisc_deref(ld); + } + /* + * FIXME: Once we trust the LDISC code better we can wait here for + * ldisc completion and fix the driver call race + */ + wake_up_interruptible_poll(&tty->write_wait, POLLOUT); + wake_up_interruptible_poll(&tty->read_wait, POLLIN); + /* + * Shutdown the current line discipline, and reset it to + * N_TTY. + */ + if (tty->driver->flags & TTY_DRIVER_RESET_TERMIOS) { + /* Avoid racing set_ldisc */ + mutex_lock(&tty->ldisc_mutex); + /* Switch back to N_TTY */ + tty_ldisc_reinit(tty); + /* At this point we have a closed ldisc and we want to + reopen it. We could defer this to the next open but + it means auditing a lot of other paths so this is a FIXME */ + WARN_ON(tty_ldisc_open(tty, tty->ldisc)); + tty_ldisc_enable(tty); + mutex_unlock(&tty->ldisc_mutex); + tty_reset_termios(tty); + } +} /** * tty_ldisc_setup - open line discipline @@ -654,24 +791,23 @@ restart: * @o_tty: pair tty for pty/tty pairs * * Called during the initial open of a tty/pty pair in order to set up the - * line discplines and bind them to the tty. + * line disciplines and bind them to the tty. This has no locking issues + * as the device isn't yet active. */ int tty_ldisc_setup(struct tty_struct *tty, struct tty_struct *o_tty) { - struct tty_ldisc *ld = &tty->ldisc; + struct tty_ldisc *ld = tty->ldisc; int retval; - if (ld->ops->open) { - retval = (ld->ops->open)(tty); - if (retval) - return retval; - } - if (o_tty && o_tty->ldisc.ops->open) { - retval = (o_tty->ldisc.ops->open)(o_tty); + retval = tty_ldisc_open(tty, ld); + if (retval) + return retval; + + if (o_tty) { + retval = tty_ldisc_open(o_tty, o_tty->ldisc); if (retval) { - if (ld->ops->close) - (ld->ops->close)(tty); + tty_ldisc_close(tty, ld); return retval; } tty_ldisc_enable(o_tty); @@ -679,34 +815,18 @@ int tty_ldisc_setup(struct tty_struct *tty, struct tty_struct *o_tty) tty_ldisc_enable(tty); return 0; } - -static void tty_ldisc_reinit(struct tty_struct *tty) -{ - struct tty_ldisc ld; - - if (tty->ldisc.ops->close) - (tty->ldisc.ops->close)(tty); - tty_ldisc_put(tty->ldisc.ops); - /* - * Switch the line discipline back - */ - WARN_ON(tty_ldisc_get(N_TTY, &ld)); - tty_ldisc_assign(tty, &ld); - tty_set_termios_ldisc(tty, N_TTY); -} - /** * tty_ldisc_release - release line discipline * @tty: tty being shut down * @o_tty: pair tty for pty/tty pairs * * Called during the final close of a tty/pty pair in order to shut down the - * line discpline layer. + * line discpline layer. On exit the ldisc assigned is N_TTY and the + * ldisc has not been opened. */ void tty_ldisc_release(struct tty_struct *tty, struct tty_struct *o_tty) { - /* * Prevent flush_to_ldisc() from rescheduling the work for later. Then * kill any delayed work. As this is the final close it does not @@ -714,6 +834,7 @@ void tty_ldisc_release(struct tty_struct *tty, struct tty_struct *o_tty) */ tty_ldisc_halt(tty); + flush_scheduled_work(); /* * Wait for any short term users (we know they are just driver @@ -730,11 +851,9 @@ void tty_ldisc_release(struct tty_struct *tty, struct tty_struct *o_tty) */ tty_ldisc_reinit(tty); - if (o_tty) { - /* FIXME: could o_tty be in setldisc here ? */ - clear_bit(TTY_LDISC, &o_tty->flags); - tty_ldisc_reinit(o_tty); - } + /* This will need doing differently if we need to lock */ + if (o_tty) + tty_ldisc_release(o_tty, NULL); } /** @@ -747,10 +866,10 @@ void tty_ldisc_release(struct tty_struct *tty, struct tty_struct *o_tty) void tty_ldisc_init(struct tty_struct *tty) { - struct tty_ldisc ld; - if (tty_ldisc_get(N_TTY, &ld) < 0) + struct tty_ldisc *ld = tty_ldisc_get(N_TTY); + if (IS_ERR(ld)) panic("n_tty: init_tty"); - tty_ldisc_assign(tty, &ld); + tty_ldisc_assign(tty, ld); } void tty_ldisc_begin(void) -- cgit v1.2.3 From f2c4c65c8350d885d74dcdea7061dcecc1223c36 Mon Sep 17 00:00:00 2001 From: Alan Cox Date: Thu, 11 Jun 2009 12:50:58 +0100 Subject: tty: Move ldisc_flush We have a tty_ldisc file now so put tty_ldisc_flush in the right place Signed-off-by: Alan Cox Signed-off-by: Linus Torvalds --- drivers/char/tty_io.c | 21 --------------------- drivers/char/tty_ldisc.c | 21 +++++++++++++++++++++ 2 files changed, 21 insertions(+), 21 deletions(-) (limited to 'drivers/char') diff --git a/drivers/char/tty_io.c b/drivers/char/tty_io.c index 2f44b0b241c..939e198d767 100644 --- a/drivers/char/tty_io.c +++ b/drivers/char/tty_io.c @@ -470,27 +470,6 @@ void tty_wakeup(struct tty_struct *tty) EXPORT_SYMBOL_GPL(tty_wakeup); -/** - * tty_ldisc_flush - flush line discipline queue - * @tty: tty - * - * Flush the line discipline queue (if any) for this tty. If there - * is no line discipline active this is a no-op. - */ - -void tty_ldisc_flush(struct tty_struct *tty) -{ - struct tty_ldisc *ld = tty_ldisc_ref(tty); - if (ld) { - if (ld->ops->flush_buffer) - ld->ops->flush_buffer(tty); - tty_ldisc_deref(ld); - } - tty_buffer_flush(tty); -} - -EXPORT_SYMBOL_GPL(tty_ldisc_flush); - /** * do_tty_hangup - actual handler for hangup events * @work: tty device diff --git a/drivers/char/tty_ldisc.c b/drivers/char/tty_ldisc.c index a58a19a6a5e..01c70c01f64 100644 --- a/drivers/char/tty_ldisc.c +++ b/drivers/char/tty_ldisc.c @@ -395,6 +395,27 @@ void tty_ldisc_enable(struct tty_struct *tty) wake_up(&tty_ldisc_wait); } +/** + * tty_ldisc_flush - flush line discipline queue + * @tty: tty + * + * Flush the line discipline queue (if any) for this tty. If there + * is no line discipline active this is a no-op. + */ + +void tty_ldisc_flush(struct tty_struct *tty) +{ + struct tty_ldisc *ld = tty_ldisc_ref(tty); + if (ld) { + if (ld->ops->flush_buffer) + ld->ops->flush_buffer(tty); + tty_ldisc_deref(ld); + } + tty_buffer_flush(tty); +} + +EXPORT_SYMBOL_GPL(tty_ldisc_flush); + /** * tty_set_termios_ldisc - set ldisc field * @tty: tty structure -- cgit v1.2.3 From 852e99d22f2231d232c45216b027565e3bae7add Mon Sep 17 00:00:00 2001 From: Alan Cox Date: Thu, 11 Jun 2009 12:51:41 +0100 Subject: tty: bring ldisc into CodingStyle Signed-off-by: Alan Cox Signed-off-by: Linus Torvalds --- drivers/char/tty_ldisc.c | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) (limited to 'drivers/char') diff --git a/drivers/char/tty_ldisc.c b/drivers/char/tty_ldisc.c index 01c70c01f64..39c8f86dedd 100644 --- a/drivers/char/tty_ldisc.c +++ b/drivers/char/tty_ldisc.c @@ -126,7 +126,7 @@ static struct tty_ldisc *tty_ldisc_try_get(int disc) struct tty_ldisc *ld; struct tty_ldisc_ops *ldops; int err = -EINVAL; - + ld = kmalloc(sizeof(struct tty_ldisc), GFP_KERNEL); if (ld == NULL) return ERR_PTR(-ENOMEM); @@ -208,12 +208,12 @@ static void tty_ldisc_put(struct tty_ldisc *ld) kfree(ld); } -static void * tty_ldiscs_seq_start(struct seq_file *m, loff_t *pos) +static void *tty_ldiscs_seq_start(struct seq_file *m, loff_t *pos) { return (*pos < NR_LDISCS) ? pos : NULL; } -static void * tty_ldiscs_seq_next(struct seq_file *m, void *v, loff_t *pos) +static void *tty_ldiscs_seq_next(struct seq_file *m, void *v, loff_t *pos) { (*pos)++; return (*pos < NR_LDISCS) ? pos : NULL; @@ -227,7 +227,7 @@ static int tty_ldiscs_seq_show(struct seq_file *m, void *v) { int i = *(loff_t *)v; struct tty_ldisc *ld; - + ld = tty_ldisc_try_get(i); if (IS_ERR(ld)) return 0; @@ -325,7 +325,6 @@ struct tty_ldisc *tty_ldisc_ref_wait(struct tty_struct *tty) WARN_ON(tty->ldisc->refcount == 0); return tty->ldisc; } - EXPORT_SYMBOL_GPL(tty_ldisc_ref_wait); /** @@ -345,7 +344,6 @@ struct tty_ldisc *tty_ldisc_ref(struct tty_struct *tty) return tty->ldisc; return NULL; } - EXPORT_SYMBOL_GPL(tty_ldisc_ref); /** @@ -373,7 +371,6 @@ void tty_ldisc_deref(struct tty_ldisc *ld) wake_up(&tty_ldisc_wait); spin_unlock_irqrestore(&tty_ldisc_lock, flags); } - EXPORT_SYMBOL_GPL(tty_ldisc_deref); /** @@ -413,7 +410,6 @@ void tty_ldisc_flush(struct tty_struct *tty) } tty_buffer_flush(tty); } - EXPORT_SYMBOL_GPL(tty_ldisc_flush); /** @@ -492,7 +488,7 @@ static void tty_ldisc_restore(struct tty_struct *tty, struct tty_ldisc *old) if (tty_ldisc_open(tty, old) < 0) { tty_ldisc_put(old); /* This driver is always present */ - new_ldisc =tty_ldisc_get(N_TTY); + new_ldisc = tty_ldisc_get(N_TTY); if (IS_ERR(new_ldisc)) panic("n_tty: get"); tty_ldisc_assign(tty, new_ldisc); @@ -514,7 +510,7 @@ static void tty_ldisc_restore(struct tty_struct *tty, struct tty_ldisc *old) * be obtained while the delayed work queue halt ensures that no more * data is fed to the ldisc. * - * In order to wait for any existing references to complete see + * In order to wait for any existing references to complete see * tty_ldisc_wait_idle. */ @@ -611,7 +607,7 @@ int tty_set_ldisc(struct tty_struct *tty, int ldisc) mutex_lock(&tty->ldisc_mutex); } set_bit(TTY_LDISC_CHANGING, &tty->flags); - + /* * No more input please, we are switching. The new ldisc * will update this value in the ldisc open function @@ -841,8 +837,8 @@ int tty_ldisc_setup(struct tty_struct *tty, struct tty_struct *o_tty) * @tty: tty being shut down * @o_tty: pair tty for pty/tty pairs * - * Called during the final close of a tty/pty pair in order to shut down the - * line discpline layer. On exit the ldisc assigned is N_TTY and the + * Called during the final close of a tty/pty pair in order to shut down + * the line discpline layer. On exit the ldisc assigned is N_TTY and the * ldisc has not been opened. */ -- cgit v1.2.3 From c481c707fe4b07783d9a2499a9bbbb94497e9b18 Mon Sep 17 00:00:00 2001 From: Alan Cox Date: Thu, 11 Jun 2009 13:04:27 +0100 Subject: tty: remove buffer special casing Long long ago a 4K kmalloc allocated two pages so the tty layer used the page allocator, except on some machines where the page size was huge. This was removed from the core tty layer with the tty buffer re-implementation but not from tty_audit or the n_tty ldisc. Signed-off-by: Alan Cox Signed-off-by: Linus Torvalds --- drivers/char/n_tty.c | 11 ++--------- drivers/char/tty_audit.c | 10 ++-------- 2 files changed, 4 insertions(+), 17 deletions(-) (limited to 'drivers/char') diff --git a/drivers/char/n_tty.c b/drivers/char/n_tty.c index f6f0e4ec2b5..b4b12b4f0ac 100644 --- a/drivers/char/n_tty.c +++ b/drivers/char/n_tty.c @@ -76,19 +76,12 @@ static inline unsigned char *alloc_buf(void) { gfp_t prio = in_interrupt() ? GFP_ATOMIC : GFP_KERNEL; - - if (PAGE_SIZE != N_TTY_BUF_SIZE) - return kmalloc(N_TTY_BUF_SIZE, prio); - else - return (unsigned char *)__get_free_page(prio); + return kmalloc(N_TTY_BUF_SIZE, prio); } static inline void free_buf(unsigned char *buf) { - if (PAGE_SIZE != N_TTY_BUF_SIZE) - kfree(buf); - else - free_page((unsigned long) buf); + kfree(buf); } static inline int tty_put_user(struct tty_struct *tty, unsigned char x, diff --git a/drivers/char/tty_audit.c b/drivers/char/tty_audit.c index 55ba6f14288..ac16fbec72d 100644 --- a/drivers/char/tty_audit.c +++ b/drivers/char/tty_audit.c @@ -29,10 +29,7 @@ static struct tty_audit_buf *tty_audit_buf_alloc(int major, int minor, buf = kmalloc(sizeof(*buf), GFP_KERNEL); if (!buf) goto err; - if (PAGE_SIZE != N_TTY_BUF_SIZE) - buf->data = kmalloc(N_TTY_BUF_SIZE, GFP_KERNEL); - else - buf->data = (unsigned char *)__get_free_page(GFP_KERNEL); + buf->data = kmalloc(N_TTY_BUF_SIZE, GFP_KERNEL); if (!buf->data) goto err_buf; atomic_set(&buf->count, 1); @@ -52,10 +49,7 @@ err: static void tty_audit_buf_free(struct tty_audit_buf *buf) { WARN_ON(buf->valid != 0); - if (PAGE_SIZE != N_TTY_BUF_SIZE) - kfree(buf->data); - else - free_page((unsigned long)buf->data); + kfree(buf->data); kfree(buf); } -- cgit v1.2.3 From 0b4068a1287b02018d1b3159e7be6f27f3e3e68c Mon Sep 17 00:00:00 2001 From: Alan Cox Date: Thu, 11 Jun 2009 13:05:49 +0100 Subject: tty: simplify buffer allocator cleanups Having cleaned up the allocators we might as well remove the inline helpers for some of it Signed-off-by: Alan Cox --- drivers/char/n_tty.c | 22 +++++----------------- 1 file changed, 5 insertions(+), 17 deletions(-) (limited to 'drivers/char') diff --git a/drivers/char/n_tty.c b/drivers/char/n_tty.c index b4b12b4f0ac..94a5d5020ab 100644 --- a/drivers/char/n_tty.c +++ b/drivers/char/n_tty.c @@ -73,17 +73,6 @@ #define ECHO_OP_SET_CANON_COL 0x81 #define ECHO_OP_ERASE_TAB 0x82 -static inline unsigned char *alloc_buf(void) -{ - gfp_t prio = in_interrupt() ? GFP_ATOMIC : GFP_KERNEL; - return kmalloc(N_TTY_BUF_SIZE, prio); -} - -static inline void free_buf(unsigned char *buf) -{ - kfree(buf); -} - static inline int tty_put_user(struct tty_struct *tty, unsigned char x, unsigned char __user *ptr) { @@ -1551,11 +1540,11 @@ static void n_tty_close(struct tty_struct *tty) { n_tty_flush_buffer(tty); if (tty->read_buf) { - free_buf(tty->read_buf); + kfree(tty->read_buf); tty->read_buf = NULL; } if (tty->echo_buf) { - free_buf(tty->echo_buf); + kfree(tty->echo_buf); tty->echo_buf = NULL; } } @@ -1577,17 +1566,16 @@ static int n_tty_open(struct tty_struct *tty) /* These are ugly. Currently a malloc failure here can panic */ if (!tty->read_buf) { - tty->read_buf = alloc_buf(); + tty->read_buf = kzalloc(N_TTY_BUF_SIZE, GFP_KERNEL); if (!tty->read_buf) return -ENOMEM; } if (!tty->echo_buf) { - tty->echo_buf = alloc_buf(); + tty->echo_buf = kzalloc(N_TTY_BUF_SIZE, GFP_KERNEL); + if (!tty->echo_buf) return -ENOMEM; } - memset(tty->read_buf, 0, N_TTY_BUF_SIZE); - memset(tty->echo_buf, 0, N_TTY_BUF_SIZE); reset_buffer_flags(tty); tty->column = 0; n_tty_set_termios(tty, NULL); -- cgit v1.2.3 From 0b91421857414f525690ee452c0b0acb6ab1dba3 Mon Sep 17 00:00:00 2001 From: Mike Frysinger Date: Thu, 11 Jun 2009 14:01:45 +0100 Subject: tty: bfin_jtag_comm: emulate a TTY over the Blackfin EMUDAT/JTAG interface The Blackfin JTAG interface has a 4 byte generic data field (EMUDAT). With a little creative thinking, we can turn this into a TTY device. Signed-off-by: Mike Frysinger Signed-off-by: Bryan Wu Signed-off-by: Alan Cox Signed-off-by: Linus Torvalds --- drivers/char/Kconfig | 13 ++ drivers/char/Makefile | 1 + drivers/char/bfin_jtag_comm.c | 365 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 379 insertions(+) create mode 100644 drivers/char/bfin_jtag_comm.c (limited to 'drivers/char') diff --git a/drivers/char/Kconfig b/drivers/char/Kconfig index 735bbe2be51..02ecfd5fa61 100644 --- a/drivers/char/Kconfig +++ b/drivers/char/Kconfig @@ -97,6 +97,19 @@ config DEVKMEM kind of kernel debugging operations. When in doubt, say "N". +config BFIN_JTAG_COMM + tristate "Blackfin JTAG Communication" + depends on BLACKFIN + help + Add support for emulating a TTY device over the Blackfin JTAG. + + To compile this driver as a module, choose M here: the + module will be called bfin_jtag_comm. + +config BFIN_JTAG_COMM_CONSOLE + bool "Console on Blackfin JTAG" + depends on BFIN_JTAG_COMM=y + config SERIAL_NONSTANDARD bool "Non-standard serial port support" depends on HAS_IOMEM diff --git a/drivers/char/Makefile b/drivers/char/Makefile index 9caf5b5ad1c..189efcff08c 100644 --- a/drivers/char/Makefile +++ b/drivers/char/Makefile @@ -13,6 +13,7 @@ obj-$(CONFIG_LEGACY_PTYS) += pty.o obj-$(CONFIG_UNIX98_PTYS) += pty.o obj-y += misc.o obj-$(CONFIG_VT) += vt_ioctl.o vc_screen.o selection.o keyboard.o +obj-$(CONFIG_BFIN_JTAG_COMM) += bfin_jtag_comm.o obj-$(CONFIG_CONSOLE_TRANSLATIONS) += consolemap.o consolemap_deftbl.o obj-$(CONFIG_HW_CONSOLE) += vt.o defkeymap.o obj-$(CONFIG_AUDIT) += tty_audit.o diff --git a/drivers/char/bfin_jtag_comm.c b/drivers/char/bfin_jtag_comm.c new file mode 100644 index 00000000000..44c113d5604 --- /dev/null +++ b/drivers/char/bfin_jtag_comm.c @@ -0,0 +1,365 @@ +/* + * TTY over Blackfin JTAG Communication + * + * Copyright 2008-2009 Analog Devices Inc. + * + * Enter bugs at http://blackfin.uclinux.org/ + * + * Licensed under the GPL-2 or later. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* See the Debug/Emulation chapter in the HRM */ +#define EMUDOF 0x00000001 /* EMUDAT_OUT full & valid */ +#define EMUDIF 0x00000002 /* EMUDAT_IN full & valid */ +#define EMUDOOVF 0x00000004 /* EMUDAT_OUT overflow */ +#define EMUDIOVF 0x00000008 /* EMUDAT_IN overflow */ + +#define DRV_NAME "bfin-jtag-comm" +#define DEV_NAME "ttyBFJC" + +#define pr_init(fmt, args...) ({ static const __initdata char __fmt[] = fmt; printk(__fmt, ## args); }) +#define debug(fmt, args...) pr_debug(DRV_NAME ": " fmt, ## args) + +static inline uint32_t bfin_write_emudat(uint32_t emudat) +{ + __asm__ __volatile__("emudat = %0;" : : "d"(emudat)); + return emudat; +} + +static inline uint32_t bfin_read_emudat(void) +{ + uint32_t emudat; + __asm__ __volatile__("%0 = emudat;" : "=d"(emudat)); + return emudat; +} + +static inline uint32_t bfin_write_emudat_chars(char a, char b, char c, char d) +{ + return bfin_write_emudat((a << 0) | (b << 8) | (c << 16) | (d << 24)); +} + +#define CIRC_SIZE 2048 /* see comment in tty_io.c:do_tty_write() */ +#define CIRC_MASK (CIRC_SIZE - 1) +#define circ_empty(circ) ((circ)->head == (circ)->tail) +#define circ_free(circ) CIRC_SPACE((circ)->head, (circ)->tail, CIRC_SIZE) +#define circ_cnt(circ) CIRC_CNT((circ)->head, (circ)->tail, CIRC_SIZE) +#define circ_byte(circ, idx) ((circ)->buf[(idx) & CIRC_MASK]) + +static struct tty_driver *bfin_jc_driver; +static struct task_struct *bfin_jc_kthread; +static struct tty_struct * volatile bfin_jc_tty; +static unsigned long bfin_jc_count; +static DEFINE_MUTEX(bfin_jc_tty_mutex); +static volatile struct circ_buf bfin_jc_write_buf; + +static int +bfin_jc_emudat_manager(void *arg) +{ + uint32_t inbound_len = 0, outbound_len = 0; + + while (!kthread_should_stop()) { + /* no one left to give data to, so sleep */ + if (bfin_jc_tty == NULL && circ_empty(&bfin_jc_write_buf)) { + debug("waiting for readers\n"); + __set_current_state(TASK_UNINTERRUPTIBLE); + schedule(); + __set_current_state(TASK_RUNNING); + } + + /* no data available, so just chill */ + if (!(bfin_read_DBGSTAT() & EMUDIF) && circ_empty(&bfin_jc_write_buf)) { + debug("waiting for data (in_len = %i) (circ: %i %i)\n", + inbound_len, bfin_jc_write_buf.tail, bfin_jc_write_buf.head); + if (inbound_len) + schedule(); + else + schedule_timeout_interruptible(HZ); + continue; + } + + /* if incoming data is ready, eat it */ + if (bfin_read_DBGSTAT() & EMUDIF) { + struct tty_struct *tty; + mutex_lock(&bfin_jc_tty_mutex); + tty = (struct tty_struct *)bfin_jc_tty; + if (tty != NULL) { + uint32_t emudat = bfin_read_emudat(); + if (inbound_len == 0) { + debug("incoming length: 0x%08x\n", emudat); + inbound_len = emudat; + } else { + size_t num_chars = (4 <= inbound_len ? 4 : inbound_len); + debug(" incoming data: 0x%08x (pushing %zu)\n", emudat, num_chars); + inbound_len -= num_chars; + tty_insert_flip_string(tty, (unsigned char *)&emudat, num_chars); + tty_flip_buffer_push(tty); + } + } + mutex_unlock(&bfin_jc_tty_mutex); + } + + /* if outgoing data is ready, post it */ + if (!(bfin_read_DBGSTAT() & EMUDOF) && !circ_empty(&bfin_jc_write_buf)) { + if (outbound_len == 0) { + outbound_len = circ_cnt(&bfin_jc_write_buf); + bfin_write_emudat(outbound_len); + debug("outgoing length: 0x%08x\n", outbound_len); + } else { + struct tty_struct *tty; + int tail = bfin_jc_write_buf.tail; + size_t ate = (4 <= outbound_len ? 4 : outbound_len); + uint32_t emudat = + bfin_write_emudat_chars( + circ_byte(&bfin_jc_write_buf, tail + 0), + circ_byte(&bfin_jc_write_buf, tail + 1), + circ_byte(&bfin_jc_write_buf, tail + 2), + circ_byte(&bfin_jc_write_buf, tail + 3) + ); + bfin_jc_write_buf.tail += ate; + outbound_len -= ate; + mutex_lock(&bfin_jc_tty_mutex); + tty = (struct tty_struct *)bfin_jc_tty; + if (tty) + tty_wakeup(tty); + mutex_unlock(&bfin_jc_tty_mutex); + debug(" outgoing data: 0x%08x (pushing %zu)\n", emudat, ate); + } + } + } + + __set_current_state(TASK_RUNNING); + return 0; +} + +static int +bfin_jc_open(struct tty_struct *tty, struct file *filp) +{ + mutex_lock(&bfin_jc_tty_mutex); + debug("open %lu\n", bfin_jc_count); + ++bfin_jc_count; + bfin_jc_tty = tty; + wake_up_process(bfin_jc_kthread); + mutex_unlock(&bfin_jc_tty_mutex); + return 0; +} + +static void +bfin_jc_close(struct tty_struct *tty, struct file *filp) +{ + mutex_lock(&bfin_jc_tty_mutex); + debug("close %lu\n", bfin_jc_count); + if (--bfin_jc_count == 0) + bfin_jc_tty = NULL; + wake_up_process(bfin_jc_kthread); + mutex_unlock(&bfin_jc_tty_mutex); +} + +/* XXX: we dont handle the put_char() case where we must handle count = 1 */ +static int +bfin_jc_circ_write(const unsigned char *buf, int count) +{ + int i; + count = min(count, circ_free(&bfin_jc_write_buf)); + debug("going to write chunk of %i bytes\n", count); + for (i = 0; i < count; ++i) + circ_byte(&bfin_jc_write_buf, bfin_jc_write_buf.head + i) = buf[i]; + bfin_jc_write_buf.head += i; + return i; +} + +#ifndef CONFIG_BFIN_JTAG_COMM_CONSOLE +# define acquire_console_sem() +# define release_console_sem() +#endif +static int +bfin_jc_write(struct tty_struct *tty, const unsigned char *buf, int count) +{ + int i; + acquire_console_sem(); + i = bfin_jc_circ_write(buf, count); + release_console_sem(); + wake_up_process(bfin_jc_kthread); + return i; +} + +static void +bfin_jc_flush_chars(struct tty_struct *tty) +{ + wake_up_process(bfin_jc_kthread); +} + +static int +bfin_jc_write_room(struct tty_struct *tty) +{ + return circ_free(&bfin_jc_write_buf); +} + +static int +bfin_jc_chars_in_buffer(struct tty_struct *tty) +{ + return circ_cnt(&bfin_jc_write_buf); +} + +static void +bfin_jc_wait_until_sent(struct tty_struct *tty, int timeout) +{ + unsigned long expire = jiffies + timeout; + while (!circ_empty(&bfin_jc_write_buf)) { + if (signal_pending(current)) + break; + if (time_after(jiffies, expire)) + break; + } +} + +static struct tty_operations bfin_jc_ops = { + .open = bfin_jc_open, + .close = bfin_jc_close, + .write = bfin_jc_write, + /*.put_char = bfin_jc_put_char,*/ + .flush_chars = bfin_jc_flush_chars, + .write_room = bfin_jc_write_room, + .chars_in_buffer = bfin_jc_chars_in_buffer, + .wait_until_sent = bfin_jc_wait_until_sent, +}; + +static int __init bfin_jc_init(void) +{ + int ret; + + bfin_jc_kthread = kthread_create(bfin_jc_emudat_manager, NULL, DRV_NAME); + if (IS_ERR(bfin_jc_kthread)) + return PTR_ERR(bfin_jc_kthread); + + ret = -ENOMEM; + + bfin_jc_write_buf.head = bfin_jc_write_buf.tail = 0; + bfin_jc_write_buf.buf = kmalloc(CIRC_SIZE, GFP_KERNEL); + if (!bfin_jc_write_buf.buf) + goto err; + + bfin_jc_driver = alloc_tty_driver(1); + if (!bfin_jc_driver) + goto err; + + bfin_jc_driver->owner = THIS_MODULE; + bfin_jc_driver->driver_name = DRV_NAME; + bfin_jc_driver->name = DEV_NAME; + bfin_jc_driver->type = TTY_DRIVER_TYPE_SERIAL; + bfin_jc_driver->subtype = SERIAL_TYPE_NORMAL; + bfin_jc_driver->init_termios = tty_std_termios; + tty_set_operations(bfin_jc_driver, &bfin_jc_ops); + + ret = tty_register_driver(bfin_jc_driver); + if (ret) + goto err; + + pr_init(KERN_INFO DRV_NAME ": initialized\n"); + + return 0; + + err: + put_tty_driver(bfin_jc_driver); + kfree(bfin_jc_write_buf.buf); + kthread_stop(bfin_jc_kthread); + return ret; +} +module_init(bfin_jc_init); + +static void __exit bfin_jc_exit(void) +{ + kthread_stop(bfin_jc_kthread); + kfree(bfin_jc_write_buf.buf); + tty_unregister_driver(bfin_jc_driver); + put_tty_driver(bfin_jc_driver); +} +module_exit(bfin_jc_exit); + +#if defined(CONFIG_BFIN_JTAG_COMM_CONSOLE) || defined(CONFIG_EARLY_PRINTK) +static void +bfin_jc_straight_buffer_write(const char *buf, unsigned count) +{ + unsigned ate = 0; + while (bfin_read_DBGSTAT() & EMUDOF) + continue; + bfin_write_emudat(count); + while (ate < count) { + while (bfin_read_DBGSTAT() & EMUDOF) + continue; + bfin_write_emudat_chars(buf[ate], buf[ate+1], buf[ate+2], buf[ate+3]); + ate += 4; + } +} +#endif + +#ifdef CONFIG_BFIN_JTAG_COMM_CONSOLE +static void +bfin_jc_console_write(struct console *co, const char *buf, unsigned count) +{ + if (bfin_jc_kthread == NULL) + bfin_jc_straight_buffer_write(buf, count); + else + bfin_jc_circ_write(buf, count); +} + +static struct tty_driver * +bfin_jc_console_device(struct console *co, int *index) +{ + *index = co->index; + return bfin_jc_driver; +} + +static struct console bfin_jc_console = { + .name = DEV_NAME, + .write = bfin_jc_console_write, + .device = bfin_jc_console_device, + .flags = CON_ANYTIME | CON_PRINTBUFFER, + .index = -1, +}; + +static int __init bfin_jc_console_init(void) +{ + register_console(&bfin_jc_console); + return 0; +} +console_initcall(bfin_jc_console_init); +#endif + +#ifdef CONFIG_EARLY_PRINTK +static void __init +bfin_jc_early_write(struct console *co, const char *buf, unsigned int count) +{ + bfin_jc_straight_buffer_write(buf, count); +} + +static struct __initdata console bfin_jc_early_console = { + .name = "early_BFJC", + .write = bfin_jc_early_write, + .flags = CON_ANYTIME | CON_PRINTBUFFER, + .index = -1, +}; + +struct console * __init +bfin_jc_early_init(unsigned int port, unsigned int cflag) +{ + return &bfin_jc_early_console; +} +#endif + +MODULE_AUTHOR("Mike Frysinger "); +MODULE_DESCRIPTION("TTY over Blackfin JTAG Communication"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3 From 26a2e20f4a966bebc312836aeb4423fbfd4e33e2 Mon Sep 17 00:00:00 2001 From: Alan Cox Date: Thu, 11 Jun 2009 14:03:13 +0100 Subject: tty: Untangle termios and mm mutex dependencies Although this doesn't cause any problems it could potentially do so for future mmap using devices. No real work is needed to sort it out so untangle it before it causes problems Signed-off-by: Alan Cox Signed-off-by: Linus Torvalds --- drivers/char/tty_ioctl.c | 75 +++++++++++++++++++++++++++++------------------- 1 file changed, 46 insertions(+), 29 deletions(-) (limited to 'drivers/char') diff --git a/drivers/char/tty_ioctl.c b/drivers/char/tty_ioctl.c index 2401dbcbee9..8116bb1c8f8 100644 --- a/drivers/char/tty_ioctl.c +++ b/drivers/char/tty_ioctl.c @@ -626,9 +626,25 @@ static int set_termios(struct tty_struct *tty, void __user *arg, int opt) return 0; } +static void copy_termios(struct tty_struct *tty, struct ktermios *kterm) +{ + mutex_lock(&tty->termios_mutex); + memcpy(kterm, tty->termios, sizeof(struct ktermios)); + mutex_unlock(&tty->termios_mutex); +} + +static void copy_termios_locked(struct tty_struct *tty, struct ktermios *kterm) +{ + mutex_lock(&tty->termios_mutex); + memcpy(kterm, tty->termios_locked, sizeof(struct ktermios)); + mutex_unlock(&tty->termios_mutex); +} + static int get_termio(struct tty_struct *tty, struct termio __user *termio) { - if (kernel_termios_to_user_termio(termio, tty->termios)) + struct ktermios kterm; + copy_termios(tty, &kterm); + if (kernel_termios_to_user_termio(termio, &kterm)) return -EFAULT; return 0; } @@ -930,6 +946,8 @@ int tty_mode_ioctl(struct tty_struct *tty, struct file *file, struct tty_struct *real_tty; void __user *p = (void __user *)arg; int ret = 0; + struct ktermios kterm; + struct termiox ktermx; if (tty->driver->type == TTY_DRIVER_TYPE_PTY && tty->driver->subtype == PTY_TYPE_MASTER) @@ -965,23 +983,20 @@ int tty_mode_ioctl(struct tty_struct *tty, struct file *file, return set_termios(real_tty, p, TERMIOS_OLD); #ifndef TCGETS2 case TCGETS: - mutex_lock(&real_tty->termios_mutex); - if (kernel_termios_to_user_termios((struct termios __user *)arg, real_tty->termios)) + copy_termios(real_tty, &kterm); + if (kernel_termios_to_user_termios((struct termios __user *)arg, &kterm)) ret = -EFAULT; - mutex_unlock(&real_tty->termios_mutex); return ret; #else case TCGETS: - mutex_lock(&real_tty->termios_mutex); - if (kernel_termios_to_user_termios_1((struct termios __user *)arg, real_tty->termios)) + copy_termios(real_tty, &kterm); + if (kernel_termios_to_user_termios_1((struct termios __user *)arg, &kterm)) ret = -EFAULT; - mutex_unlock(&real_tty->termios_mutex); return ret; case TCGETS2: - mutex_lock(&real_tty->termios_mutex); - if (kernel_termios_to_user_termios((struct termios2 __user *)arg, real_tty->termios)) + copy_termios(real_tty, &kterm); + if (kernel_termios_to_user_termios((struct termios2 __user *)arg, &kterm)) ret = -EFAULT; - mutex_unlock(&real_tty->termios_mutex); return ret; case TCSETSF2: return set_termios(real_tty, p, TERMIOS_FLUSH | TERMIOS_WAIT); @@ -1000,34 +1015,36 @@ int tty_mode_ioctl(struct tty_struct *tty, struct file *file, return set_termios(real_tty, p, TERMIOS_TERMIO); #ifndef TCGETS2 case TIOCGLCKTRMIOS: - mutex_lock(&real_tty->termios_mutex); - if (kernel_termios_to_user_termios((struct termios __user *)arg, real_tty->termios_locked)) + copy_termios_locked(real_tty, &kterm); + if (kernel_termios_to_user_termios((struct termios __user *)arg, &kterm)) ret = -EFAULT; - mutex_unlock(&real_tty->termios_mutex); return ret; case TIOCSLCKTRMIOS: if (!capable(CAP_SYS_ADMIN)) return -EPERM; - mutex_lock(&real_tty->termios_mutex); - if (user_termios_to_kernel_termios(real_tty->termios_locked, + copy_termios_locked(real_tty, &kterm); + if (user_termios_to_kernel_termios(&kterm, (struct termios __user *) arg)) - ret = -EFAULT; + return -EFAULT; + mutex_lock(&real_tty->termios_mutex); + memcpy(real_tty->termios_locked, &kterm, sizeof(struct ktermios)); mutex_unlock(&real_tty->termios_mutex); - return ret; + return 0; #else case TIOCGLCKTRMIOS: - mutex_lock(&real_tty->termios_mutex); - if (kernel_termios_to_user_termios_1((struct termios __user *)arg, real_tty->termios_locked)) + copy_termios_locked(real_tty, &kterm); + if (kernel_termios_to_user_termios_1((struct termios __user *)arg, &kterm)) ret = -EFAULT; - mutex_unlock(&real_tty->termios_mutex); return ret; case TIOCSLCKTRMIOS: if (!capable(CAP_SYS_ADMIN)) - ret = -EPERM; - mutex_lock(&real_tty->termios_mutex); - if (user_termios_to_kernel_termios_1(real_tty->termios_locked, + return -EPERM; + copy_termios_locked(real_tty, &kterm); + if (user_termios_to_kernel_termios_1(&kterm, (struct termios __user *) arg)) - ret = -EFAULT; + return -EFAULT; + mutex_lock(&real_tty->termios_mutex); + memcpy(real_tty->termios_locked, &kterm, sizeof(struct ktermios)); mutex_unlock(&real_tty->termios_mutex); return ret; #endif @@ -1036,9 +1053,10 @@ int tty_mode_ioctl(struct tty_struct *tty, struct file *file, if (real_tty->termiox == NULL) return -EINVAL; mutex_lock(&real_tty->termios_mutex); - if (copy_to_user(p, real_tty->termiox, sizeof(struct termiox))) - ret = -EFAULT; + memcpy(&ktermx, real_tty->termiox, sizeof(struct termiox)); mutex_unlock(&real_tty->termios_mutex); + if (copy_to_user(p, &ktermx, sizeof(struct termiox))) + ret = -EFAULT; return ret; case TCSETX: return set_termiox(real_tty, p, 0); @@ -1048,10 +1066,9 @@ int tty_mode_ioctl(struct tty_struct *tty, struct file *file, return set_termiox(real_tty, p, TERMIOS_FLUSH); #endif case TIOCGSOFTCAR: - mutex_lock(&real_tty->termios_mutex); - ret = put_user(C_CLOCAL(real_tty) ? 1 : 0, + copy_termios(real_tty, &kterm); + ret = put_user((kterm.c_cflag & CLOCAL) ? 1 : 0, (int __user *)arg); - mutex_unlock(&real_tty->termios_mutex); return ret; case TIOCSSOFTCAR: if (get_user(arg, (unsigned int __user *) arg)) -- cgit v1.2.3 From 5fc5b42a3bb564f0b6e03f0f1b522ed9100250ad Mon Sep 17 00:00:00 2001 From: Jiri Slaby Date: Thu, 11 Jun 2009 14:32:42 +0100 Subject: tty: remove sleep_on Use wait_event instead of sleep_on in tty_block_til_ready. Wait for ASYNC_CLOSING flag being 0. Signed-off-by: Jiri Slaby Signed-off-by: Linus Torvalds --- drivers/char/tty_port.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'drivers/char') diff --git a/drivers/char/tty_port.c b/drivers/char/tty_port.c index 4d08b6d27c2..931af103074 100644 --- a/drivers/char/tty_port.c +++ b/drivers/char/tty_port.c @@ -198,7 +198,8 @@ int tty_port_block_til_ready(struct tty_port *port, /* block if port is in the process of being closed */ if (tty_hung_up_p(filp) || port->flags & ASYNC_CLOSING) { - interruptible_sleep_on(&port->close_wait); + wait_event_interruptible(port->close_wait, + !(port->flags & ASYNC_CLOSING)); if (port->flags & ASYNC_HUP_NOTIFY) return -EAGAIN; else -- cgit v1.2.3 From 3e3b5c087799e536871c8261b05bc28e4783c8da Mon Sep 17 00:00:00 2001 From: Jiri Slaby Date: Thu, 11 Jun 2009 14:33:37 +0100 Subject: tty: use prepare/finish_wait Use prepare_to_wait and finish_wait instead of add_wait_queue and remove_wait_queue. This avoids us setting a task state. Signed-off-by: Jiri Slaby Signed-off-by: Alan Cox Signed-off-by: Linus Torvalds --- drivers/char/tty_port.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'drivers/char') diff --git a/drivers/char/tty_port.c b/drivers/char/tty_port.c index 931af103074..62dadfc95e3 100644 --- a/drivers/char/tty_port.c +++ b/drivers/char/tty_port.c @@ -222,7 +222,6 @@ int tty_port_block_til_ready(struct tty_port *port, before the next open may complete */ retval = 0; - add_wait_queue(&port->open_wait, &wait); /* The port lock protects the port counts */ spin_lock_irqsave(&port->lock, flags); @@ -236,7 +235,7 @@ int tty_port_block_til_ready(struct tty_port *port, if (tty->termios->c_cflag & CBAUD) tty_port_raise_dtr_rts(port); - set_current_state(TASK_INTERRUPTIBLE); + prepare_to_wait(&port->open_wait, &wait, TASK_INTERRUPTIBLE); /* Check for a hangup or uninitialised port. Return accordingly */ if (tty_hung_up_p(filp) || !(port->flags & ASYNC_INITIALIZED)) { if (port->flags & ASYNC_HUP_NOTIFY) @@ -257,8 +256,7 @@ int tty_port_block_til_ready(struct tty_port *port, } schedule(); } - set_current_state(TASK_RUNNING); - remove_wait_queue(&port->open_wait, &wait); + finish_wait(&port->open_wait, &wait); /* Update counts. A parallel hangup will have set count to zero and we must not mess that up further */ -- cgit v1.2.3 From f0e8527726b9e56649b9eafde3bc0fbc4dd2dd47 Mon Sep 17 00:00:00 2001 From: Dirk Eibach Date: Thu, 11 Jun 2009 14:56:44 +0100 Subject: moxa: prevent opening unavailable ports In moxa.c there are 32 minor numbers reserved for each device. The number of ports actually available per device is stored in moxa_board_conf->numPorts. This number is not considered in moxa_open(). Opening a port that is not available results in a kernel oops. This patch adds a test to moxa_open() that prevents opening unavailable ports. Signed-off-by: Dirk Eibach Signed-off-by: Jiri Slaby Signed-off-by: Alan Cox Signed-off-by: Linus Torvalds --- drivers/char/moxa.c | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'drivers/char') diff --git a/drivers/char/moxa.c b/drivers/char/moxa.c index 4a4cab73d0b..65b6ff2442c 100644 --- a/drivers/char/moxa.c +++ b/drivers/char/moxa.c @@ -1184,6 +1184,11 @@ static int moxa_open(struct tty_struct *tty, struct file *filp) return -ENODEV; } + if (port % MAX_PORTS_PER_BOARD >= brd->numPorts) { + mutex_unlock(&moxa_openlock); + return -ENODEV; + } + ch = &brd->ports[port % MAX_PORTS_PER_BOARD]; ch->port.count++; tty->driver_data = ch; -- cgit v1.2.3