X-Git-Url: http://git.onelab.eu/?a=blobdiff_plain;f=drivers%2Fchar%2Fisicom.c;h=2ccde4de37cc26a693e1b143754b477af0edba68;hb=6a77f38946aaee1cd85eeec6cf4229b204c15071;hp=b454bf2084bf8c450ba0b44ff5991eb0eb2ca3bd;hpb=87fc8d1bb10cd459024a742c6a10961fefcef18f;p=linux-2.6.git diff --git a/drivers/char/isicom.c b/drivers/char/isicom.c index b454bf208..2ccde4de3 100644 --- a/drivers/char/isicom.c +++ b/drivers/char/isicom.c @@ -16,10 +16,81 @@ * * 10/6/99 sameer Merged the ISA and PCI drivers to * a new unified driver. + * + * 3/9/99 sameer Added support for ISI4616 cards. + * + * 16/9/99 sameer We do not force RTS low anymore. + * This is to prevent the firmware + * from getting confused. + * + * 26/10/99 sameer Cosmetic changes:The driver now + * dumps the Port Count information + * along with I/O address and IRQ. + * + * 13/12/99 sameer Fixed the problem with IRQ sharing. + * + * 10/5/00 sameer Fixed isicom_shutdown_board() + * to not lower DTR on all the ports + * when the last port on the card is + * closed. + * + * 10/5/00 sameer Signal mask setup command added + * to isicom_setup_port and + * isicom_shutdown_port. + * + * 24/5/00 sameer The driver is now SMP aware. + * + * + * 27/11/00 Vinayak P Risbud Fixed the Driver Crash Problem + * + * + * 03/01/01 anil .s Added support for resetting the + * internal modems on ISI cards. + * + * 08/02/01 anil .s Upgraded the driver for kernel + * 2.4.x + * + * 11/04/01 Kevin Fixed firmware load problem with + * ISIHP-4X card + * + * 30/04/01 anil .s Fixed the remote login through + * ISI port problem. Now the link + * does not go down before password + * prompt. + * + * 03/05/01 anil .s Fixed the problem with IRQ sharing + * among ISI-PCI cards. + * + * 03/05/01 anil .s Added support to display the version + * info during insmod as well as module + * listing by lsmod. + * + * 10/05/01 anil .s Done the modifications to the source + * file and Install script so that the + * same installation can be used for + * 2.2.x and 2.4.x kernel. + * + * 06/06/01 anil .s Now we drop both dtr and rts during + * shutdown_port as well as raise them + * during isicom_config_port. + * * 09/06/01 acme@conectiva.com.br use capable, not suser, do * restore_flags on failure in * isicom_send_break, verify put_user * result + * + * 11/02/03 ranjeeth Added support for 230 Kbps and 460 Kbps + * Baud index extended to 21 + * + * 20/03/03 ranjeeth Made to work for Linux Advanced server. + * Taken care of license warning. + * + * 10/12/03 Ravindra Made to work for Fedora Core 1 of + * Red Hat Distribution + * + * 06/01/05 Alan Cox Merged the ISI and base kernel strands + * into a single 2.6 driver + * * *********************************************************** * * To use this driver you also need the support package. You @@ -35,6 +106,10 @@ * * Omit those entries for boards you don't have installed. * + * TODO + * Hotplug + * Merge testing + * 64-bit verification */ #include @@ -74,7 +149,6 @@ static struct pci_device_id isicom_pci_tbl[] = { MODULE_DEVICE_TABLE(pci, isicom_pci_tbl); static int prev_card = 3; /* start servicing isi_card[0] */ -static struct isi_board * irq_to_board[16]; static struct tty_driver *isicom_normal; static struct isi_board isi_card[BOARD_COUNT]; @@ -101,9 +175,205 @@ static signed char linuxb_to_isib[] = { 18, 19 }; +struct isi_board { + unsigned short base; + unsigned char irq; + unsigned char port_count; + unsigned short status; + unsigned short port_status; /* each bit represents a single port */ + unsigned short shift_count; + struct isi_port * ports; + signed char count; + unsigned char isa; + spinlock_t card_lock; /* Card wide lock 11/5/00 -sameer */ + unsigned long flags; +}; + +struct isi_port { + unsigned short magic; + unsigned int flags; + int count; + int blocked_open; + int close_delay; + unsigned short channel; + unsigned short status; + unsigned short closing_wait; + struct isi_board * card; + struct tty_struct * tty; + wait_queue_head_t close_wait; + wait_queue_head_t open_wait; + struct work_struct hangup_tq; + struct work_struct bh_tqueue; + unsigned char * xmit_buf; + int xmit_head; + int xmit_tail; + int xmit_cnt; +}; + +/* + * Locking functions for card level locking. We need to own both + * the kernel lock for the card and have the card in a position that + * it wants to talk. + */ + +static int lock_card(struct isi_board *card) +{ + char retries; + unsigned short base = card->base; + + for (retries = 0; retries < 100; retries++) { + spin_lock_irqsave(&card->card_lock, card->flags); + if (inw(base + 0xe) & 0x1) { + return 1; + } else { + spin_unlock_irqrestore(&card->card_lock, card->flags); + udelay(1000); /* 1ms */ + } + } + printk(KERN_WARNING "ISICOM: Failed to lock Card (0x%x)\n", card->base); + return 0; /* Failed to aquire the card! */ +} + +static int lock_card_at_interrupt(struct isi_board *card) +{ + unsigned char retries; + unsigned short base = card->base; + + for (retries = 0; retries < 200; retries++) { + spin_lock_irqsave(&card->card_lock, card->flags); + + if (inw(base + 0xe) & 0x1) + return 1; + else + spin_unlock_irqrestore(&card->card_lock, card->flags); + } + /* Failing in interrupt is an acceptable event */ + return 0; /* Failed to aquire the card! */ +} + +static void unlock_card(struct isi_board *card) +{ + spin_unlock_irqrestore(&card->card_lock, card->flags); +} + +/* + * ISI Card specific ops ... + */ + +static void raise_dtr(struct isi_port * port) +{ + struct isi_board * card = port->card; + unsigned short base = card->base; + unsigned char channel = port->channel; + + if (!lock_card(card)) + return; + + outw(0x8000 | (channel << card->shift_count) | 0x02 , base); + outw(0x0504, base); + InterruptTheCard(base); + port->status |= ISI_DTR; + unlock_card(card); +} + +static inline void drop_dtr(struct isi_port * port) +{ + struct isi_board * card = port->card; + unsigned short base = card->base; + unsigned char channel = port->channel; + + if (!lock_card(card)) + return; + + outw(0x8000 | (channel << card->shift_count) | 0x02 , base); + outw(0x0404, base); + InterruptTheCard(base); + port->status &= ~ISI_DTR; + unlock_card(card); +} + +static inline void raise_rts(struct isi_port * port) +{ + struct isi_board * card = port->card; + unsigned short base = card->base; + unsigned char channel = port->channel; + + if (!lock_card(card)) + return; + + outw(0x8000 | (channel << card->shift_count) | 0x02 , base); + outw(0x0a04, base); + InterruptTheCard(base); + port->status |= ISI_RTS; + unlock_card(card); +} +static inline void drop_rts(struct isi_port * port) +{ + struct isi_board * card = port->card; + unsigned short base = card->base; + unsigned char channel = port->channel; + + if (!lock_card(card)) + return; + + outw(0x8000 | (channel << card->shift_count) | 0x02 , base); + outw(0x0804, base); + InterruptTheCard(base); + port->status &= ~ISI_RTS; + unlock_card(card); +} + +static inline void raise_dtr_rts(struct isi_port * port) +{ + struct isi_board * card = port->card; + unsigned short base = card->base; + unsigned char channel = port->channel; + + if (!lock_card(card)) + return; + + outw(0x8000 | (channel << card->shift_count) | 0x02 , base); + outw(0x0f04, base); + InterruptTheCard(base); + port->status |= (ISI_DTR | ISI_RTS); + unlock_card(card); +} + +static void drop_dtr_rts(struct isi_port * port) +{ + struct isi_board * card = port->card; + unsigned short base = card->base; + unsigned char channel = port->channel; + + if (!lock_card(card)) + return; + + outw(0x8000 | (channel << card->shift_count) | 0x02 , base); + outw(0x0c04, base); + InterruptTheCard(base); + port->status &= ~(ISI_RTS | ISI_DTR); + unlock_card(card); +} + +static inline void kill_queue(struct isi_port * port, short queue) +{ + struct isi_board * card = port->card; + unsigned short base = card->base; + unsigned char channel = port->channel; + + if (!lock_card(card)) + return; + + outw(0x8000 | (channel << card->shift_count) | 0x02 , base); + outw((queue << 8) | 0x06, base); + InterruptTheCard(base); + unlock_card(card); +} + + /* - * Firmware loader driver specific routines - * + * Firmware loader driver specific routines. This needs to mostly die + * and be replaced with request_firmware. */ static struct file_operations ISILoad_fops = { @@ -354,15 +624,19 @@ static inline int isicom_paranoia_check(struct isi_port const * port, char *name return 0; } -/* Transmitter */ +/* + * Transmitter. + * + * We shovel data into the card buffers on a regular basis. The card + * will do the rest of the work for us. + */ static void isicom_tx(unsigned long _data) { short count = (BOARD_COUNT-1), card, base; - short txcount, wait, wrd, residue, word_count, cnt; + short txcount, wrd, residue, word_count, cnt; struct isi_port * port; struct tty_struct * tty; - unsigned long flags; #ifdef ISICOM_DEBUG ++tx_count; @@ -384,33 +658,29 @@ static void isicom_tx(unsigned long _data) port = isi_card[card].ports; base = isi_card[card].base; for (;count > 0;count--, port++) { + if (!lock_card_at_interrupt(&isi_card[card])) + continue; /* port not active or tx disabled to force flow control */ - if (!(port->status & ISI_TXOK)) + if (!(port->flags & ASYNC_INITIALIZED) || + !(port->status & ISI_TXOK)) + unlock_card(&isi_card[card]); continue; tty = port->tty; - save_flags(flags); cli(); - txcount = min_t(short, TX_SIZE, port->xmit_cnt); - if ((txcount <= 0) || tty->stopped || tty->hw_stopped) { - restore_flags(flags); + + + if(tty == NULL) { + unlock_card(&isi_card[card]); continue; } - wait = 200; - while(((inw(base+0x0e) & 0x01) == 0) && (wait-- > 0)); - if (wait <= 0) { - restore_flags(flags); -#ifdef ISICOM_DEBUG - printk(KERN_DEBUG "ISICOM: isicom_tx:Card(0x%x) found busy.\n", - card); -#endif + + txcount = min_t(short, TX_SIZE, port->xmit_cnt); + if (txcount <= 0 || tty->stopped || tty->hw_stopped) { + unlock_card(&isi_card[card]); continue; } if (!(inw(base + 0x02) & (1 << port->channel))) { - restore_flags(flags); -#ifdef ISICOM_DEBUG - printk(KERN_DEBUG "ISICOM: isicom_tx: cannot tx to 0x%x:%d.\n", - base, port->channel + 1); -#endif + unlock_card(&isi_card[card]); continue; } #ifdef ISICOM_DEBUG @@ -459,10 +729,10 @@ static void isicom_tx(unsigned long _data) port->status &= ~ISI_TXOK; if (port->xmit_cnt <= WAKEUP_CHARS) schedule_work(&port->bh_tqueue); - restore_flags(flags); + unlock_card(&isi_card[card]); } - /* schedule another tx for hopefully in about 10ms */ + /* schedule another tx for hopefully in about 10ms */ sched_again: if (!re_schedule) return; @@ -490,7 +760,10 @@ static void isicom_bottomhalf(void * data) wake_up_interruptible(&tty->write_wait); } -/* main interrupt handler routine */ +/* + * Main interrupt handler routine + */ + static irqreturn_t isicom_interrupt(int irq, void *dev_id, struct pt_regs *regs) { @@ -501,31 +774,19 @@ static irqreturn_t isicom_interrupt(int irq, void *dev_id, unsigned char channel; short byte_count; - /* - * find the source of interrupt - */ - - for(count = 0; count < BOARD_COUNT; count++) { - card = &isi_card[count]; - if (card->base != 0) { - if (((card->isa == YES) && (card->irq == irq)) || - ((card->isa == NO) && (card->irq == irq) && (inw(card->base+0x0e) & 0x02))) - break; - } - card = NULL; - } + card = (struct isi_board *) dev_id; - if (!card || !(card->status & FIRMWARE_LOADED)) { -/* printk(KERN_DEBUG "ISICOM: interrupt: not handling irq%d!.\n", irq);*/ + if (!card || !(card->status & FIRMWARE_LOADED)) return IRQ_NONE; - } base = card->base; + spin_lock(&card->card_lock); + if (card->isa == NO) { - /* - * disable any interrupts from the PCI card and lower the - * interrupt line - */ + /* + * disable any interrupts from the PCI card and lower the + * interrupt line + */ outw(0x8000, base+0x04); ClearInterrupt(base); } @@ -534,16 +795,15 @@ static irqreturn_t isicom_interrupt(int irq, void *dev_id, header = inw(base); channel = (header & 0x7800) >> card->shift_count; byte_count = header & 0xff; -#ifdef ISICOM_DEBUG - printk(KERN_DEBUG "ISICOM:Intr:(0x%x:%d).\n", base, channel+1); -#endif - if ((channel+1) > card->port_count) { + + if (channel + 1 > card->port_count) { printk(KERN_WARNING "ISICOM: isicom_interrupt(0x%x): %d(channel) > port_count.\n", base, channel+1); if (card->isa) ClearInterrupt(base); else outw(0x0000, base+0x04); /* enable interrupts */ + spin_unlock(&card->card_lock); return IRQ_HANDLED; } port = card->ports + channel; @@ -556,6 +816,21 @@ static irqreturn_t isicom_interrupt(int irq, void *dev_id, } tty = port->tty; + if (tty == NULL) { + word_count = byte_count >> 1; + while(byte_count > 1) { + inw(base); + byte_count -= 2; + } + if (byte_count & 0x01) + inw(base); + if (card->isa == YES) + ClearInterrupt(base); + else + outw(0x0000, base+0x04); /* enable interrupts */ + spin_unlock(&card->card_lock); + return IRQ_HANDLED; + } if (header & 0x8000) { /* Status Packet */ header = inw(base); @@ -631,7 +906,6 @@ static irqreturn_t isicom_interrupt(int irq, void *dev_id, if (tty->flip.count >= TTY_FLIPBUF_SIZE) break; *tty->flip.flag_buf_ptr++ = TTY_BREAK; - /* dunno if this is right */ *tty->flip.char_buf_ptr++ = 0; tty->flip.count++; if (port->flags & ASYNC_SAK) @@ -683,13 +957,12 @@ static irqreturn_t isicom_interrupt(int irq, void *dev_id, return IRQ_HANDLED; } - /* called with interrupts disabled */ static void isicom_config_port(struct isi_port * port) { struct isi_board * card = port->card; struct tty_struct * tty; unsigned long baud; - unsigned short channel_setup, wait, base = card->base; + unsigned short channel_setup, base = card->base; unsigned short channel = port->channel, shift_count = card->shift_count; unsigned char flow_ctrl; @@ -729,40 +1002,36 @@ static void isicom_config_port(struct isi_port * port) else raise_dtr(port); - wait = 100; - while (((inw(base + 0x0e) & 0x0001) == 0) && (wait-- > 0)); - if (!wait) { - printk(KERN_WARNING "ISICOM: Card found busy in isicom_config_port at channel setup.\n"); - return; - } - outw(0x8000 | (channel << shift_count) |0x03, base); - outw(linuxb_to_isib[baud] << 8 | 0x03, base); - channel_setup = 0; - switch(C_CSIZE(tty)) { - case CS5: - channel_setup |= ISICOM_CS5; - break; - case CS6: - channel_setup |= ISICOM_CS6; - break; - case CS7: - channel_setup |= ISICOM_CS7; - break; - case CS8: - channel_setup |= ISICOM_CS8; - break; - } - - if (C_CSTOPB(tty)) - channel_setup |= ISICOM_2SB; - - if (C_PARENB(tty)) - channel_setup |= ISICOM_EVPAR; - if (C_PARODD(tty)) - channel_setup |= ISICOM_ODPAR; - outw(channel_setup, base); - InterruptTheCard(base); - + if (lock_card(card)) { + outw(0x8000 | (channel << shift_count) |0x03, base); + outw(linuxb_to_isib[baud] << 8 | 0x03, base); + channel_setup = 0; + switch(C_CSIZE(tty)) { + case CS5: + channel_setup |= ISICOM_CS5; + break; + case CS6: + channel_setup |= ISICOM_CS6; + break; + case CS7: + channel_setup |= ISICOM_CS7; + break; + case CS8: + channel_setup |= ISICOM_CS8; + break; + } + + if (C_CSTOPB(tty)) + channel_setup |= ISICOM_2SB; + if (C_PARENB(tty)) { + channel_setup |= ISICOM_EVPAR; + if (C_PARODD(tty)) + channel_setup |= ISICOM_ODPAR; + } + outw(channel_setup, base); + InterruptTheCard(base); + unlock_card(card); + } if (C_CLOCAL(tty)) port->flags &= ~ASYNC_CHECK_CD; else @@ -780,23 +1049,19 @@ static void isicom_config_port(struct isi_port * port) if (I_IXOFF(tty)) flow_ctrl |= ISICOM_INITIATE_XONXOFF; - wait = 100; - while (((inw(base + 0x0e) & 0x0001) == 0) && (wait-- > 0)); - if (!wait) { - printk(KERN_WARNING "ISICOM: Card found busy in isicom_config_port at flow setup.\n"); - return; - } - outw(0x8000 | (channel << shift_count) |0x04, base); - outw(flow_ctrl << 8 | 0x05, base); - outw((STOP_CHAR(tty)) << 8 | (START_CHAR(tty)), base); - InterruptTheCard(base); + if (lock_card(card)) { + outw(0x8000 | (channel << shift_count) |0x04, base); + outw(flow_ctrl << 8 | 0x05, base); + outw((STOP_CHAR(tty)) << 8 | (START_CHAR(tty)), base); + InterruptTheCard(base); + unlock_card(card); + } /* rx enabled -> enable port for rx on the card */ if (C_CREAD(tty)) { card->port_status |= (1 << channel); outw(card->port_status, base + 0x02); } - } /* open et all */ @@ -807,22 +1072,16 @@ static inline void isicom_setup_board(struct isi_board * bp) struct isi_port * port; unsigned long flags; - if (bp->status & BOARD_ACTIVE) + spin_lock_irqsave(&bp->card_lock, flags); + if (bp->status & BOARD_ACTIVE) { + spin_unlock_irqrestore(&bp->card_lock, flags); return; - port = bp->ports; -#ifdef ISICOM_DEBUG - printk(KERN_DEBUG "ISICOM: setup_board: drop_dtr_rts start, port_count %d...\n", bp->port_count); -#endif - for(channel = 0; channel < bp->port_count; channel++, port++) { - save_flags(flags); cli(); - drop_dtr_rts(port); - restore_flags(flags); } -#ifdef ISICOM_DEBUG - printk(KERN_DEBUG "ISICOM: setup_board: drop_dtr_rts stop...\n"); -#endif - + port = bp->ports; bp->status |= BOARD_ACTIVE; + spin_unlock_irqrestore(&bp->card_lock, flags); + for(channel = 0; channel < bp->port_count; channel++, port++) + drop_dtr_rts(port); return; } @@ -831,8 +1090,9 @@ static int isicom_setup_port(struct isi_port * port) struct isi_board * card = port->card; unsigned long flags; - if (port->flags & ASYNC_INITIALIZED) + if (port->flags & ASYNC_INITIALIZED) { return 0; + } if (!port->xmit_buf) { unsigned long page; @@ -845,7 +1105,8 @@ static int isicom_setup_port(struct isi_port * port) } port->xmit_buf = (unsigned char *) page; } - save_flags(flags); cli(); + + spin_lock_irqsave(&card->card_lock, flags); if (port->tty) clear_bit(TTY_IO_ERROR, &port->tty->flags); if (port->count == 1) @@ -858,15 +1119,16 @@ static int isicom_setup_port(struct isi_port * port) isicom_config_port(port); port->flags |= ASYNC_INITIALIZED; - - restore_flags(flags); + spin_unlock_irqrestore(&card->card_lock, flags); return 0; } static int block_til_ready(struct tty_struct * tty, struct file * filp, struct isi_port * port) { + struct isi_board * card = port->card; int do_clocal = 0, retval; + unsigned long flags; DECLARE_WAITQUEUE(wait, current); /* block if port is in the process of being closed */ @@ -894,49 +1156,34 @@ static int block_til_ready(struct tty_struct * tty, struct file * filp, struct i if (C_CLOCAL(tty)) do_clocal = 1; -#ifdef ISICOM_DEBUG - if (do_clocal) - printk(KERN_DEBUG "ISICOM: block_til_ready: CLOCAL set.\n"); -#endif /* block waiting for DCD to be asserted, and while callout dev is busy */ retval = 0; add_wait_queue(&port->open_wait, &wait); - cli(); - if (!tty_hung_up_p(filp)) - port->count--; - sti(); + + spin_lock_irqsave(&card->card_lock, flags); + if (!tty_hung_up_p(filp)) + port->count--; port->blocked_open++; -#ifdef ISICOM_DEBUG - printk(KERN_DEBUG "ISICOM: block_til_ready: waiting for DCD...\n"); -#endif + spin_unlock_irqrestore(&card->card_lock, flags); + while (1) { - cli(); raise_dtr_rts(port); - sti(); + set_current_state(TASK_INTERRUPTIBLE); if (tty_hung_up_p(filp) || !(port->flags & ASYNC_INITIALIZED)) { if (port->flags & ASYNC_HUP_NOTIFY) retval = -EAGAIN; else retval = -ERESTARTSYS; -#ifdef ISICOM_DEBUG - printk(KERN_DEBUG "ISICOM: block_til_ready: tty_hung_up_p || not init.\n"); -#endif break; } if (!(port->flags & ASYNC_CLOSING) && (do_clocal || (port->status & ISI_DCD))) { -#ifdef ISICOM_DEBUG - printk(KERN_DEBUG "ISICOM: block_til_ready: do_clocal || DCD.\n"); -#endif break; } if (signal_pending(current)) { -#ifdef ISICOM_DEBUG - printk(KERN_DEBUG "ISICOM: block_til_ready: sig blocked.\n"); -#endif retval = -ERESTARTSYS; break; } @@ -944,9 +1191,11 @@ static int block_til_ready(struct tty_struct * tty, struct file * filp, struct i } set_current_state(TASK_RUNNING); remove_wait_queue(&port->open_wait, &wait); + spin_lock_irqsave(&card->card_lock, flags); if (!tty_hung_up_p(filp)) port->count++; port->blocked_open--; + spin_unlock_irqrestore(&card->card_lock, flags); if (retval) return retval; port->flags |= ASYNC_NORMAL_ACTIVE; @@ -960,62 +1209,33 @@ static int isicom_open(struct tty_struct * tty, struct file * filp) unsigned int line, board; int error; -#ifdef ISICOM_DEBUG - printk(KERN_DEBUG "ISICOM: open start!!!.\n"); -#endif line = tty->index; - -#ifdef ISICOM_DEBUG - printk(KERN_DEBUG "line = %d.\n", line); -#endif - - if ((line < 0) || (line > (PORT_COUNT-1))) + if (line < 0 || line > PORT_COUNT-1) return -ENODEV; board = BOARD(line); - -#ifdef ISICOM_DEBUG - printk(KERN_DEBUG "board = %d.\n", board); -#endif - card = &isi_card[board]; - if (!(card->status & FIRMWARE_LOADED)) { -#ifdef ISICOM_DEBUG - printk(KERN_DEBUG"ISICOM: Firmware not loaded to card%d.\n", board); -#endif + + if (!(card->status & FIRMWARE_LOADED)) return -ENODEV; - } /* open on a port greater than the port count for the card !!! */ - if (line > ((board * 16) + card->port_count - 1)) { - printk(KERN_ERR "ISICOM: Open on a port which exceeds the port_count of the card!\n"); + if (line > ((board * 16) + card->port_count - 1)) return -ENODEV; - } + port = &isi_ports[line]; if (isicom_paranoia_check(port, tty->name, "isicom_open")) return -ENODEV; -#ifdef ISICOM_DEBUG - printk(KERN_DEBUG "ISICOM: isicom_setup_board ...\n"); -#endif isicom_setup_board(card); port->count++; tty->driver_data = port; port->tty = tty; -#ifdef ISICOM_DEBUG - printk(KERN_DEBUG "ISICOM: isicom_setup_port ...\n"); -#endif if ((error = isicom_setup_port(port))!=0) return error; -#ifdef ISICOM_DEBUG - printk(KERN_DEBUG "ISICOM: block_til_ready ...\n"); -#endif if ((error = block_til_ready(tty, filp, port))!=0) return error; -#ifdef ISICOM_DEBUG - printk(KERN_DEBUG "ISICOM: open end!!!.\n"); -#endif return 0; } @@ -1023,38 +1243,50 @@ static int isicom_open(struct tty_struct * tty, struct file * filp) static inline void isicom_shutdown_board(struct isi_board * bp) { - int channel; - struct isi_port * port; - - if (!(bp->status & BOARD_ACTIVE)) - return; - bp->status &= ~BOARD_ACTIVE; - port = bp->ports; - for(channel = 0; channel < bp->port_count; channel++, port++) { - drop_dtr_rts(port); - } + unsigned long flags; + + spin_lock_irqsave(&bp->card_lock, flags); + if (bp->status & BOARD_ACTIVE) { + bp->status &= ~BOARD_ACTIVE; + } + spin_unlock_irqrestore(&bp->card_lock, flags); } static void isicom_shutdown_port(struct isi_port * port) { struct isi_board * card = port->card; struct tty_struct * tty; + unsigned long flags; - if (!(port->flags & ASYNC_INITIALIZED)) + tty = port->tty; + + spin_lock_irqsave(&card->card_lock, flags); + if (!(port->flags & ASYNC_INITIALIZED)) { + spin_unlock_irqrestore(&card->card_lock, flags); return; + } if (port->xmit_buf) { free_page((unsigned long) port->xmit_buf); port->xmit_buf = NULL; } - if (!(tty = port->tty) || C_HUPCL(tty)) + port->flags &= ~ASYNC_INITIALIZED; + /* 3rd October 2000 : Vinayak P Risbud */ + port->tty = 0; + spin_unlock_irqrestore(&card->card_lock, flags); + + /*Fix done by Anil .S on 30-04-2001 + remote login through isi port has dtr toggle problem + due to which the carrier drops before the password prompt + appears on the remote end. Now we drop the dtr only if the + HUPCL(Hangup on close) flag is set for the tty*/ + + if (C_HUPCL(tty)) /* drop dtr on this port */ drop_dtr(port); /* any other port uninits */ - if (tty) set_bit(TTY_IO_ERROR, &tty->flags); - port->flags &= ~ASYNC_INITIALIZED; if (--card->count < 0) { printk(KERN_DEBUG "ISICOM: isicom_shutdown_port: bad board(0x%x) count %d.\n", @@ -1063,8 +1295,10 @@ static void isicom_shutdown_port(struct isi_port * port) } /* last port was closed , shutdown that boad too */ - if (!card->count) - isicom_shutdown_board(card); + if(C_HUPCL(tty)) { + if (!card->count) + isicom_shutdown_board(card); + } } static void isicom_close(struct tty_struct * tty, struct file * filp) @@ -1082,13 +1316,13 @@ static void isicom_close(struct tty_struct * tty, struct file * filp) printk(KERN_DEBUG "ISICOM: Close start!!!.\n"); #endif - save_flags(flags); cli(); + spin_lock_irqsave(&card->card_lock, flags); if (tty_hung_up_p(filp)) { - restore_flags(flags); + spin_unlock_irqrestore(&card->card_lock, flags); return; } - if ((tty->count == 1) && (port->count != 1)) { + if (tty->count == 1 && port->count != 1) { printk(KERN_WARNING "ISICOM:(0x%x) isicom_close: bad port count" "tty->count = 1 port count = %d.\n", card->base, port->count); @@ -1102,104 +1336,81 @@ static void isicom_close(struct tty_struct * tty, struct file * filp) } if (port->count) { - restore_flags(flags); + spin_unlock_irqrestore(&card->card_lock, flags); return; } port->flags |= ASYNC_CLOSING; tty->closing = 1; + spin_unlock_irqrestore(&card->card_lock, flags); + if (port->closing_wait != ASYNC_CLOSING_WAIT_NONE) tty_wait_until_sent(tty, port->closing_wait); /* indicate to the card that no more data can be received on this port */ + spin_lock_irqsave(&card->card_lock, flags); if (port->flags & ASYNC_INITIALIZED) { card->port_status &= ~(1 << port->channel); outw(card->port_status, card->base + 0x02); } isicom_shutdown_port(port); + spin_unlock_irqrestore(&card->card_lock, flags); + if (tty->driver->flush_buffer) tty->driver->flush_buffer(tty); - tty_ldisc_flush(tty); + + spin_lock_irqsave(&card->card_lock, flags); tty->closing = 0; - port->tty = NULL; + if (port->blocked_open) { + spin_unlock_irqrestore(&card->card_lock, flags); if (port->close_delay) { - set_current_state(TASK_INTERRUPTIBLE); #ifdef ISICOM_DEBUG printk(KERN_DEBUG "ISICOM: scheduling until time out.\n"); #endif - schedule_timeout(port->close_delay); + msleep_interruptible(jiffies_to_msecs(port->close_delay)); } + spin_lock_irqsave(&card->card_lock, flags); wake_up_interruptible(&port->open_wait); } port->flags &= ~(ASYNC_NORMAL_ACTIVE | ASYNC_CLOSING); wake_up_interruptible(&port->close_wait); - restore_flags(flags); -#ifdef ISICOM_DEBUG - printk(KERN_DEBUG "ISICOM: Close end!!!.\n"); -#endif + spin_unlock_irqrestore(&card->card_lock, flags); } /* write et all */ -static int isicom_write(struct tty_struct * tty, int from_user, +static int isicom_write(struct tty_struct * tty, const unsigned char * buf, int count) { struct isi_port * port = (struct isi_port *) tty->driver_data; + struct isi_board * card = port->card; unsigned long flags; int cnt, total = 0; -#ifdef ISICOM_DEBUG - printk(KERN_DEBUG "ISICOM: isicom_write for port%d: %d bytes.\n", - port->channel+1, count); -#endif + if (isicom_paranoia_check(port, tty->name, "isicom_write")) return 0; if (!tty || !port->xmit_buf || !tmp_buf) return 0; - if (from_user) - down(&tmp_buf_sem); /* acquire xclusive access to tmp_buf */ - save_flags(flags); + spin_lock_irqsave(&card->card_lock, flags); + while(1) { - cli(); cnt = min_t(int, count, min(SERIAL_XMIT_SIZE - port->xmit_cnt - 1, SERIAL_XMIT_SIZE - port->xmit_head)); if (cnt <= 0) break; - if (from_user) { - /* the following may block for paging... hence - enabling interrupts but tx routine may have - created more space in xmit_buf when the ctrl - gets back here */ - sti(); - if (copy_from_user(tmp_buf, buf, cnt)) { - up(&tmp_buf_sem); - restore_flags(flags); - return -EFAULT; - } - cli(); - cnt = min_t(int, cnt, min(SERIAL_XMIT_SIZE - port->xmit_cnt - 1, - SERIAL_XMIT_SIZE - port->xmit_head)); - memcpy(port->xmit_buf + port->xmit_head, tmp_buf, cnt); - } - else - memcpy(port->xmit_buf + port->xmit_head, buf, cnt); + memcpy(port->xmit_buf + port->xmit_head, buf, cnt); port->xmit_head = (port->xmit_head + cnt) & (SERIAL_XMIT_SIZE - 1); port->xmit_cnt += cnt; - restore_flags(flags); buf += cnt; count -= cnt; total += cnt; } - if (from_user) - up(&tmp_buf_sem); if (port->xmit_cnt && !tty->stopped && !tty->hw_stopped) port->status |= ISI_TXOK; - restore_flags(flags); -#ifdef ISICOM_DEBUG - printk(KERN_DEBUG "ISICOM: isicom_write %d bytes written.\n", total); -#endif + spin_unlock_irqrestore(&card->card_lock, flags); return total; } @@ -1207,6 +1418,7 @@ static int isicom_write(struct tty_struct * tty, int from_user, static void isicom_put_char(struct tty_struct * tty, unsigned char ch) { struct isi_port * port = (struct isi_port *) tty->driver_data; + struct isi_board * card = port->card; unsigned long flags; if (isicom_paranoia_check(port, tty->name, "isicom_put_char")) @@ -1214,21 +1426,17 @@ static void isicom_put_char(struct tty_struct * tty, unsigned char ch) if (!tty || !port->xmit_buf) return; -#ifdef ISICOM_DEBUG - printk(KERN_DEBUG "ISICOM: put_char, port %d, char %c.\n", port->channel+1, ch); -#endif - - save_flags(flags); cli(); - - if (port->xmit_cnt >= (SERIAL_XMIT_SIZE - 1)) { - restore_flags(flags); + + spin_lock_irqsave(&card->card_lock, flags); + if (port->xmit_cnt >= SERIAL_XMIT_SIZE - 1) { + spin_unlock_irqrestore(&card->card_lock, flags); return; } port->xmit_buf[port->xmit_head++] = ch; port->xmit_head &= (SERIAL_XMIT_SIZE - 1); port->xmit_cnt++; - restore_flags(flags); + spin_unlock_irqrestore(&card->card_lock, flags); } /* flush_chars et all */ @@ -1239,8 +1447,7 @@ static void isicom_flush_chars(struct tty_struct * tty) if (isicom_paranoia_check(port, tty->name, "isicom_flush_chars")) return; - if (port->xmit_cnt <= 0 || tty->stopped || tty->hw_stopped || - !port->xmit_buf) + if (port->xmit_cnt <= 0 || tty->stopped || tty->hw_stopped || !port->xmit_buf) return; /* this tells the transmitter to consider this port for @@ -1253,6 +1460,7 @@ static int isicom_write_room(struct tty_struct * tty) { struct isi_port * port = (struct isi_port *) tty->driver_data; int free; + if (isicom_paranoia_check(port, tty->name, "isicom_write_room")) return 0; @@ -1275,21 +1483,17 @@ static int isicom_chars_in_buffer(struct tty_struct * tty) static inline void isicom_send_break(struct isi_port * port, unsigned long length) { struct isi_board * card = port->card; - short wait = 10; unsigned short base = card->base; - unsigned long flags; - save_flags(flags); cli(); - while (((inw(base + 0x0e) & 0x0001) == 0) && (wait-- > 0)); - if (!wait) { - printk(KERN_DEBUG "ISICOM: Card found busy in isicom_send_break.\n"); - goto out; - } + if(!lock_card(card)) + return; + outw(0x8000 | ((port->channel) << (card->shift_count)) | 0x3, base); outw((length & 0xff) << 8 | 0x00, base); outw((length & 0xff00), base); InterruptTheCard(base); -out: restore_flags(flags); + + unlock_card(card); } static int isicom_tiocmget(struct tty_struct *tty, struct file *file) @@ -1313,12 +1517,10 @@ static int isicom_tiocmset(struct tty_struct *tty, struct file *file, unsigned int set, unsigned int clear) { struct isi_port * port = (struct isi_port *) tty->driver_data; - unsigned long flags; if (isicom_paranoia_check(port, tty->name, "isicom_ioctl")) return -ENODEV; - save_flags(flags); cli(); if (set & TIOCM_RTS) raise_rts(port); if (set & TIOCM_DTR) @@ -1329,7 +1531,6 @@ static int isicom_tiocmset(struct tty_struct *tty, struct file *file, if (clear & TIOCM_DTR) drop_dtr(port); - restore_flags(flags); return 0; } @@ -1337,7 +1538,6 @@ static int isicom_set_serial_info(struct isi_port * port, struct serial_struct __user *info) { struct serial_struct newinfo; - unsigned long flags; int reconfig_port; if(copy_from_user(&newinfo, info, sizeof(newinfo))) @@ -1362,9 +1562,7 @@ static int isicom_set_serial_info(struct isi_port * port, (newinfo.flags & ASYNC_FLAGS)); } if (reconfig_port) { - save_flags(flags); cli(); isicom_config_port(port); - restore_flags(flags); } return 0; } @@ -1443,7 +1641,6 @@ static int isicom_ioctl(struct tty_struct * tty, struct file * filp, static void isicom_set_termios(struct tty_struct * tty, struct termios * old_termios) { struct isi_port * port = (struct isi_port *) tty->driver_data; - unsigned long flags; if (isicom_paranoia_check(port, tty->name, "isicom_set_termios")) return; @@ -1452,9 +1649,7 @@ static void isicom_set_termios(struct tty_struct * tty, struct termios * old_ter tty->termios->c_iflag == old_termios->c_iflag) return; - save_flags(flags); cli(); isicom_config_port(port); - restore_flags(flags); if ((old_termios->c_cflag & CRTSCTS) && !(tty->termios->c_cflag & CRTSCTS)) { @@ -1468,16 +1663,13 @@ static void isicom_throttle(struct tty_struct * tty) { struct isi_port * port = (struct isi_port *) tty->driver_data; struct isi_board * card = port->card; - unsigned long flags; if (isicom_paranoia_check(port, tty->name, "isicom_throttle")) return; /* tell the card that this port cannot handle any more data for now */ - save_flags(flags); cli(); card->port_status &= ~(1 << port->channel); outw(card->port_status, card->base + 0x02); - restore_flags(flags); } /* unthrottle et all */ @@ -1485,16 +1677,13 @@ static void isicom_unthrottle(struct tty_struct * tty) { struct isi_port * port = (struct isi_port *) tty->driver_data; struct isi_board * card = port->card; - unsigned long flags; if (isicom_paranoia_check(port, tty->name, "isicom_unthrottle")) return; /* tell the card that this port is ready to accept more data */ - save_flags(flags); cli(); card->port_status |= (1 << port->channel); outw(card->port_status, card->base + 0x02); - restore_flags(flags); } /* stop et all */ @@ -1531,7 +1720,7 @@ static void do_isicom_hangup(void * data) tty = port->tty; if (tty) - tty_hangup(tty); /* FIXME: module removal race here - AKPM */ + tty_hangup(tty); } static void isicom_hangup(struct tty_struct * tty) @@ -1552,21 +1741,22 @@ static void isicom_hangup(struct tty_struct * tty) static void isicom_flush_buffer(struct tty_struct * tty) { struct isi_port * port = (struct isi_port *) tty->driver_data; + struct isi_board * card = port->card; unsigned long flags; if (isicom_paranoia_check(port, tty->name, "isicom_flush_buffer")) return; - save_flags(flags); cli(); + spin_lock_irqsave(&card->card_lock, flags); port->xmit_cnt = port->xmit_head = port->xmit_tail = 0; - restore_flags(flags); + spin_unlock_irqrestore(&card->card_lock, flags); wake_up_interruptible(&tty->write_wait); tty_wakeup(tty); } -static int register_ioregion(void) +static int __init register_ioregion(void) { int count, done=0; for (count=0; count < BOARD_COUNT; count++ ) { @@ -1581,7 +1771,7 @@ static int register_ioregion(void) return done; } -static void unregister_ioregion(void) +static void __exit unregister_ioregion(void) { int count; for (count=0; count < BOARD_COUNT; count++ ) @@ -1613,7 +1803,7 @@ static struct tty_operations isicom_ops = { .tiocmset = isicom_tiocmset, }; -static int register_drivers(void) +static int __init register_drivers(void) { int error; @@ -1644,7 +1834,7 @@ static int register_drivers(void) return 0; } -static void unregister_drivers(void) +static void __exit unregister_drivers(void) { int error = tty_unregister_driver(isicom_normal); if (error) @@ -1652,78 +1842,48 @@ static void unregister_drivers(void) put_tty_driver(isicom_normal); } -static int register_isr(void) +static int __init register_isr(void) { - int count, done=0, card; - int flag; - unsigned char request; + int count, done=0; + unsigned long irqflags; + for (count=0; count < BOARD_COUNT; count++ ) { if (isi_card[count].base) { - /* - * verify if the required irq has already been requested for - * another ISI Card, if so we already have it, else request it - */ - request = YES; - for(card = 0; card < count; card++) - if ((isi_card[card].base) && (isi_card[card].irq == isi_card[count].irq)) { - request = NO; - if ((isi_card[count].isa == NO) && (isi_card[card].isa == NO)) - break; - /* - * ISA cards cannot share interrupts with other - * PCI or ISA devices hence disable this card. - */ + irqflags = (isi_card[count].isa == YES) ? + SA_INTERRUPT : + (SA_INTERRUPT | SA_SHIRQ); + + if (request_irq(isi_card[count].irq, + isicom_interrupt, + irqflags, + ISICOM_NAME, &isi_card[count])) { + + printk(KERN_WARNING "ISICOM: Could not" + " install handler at Irq %d." + " Card%d will be disabled.\n", + isi_card[count].irq, count+1); + release_region(isi_card[count].base,16); - isi_card[count].base = 0; - break; - } - flag=0; - if(isi_card[count].isa == NO) - flag |= SA_SHIRQ; - - if (request == YES) { - if (request_irq(isi_card[count].irq, isicom_interrupt, SA_INTERRUPT|flag, ISICOM_NAME, NULL)) { - printk(KERN_WARNING "ISICOM: Could not install handler at Irq %d. Card%d will be disabled.\n", - isi_card[count].irq, count+1); - release_region(isi_card[count].base,16); - isi_card[count].base=0; - } - else { - printk(KERN_INFO "ISICOM: Card%d at 0x%x using irq %d.\n", - count+1, isi_card[count].base, isi_card[count].irq); - - irq_to_board[isi_card[count].irq]=&isi_card[count]; - done++; - } + isi_card[count].base=0; } + else + done++; } } return done; } -static void unregister_isr(void) +static void __exit unregister_isr(void) { - int count, card; - unsigned char freeirq; + int count; + for (count=0; count < BOARD_COUNT; count++ ) { - if (isi_card[count].base) { - freeirq = YES; - for(card = 0; card < count; card++) - if ((isi_card[card].base) && (isi_card[card].irq == isi_card[count].irq)) { - freeirq = NO; - break; - } - if (freeirq == YES) { - free_irq(isi_card[count].irq, NULL); -#ifdef ISICOM_DEBUG - printk(KERN_DEBUG "ISICOM: Irq %d released for Card%d.\n",isi_card[count].irq, count+1); -#endif - } - } + if (isi_card[count].base) + free_irq(isi_card[count].irq, &isi_card[count]); } } -static int isicom_init(void) +static int __init isicom_init(void) { int card, channel, base; struct isi_port * port; @@ -1766,11 +1926,12 @@ static int isicom_init(void) for (card = 0; card < BOARD_COUNT; card++) { port = &isi_ports[card * 16]; isi_card[card].ports = port; + spin_lock_init(&isi_card[card].card_lock); base = isi_card[card].base; for (channel = 0; channel < 16; channel++, port++) { port->magic = ISICOM_MAGIC; port->card = &isi_card[card]; - port->channel = channel; + port->channel = channel; port->close_delay = 50 * HZ/100; port->closing_wait = 3000 * HZ/100; INIT_WORK(&port->hangup_tq, do_isicom_hangup, port); @@ -1795,12 +1956,12 @@ static int irq[4]; MODULE_AUTHOR("MultiTech"); MODULE_DESCRIPTION("Driver for the ISI series of cards by MultiTech"); MODULE_LICENSE("GPL"); -MODULE_PARM(io, "1-4i"); +module_param_array(io, int, NULL, 0); MODULE_PARM_DESC(io, "I/O ports for the cards"); -MODULE_PARM(irq, "1-4i"); +module_param_array(irq, int, NULL, 0); MODULE_PARM_DESC(irq, "Interrupts for the cards"); -int init_module(void) +static int __devinit isicom_setup(void) { struct pci_dev *dev = NULL; int retval, card, idx, count; @@ -1900,38 +2061,19 @@ int init_module(void) return 0; } -void cleanup_module(void) +static void __exit isicom_exit(void) { re_schedule = 0; + /* FIXME */ msleep(1000); - -#ifdef ISICOM_DEBUG - printk("ISICOM: isicom_tx tx_count = %ld.\n", tx_count); -#endif - -#ifdef ISICOM_DEBUG - printk("ISICOM: uregistering isr ...\n"); -#endif unregister_isr(); - -#ifdef ISICOM_DEBUG - printk("ISICOM: unregistering drivers ...\n"); -#endif unregister_drivers(); - -#ifdef ISICOM_DEBUG - printk("ISICOM: unregistering ioregion ...\n"); -#endif unregister_ioregion(); - -#ifdef ISICOM_DEBUG - printk("ISICOM: freeing tmp_buf ...\n"); -#endif - free_page((unsigned long)tmp_buf); - -#ifdef ISICOM_DEBUG - printk("ISICOM: unregistering firmware loader ...\n"); -#endif + if(tmp_buf) + free_page((unsigned long)tmp_buf); if (misc_deregister(&isiloader_device)) printk(KERN_ERR "ISICOM: Unable to unregister Firmware Loader driver\n"); } + +module_init(isicom_setup); +module_exit(isicom_exit);