Index: head/sys/amd64/amd64/apic_vector.S =================================================================== --- head/sys/amd64/amd64/apic_vector.S (revision 38245) +++ head/sys/amd64/amd64/apic_vector.S (revision 38246) @@ -1,1047 +1,1047 @@ /* * from: vector.s, 386BSD 0.1 unknown origin - * $Id: apic_vector.s,v 1.30 1998/05/17 22:12:04 tegge Exp $ + * $Id: apic_vector.s,v 1.31 1998/08/11 15:08:12 bde Exp $ */ #include #include #include "i386/isa/intr_machdep.h" #ifdef FAST_SIMPLELOCK #define GET_FAST_INTR_LOCK \ pushl $_fast_intr_lock ; /* address of lock */ \ call _s_lock ; /* MP-safe */ \ addl $4,%esp #define REL_FAST_INTR_LOCK \ pushl $_fast_intr_lock ; /* address of lock */ \ call _s_unlock ; /* MP-safe */ \ addl $4,%esp #else /* FAST_SIMPLELOCK */ #define GET_FAST_INTR_LOCK \ call _get_isrlock #define REL_FAST_INTR_LOCK \ pushl $_mp_lock ; /* GIANT_LOCK */ \ call _MPrellock ; \ add $4, %esp #endif /* FAST_SIMPLELOCK */ /* convert an absolute IRQ# into a bitmask */ #define IRQ_BIT(irq_num) (1 << (irq_num)) /* make an index into the IO APIC from the IRQ# */ #define REDTBL_IDX(irq_num) (0x10 + ((irq_num) * 2)) /* * Macros for interrupt interrupt entry, call to handler, and exit. */ #ifdef FAST_WITHOUTCPL /* */ #define FAST_INTR(irq_num, vec_name) \ .text ; \ SUPERALIGN_TEXT ; \ IDTVEC(vec_name) ; \ pushl %eax ; /* save only call-used registers */ \ pushl %ecx ; \ pushl %edx ; \ pushl %ds ; \ MAYBE_PUSHL_ES ; \ movl $KDSEL,%eax ; \ movl %ax,%ds ; \ MAYBE_MOVW_AX_ES ; \ FAKE_MCOUNT((4+ACTUALLY_PUSHED)*4(%esp)) ; \ pushl _intr_unit + (irq_num) * 4 ; \ GET_FAST_INTR_LOCK ; \ call *_intr_handler + (irq_num) * 4 ; /* do the work ASAP */ \ REL_FAST_INTR_LOCK ; \ addl $4, %esp ; \ movl $0, lapic_eoi ; \ lock ; \ incl _cnt+V_INTR ; /* book-keeping can wait */ \ movl _intr_countp + (irq_num) * 4, %eax ; \ lock ; \ incl (%eax) ; \ MEXITCOUNT ; \ MAYBE_POPL_ES ; \ popl %ds ; \ popl %edx ; \ popl %ecx ; \ popl %eax ; \ iret #else /* FAST_WITHOUTCPL */ #define FAST_INTR(irq_num, vec_name) \ .text ; \ SUPERALIGN_TEXT ; \ IDTVEC(vec_name) ; \ pushl %eax ; /* save only call-used registers */ \ pushl %ecx ; \ pushl %edx ; \ pushl %ds ; \ MAYBE_PUSHL_ES ; \ movl $KDSEL, %eax ; \ movl %ax, %ds ; \ MAYBE_MOVW_AX_ES ; \ FAKE_MCOUNT((4+ACTUALLY_PUSHED)*4(%esp)) ; \ GET_FAST_INTR_LOCK ; \ pushl _intr_unit + (irq_num) * 4 ; \ call *_intr_handler + (irq_num) * 4 ; /* do the work ASAP */ \ addl $4, %esp ; \ movl $0, lapic_eoi ; \ lock ; \ incl _cnt+V_INTR ; /* book-keeping can wait */ \ movl _intr_countp + (irq_num) * 4,%eax ; \ lock ; \ incl (%eax) ; \ movl _cpl, %eax ; /* unmasking pending HWIs or SWIs? */ \ notl %eax ; \ andl _ipending, %eax ; \ jne 2f ; /* yes, maybe handle them */ \ 1: ; \ MEXITCOUNT ; \ REL_FAST_INTR_LOCK ; \ MAYBE_POPL_ES ; \ popl %ds ; \ popl %edx ; \ popl %ecx ; \ popl %eax ; \ iret ; \ ; \ ALIGN_TEXT ; \ 2: ; \ cmpb $3, _intr_nesting_level ; /* enough stack? */ \ jae 1b ; /* no, return */ \ movl _cpl, %eax ; \ /* XXX next line is probably unnecessary now. */ \ movl $HWI_MASK|SWI_MASK, _cpl ; /* limit nesting ... */ \ lock ; \ incb _intr_nesting_level ; /* ... really limit it ... */ \ sti ; /* to do this as early as possible */ \ MAYBE_POPL_ES ; /* discard most of thin frame ... */ \ popl %ecx ; /* ... original %ds ... */ \ popl %edx ; \ xchgl %eax, 4(%esp) ; /* orig %eax; save cpl */ \ pushal ; /* build fat frame (grrr) ... */ \ pushl %ecx ; /* ... actually %ds ... */ \ pushl %es ; \ movl $KDSEL, %eax ; \ movl %ax, %es ; \ movl (2+8+0)*4(%esp), %ecx ; /* %ecx from thin frame ... */ \ movl %ecx, (2+6)*4(%esp) ; /* ... to fat frame ... */ \ movl (2+8+1)*4(%esp), %eax ; /* ... cpl from thin frame */ \ pushl %eax ; \ subl $4, %esp ; /* junk for unit number */ \ MEXITCOUNT ; \ jmp _doreti #endif /** FAST_WITHOUTCPL */ /* * */ #define PUSH_FRAME \ pushl $0 ; /* dummy error code */ \ pushl $0 ; /* dummy trap type */ \ pushal ; \ pushl %ds ; /* save data and extra segments ... */ \ pushl %es #define POP_FRAME \ popl %es ; \ popl %ds ; \ popal ; \ addl $4+4,%esp #define MASK_IRQ(irq_num) \ IMASK_LOCK ; /* into critical reg */ \ testl $IRQ_BIT(irq_num), _apic_imen ; \ jne 7f ; /* masked, don't mask */ \ orl $IRQ_BIT(irq_num), _apic_imen ; /* set the mask bit */ \ movl _ioapic, %ecx ; /* ioapic[0] addr */ \ movl $REDTBL_IDX(irq_num), (%ecx) ; /* write the index */ \ movl IOAPIC_WINDOW(%ecx), %eax ; /* current value */ \ orl $IOART_INTMASK, %eax ; /* set the mask */ \ movl %eax, IOAPIC_WINDOW(%ecx) ; /* new value */ \ 7: ; /* already masked */ \ IMASK_UNLOCK /* * Test to see whether we are handling an edge or level triggered INT. * Level-triggered INTs must still be masked as we don't clear the source, * and the EOI cycle would cause redundant INTs to occur. */ #define MASK_LEVEL_IRQ(irq_num) \ testl $IRQ_BIT(irq_num), _apic_pin_trigger ; \ jz 9f ; /* edge, don't mask */ \ MASK_IRQ(irq_num) ; \ 9: #ifdef APIC_INTR_REORDER #define EOI_IRQ(irq_num) \ movl _apic_isrbit_location + 8 * (irq_num), %eax ; \ movl (%eax), %eax ; \ testl _apic_isrbit_location + 4 + 8 * (irq_num), %eax ; \ jz 9f ; /* not active */ \ movl $0, lapic_eoi ; \ APIC_ITRACE(apic_itrace_eoi, irq_num, APIC_ITRACE_EOI) ; \ 9: #else #define EOI_IRQ(irq_num) \ testl $IRQ_BIT(irq_num), lapic_isr1; \ jz 9f ; /* not active */ \ movl $0, lapic_eoi; \ APIC_ITRACE(apic_itrace_eoi, irq_num, APIC_ITRACE_EOI) ; \ 9: #endif /* * Test to see if the source is currntly masked, clear if so. */ #define UNMASK_IRQ(irq_num) \ IMASK_LOCK ; /* into critical reg */ \ testl $IRQ_BIT(irq_num), _apic_imen ; \ je 7f ; /* bit clear, not masked */ \ andl $~IRQ_BIT(irq_num), _apic_imen ;/* clear mask bit */ \ movl _ioapic,%ecx ; /* ioapic[0]addr */ \ movl $REDTBL_IDX(irq_num),(%ecx) ; /* write the index */ \ movl IOAPIC_WINDOW(%ecx),%eax ; /* current value */ \ andl $~IOART_INTMASK,%eax ; /* clear the mask */ \ movl %eax,IOAPIC_WINDOW(%ecx) ; /* new value */ \ 7: ; \ IMASK_UNLOCK #ifdef INTR_SIMPLELOCK #define ENLOCK #define DELOCK #define LATELOCK call _get_isrlock #else #define ENLOCK \ ISR_TRYLOCK ; /* XXX this is going away... */ \ testl %eax, %eax ; /* did we get it? */ \ jz 3f #define DELOCK ISR_RELLOCK #define LATELOCK #endif #ifdef APIC_INTR_DIAGNOSTIC #ifdef APIC_INTR_DIAGNOSTIC_IRQ log_intr_event: pushf cli pushl $CNAME(apic_itrace_debuglock) call _s_lock_np addl $4, %esp movl CNAME(apic_itrace_debugbuffer_idx), %ecx andl $32767, %ecx movl _cpuid, %eax shll $8, %eax orl 8(%esp), %eax movw %ax, CNAME(apic_itrace_debugbuffer)(,%ecx,2) incl %ecx andl $32767, %ecx movl %ecx, CNAME(apic_itrace_debugbuffer_idx) pushl $CNAME(apic_itrace_debuglock) call _s_unlock_np addl $4, %esp popf ret #define APIC_ITRACE(name, irq_num, id) \ lock ; /* MP-safe */ \ incl CNAME(name) + (irq_num) * 4 ; \ pushl %eax ; \ pushl %ecx ; \ pushl %edx ; \ movl $(irq_num), %eax ; \ cmpl $APIC_INTR_DIAGNOSTIC_IRQ, %eax ; \ jne 7f ; \ pushl $id ; \ call log_intr_event ; \ addl $4, %esp ; \ 7: ; \ popl %edx ; \ popl %ecx ; \ popl %eax #else #define APIC_ITRACE(name, irq_num, id) \ lock ; /* MP-safe */ \ incl CNAME(name) + (irq_num) * 4 #endif #define APIC_ITRACE_ENTER 1 #define APIC_ITRACE_EOI 2 #define APIC_ITRACE_TRYISRLOCK 3 #define APIC_ITRACE_GOTISRLOCK 4 #define APIC_ITRACE_ENTER2 5 #define APIC_ITRACE_LEAVE 6 #define APIC_ITRACE_UNMASK 7 #define APIC_ITRACE_ACTIVE 8 #define APIC_ITRACE_MASKED 9 #define APIC_ITRACE_NOISRLOCK 10 #define APIC_ITRACE_MASKED2 11 #define APIC_ITRACE_SPLZ 12 #define APIC_ITRACE_DORETI 13 #else #define APIC_ITRACE(name, irq_num, id) #endif #ifdef CPL_AND_CML #define INTR(irq_num, vec_name) \ .text ; \ SUPERALIGN_TEXT ; \ /* _XintrNN: entry point used by IDT/HWIs & splz_unpend via _vec[]. */ \ IDTVEC(vec_name) ; \ PUSH_FRAME ; \ movl $KDSEL, %eax ; /* reload with kernel's data segment */ \ movl %ax, %ds ; \ movl %ax, %es ; \ ; \ APIC_ITRACE(apic_itrace_enter, irq_num, APIC_ITRACE_ENTER) ; \ lock ; /* MP-safe */ \ btsl $(irq_num), iactive ; /* lazy masking */ \ jc 1f ; /* already active */ \ ; \ MASK_LEVEL_IRQ(irq_num) ; \ EOI_IRQ(irq_num) ; \ 0: ; \ APIC_ITRACE(apic_itrace_tryisrlock, irq_num, APIC_ITRACE_TRYISRLOCK) ;\ ENLOCK ; \ ; \ APIC_ITRACE(apic_itrace_gotisrlock, irq_num, APIC_ITRACE_GOTISRLOCK) ;\ AVCPL_LOCK ; /* MP-safe */ \ testl $IRQ_BIT(irq_num), _cpl ; \ jne 2f ; /* this INT masked */ \ testl $IRQ_BIT(irq_num), _cml ; \ jne 2f ; /* this INT masked */ \ orl $IRQ_BIT(irq_num), _cil ; \ AVCPL_UNLOCK ; \ ; \ incb _intr_nesting_level ; \ ; \ /* entry point used by doreti_unpend for HWIs. */ \ __CONCAT(Xresume,irq_num): ; \ FAKE_MCOUNT(12*4(%esp)) ; /* XXX avoid dbl cnt */ \ lock ; incl _cnt+V_INTR ; /* tally interrupts */ \ movl _intr_countp + (irq_num) * 4, %eax ; \ lock ; incl (%eax) ; \ ; \ AVCPL_LOCK ; /* MP-safe */ \ movl _cml, %eax ; \ pushl %eax ; \ orl _intr_mask + (irq_num) * 4, %eax ; \ movl %eax, _cml ; \ AVCPL_UNLOCK ; \ ; \ pushl _intr_unit + (irq_num) * 4 ; \ incl _inside_intr ; \ APIC_ITRACE(apic_itrace_enter2, irq_num, APIC_ITRACE_ENTER2) ; \ sti ; \ call *_intr_handler + (irq_num) * 4 ; \ cli ; \ APIC_ITRACE(apic_itrace_leave, irq_num, APIC_ITRACE_LEAVE) ; \ decl _inside_intr ; \ ; \ lock ; andl $~IRQ_BIT(irq_num), iactive ; \ lock ; andl $~IRQ_BIT(irq_num), _cil ; \ UNMASK_IRQ(irq_num) ; \ APIC_ITRACE(apic_itrace_unmask, irq_num, APIC_ITRACE_UNMASK) ; \ sti ; /* doreti repeats cli/sti */ \ MEXITCOUNT ; \ LATELOCK ; \ jmp _doreti ; \ ; \ ALIGN_TEXT ; \ 1: ; /* active */ \ APIC_ITRACE(apic_itrace_active, irq_num, APIC_ITRACE_ACTIVE) ; \ MASK_IRQ(irq_num) ; \ EOI_IRQ(irq_num) ; \ AVCPL_LOCK ; /* MP-safe */ \ orl $IRQ_BIT(irq_num), _ipending ; \ AVCPL_UNLOCK ; \ lock ; \ btsl $(irq_num), iactive ; /* still active */ \ jnc 0b ; /* retry */ \ POP_FRAME ; \ iret ; \ ; \ ALIGN_TEXT ; \ 2: ; /* masked by cpl|cml */ \ APIC_ITRACE(apic_itrace_masked, irq_num, APIC_ITRACE_MASKED) ; \ orl $IRQ_BIT(irq_num), _ipending ; \ AVCPL_UNLOCK ; \ DELOCK ; /* XXX this is going away... */ \ POP_FRAME ; \ iret ; \ ALIGN_TEXT ; \ 3: ; /* other cpu has isr lock */ \ APIC_ITRACE(apic_itrace_noisrlock, irq_num, APIC_ITRACE_NOISRLOCK) ;\ AVCPL_LOCK ; /* MP-safe */ \ orl $IRQ_BIT(irq_num), _ipending ; \ testl $IRQ_BIT(irq_num), _cpl ; \ jne 4f ; /* this INT masked */ \ testl $IRQ_BIT(irq_num), _cml ; \ jne 4f ; /* this INT masked */ \ orl $IRQ_BIT(irq_num), _cil ; \ AVCPL_UNLOCK ; \ call forward_irq ; /* forward irq to lock holder */ \ POP_FRAME ; /* and return */ \ iret ; \ ALIGN_TEXT ; \ 4: ; /* blocked */ \ APIC_ITRACE(apic_itrace_masked2, irq_num, APIC_ITRACE_MASKED2) ;\ AVCPL_UNLOCK ; \ POP_FRAME ; /* and return */ \ iret #else /* CPL_AND_CML */ #define INTR(irq_num, vec_name) \ .text ; \ SUPERALIGN_TEXT ; \ /* _XintrNN: entry point used by IDT/HWIs & splz_unpend via _vec[]. */ \ IDTVEC(vec_name) ; \ PUSH_FRAME ; \ movl $KDSEL, %eax ; /* reload with kernel's data segment */ \ movl %ax, %ds ; \ movl %ax, %es ; \ ; \ APIC_ITRACE(apic_itrace_enter, irq_num, APIC_ITRACE_ENTER) ; \ lock ; /* MP-safe */ \ btsl $(irq_num), iactive ; /* lazy masking */ \ jc 1f ; /* already active */ \ ; \ MASK_LEVEL_IRQ(irq_num) ; \ EOI_IRQ(irq_num) ; \ 0: ; \ APIC_ITRACE(apic_itrace_tryisrlock, irq_num, APIC_ITRACE_TRYISRLOCK) ;\ ISR_TRYLOCK ; /* XXX this is going away... */ \ testl %eax, %eax ; /* did we get it? */ \ jz 3f ; /* no */ \ ; \ APIC_ITRACE(apic_itrace_gotisrlock, irq_num, APIC_ITRACE_GOTISRLOCK) ;\ AVCPL_LOCK ; /* MP-safe */ \ testl $IRQ_BIT(irq_num), _cpl ; \ jne 2f ; /* this INT masked */ \ AVCPL_UNLOCK ; \ ; \ incb _intr_nesting_level ; \ ; \ /* entry point used by doreti_unpend for HWIs. */ \ __CONCAT(Xresume,irq_num): ; \ FAKE_MCOUNT(12*4(%esp)) ; /* XXX avoid dbl cnt */ \ lock ; incl _cnt+V_INTR ; /* tally interrupts */ \ movl _intr_countp + (irq_num) * 4, %eax ; \ lock ; incl (%eax) ; \ ; \ AVCPL_LOCK ; /* MP-safe */ \ movl _cpl, %eax ; \ pushl %eax ; \ orl _intr_mask + (irq_num) * 4, %eax ; \ movl %eax, _cpl ; \ andl $~IRQ_BIT(irq_num), _ipending ; \ AVCPL_UNLOCK ; \ ; \ pushl _intr_unit + (irq_num) * 4 ; \ APIC_ITRACE(apic_itrace_enter2, irq_num, APIC_ITRACE_ENTER2) ; \ sti ; \ call *_intr_handler + (irq_num) * 4 ; \ cli ; \ APIC_ITRACE(apic_itrace_leave, irq_num, APIC_ITRACE_LEAVE) ; \ ; \ lock ; andl $~IRQ_BIT(irq_num), iactive ; \ UNMASK_IRQ(irq_num) ; \ APIC_ITRACE(apic_itrace_unmask, irq_num, APIC_ITRACE_UNMASK) ; \ sti ; /* doreti repeats cli/sti */ \ MEXITCOUNT ; \ jmp _doreti ; \ ; \ ALIGN_TEXT ; \ 1: ; /* active */ \ APIC_ITRACE(apic_itrace_active, irq_num, APIC_ITRACE_ACTIVE) ; \ MASK_IRQ(irq_num) ; \ EOI_IRQ(irq_num) ; \ AVCPL_LOCK ; /* MP-safe */ \ orl $IRQ_BIT(irq_num), _ipending ; \ AVCPL_UNLOCK ; \ lock ; \ btsl $(irq_num), iactive ; /* still active */ \ jnc 0b ; /* retry */ \ POP_FRAME ; \ iret ; /* XXX: iactive bit might be 0 now */ \ ALIGN_TEXT ; \ 2: ; /* masked by cpl, leave iactive set */ \ APIC_ITRACE(apic_itrace_masked, irq_num, APIC_ITRACE_MASKED) ; \ orl $IRQ_BIT(irq_num), _ipending ; \ AVCPL_UNLOCK ; \ ISR_RELLOCK ; /* XXX this is going away... */ \ POP_FRAME ; \ iret ; \ ALIGN_TEXT ; \ 3: ; /* other cpu has isr lock */ \ APIC_ITRACE(apic_itrace_noisrlock, irq_num, APIC_ITRACE_NOISRLOCK) ;\ AVCPL_LOCK ; /* MP-safe */ \ orl $IRQ_BIT(irq_num), _ipending ; \ testl $IRQ_BIT(irq_num), _cpl ; \ jne 4f ; /* this INT masked */ \ AVCPL_UNLOCK ; \ call forward_irq ; /* forward irq to lock holder */ \ POP_FRAME ; /* and return */ \ iret ; \ ALIGN_TEXT ; \ 4: ; /* blocked */ \ APIC_ITRACE(apic_itrace_masked2, irq_num, APIC_ITRACE_MASKED2) ;\ AVCPL_UNLOCK ; \ POP_FRAME ; /* and return */ \ iret #endif /* CPL_AND_CML */ /* * Handle "spurious INTerrupts". * Notes: * This is different than the "spurious INTerrupt" generated by an * 8259 PIC for missing INTs. See the APIC documentation for details. * This routine should NOT do an 'EOI' cycle. */ .text SUPERALIGN_TEXT .globl _Xspuriousint _Xspuriousint: /* No EOI cycle used here */ iret /* * Handle TLB shootdowns. */ .text SUPERALIGN_TEXT .globl _Xinvltlb _Xinvltlb: pushl %eax #ifdef COUNT_XINVLTLB_HITS ss movl _cpuid, %eax ss incl _xhits(,%eax,4) #endif /* COUNT_XINVLTLB_HITS */ movl %cr3, %eax /* invalidate the TLB */ movl %eax, %cr3 ss /* stack segment, avoid %ds load */ movl $0, lapic_eoi /* End Of Interrupt to APIC */ popl %eax iret #ifdef BETTER_CLOCK /* * Executed by a CPU when it receives an Xcpucheckstate IPI from another CPU, * * - Stores current cpu state in checkstate_cpustate[cpuid] * 0 == user, 1 == sys, 2 == intr * - Stores current process in checkstate_curproc[cpuid] * * - Signals its receipt by setting bit cpuid in checkstate_probed_cpus. * * stack: 0 -> ds, 4 -> ebx, 8 -> eax, 12 -> eip, 16 -> cs, 20 -> eflags */ .text SUPERALIGN_TEXT .globl _Xcpucheckstate .globl _checkstate_cpustate .globl _checkstate_curproc .globl _checkstate_pc _Xcpucheckstate: pushl %eax pushl %ebx pushl %ds /* save current data segment */ movl $KDSEL, %eax movl %ax, %ds /* use KERNEL data segment */ movl $0, lapic_eoi /* End Of Interrupt to APIC */ movl $0, %ebx movl 16(%esp), %eax andl $3, %eax cmpl $3, %eax je 1f #ifdef VM86 testl $PSL_VM, 20(%esp) jne 1f #endif incl %ebx /* system or interrupt */ #ifdef CPL_AND_CML cmpl $0, _inside_intr je 1f incl %ebx /* interrupt */ #endif 1: movl _cpuid, %eax movl %ebx, _checkstate_cpustate(,%eax,4) movl _curproc, %ebx movl %ebx, _checkstate_curproc(,%eax,4) movl 12(%esp), %ebx movl %ebx, _checkstate_pc(,%eax,4) lock /* checkstate_probed_cpus |= (1<> 3) #ifdef AUTO_EOI_1 #define ENABLE_ICU1 /* use auto-EOI to reduce i/o */ #define OUTB_ICU1 #else #define ENABLE_ICU1 \ movb $ICU_EOI,%al ; /* as soon as possible send EOI ... */ \ OUTB_ICU1 /* ... to clear in service bit */ #define OUTB_ICU1 \ outb %al,$IO_ICU1 #endif #ifdef AUTO_EOI_2 /* * The data sheet says no auto-EOI on slave, but it sometimes works. */ #define ENABLE_ICU1_AND_2 ENABLE_ICU1 #else #define ENABLE_ICU1_AND_2 \ movb $ICU_EOI,%al ; /* as above */ \ outb %al,$IO_ICU2 ; /* but do second icu first ... */ \ OUTB_ICU1 /* ... then first icu (if !AUTO_EOI_1) */ #endif /* * Macros for interrupt interrupt entry, call to handler, and exit. */ #define FAST_INTR(irq_num, vec_name, enable_icus) \ .text ; \ SUPERALIGN_TEXT ; \ IDTVEC(vec_name) ; \ pushl %eax ; /* save only call-used registers */ \ pushl %ecx ; \ pushl %edx ; \ pushl %ds ; \ MAYBE_PUSHL_ES ; \ movl $KDSEL,%eax ; \ movl %ax,%ds ; \ MAYBE_MOVW_AX_ES ; \ FAKE_MCOUNT((4+ACTUALLY_PUSHED)*4(%esp)) ; \ pushl _intr_unit + (irq_num) * 4 ; \ call *_intr_handler + (irq_num) * 4 ; /* do the work ASAP */ \ enable_icus ; /* (re)enable ASAP (helps edge trigger?) */ \ addl $4,%esp ; \ incl _cnt+V_INTR ; /* book-keeping can wait */ \ movl _intr_countp + (irq_num) * 4,%eax ; \ incl (%eax) ; \ movl _cpl,%eax ; /* are we unmasking pending HWIs or SWIs? */ \ notl %eax ; \ andl _ipending,%eax ; \ jne 2f ; /* yes, maybe handle them */ \ 1: ; \ MEXITCOUNT ; \ MAYBE_POPL_ES ; \ popl %ds ; \ popl %edx ; \ popl %ecx ; \ popl %eax ; \ iret ; \ ; \ ALIGN_TEXT ; \ 2: ; \ cmpb $3,_intr_nesting_level ; /* is there enough stack? */ \ jae 1b ; /* no, return */ \ movl _cpl,%eax ; \ /* XXX next line is probably unnecessary now. */ \ movl $HWI_MASK|SWI_MASK,_cpl ; /* limit nesting ... */ \ incb _intr_nesting_level ; /* ... really limit it ... */ \ sti ; /* ... to do this as early as possible */ \ MAYBE_POPL_ES ; /* discard most of thin frame ... */ \ popl %ecx ; /* ... original %ds ... */ \ popl %edx ; \ xchgl %eax,4(%esp) ; /* orig %eax; save cpl */ \ pushal ; /* build fat frame (grrr) ... */ \ pushl %ecx ; /* ... actually %ds ... */ \ pushl %es ; \ movl $KDSEL,%eax ; \ movl %ax,%es ; \ movl (2+8+0)*4(%esp),%ecx ; /* ... %ecx from thin frame ... */ \ movl %ecx,(2+6)*4(%esp) ; /* ... to fat frame ... */ \ movl (2+8+1)*4(%esp),%eax ; /* ... cpl from thin frame */ \ pushl %eax ; \ subl $4,%esp ; /* junk for unit number */ \ MEXITCOUNT ; \ jmp _doreti #define INTR(irq_num, vec_name, icu, enable_icus, reg) \ .text ; \ SUPERALIGN_TEXT ; \ IDTVEC(vec_name) ; \ pushl $0 ; /* dummy error code */ \ pushl $0 ; /* dummy trap type */ \ pushal ; \ pushl %ds ; /* save our data and extra segments ... */ \ pushl %es ; \ movl $KDSEL,%eax ; /* ... and reload with kernel's own ... */ \ movl %ax,%ds ; /* ... early for obsolete reasons */ \ movl %ax,%es ; \ movb _imen + IRQ_BYTE(irq_num),%al ; \ orb $IRQ_BIT(irq_num),%al ; \ movb %al,_imen + IRQ_BYTE(irq_num) ; \ outb %al,$icu+ICU_IMR_OFFSET ; \ enable_icus ; \ movl _cpl,%eax ; \ testb $IRQ_BIT(irq_num),%reg ; \ jne 2f ; \ incb _intr_nesting_level ; \ __CONCAT(Xresume,irq_num): ; \ FAKE_MCOUNT(12*4(%esp)) ; /* XXX late to avoid double count */ \ incl _cnt+V_INTR ; /* tally interrupts */ \ movl _intr_countp + (irq_num) * 4,%eax ; \ incl (%eax) ; \ movl _cpl,%eax ; \ pushl %eax ; \ pushl _intr_unit + (irq_num) * 4 ; \ orl _intr_mask + (irq_num) * 4,%eax ; \ movl %eax,_cpl ; \ sti ; \ call *_intr_handler + (irq_num) * 4 ; \ cli ; /* must unmask _imen and icu atomically */ \ movb _imen + IRQ_BYTE(irq_num),%al ; \ andb $~IRQ_BIT(irq_num),%al ; \ movb %al,_imen + IRQ_BYTE(irq_num) ; \ outb %al,$icu+ICU_IMR_OFFSET ; \ sti ; /* XXX _doreti repeats the cli/sti */ \ MEXITCOUNT ; \ /* We could usually avoid the following jmp by inlining some of */ \ /* _doreti, but it's probably better to use less cache. */ \ jmp _doreti ; \ ; \ ALIGN_TEXT ; \ 2: ; \ /* XXX skip mcounting here to avoid double count */ \ orb $IRQ_BIT(irq_num),_ipending + IRQ_BYTE(irq_num) ; \ popl %es ; \ popl %ds ; \ popal ; \ addl $4+4,%esp ; \ iret MCOUNT_LABEL(bintr) FAST_INTR(0,fastintr0, ENABLE_ICU1) FAST_INTR(1,fastintr1, ENABLE_ICU1) FAST_INTR(2,fastintr2, ENABLE_ICU1) FAST_INTR(3,fastintr3, ENABLE_ICU1) FAST_INTR(4,fastintr4, ENABLE_ICU1) FAST_INTR(5,fastintr5, ENABLE_ICU1) FAST_INTR(6,fastintr6, ENABLE_ICU1) FAST_INTR(7,fastintr7, ENABLE_ICU1) FAST_INTR(8,fastintr8, ENABLE_ICU1_AND_2) FAST_INTR(9,fastintr9, ENABLE_ICU1_AND_2) FAST_INTR(10,fastintr10, ENABLE_ICU1_AND_2) FAST_INTR(11,fastintr11, ENABLE_ICU1_AND_2) FAST_INTR(12,fastintr12, ENABLE_ICU1_AND_2) FAST_INTR(13,fastintr13, ENABLE_ICU1_AND_2) FAST_INTR(14,fastintr14, ENABLE_ICU1_AND_2) FAST_INTR(15,fastintr15, ENABLE_ICU1_AND_2) INTR(0,intr0, IO_ICU1, ENABLE_ICU1, al) INTR(1,intr1, IO_ICU1, ENABLE_ICU1, al) INTR(2,intr2, IO_ICU1, ENABLE_ICU1, al) INTR(3,intr3, IO_ICU1, ENABLE_ICU1, al) INTR(4,intr4, IO_ICU1, ENABLE_ICU1, al) INTR(5,intr5, IO_ICU1, ENABLE_ICU1, al) INTR(6,intr6, IO_ICU1, ENABLE_ICU1, al) INTR(7,intr7, IO_ICU1, ENABLE_ICU1, al) INTR(8,intr8, IO_ICU2, ENABLE_ICU1_AND_2, ah) INTR(9,intr9, IO_ICU2, ENABLE_ICU1_AND_2, ah) INTR(10,intr10, IO_ICU2, ENABLE_ICU1_AND_2, ah) INTR(11,intr11, IO_ICU2, ENABLE_ICU1_AND_2, ah) INTR(12,intr12, IO_ICU2, ENABLE_ICU1_AND_2, ah) INTR(13,intr13, IO_ICU2, ENABLE_ICU1_AND_2, ah) INTR(14,intr14, IO_ICU2, ENABLE_ICU1_AND_2, ah) INTR(15,intr15, IO_ICU2, ENABLE_ICU1_AND_2, ah) MCOUNT_LABEL(eintr) .data .globl _ihandlers _ihandlers: /* addresses of interrupt handlers */ /* actually resumption addresses for HWI's */ .long Xresume0, Xresume1, Xresume2, Xresume3 .long Xresume4, Xresume5, Xresume6, Xresume7 .long Xresume8, Xresume9, Xresume10, Xresume11 .long Xresume12, Xresume13, Xresume14, Xresume15 - .long swi_tty, swi_net, dummycamisr, dummycamisr + .long _swi_null, swi_net, _swi_null, _swi_null .long _swi_vm, _swi_null, _swi_null, _swi_null .long _swi_null, _swi_null, _swi_null, _swi_null .long _swi_null, _swi_null, _softclock, swi_ast imasks: /* masks for interrupt handlers */ .space NHWI*4 /* padding; HWI masks are elsewhere */ .long SWI_TTY_MASK, SWI_NET_MASK, SWI_CAMNET_MASK, SWI_CAMBIO_MASK .long SWI_VM_MASK, 0, 0, 0 .long 0, 0, 0, 0 .long 0, 0, SWI_CLOCK_MASK, SWI_AST_MASK /* * Interrupt counters and names. The format of these and the label names * must agree with what vmstat expects. The tables are indexed by device * ids so that we don't have to move the names around as devices are * attached. */ #include "vector.h" .globl _intrcnt, _eintrcnt _intrcnt: .space (NR_DEVICES + ICU_LEN) * 4 _eintrcnt: .globl _intrnames, _eintrnames _intrnames: .ascii DEVICE_NAMES .asciz "stray irq0" .asciz "stray irq1" .asciz "stray irq2" .asciz "stray irq3" .asciz "stray irq4" .asciz "stray irq5" .asciz "stray irq6" .asciz "stray irq7" .asciz "stray irq8" .asciz "stray irq9" .asciz "stray irq10" .asciz "stray irq11" .asciz "stray irq12" .asciz "stray irq13" .asciz "stray irq14" .asciz "stray irq15" _eintrnames: .text Index: head/sys/amd64/isa/icu_vector.S =================================================================== --- head/sys/amd64/isa/icu_vector.S (revision 38245) +++ head/sys/amd64/isa/icu_vector.S (revision 38246) @@ -1,250 +1,250 @@ /* * from: vector.s, 386BSD 0.1 unknown origin - * $Id: icu_vector.s,v 1.7 1998/01/15 07:33:59 gibbs Exp $ + * $Id: icu_vector.s,v 1.8 1998/08/11 15:08:12 bde Exp $ */ /* * modified for PC98 by Kakefuda */ #ifdef PC98 #define ICU_IMR_OFFSET 2 /* IO_ICU{1,2} + 2 */ #else #define ICU_IMR_OFFSET 1 /* IO_ICU{1,2} + 1 */ #endif #define ICU_EOI 0x20 /* XXX - define elsewhere */ #define IRQ_BIT(irq_num) (1 << ((irq_num) % 8)) #define IRQ_BYTE(irq_num) ((irq_num) >> 3) #ifdef AUTO_EOI_1 #define ENABLE_ICU1 /* use auto-EOI to reduce i/o */ #define OUTB_ICU1 #else #define ENABLE_ICU1 \ movb $ICU_EOI,%al ; /* as soon as possible send EOI ... */ \ OUTB_ICU1 /* ... to clear in service bit */ #define OUTB_ICU1 \ outb %al,$IO_ICU1 #endif #ifdef AUTO_EOI_2 /* * The data sheet says no auto-EOI on slave, but it sometimes works. */ #define ENABLE_ICU1_AND_2 ENABLE_ICU1 #else #define ENABLE_ICU1_AND_2 \ movb $ICU_EOI,%al ; /* as above */ \ outb %al,$IO_ICU2 ; /* but do second icu first ... */ \ OUTB_ICU1 /* ... then first icu (if !AUTO_EOI_1) */ #endif /* * Macros for interrupt interrupt entry, call to handler, and exit. */ #define FAST_INTR(irq_num, vec_name, enable_icus) \ .text ; \ SUPERALIGN_TEXT ; \ IDTVEC(vec_name) ; \ pushl %eax ; /* save only call-used registers */ \ pushl %ecx ; \ pushl %edx ; \ pushl %ds ; \ MAYBE_PUSHL_ES ; \ movl $KDSEL,%eax ; \ movl %ax,%ds ; \ MAYBE_MOVW_AX_ES ; \ FAKE_MCOUNT((4+ACTUALLY_PUSHED)*4(%esp)) ; \ pushl _intr_unit + (irq_num) * 4 ; \ call *_intr_handler + (irq_num) * 4 ; /* do the work ASAP */ \ enable_icus ; /* (re)enable ASAP (helps edge trigger?) */ \ addl $4,%esp ; \ incl _cnt+V_INTR ; /* book-keeping can wait */ \ movl _intr_countp + (irq_num) * 4,%eax ; \ incl (%eax) ; \ movl _cpl,%eax ; /* are we unmasking pending HWIs or SWIs? */ \ notl %eax ; \ andl _ipending,%eax ; \ jne 2f ; /* yes, maybe handle them */ \ 1: ; \ MEXITCOUNT ; \ MAYBE_POPL_ES ; \ popl %ds ; \ popl %edx ; \ popl %ecx ; \ popl %eax ; \ iret ; \ ; \ ALIGN_TEXT ; \ 2: ; \ cmpb $3,_intr_nesting_level ; /* is there enough stack? */ \ jae 1b ; /* no, return */ \ movl _cpl,%eax ; \ /* XXX next line is probably unnecessary now. */ \ movl $HWI_MASK|SWI_MASK,_cpl ; /* limit nesting ... */ \ incb _intr_nesting_level ; /* ... really limit it ... */ \ sti ; /* ... to do this as early as possible */ \ MAYBE_POPL_ES ; /* discard most of thin frame ... */ \ popl %ecx ; /* ... original %ds ... */ \ popl %edx ; \ xchgl %eax,4(%esp) ; /* orig %eax; save cpl */ \ pushal ; /* build fat frame (grrr) ... */ \ pushl %ecx ; /* ... actually %ds ... */ \ pushl %es ; \ movl $KDSEL,%eax ; \ movl %ax,%es ; \ movl (2+8+0)*4(%esp),%ecx ; /* ... %ecx from thin frame ... */ \ movl %ecx,(2+6)*4(%esp) ; /* ... to fat frame ... */ \ movl (2+8+1)*4(%esp),%eax ; /* ... cpl from thin frame */ \ pushl %eax ; \ subl $4,%esp ; /* junk for unit number */ \ MEXITCOUNT ; \ jmp _doreti #define INTR(irq_num, vec_name, icu, enable_icus, reg) \ .text ; \ SUPERALIGN_TEXT ; \ IDTVEC(vec_name) ; \ pushl $0 ; /* dummy error code */ \ pushl $0 ; /* dummy trap type */ \ pushal ; \ pushl %ds ; /* save our data and extra segments ... */ \ pushl %es ; \ movl $KDSEL,%eax ; /* ... and reload with kernel's own ... */ \ movl %ax,%ds ; /* ... early for obsolete reasons */ \ movl %ax,%es ; \ movb _imen + IRQ_BYTE(irq_num),%al ; \ orb $IRQ_BIT(irq_num),%al ; \ movb %al,_imen + IRQ_BYTE(irq_num) ; \ outb %al,$icu+ICU_IMR_OFFSET ; \ enable_icus ; \ movl _cpl,%eax ; \ testb $IRQ_BIT(irq_num),%reg ; \ jne 2f ; \ incb _intr_nesting_level ; \ __CONCAT(Xresume,irq_num): ; \ FAKE_MCOUNT(12*4(%esp)) ; /* XXX late to avoid double count */ \ incl _cnt+V_INTR ; /* tally interrupts */ \ movl _intr_countp + (irq_num) * 4,%eax ; \ incl (%eax) ; \ movl _cpl,%eax ; \ pushl %eax ; \ pushl _intr_unit + (irq_num) * 4 ; \ orl _intr_mask + (irq_num) * 4,%eax ; \ movl %eax,_cpl ; \ sti ; \ call *_intr_handler + (irq_num) * 4 ; \ cli ; /* must unmask _imen and icu atomically */ \ movb _imen + IRQ_BYTE(irq_num),%al ; \ andb $~IRQ_BIT(irq_num),%al ; \ movb %al,_imen + IRQ_BYTE(irq_num) ; \ outb %al,$icu+ICU_IMR_OFFSET ; \ sti ; /* XXX _doreti repeats the cli/sti */ \ MEXITCOUNT ; \ /* We could usually avoid the following jmp by inlining some of */ \ /* _doreti, but it's probably better to use less cache. */ \ jmp _doreti ; \ ; \ ALIGN_TEXT ; \ 2: ; \ /* XXX skip mcounting here to avoid double count */ \ orb $IRQ_BIT(irq_num),_ipending + IRQ_BYTE(irq_num) ; \ popl %es ; \ popl %ds ; \ popal ; \ addl $4+4,%esp ; \ iret MCOUNT_LABEL(bintr) FAST_INTR(0,fastintr0, ENABLE_ICU1) FAST_INTR(1,fastintr1, ENABLE_ICU1) FAST_INTR(2,fastintr2, ENABLE_ICU1) FAST_INTR(3,fastintr3, ENABLE_ICU1) FAST_INTR(4,fastintr4, ENABLE_ICU1) FAST_INTR(5,fastintr5, ENABLE_ICU1) FAST_INTR(6,fastintr6, ENABLE_ICU1) FAST_INTR(7,fastintr7, ENABLE_ICU1) FAST_INTR(8,fastintr8, ENABLE_ICU1_AND_2) FAST_INTR(9,fastintr9, ENABLE_ICU1_AND_2) FAST_INTR(10,fastintr10, ENABLE_ICU1_AND_2) FAST_INTR(11,fastintr11, ENABLE_ICU1_AND_2) FAST_INTR(12,fastintr12, ENABLE_ICU1_AND_2) FAST_INTR(13,fastintr13, ENABLE_ICU1_AND_2) FAST_INTR(14,fastintr14, ENABLE_ICU1_AND_2) FAST_INTR(15,fastintr15, ENABLE_ICU1_AND_2) INTR(0,intr0, IO_ICU1, ENABLE_ICU1, al) INTR(1,intr1, IO_ICU1, ENABLE_ICU1, al) INTR(2,intr2, IO_ICU1, ENABLE_ICU1, al) INTR(3,intr3, IO_ICU1, ENABLE_ICU1, al) INTR(4,intr4, IO_ICU1, ENABLE_ICU1, al) INTR(5,intr5, IO_ICU1, ENABLE_ICU1, al) INTR(6,intr6, IO_ICU1, ENABLE_ICU1, al) INTR(7,intr7, IO_ICU1, ENABLE_ICU1, al) INTR(8,intr8, IO_ICU2, ENABLE_ICU1_AND_2, ah) INTR(9,intr9, IO_ICU2, ENABLE_ICU1_AND_2, ah) INTR(10,intr10, IO_ICU2, ENABLE_ICU1_AND_2, ah) INTR(11,intr11, IO_ICU2, ENABLE_ICU1_AND_2, ah) INTR(12,intr12, IO_ICU2, ENABLE_ICU1_AND_2, ah) INTR(13,intr13, IO_ICU2, ENABLE_ICU1_AND_2, ah) INTR(14,intr14, IO_ICU2, ENABLE_ICU1_AND_2, ah) INTR(15,intr15, IO_ICU2, ENABLE_ICU1_AND_2, ah) MCOUNT_LABEL(eintr) .data .globl _ihandlers _ihandlers: /* addresses of interrupt handlers */ /* actually resumption addresses for HWI's */ .long Xresume0, Xresume1, Xresume2, Xresume3 .long Xresume4, Xresume5, Xresume6, Xresume7 .long Xresume8, Xresume9, Xresume10, Xresume11 .long Xresume12, Xresume13, Xresume14, Xresume15 - .long swi_tty, swi_net, dummycamisr, dummycamisr + .long _swi_null, swi_net, _swi_null, _swi_null .long _swi_vm, _swi_null, _swi_null, _swi_null .long _swi_null, _swi_null, _swi_null, _swi_null .long _swi_null, _swi_null, _softclock, swi_ast imasks: /* masks for interrupt handlers */ .space NHWI*4 /* padding; HWI masks are elsewhere */ .long SWI_TTY_MASK, SWI_NET_MASK, SWI_CAMNET_MASK, SWI_CAMBIO_MASK .long SWI_VM_MASK, 0, 0, 0 .long 0, 0, 0, 0 .long 0, 0, SWI_CLOCK_MASK, SWI_AST_MASK /* * Interrupt counters and names. The format of these and the label names * must agree with what vmstat expects. The tables are indexed by device * ids so that we don't have to move the names around as devices are * attached. */ #include "vector.h" .globl _intrcnt, _eintrcnt _intrcnt: .space (NR_DEVICES + ICU_LEN) * 4 _eintrcnt: .globl _intrnames, _eintrnames _intrnames: .ascii DEVICE_NAMES .asciz "stray irq0" .asciz "stray irq1" .asciz "stray irq2" .asciz "stray irq3" .asciz "stray irq4" .asciz "stray irq5" .asciz "stray irq6" .asciz "stray irq7" .asciz "stray irq8" .asciz "stray irq9" .asciz "stray irq10" .asciz "stray irq11" .asciz "stray irq12" .asciz "stray irq13" .asciz "stray irq14" .asciz "stray irq15" _eintrnames: .text Index: head/sys/amd64/isa/icu_vector.s =================================================================== --- head/sys/amd64/isa/icu_vector.s (revision 38245) +++ head/sys/amd64/isa/icu_vector.s (revision 38246) @@ -1,250 +1,250 @@ /* * from: vector.s, 386BSD 0.1 unknown origin - * $Id: icu_vector.s,v 1.7 1998/01/15 07:33:59 gibbs Exp $ + * $Id: icu_vector.s,v 1.8 1998/08/11 15:08:12 bde Exp $ */ /* * modified for PC98 by Kakefuda */ #ifdef PC98 #define ICU_IMR_OFFSET 2 /* IO_ICU{1,2} + 2 */ #else #define ICU_IMR_OFFSET 1 /* IO_ICU{1,2} + 1 */ #endif #define ICU_EOI 0x20 /* XXX - define elsewhere */ #define IRQ_BIT(irq_num) (1 << ((irq_num) % 8)) #define IRQ_BYTE(irq_num) ((irq_num) >> 3) #ifdef AUTO_EOI_1 #define ENABLE_ICU1 /* use auto-EOI to reduce i/o */ #define OUTB_ICU1 #else #define ENABLE_ICU1 \ movb $ICU_EOI,%al ; /* as soon as possible send EOI ... */ \ OUTB_ICU1 /* ... to clear in service bit */ #define OUTB_ICU1 \ outb %al,$IO_ICU1 #endif #ifdef AUTO_EOI_2 /* * The data sheet says no auto-EOI on slave, but it sometimes works. */ #define ENABLE_ICU1_AND_2 ENABLE_ICU1 #else #define ENABLE_ICU1_AND_2 \ movb $ICU_EOI,%al ; /* as above */ \ outb %al,$IO_ICU2 ; /* but do second icu first ... */ \ OUTB_ICU1 /* ... then first icu (if !AUTO_EOI_1) */ #endif /* * Macros for interrupt interrupt entry, call to handler, and exit. */ #define FAST_INTR(irq_num, vec_name, enable_icus) \ .text ; \ SUPERALIGN_TEXT ; \ IDTVEC(vec_name) ; \ pushl %eax ; /* save only call-used registers */ \ pushl %ecx ; \ pushl %edx ; \ pushl %ds ; \ MAYBE_PUSHL_ES ; \ movl $KDSEL,%eax ; \ movl %ax,%ds ; \ MAYBE_MOVW_AX_ES ; \ FAKE_MCOUNT((4+ACTUALLY_PUSHED)*4(%esp)) ; \ pushl _intr_unit + (irq_num) * 4 ; \ call *_intr_handler + (irq_num) * 4 ; /* do the work ASAP */ \ enable_icus ; /* (re)enable ASAP (helps edge trigger?) */ \ addl $4,%esp ; \ incl _cnt+V_INTR ; /* book-keeping can wait */ \ movl _intr_countp + (irq_num) * 4,%eax ; \ incl (%eax) ; \ movl _cpl,%eax ; /* are we unmasking pending HWIs or SWIs? */ \ notl %eax ; \ andl _ipending,%eax ; \ jne 2f ; /* yes, maybe handle them */ \ 1: ; \ MEXITCOUNT ; \ MAYBE_POPL_ES ; \ popl %ds ; \ popl %edx ; \ popl %ecx ; \ popl %eax ; \ iret ; \ ; \ ALIGN_TEXT ; \ 2: ; \ cmpb $3,_intr_nesting_level ; /* is there enough stack? */ \ jae 1b ; /* no, return */ \ movl _cpl,%eax ; \ /* XXX next line is probably unnecessary now. */ \ movl $HWI_MASK|SWI_MASK,_cpl ; /* limit nesting ... */ \ incb _intr_nesting_level ; /* ... really limit it ... */ \ sti ; /* ... to do this as early as possible */ \ MAYBE_POPL_ES ; /* discard most of thin frame ... */ \ popl %ecx ; /* ... original %ds ... */ \ popl %edx ; \ xchgl %eax,4(%esp) ; /* orig %eax; save cpl */ \ pushal ; /* build fat frame (grrr) ... */ \ pushl %ecx ; /* ... actually %ds ... */ \ pushl %es ; \ movl $KDSEL,%eax ; \ movl %ax,%es ; \ movl (2+8+0)*4(%esp),%ecx ; /* ... %ecx from thin frame ... */ \ movl %ecx,(2+6)*4(%esp) ; /* ... to fat frame ... */ \ movl (2+8+1)*4(%esp),%eax ; /* ... cpl from thin frame */ \ pushl %eax ; \ subl $4,%esp ; /* junk for unit number */ \ MEXITCOUNT ; \ jmp _doreti #define INTR(irq_num, vec_name, icu, enable_icus, reg) \ .text ; \ SUPERALIGN_TEXT ; \ IDTVEC(vec_name) ; \ pushl $0 ; /* dummy error code */ \ pushl $0 ; /* dummy trap type */ \ pushal ; \ pushl %ds ; /* save our data and extra segments ... */ \ pushl %es ; \ movl $KDSEL,%eax ; /* ... and reload with kernel's own ... */ \ movl %ax,%ds ; /* ... early for obsolete reasons */ \ movl %ax,%es ; \ movb _imen + IRQ_BYTE(irq_num),%al ; \ orb $IRQ_BIT(irq_num),%al ; \ movb %al,_imen + IRQ_BYTE(irq_num) ; \ outb %al,$icu+ICU_IMR_OFFSET ; \ enable_icus ; \ movl _cpl,%eax ; \ testb $IRQ_BIT(irq_num),%reg ; \ jne 2f ; \ incb _intr_nesting_level ; \ __CONCAT(Xresume,irq_num): ; \ FAKE_MCOUNT(12*4(%esp)) ; /* XXX late to avoid double count */ \ incl _cnt+V_INTR ; /* tally interrupts */ \ movl _intr_countp + (irq_num) * 4,%eax ; \ incl (%eax) ; \ movl _cpl,%eax ; \ pushl %eax ; \ pushl _intr_unit + (irq_num) * 4 ; \ orl _intr_mask + (irq_num) * 4,%eax ; \ movl %eax,_cpl ; \ sti ; \ call *_intr_handler + (irq_num) * 4 ; \ cli ; /* must unmask _imen and icu atomically */ \ movb _imen + IRQ_BYTE(irq_num),%al ; \ andb $~IRQ_BIT(irq_num),%al ; \ movb %al,_imen + IRQ_BYTE(irq_num) ; \ outb %al,$icu+ICU_IMR_OFFSET ; \ sti ; /* XXX _doreti repeats the cli/sti */ \ MEXITCOUNT ; \ /* We could usually avoid the following jmp by inlining some of */ \ /* _doreti, but it's probably better to use less cache. */ \ jmp _doreti ; \ ; \ ALIGN_TEXT ; \ 2: ; \ /* XXX skip mcounting here to avoid double count */ \ orb $IRQ_BIT(irq_num),_ipending + IRQ_BYTE(irq_num) ; \ popl %es ; \ popl %ds ; \ popal ; \ addl $4+4,%esp ; \ iret MCOUNT_LABEL(bintr) FAST_INTR(0,fastintr0, ENABLE_ICU1) FAST_INTR(1,fastintr1, ENABLE_ICU1) FAST_INTR(2,fastintr2, ENABLE_ICU1) FAST_INTR(3,fastintr3, ENABLE_ICU1) FAST_INTR(4,fastintr4, ENABLE_ICU1) FAST_INTR(5,fastintr5, ENABLE_ICU1) FAST_INTR(6,fastintr6, ENABLE_ICU1) FAST_INTR(7,fastintr7, ENABLE_ICU1) FAST_INTR(8,fastintr8, ENABLE_ICU1_AND_2) FAST_INTR(9,fastintr9, ENABLE_ICU1_AND_2) FAST_INTR(10,fastintr10, ENABLE_ICU1_AND_2) FAST_INTR(11,fastintr11, ENABLE_ICU1_AND_2) FAST_INTR(12,fastintr12, ENABLE_ICU1_AND_2) FAST_INTR(13,fastintr13, ENABLE_ICU1_AND_2) FAST_INTR(14,fastintr14, ENABLE_ICU1_AND_2) FAST_INTR(15,fastintr15, ENABLE_ICU1_AND_2) INTR(0,intr0, IO_ICU1, ENABLE_ICU1, al) INTR(1,intr1, IO_ICU1, ENABLE_ICU1, al) INTR(2,intr2, IO_ICU1, ENABLE_ICU1, al) INTR(3,intr3, IO_ICU1, ENABLE_ICU1, al) INTR(4,intr4, IO_ICU1, ENABLE_ICU1, al) INTR(5,intr5, IO_ICU1, ENABLE_ICU1, al) INTR(6,intr6, IO_ICU1, ENABLE_ICU1, al) INTR(7,intr7, IO_ICU1, ENABLE_ICU1, al) INTR(8,intr8, IO_ICU2, ENABLE_ICU1_AND_2, ah) INTR(9,intr9, IO_ICU2, ENABLE_ICU1_AND_2, ah) INTR(10,intr10, IO_ICU2, ENABLE_ICU1_AND_2, ah) INTR(11,intr11, IO_ICU2, ENABLE_ICU1_AND_2, ah) INTR(12,intr12, IO_ICU2, ENABLE_ICU1_AND_2, ah) INTR(13,intr13, IO_ICU2, ENABLE_ICU1_AND_2, ah) INTR(14,intr14, IO_ICU2, ENABLE_ICU1_AND_2, ah) INTR(15,intr15, IO_ICU2, ENABLE_ICU1_AND_2, ah) MCOUNT_LABEL(eintr) .data .globl _ihandlers _ihandlers: /* addresses of interrupt handlers */ /* actually resumption addresses for HWI's */ .long Xresume0, Xresume1, Xresume2, Xresume3 .long Xresume4, Xresume5, Xresume6, Xresume7 .long Xresume8, Xresume9, Xresume10, Xresume11 .long Xresume12, Xresume13, Xresume14, Xresume15 - .long swi_tty, swi_net, dummycamisr, dummycamisr + .long _swi_null, swi_net, _swi_null, _swi_null .long _swi_vm, _swi_null, _swi_null, _swi_null .long _swi_null, _swi_null, _swi_null, _swi_null .long _swi_null, _swi_null, _softclock, swi_ast imasks: /* masks for interrupt handlers */ .space NHWI*4 /* padding; HWI masks are elsewhere */ .long SWI_TTY_MASK, SWI_NET_MASK, SWI_CAMNET_MASK, SWI_CAMBIO_MASK .long SWI_VM_MASK, 0, 0, 0 .long 0, 0, 0, 0 .long 0, 0, SWI_CLOCK_MASK, SWI_AST_MASK /* * Interrupt counters and names. The format of these and the label names * must agree with what vmstat expects. The tables are indexed by device * ids so that we don't have to move the names around as devices are * attached. */ #include "vector.h" .globl _intrcnt, _eintrcnt _intrcnt: .space (NR_DEVICES + ICU_LEN) * 4 _eintrcnt: .globl _intrnames, _eintrnames _intrnames: .ascii DEVICE_NAMES .asciz "stray irq0" .asciz "stray irq1" .asciz "stray irq2" .asciz "stray irq3" .asciz "stray irq4" .asciz "stray irq5" .asciz "stray irq6" .asciz "stray irq7" .asciz "stray irq8" .asciz "stray irq9" .asciz "stray irq10" .asciz "stray irq11" .asciz "stray irq12" .asciz "stray irq13" .asciz "stray irq14" .asciz "stray irq15" _eintrnames: .text Index: head/sys/dev/cy/cy.c =================================================================== --- head/sys/dev/cy/cy.c (revision 38245) +++ head/sys/dev/cy/cy.c (revision 38246) @@ -1,2589 +1,2585 @@ /*- * cyclades cyclom-y serial driver * Andrew Herbert , 17 August 1993 * * Copyright (c) 1993 Andrew Herbert. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. The name Andrew Herbert may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY ``AS IS'' AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN * NO EVENT SHALL I BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * - * $Id: cy.c,v 1.64 1998/07/15 12:18:12 bde Exp $ + * $Id: cy.c,v 1.65 1998/07/29 18:48:20 bde Exp $ */ #include "opt_compat.h" #include "opt_devfs.h" #include "cy.h" /* * TODO: * Implement BREAK. * Fix overflows when closing line. * Atomic COR change. * Consoles. */ /* * Temporary compile-time configuration options. */ #define RxFifoThreshold (CD1400_RX_FIFO_SIZE / 2) /* Number of chars in the receiver FIFO before an * an interrupt is generated. Should depend on * line speed. Needs to be about 6 on a 486DX33 * for 4 active ports at 115200 bps. Why doesn't * 10 work? */ #define PollMode /* Use polling-based irq service routine, not the * hardware svcack lines. Must be defined for * Cyclom-16Y boards. Less efficient for Cyclom-8Ys, * and stops 4 * 115200 bps from working. */ #undef Smarts /* Enable slightly more CD1400 intelligence. Mainly * the output CR/LF processing, plus we can avoid a * few checks usually done in ttyinput(). * * XXX not fully implemented, and not particularly * worthwhile. */ #undef CyDebug /* Include debugging code (not very expensive). */ /* These will go away. */ #undef SOFT_CTS_OFLOW #define SOFT_HOTCHAR #include #include #include #include #include #include #include +#include #include #include #include #ifdef DEVFS #include #endif #include +#include #include #include #include #ifdef SMP #define disable_intr() COM_DISABLE_INTR() #define enable_intr() COM_ENABLE_INTR() #endif /* SMP */ /* * Dictionary so that I can name everything *sio* or *com* to compare with * sio.c. There is also lots of ugly formatting and unnecessary ifdefs to * simplify the comparision. These will go away. */ #define LSR_BI CD1400_RDSR_BREAK #define LSR_FE CD1400_RDSR_FE #define LSR_OE CD1400_RDSR_OE #define LSR_PE CD1400_RDSR_PE #define MCR_DTR CD1400_MSVR2_DTR #define MCR_RTS CD1400_MSVR1_RTS #define MSR_CTS CD1400_MSVR2_CTS #define MSR_DCD CD1400_MSVR2_CD #define MSR_DSR CD1400_MSVR2_DSR #define MSR_RI CD1400_MSVR2_RI #define NSIO (NCY * CY_MAX_PORTS) #define comconsole cyconsole #define comdefaultrate cydefaultrate #define com_events cy_events #define comhardclose cyhardclose #define commctl cymctl #define comparam cyparam +#define siopoll_registered cypoll_registered #define comspeed cyspeed #define comstart cystart #define comwakeup cywakeup #define nsio_tty ncy_tty #define p_com_addr p_cy_addr #define sioattach cyattach #define sioclose cyclose #define siodevtotty cydevtotty #define siodriver cydriver #define siodtrwakeup cydtrwakeup #define sioioctl cyioctl #define siointr cyintr #define siointr1 cyintr1 #define sioopen cyopen #define siopoll cypoll #define sioprobe cyprobe #define sioread cyread #define siosettimeout cysettimeout #define siostop cystop #define siowrite cywrite #define sio_timeout cy_timeout #define sio_timeouts_until_log cy_timeouts_until_log #define sio_timeout_handle cy_timeout_handle #define sio_tty cy_tty #define CY_MAX_PORTS (CD1400_NO_OF_CHANNELS * CY_MAX_CD1400s) /* We encode the cyclom unit number (cyu) in spare bits in the IVR's. */ #define CD1400_xIVR_CHAN_SHIFT 3 #define CD1400_xIVR_CHAN 0x1F #define LOTS_OF_EVENTS 64 /* helps separate urgent events from input */ #define RB_I_HIGH_WATER (TTYHOG - 2 * RS_IBUFSIZE) #define RS_IBUFSIZE 256 #define CALLOUT_MASK 0x80 #define CONTROL_MASK 0x60 #define CONTROL_INIT_STATE 0x20 #define CONTROL_LOCK_STATE 0x40 #define DEV_TO_UNIT(dev) (MINOR_TO_UNIT(minor(dev))) #define MINOR_MAGIC_MASK (CALLOUT_MASK | CONTROL_MASK) #define MINOR_TO_UNIT(mynor) (((mynor) >> 16) * CY_MAX_PORTS \ | (((mynor) & 0xff) & ~MINOR_MAGIC_MASK)) /* * Input buffer watermarks. * The external device is asked to stop sending when the buffer exactly reaches * high water, or when the high level requests it. * The high level is notified immediately (rather than at a later clock tick) * when this watermark is reached. * The buffer size is chosen so the watermark should almost never be reached. * The low watermark is invisibly 0 since the buffer is always emptied all at * once. */ #define RS_IHIGHWATER (3 * RS_IBUFSIZE / 4) /* * com state bits. * (CS_BUSY | CS_TTGO) and (CS_BUSY | CS_TTGO | CS_ODEVREADY) must be higher * than the other bits so that they can be tested as a group without masking * off the low bits. * * The following com and tty flags correspond closely: * CS_BUSY = TS_BUSY (maintained by comstart(), siopoll() and * siostop()) * CS_TTGO = ~TS_TTSTOP (maintained by comparam() and comstart()) * CS_CTS_OFLOW = CCTS_OFLOW (maintained by comparam()) * CS_RTS_IFLOW = CRTS_IFLOW (maintained by comparam()) * TS_FLUSH is not used. * XXX I think TIOCSETA doesn't clear TS_TTSTOP when it clears IXON. * XXX CS_*FLOW should be CF_*FLOW in com->flags (control flags not state). */ #define CS_BUSY 0x80 /* output in progress */ #define CS_TTGO 0x40 /* output not stopped by XOFF */ #define CS_ODEVREADY 0x20 /* external device h/w ready (CTS) */ #define CS_CHECKMSR 1 /* check of MSR scheduled */ #define CS_CTS_OFLOW 2 /* use CTS output flow control */ #define CS_DTR_OFF 0x10 /* DTR held off */ #define CS_ODONE 4 /* output completed */ #define CS_RTS_IFLOW 8 /* use RTS input flow control */ static char const * const error_desc[] = { #define CE_OVERRUN 0 "silo overflow", #define CE_INTERRUPT_BUF_OVERFLOW 1 "interrupt-level buffer overflow", #define CE_TTY_BUF_OVERFLOW 2 "tty-level buffer overflow", }; #define CE_NTYPES 3 #define CE_RECORD(com, errnum) (++(com)->delta_error_counts[errnum]) /* types. XXX - should be elsewhere */ typedef u_char bool_t; /* boolean */ typedef u_char volatile *cy_addr; /* queue of linear buffers */ struct lbq { u_char *l_head; /* next char to process */ u_char *l_tail; /* one past the last char to process */ struct lbq *l_next; /* next in queue */ bool_t l_queued; /* nonzero if queued */ }; /* com device structure */ struct com_s { u_char state; /* miscellaneous flag bits */ bool_t active_out; /* nonzero if the callout device is open */ #if 0 u_char cfcr_image; /* copy of value written to CFCR */ u_char fifo_image; /* copy of value written to FIFO */ bool_t hasfifo; /* nonzero for 16550 UARTs */ bool_t loses_outints; /* nonzero if device loses output interrupts */ #endif u_char mcr_image; /* copy of value written to MCR */ #if 0 #ifdef COM_MULTIPORT bool_t multiport; /* is this unit part of a multiport device? */ #endif /* COM_MULTIPORT */ bool_t no_irq; /* nonzero if irq is not attached */ bool_t poll; /* nonzero if polling is required */ bool_t poll_output; /* nonzero if polling for output is required */ #endif int unit; /* unit number */ int dtr_wait; /* time to hold DTR down on close (* 1/hz) */ #if 0 u_int tx_fifo_size; #endif u_int wopeners; /* # processes waiting for DCD in open() */ /* * The high level of the driver never reads status registers directly * because there would be too many side effects to handle conveniently. * Instead, it reads copies of the registers stored here by the * interrupt handler. */ u_char last_modem_status; /* last MSR read by intr handler */ u_char prev_modem_status; /* last MSR handled by high level */ u_char hotchar; /* ldisc-specific char to be handled ASAP */ u_char *ibuf; /* start of input buffer */ u_char *ibufend; /* end of input buffer */ u_char *ihighwater; /* threshold in input buffer */ u_char *iptr; /* next free spot in input buffer */ struct lbq obufq; /* head of queue of output buffers */ struct lbq obufs[2]; /* output buffers */ int cy_align; /* index for register alignment */ cy_addr cy_iobase; /* base address of this port's cyclom */ cy_addr iobase; /* base address of this port's cd1400 */ struct tty *tp; /* cross reference */ /* Initial state. */ struct termios it_in; /* should be in struct tty */ struct termios it_out; /* Lock state. */ struct termios lt_in; /* should be in struct tty */ struct termios lt_out; bool_t do_timestamp; bool_t do_dcd_timestamp; struct timeval timestamp; struct timeval dcd_timestamp; u_long bytes_in; /* statistics */ u_long bytes_out; u_int delta_error_counts[CE_NTYPES]; u_long error_counts[CE_NTYPES]; u_int recv_exception; /* exception chars received */ u_int mdm; /* modem signal changes */ #ifdef CyDebug u_int start_count; /* no. of calls to comstart() */ u_int start_real; /* no. of calls that did something */ #endif u_char channel_control;/* CD1400 CCR control command shadow */ u_char cor[3]; /* CD1400 COR1-3 shadows */ u_char intr_enable; /* CD1400 SRER shadow */ /* * Ping-pong input buffers. The extra factor of 2 in the sizes is * to allow for an error byte for each input byte. */ #define CE_INPUT_OFFSET RS_IBUFSIZE u_char ibuf1[2 * RS_IBUFSIZE]; u_char ibuf2[2 * RS_IBUFSIZE]; /* * Data area for output buffers. Someday we should build the output * buffer queue without copying data. */ u_char obuf1[256]; u_char obuf2[256]; #ifdef DEVFS void *devfs_token_ttyd; void *devfs_token_ttyl; void *devfs_token_ttyi; void *devfs_token_cuaa; void *devfs_token_cual; void *devfs_token_cuai; #endif }; -/* - * XXX public functions in drivers should be declared in headers produced - * by `config', not here. - */ - -/* Interrupt handling entry point. */ -void siopoll __P((void)); - -/* Device switch entry points. */ -#define sioreset noreset -#define siommap nommap -#define siostrategy nostrategy - /* PCI driver entry point. */ int cyattach_common __P((cy_addr cy_iobase, int cy_align)); static int cy_units __P((cy_addr cy_iobase, int cy_align)); static int sioattach __P((struct isa_device *dev)); static void cd1400_channel_cmd __P((cy_addr iobase, int cmd, int cy_align)); static timeout_t siodtrwakeup; static void comhardclose __P((struct com_s *com)); #if 0 static void siointr1 __P((struct com_s *com)); #endif static int commctl __P((struct com_s *com, int bits, int how)); static int comparam __P((struct tty *tp, struct termios *t)); +static swihand_t siopoll; static int sioprobe __P((struct isa_device *dev)); static void siosettimeout __P((void)); static int comspeed __P((speed_t speed, int *prescaler_io)); static void comstart __P((struct tty *tp)); static timeout_t comwakeup; static void disc_optim __P((struct tty *tp, struct termios *t, struct com_s *com)); #ifdef CyDebug void cystatus __P((int unit)); #endif static char driver_name[] = "cy"; /* table and macro for fast conversion from a unit number to its com struct */ static struct com_s *p_com_addr[NSIO]; #define com_addr(unit) (p_com_addr[unit]) struct isa_driver siodriver = { sioprobe, sioattach, driver_name }; static d_open_t sioopen; static d_close_t sioclose; static d_read_t sioread; static d_write_t siowrite; static d_ioctl_t sioioctl; static d_stop_t siostop; static d_devtotty_t siodevtotty; #define CDEV_MAJOR 48 static struct cdevsw sio_cdevsw = { sioopen, sioclose, sioread, siowrite, sioioctl, siostop, noreset, siodevtotty, ttpoll, nommap, NULL, driver_name, NULL, -1, }; static int comconsole = -1; static speed_t comdefaultrate = TTYDEF_SPEED; static u_int com_events; /* input chars + weighted output completions */ +static bool_t siopoll_registered; static int sio_timeout; static int sio_timeouts_until_log; static struct callout_handle sio_timeout_handle = CALLOUT_HANDLE_INITIALIZER(&sio_timeout_handle); #if 0 /* XXX */ static struct tty *sio_tty[NSIO]; #else static struct tty sio_tty[NSIO]; #endif static const int nsio_tty = NSIO; #ifdef CyDebug static u_int cd_inbs; static u_int cy_inbs; static u_int cd_outbs; static u_int cy_outbs; static u_int cy_svrr_probes; static u_int cy_timeouts; #endif static int cy_chip_offset[] = { 0x0000, 0x0400, 0x0800, 0x0c00, 0x0200, 0x0600, 0x0a00, 0x0e00, }; static int cy_nr_cd1400s[NCY]; static int cy_total_devices; #undef RxFifoThreshold static int volatile RxFifoThreshold = (CD1400_RX_FIFO_SIZE / 2); static int sioprobe(dev) struct isa_device *dev; { cy_addr iobase; iobase = (cy_addr)dev->id_maddr; /* Cyclom-16Y hardware reset (Cyclom-8Ys don't care) */ cd_inb(iobase, CY16_RESET, 0); /* XXX? */ DELAY(500); /* wait for the board to get its act together */ /* this is needed to get the board out of reset */ cd_outb(iobase, CY_CLEAR_INTR, 0, 0); DELAY(500); return (cy_units(iobase, 0) == 0 ? 0 : -1); } static int cy_units(cy_iobase, cy_align) cy_addr cy_iobase; int cy_align; { int cyu; u_char firmware_version; int i; cy_addr iobase; for (cyu = 0; cyu < CY_MAX_CD1400s; ++cyu) { iobase = cy_iobase + (cy_chip_offset[cyu] << cy_align); /* wait for chip to become ready for new command */ for (i = 0; i < 10; i++) { DELAY(50); if (!cd_inb(iobase, CD1400_CCR, cy_align)) break; } /* clear the GFRCR register */ cd_outb(iobase, CD1400_GFRCR, cy_align, 0); /* issue a reset command */ cd_outb(iobase, CD1400_CCR, cy_align, CD1400_CCR_CMDRESET | CD1400_CCR_FULLRESET); /* wait for the CD1400 to initialize itself */ for (i = 0; i < 200; i++) { DELAY(50); /* retrieve firmware version */ firmware_version = cd_inb(iobase, CD1400_GFRCR, cy_align); if ((firmware_version & 0xf0) == 0x40) break; } /* * Anything in the 0x40-0x4F range is fine. * If one CD1400 is bad then we don't support higher * numbered good ones on this board. */ if ((firmware_version & 0xf0) != 0x40) break; } return (cyu); } static int sioattach(isdp) struct isa_device *isdp; { int adapter; adapter = cyattach_common((cy_addr) isdp->id_maddr, 0); if (adapter < 0) return (0); /* * XXX * This kludge is to allow ISA/PCI device specifications in the * kernel config file to be in any order. */ if (isdp->id_unit != adapter) { printf("cy%d: attached as cy%d\n", isdp->id_unit, adapter); isdp->id_unit = adapter; /* XXX */ } isdp->id_ri_flags |= RI_FAST; return (1); } int cyattach_common(cy_iobase, cy_align) cy_addr cy_iobase; int cy_align; { int adapter; int cyu; dev_t dev; cy_addr iobase; int ncyu; int unit; adapter = cy_total_devices; if ((u_int)adapter >= NCY) { printf( "cy%d: can't attach adapter: insufficient cy devices configured\n", adapter); return (-1); } ncyu = cy_units(cy_iobase, cy_align); if (ncyu == 0) return (-1); cy_nr_cd1400s[adapter] = ncyu; cy_total_devices++; unit = adapter * CY_MAX_PORTS; for (cyu = 0; cyu < ncyu; ++cyu) { int cdu; iobase = (cy_addr) (cy_iobase + (cy_chip_offset[cyu] << cy_align)); /* Set up a receive timeout period of than 1+ ms. */ cd_outb(iobase, CD1400_PPR, cy_align, howmany(CY_CLOCK / CD1400_PPR_PRESCALER, 1000)); for (cdu = 0; cdu < CD1400_NO_OF_CHANNELS; ++cdu, ++unit) { struct com_s *com; int s; com = malloc(sizeof *com, M_DEVBUF, M_NOWAIT); if (com == NULL) break; bzero(com, sizeof *com); com->unit = unit; com->dtr_wait = 3 * hz; com->iptr = com->ibuf = com->ibuf1; com->ibufend = com->ibuf1 + RS_IBUFSIZE; com->ihighwater = com->ibuf1 + RS_IHIGHWATER; com->obufs[0].l_head = com->obuf1; com->obufs[1].l_head = com->obuf2; com->cy_align = cy_align; com->cy_iobase = cy_iobase; com->iobase = iobase; /* * We don't use all the flags from since they * are only relevant for logins. It's important to have echo off * initially so that the line doesn't start blathering before the * echo flag can be turned off. */ com->it_in.c_iflag = 0; com->it_in.c_oflag = 0; com->it_in.c_cflag = TTYDEF_CFLAG; com->it_in.c_lflag = 0; if (unit == comconsole) { com->it_in.c_iflag = TTYDEF_IFLAG; com->it_in.c_oflag = TTYDEF_OFLAG; com->it_in.c_cflag = TTYDEF_CFLAG | CLOCAL; com->it_in.c_lflag = TTYDEF_LFLAG; com->lt_out.c_cflag = com->lt_in.c_cflag = CLOCAL; } termioschars(&com->it_in); com->it_in.c_ispeed = com->it_in.c_ospeed = comdefaultrate; com->it_out = com->it_in; s = spltty(); com_addr(unit) = com; splx(s); dev = makedev(CDEV_MAJOR, 0); cdevsw_add(&dev, &sio_cdevsw, NULL); #ifdef DEVFS com->devfs_token_ttyd = devfs_add_devswf(&sio_cdevsw, unit, DV_CHR, UID_ROOT, GID_WHEEL, 0600, "ttyc%r%r", adapter, unit % CY_MAX_PORTS); com->devfs_token_ttyi = devfs_add_devswf(&sio_cdevsw, unit | CONTROL_INIT_STATE, DV_CHR, UID_ROOT, GID_WHEEL, 0600, "ttyic%r%r", adapter, unit % CY_MAX_PORTS); com->devfs_token_ttyl = devfs_add_devswf(&sio_cdevsw, unit | CONTROL_LOCK_STATE, DV_CHR, UID_ROOT, GID_WHEEL, 0600, "ttylc%r%r", adapter, unit % CY_MAX_PORTS); com->devfs_token_cuaa = devfs_add_devswf(&sio_cdevsw, unit | CALLOUT_MASK, DV_CHR, UID_UUCP, GID_DIALER, 0660, "cuac%r%r", adapter, unit % CY_MAX_PORTS); com->devfs_token_cuai = devfs_add_devswf(&sio_cdevsw, unit | CALLOUT_MASK | CONTROL_INIT_STATE, DV_CHR, UID_UUCP, GID_DIALER, 0660, "cuaic%r%r", adapter, unit % CY_MAX_PORTS); com->devfs_token_cual = devfs_add_devswf(&sio_cdevsw, unit | CALLOUT_MASK | CONTROL_LOCK_STATE, DV_CHR, UID_UUCP, GID_DIALER, 0660, "cualc%r%r", adapter, unit % CY_MAX_PORTS); #endif } } + if (!siopoll_registered) { + register_swi(SWI_TTY, siopoll); + siopoll_registered = TRUE; + } /* ensure an edge for the next interrupt */ cd_outb(cy_iobase, CY_CLEAR_INTR, cy_align, 0); return (adapter); } static int sioopen(dev, flag, mode, p) dev_t dev; int flag; int mode; struct proc *p; { struct com_s *com; int error; cy_addr iobase; int mynor; int s; struct tty *tp; int unit; mynor = minor(dev); unit = MINOR_TO_UNIT(mynor); if ((u_int) unit >= NSIO || (com = com_addr(unit)) == NULL) return (ENXIO); if (mynor & CONTROL_MASK) return (0); #if 0 /* XXX */ tp = com->tp = sio_tty[unit] = ttymalloc(sio_tty[unit]); #else tp = com->tp = &sio_tty[unit]; #endif s = spltty(); /* * We jump to this label after all non-interrupted sleeps to pick * up any changes of the device state. */ open_top: while (com->state & CS_DTR_OFF) { error = tsleep(&com->dtr_wait, TTIPRI | PCATCH, "cydtr", 0); if (error != 0) goto out; } if (tp->t_state & TS_ISOPEN) { /* * The device is open, so everything has been initialized. * Handle conflicts. */ if (mynor & CALLOUT_MASK) { if (!com->active_out) { error = EBUSY; goto out; } } else { if (com->active_out) { if (flag & O_NONBLOCK) { error = EBUSY; goto out; } error = tsleep(&com->active_out, TTIPRI | PCATCH, "cybi", 0); if (error != 0) goto out; goto open_top; } } if (tp->t_state & TS_XCLUDE && p->p_ucred->cr_uid != 0) { error = EBUSY; goto out; } } else { /* * The device isn't open, so there are no conflicts. * Initialize it. Initialization is done twice in many * cases: to preempt sleeping callin opens if we are * callout, and to complete a callin open after DCD rises. */ tp->t_oproc = comstart; tp->t_param = comparam; tp->t_dev = dev; tp->t_termios = mynor & CALLOUT_MASK ? com->it_out : com->it_in; #if 0 (void)commctl(com, TIOCM_DTR | TIOCM_RTS, DMSET); com->poll = com->no_irq; com->poll_output = com->loses_outints; #endif ++com->wopeners; iobase = com->iobase; /* reset this channel */ cd_outb(iobase, CD1400_CAR, com->cy_align, unit & CD1400_CAR_CHAN); cd1400_channel_cmd(iobase, CD1400_CCR_CMDRESET, com->cy_align); /* * Resetting disables the transmitter and receiver as well as * flushing the fifos so some of our cached state becomes * invalid. The documentation suggests that all registers * for the current channel are reset to defaults, but * apparently none are. We wouldn't want DTR cleared. */ com->channel_control = 0; /* Encode per-board unit in LIVR for access in intr routines. */ cd_outb(iobase, CD1400_LIVR, com->cy_align, (unit & CD1400_xIVR_CHAN) << CD1400_xIVR_CHAN_SHIFT); /* * raise dtr and generally set things up correctly. this * has the side-effect of selecting the appropriate cd1400 * channel, to help us with subsequent channel control stuff */ error = comparam(tp, &tp->t_termios); --com->wopeners; if (error != 0) goto out; /* * XXX we should goto open_top if comparam() slept. */ ttsetwater(tp); #if 0 if (com->hasfifo) { /* * (Re)enable and drain fifos. * * Certain SMC chips cause problems if the fifos * are enabled while input is ready. Turn off the * fifo if necessary to clear the input. We test * the input ready bit after enabling the fifos * since we've already enabled them in comparam() * and to handle races between enabling and fresh * input. */ while (TRUE) { outb(iobase + com_fifo, FIFO_RCV_RST | FIFO_XMT_RST | com->fifo_image); DELAY(100); if (!(inb(com->line_status_port) & LSR_RXRDY)) break; outb(iobase + com_fifo, 0); DELAY(100); (void) inb(com->data_port); } } disable_intr(); (void) inb(com->line_status_port); (void) inb(com->data_port); com->prev_modem_status = com->last_modem_status = inb(com->modem_status_port); outb(iobase + com_ier, IER_ERXRDY | IER_ETXRDY | IER_ERLS | IER_EMSC); enable_intr(); #else /* !0 */ /* XXX raise RTS too */ (void)commctl(com, TIOCM_DTR | TIOCM_RTS, DMSET); disable_intr(); com->prev_modem_status = com->last_modem_status = cd_inb(iobase, CD1400_MSVR2, com->cy_align); cd_outb(iobase, CD1400_SRER, com->cy_align, com->intr_enable = CD1400_SRER_MDMCH | CD1400_SRER_RXDATA); enable_intr(); #endif /* 0 */ /* * Handle initial DCD. Callout devices get a fake initial * DCD (trapdoor DCD). If we are callout, then any sleeping * callin opens get woken up and resume sleeping on "cybi" * instead of "cydcd". */ /* * XXX `mynor & CALLOUT_MASK' should be * `tp->t_cflag & (SOFT_CARRIER | TRAPDOOR_CARRIER) where * TRAPDOOR_CARRIER is the default initial state for callout * devices and SOFT_CARRIER is like CLOCAL except it hides * the true carrier. */ if (com->prev_modem_status & MSR_DCD || mynor & CALLOUT_MASK) (*linesw[tp->t_line].l_modem)(tp, 1); } /* * Wait for DCD if necessary. */ if (!(tp->t_state & TS_CARR_ON) && !(mynor & CALLOUT_MASK) && !(tp->t_cflag & CLOCAL) && !(flag & O_NONBLOCK)) { ++com->wopeners; error = tsleep(TSA_CARR_ON(tp), TTIPRI | PCATCH, "cydcd", 0); --com->wopeners; if (error != 0) goto out; goto open_top; } error = (*linesw[tp->t_line].l_open)(dev, tp); disc_optim(tp, &tp->t_termios, com); if (tp->t_state & TS_ISOPEN && mynor & CALLOUT_MASK) com->active_out = TRUE; siosettimeout(); out: splx(s); if (!(tp->t_state & TS_ISOPEN) && com->wopeners == 0) comhardclose(com); return (error); } static int sioclose(dev, flag, mode, p) dev_t dev; int flag; int mode; struct proc *p; { struct com_s *com; int mynor; int s; struct tty *tp; mynor = minor(dev); if (mynor & CONTROL_MASK) return (0); com = com_addr(MINOR_TO_UNIT(mynor)); tp = com->tp; s = spltty(); (*linesw[tp->t_line].l_close)(tp, flag); disc_optim(tp, &tp->t_termios, com); siostop(tp, FREAD | FWRITE); comhardclose(com); ttyclose(tp); siosettimeout(); splx(s); #ifdef broken /* session holds a ref to the tty; can't deallocate */ ttyfree(tp); com->tp = sio_tty[unit] = NULL; #endif return (0); } static void comhardclose(com) struct com_s *com; { cy_addr iobase; int s; struct tty *tp; int unit; unit = com->unit; iobase = com->iobase; s = spltty(); #if 0 com->poll = FALSE; com->poll_output = FALSE; #endif com->do_timestamp = 0; cd_outb(iobase, CD1400_CAR, com->cy_align, unit & CD1400_CAR_CHAN); #if 0 outb(iobase + com_cfcr, com->cfcr_image &= ~CFCR_SBREAK); #endif { #if 0 outb(iobase + com_ier, 0); #else disable_intr(); cd_outb(iobase, CD1400_SRER, com->cy_align, com->intr_enable = 0); enable_intr(); #endif tp = com->tp; if (tp->t_cflag & HUPCL /* * XXX we will miss any carrier drop between here and the * next open. Perhaps we should watch DCD even when the * port is closed; it is not sufficient to check it at * the next open because it might go up and down while * we're not watching. */ || !com->active_out && !(com->prev_modem_status & MSR_DCD) && !(com->it_in.c_cflag & CLOCAL) || !(tp->t_state & TS_ISOPEN)) { (void)commctl(com, TIOCM_DTR, DMBIC); /* Disable receiver (leave transmitter enabled). */ com->channel_control = CD1400_CCR_CMDCHANCTL | CD1400_CCR_XMTEN | CD1400_CCR_RCVDIS; cd1400_channel_cmd(iobase, com->channel_control, com->cy_align); if (com->dtr_wait != 0) { timeout(siodtrwakeup, com, com->dtr_wait); com->state |= CS_DTR_OFF; } } } #if 0 if (com->hasfifo) { /* * Disable fifos so that they are off after controlled * reboots. Some BIOSes fail to detect 16550s when the * fifos are enabled. */ outb(iobase + com_fifo, 0); } #endif com->active_out = FALSE; wakeup(&com->active_out); wakeup(TSA_CARR_ON(tp)); /* restart any wopeners */ splx(s); } static int sioread(dev, uio, flag) dev_t dev; struct uio *uio; int flag; { int mynor; struct tty *tp; mynor = minor(dev); if (mynor & CONTROL_MASK) return (ENODEV); tp = com_addr(MINOR_TO_UNIT(mynor))->tp; return ((*linesw[tp->t_line].l_read)(tp, uio, flag)); } static int siowrite(dev, uio, flag) dev_t dev; struct uio *uio; int flag; { int mynor; struct tty *tp; int unit; mynor = minor(dev); if (mynor & CONTROL_MASK) return (ENODEV); unit = MINOR_TO_UNIT(mynor); tp = com_addr(unit)->tp; /* * (XXX) We disallow virtual consoles if the physical console is * a serial port. This is in case there is a display attached that * is not the console. In that situation we don't need/want the X * server taking over the console. */ if (constty != NULL && unit == comconsole) constty = NULL; #ifdef Smarts /* XXX duplicate ttwrite(), but without so much output processing on * CR & LF chars. Hardly worth the effort, given that high-throughput * sessions are raw anyhow. */ #else return ((*linesw[tp->t_line].l_write)(tp, uio, flag)); #endif } static void siodtrwakeup(chan) void *chan; { struct com_s *com; com = (struct com_s *)chan; com->state &= ~CS_DTR_OFF; wakeup(&com->dtr_wait); } void siointr(unit) int unit; { int baseu; int cy_align; cy_addr cy_iobase; int cyu; cy_addr iobase; u_char status; COM_LOCK(); /* XXX could this be placed down lower in the loop? */ baseu = unit * CY_MAX_PORTS; cy_align = com_addr(baseu)->cy_align; cy_iobase = com_addr(baseu)->cy_iobase; /* check each CD1400 in turn */ for (cyu = 0; cyu < cy_nr_cd1400s[unit]; ++cyu) { iobase = (cy_addr) (cy_iobase + (cy_chip_offset[cyu] << cy_align)); /* poll to see if it has any work */ status = cd_inb(iobase, CD1400_SVRR, cy_align); if (status == 0) continue; #ifdef CyDebug ++cy_svrr_probes; #endif /* service requests as appropriate, giving priority to RX */ if (status & CD1400_SVRR_RXRDY) { struct com_s *com; u_int count; u_char *ioptr; u_char line_status; u_char recv_data; u_char serv_type; #ifdef PollMode u_char save_car; u_char save_rir; #endif #ifdef PollMode save_rir = cd_inb(iobase, CD1400_RIR, cy_align); save_car = cd_inb(iobase, CD1400_CAR, cy_align); /* enter rx service */ cd_outb(iobase, CD1400_CAR, cy_align, save_rir); serv_type = cd_inb(iobase, CD1400_RIVR, cy_align); com = com_addr(baseu + ((serv_type >> CD1400_xIVR_CHAN_SHIFT) & CD1400_xIVR_CHAN)); #else /* ack receive service */ serv_type = cy_inb(iobase, CY8_SVCACKR); com = com_addr(baseu + + ((serv_type >> CD1400_xIVR_CHAN_SHIFT) & CD1400_xIVR_CHAN)); #endif if (serv_type & CD1400_RIVR_EXCEPTION) { ++com->recv_exception; line_status = cd_inb(iobase, CD1400_RDSR, cy_align); /* break/unnattached error bits or real input? */ recv_data = cd_inb(iobase, CD1400_RDSR, cy_align); #ifndef SOFT_HOTCHAR if (line_status & CD1400_RDSR_SPECIAL && com->hotchar != 0) setsofttty(); #endif #if 1 /* XXX "intelligent" PFO error handling would break O error handling */ if (line_status & (LSR_PE|LSR_FE|LSR_BI)) { /* Don't store PE if IGNPAR and BI if IGNBRK, this hack allows "raw" tty optimization works even if IGN* is set. */ if ( com->tp == NULL || !(com->tp->t_state & TS_ISOPEN) || (line_status & (LSR_PE|LSR_FE)) && (com->tp->t_iflag & IGNPAR) || (line_status & LSR_BI) && (com->tp->t_iflag & IGNBRK)) goto cont; if ( (line_status & (LSR_PE|LSR_FE)) && (com->tp->t_state & TS_CAN_BYPASS_L_RINT) && ((line_status & LSR_FE) || (line_status & LSR_PE) && (com->tp->t_iflag & INPCK))) recv_data = 0; } #endif /* 1 */ ++com->bytes_in; #ifdef SOFT_HOTCHAR if (com->hotchar != 0 && recv_data == com->hotchar) setsofttty(); #endif ioptr = com->iptr; if (ioptr >= com->ibufend) CE_RECORD(com, CE_INTERRUPT_BUF_OVERFLOW); else { if (com->do_timestamp) microtime(&com->timestamp); ++com_events; ioptr[0] = recv_data; ioptr[CE_INPUT_OFFSET] = line_status; com->iptr = ++ioptr; if (ioptr == com->ihighwater && com->state & CS_RTS_IFLOW) #if 0 outb(com->modem_ctl_port, com->mcr_image &= ~MCR_RTS); #else cd_outb(iobase, CD1400_MSVR1, cy_align, com->mcr_image &= ~MCR_RTS); #endif if (line_status & LSR_OE) CE_RECORD(com, CE_OVERRUN); } goto cont; } else { int ifree; count = cd_inb(iobase, CD1400_RDCR, cy_align); if (!count) goto cont; com->bytes_in += count; ioptr = com->iptr; ifree = com->ibufend - ioptr; if (count > ifree) { count -= ifree; com_events += ifree; if (ifree != 0) { if (com->do_timestamp) microtime(&com->timestamp); do { recv_data = cd_inb(iobase, CD1400_RDSR, cy_align); #ifdef SOFT_HOTCHAR if (com->hotchar != 0 && recv_data == com->hotchar) setsofttty(); #endif ioptr[0] = recv_data; ioptr[CE_INPUT_OFFSET] = 0; ++ioptr; } while (--ifree != 0); } com->delta_error_counts [CE_INTERRUPT_BUF_OVERFLOW] += count; do { recv_data = cd_inb(iobase, CD1400_RDSR, cy_align); #ifdef SOFT_HOTCHAR if (com->hotchar != 0 && recv_data == com->hotchar) setsofttty(); #endif } while (--count != 0); } else { if (com->do_timestamp) microtime(&com->timestamp); if (ioptr <= com->ihighwater && ioptr + count > com->ihighwater && com->state & CS_RTS_IFLOW) #if 0 outb(com->modem_ctl_port, com->mcr_image &= ~MCR_RTS); #else cd_outb(iobase, CD1400_MSVR1, cy_align, com->mcr_image &= ~MCR_RTS); #endif com_events += count; do { recv_data = cd_inb(iobase, CD1400_RDSR, cy_align); #ifdef SOFT_HOTCHAR if (com->hotchar != 0 && recv_data == com->hotchar) setsofttty(); #endif ioptr[0] = recv_data; ioptr[CE_INPUT_OFFSET] = 0; ++ioptr; } while (--count != 0); } com->iptr = ioptr; } cont: /* terminate service context */ #ifdef PollMode cd_outb(iobase, CD1400_RIR, cy_align, save_rir & ~(CD1400_RIR_RDIREQ | CD1400_RIR_RBUSY)); cd_outb(iobase, CD1400_CAR, cy_align, save_car); #else cd_outb(iobase, CD1400_EOSRR, cy_align, 0); #endif } if (status & CD1400_SVRR_MDMCH) { struct com_s *com; u_char modem_status; #ifdef PollMode u_char save_car; u_char save_mir; #else u_char vector; #endif #ifdef PollMode save_mir = cd_inb(iobase, CD1400_MIR, cy_align); save_car = cd_inb(iobase, CD1400_CAR, cy_align); /* enter modem service */ cd_outb(iobase, CD1400_CAR, cy_align, save_mir); com = com_addr(baseu + cyu * CD1400_NO_OF_CHANNELS + (save_mir & CD1400_MIR_CHAN)); #else /* ack modem service */ vector = cy_inb(iobase, CY8_SVCACKM); com = com_addr(baseu + ((vector >> CD1400_xIVR_CHAN_SHIFT) & CD1400_xIVR_CHAN)); #endif ++com->mdm; modem_status = cd_inb(iobase, CD1400_MSVR2, cy_align); if (modem_status != com->last_modem_status) { if (com->do_dcd_timestamp && !(com->last_modem_status & MSR_DCD) && modem_status & MSR_DCD) microtime(&com->dcd_timestamp); /* * Schedule high level to handle DCD changes. Note * that we don't use the delta bits anywhere. Some * UARTs mess them up, and it's easy to remember the * previous bits and calculate the delta. */ com->last_modem_status = modem_status; if (!(com->state & CS_CHECKMSR)) { com_events += LOTS_OF_EVENTS; com->state |= CS_CHECKMSR; setsofttty(); } #ifdef SOFT_CTS_OFLOW /* handle CTS change immediately for crisp flow ctl */ if (com->state & CS_CTS_OFLOW) { if (modem_status & MSR_CTS) { com->state |= CS_ODEVREADY; if (com->state >= (CS_BUSY | CS_TTGO | CS_ODEVREADY) && !(com->intr_enable & CD1400_SRER_TXRDY)) cd_outb(iobase, CD1400_SRER, cy_align, com->intr_enable |= CD1400_SRER_TXRDY); } else { com->state &= ~CS_ODEVREADY; if (com->intr_enable & CD1400_SRER_TXRDY) cd_outb(iobase, CD1400_SRER, cy_align, com->intr_enable &= ~CD1400_SRER_TXRDY); } } #endif } /* terminate service context */ #ifdef PollMode cd_outb(iobase, CD1400_MIR, cy_align, save_mir & ~(CD1400_MIR_RDIREQ | CD1400_MIR_RBUSY)); cd_outb(iobase, CD1400_CAR, cy_align, save_car); #else cd_outb(iobase, CD1400_EOSRR, cy_align, 0); #endif } if (status & CD1400_SVRR_TXRDY) { struct com_s *com; #ifdef PollMode u_char save_car; u_char save_tir; #else u_char vector; #endif #ifdef PollMode save_tir = cd_inb(iobase, CD1400_TIR, cy_align); save_car = cd_inb(iobase, CD1400_CAR, cy_align); /* enter tx service */ cd_outb(iobase, CD1400_CAR, cy_align, save_tir); com = com_addr(baseu + cyu * CD1400_NO_OF_CHANNELS + (save_tir & CD1400_TIR_CHAN)); #else /* ack transmit service */ vector = cy_inb(iobase, CY8_SVCACKT); com = com_addr(baseu + ((vector >> CD1400_xIVR_CHAN_SHIFT) & CD1400_xIVR_CHAN)); #endif if (com->state >= (CS_BUSY | CS_TTGO | CS_ODEVREADY)) { u_char *ioptr; u_int ocount; ioptr = com->obufq.l_head; ocount = com->obufq.l_tail - ioptr; if (ocount > CD1400_TX_FIFO_SIZE) ocount = CD1400_TX_FIFO_SIZE; com->bytes_out += ocount; do cd_outb(iobase, CD1400_TDR, cy_align, *ioptr++); while (--ocount != 0); com->obufq.l_head = ioptr; if (ioptr >= com->obufq.l_tail) { struct lbq *qp; qp = com->obufq.l_next; qp->l_queued = FALSE; qp = qp->l_next; if (qp != NULL) { com->obufq.l_head = qp->l_head; com->obufq.l_tail = qp->l_tail; com->obufq.l_next = qp; } else { /* output just completed */ com->state &= ~CS_BUSY; cd_outb(iobase, CD1400_SRER, cy_align, com->intr_enable &= ~CD1400_SRER_TXRDY); } if (!(com->state & CS_ODONE)) { com_events += LOTS_OF_EVENTS; com->state |= CS_ODONE; /* handle at high level ASAP */ setsofttty(); } } } /* terminate service context */ #ifdef PollMode cd_outb(iobase, CD1400_TIR, cy_align, save_tir & ~(CD1400_TIR_RDIREQ | CD1400_TIR_RBUSY)); cd_outb(iobase, CD1400_CAR, cy_align, save_car); #else cd_outb(iobase, CD1400_EOSRR, cy_align, 0); #endif } } /* ensure an edge for the next interrupt */ cd_outb(cy_iobase, CY_CLEAR_INTR, cy_align, 0); schedsofttty(); COM_UNLOCK(); } #if 0 static void siointr1(com) struct com_s *com; { } #endif static int sioioctl(dev, cmd, data, flag, p) dev_t dev; u_long cmd; caddr_t data; int flag; struct proc *p; { struct com_s *com; int error; cy_addr iobase; int mynor; int s; struct tty *tp; #if defined(COMPAT_43) || defined(COMPAT_SUNOS) int oldcmd; struct termios term; #endif mynor = minor(dev); com = com_addr(MINOR_TO_UNIT(mynor)); iobase = com->iobase; if (mynor & CONTROL_MASK) { struct termios *ct; switch (mynor & CONTROL_MASK) { case CONTROL_INIT_STATE: ct = mynor & CALLOUT_MASK ? &com->it_out : &com->it_in; break; case CONTROL_LOCK_STATE: ct = mynor & CALLOUT_MASK ? &com->lt_out : &com->lt_in; break; default: return (ENODEV); /* /dev/nodev */ } switch (cmd) { case TIOCSETA: error = suser(p->p_ucred, &p->p_acflag); if (error != 0) return (error); *ct = *(struct termios *)data; return (0); case TIOCGETA: *(struct termios *)data = *ct; return (0); case TIOCGETD: *(int *)data = TTYDISC; return (0); case TIOCGWINSZ: bzero(data, sizeof(struct winsize)); return (0); default: return (ENOTTY); } } tp = com->tp; #if defined(COMPAT_43) || defined(COMPAT_SUNOS) term = tp->t_termios; oldcmd = cmd; error = ttsetcompat(tp, &cmd, data, &term); if (error != 0) return (error); if (cmd != oldcmd) data = (caddr_t)&term; #endif if (cmd == TIOCSETA || cmd == TIOCSETAW || cmd == TIOCSETAF) { int cc; struct termios *dt = (struct termios *)data; struct termios *lt = mynor & CALLOUT_MASK ? &com->lt_out : &com->lt_in; dt->c_iflag = (tp->t_iflag & lt->c_iflag) | (dt->c_iflag & ~lt->c_iflag); dt->c_oflag = (tp->t_oflag & lt->c_oflag) | (dt->c_oflag & ~lt->c_oflag); dt->c_cflag = (tp->t_cflag & lt->c_cflag) | (dt->c_cflag & ~lt->c_cflag); dt->c_lflag = (tp->t_lflag & lt->c_lflag) | (dt->c_lflag & ~lt->c_lflag); for (cc = 0; cc < NCCS; ++cc) if (lt->c_cc[cc] != 0) dt->c_cc[cc] = tp->t_cc[cc]; if (lt->c_ispeed != 0) dt->c_ispeed = tp->t_ispeed; if (lt->c_ospeed != 0) dt->c_ospeed = tp->t_ospeed; } error = (*linesw[tp->t_line].l_ioctl)(tp, cmd, data, flag, p); if (error != ENOIOCTL) return (error); s = spltty(); error = ttioctl(tp, cmd, data, flag); disc_optim(tp, &tp->t_termios, com); if (error != ENOIOCTL) { splx(s); return (error); } cd_outb(iobase, CD1400_CAR, com->cy_align, MINOR_TO_UNIT(mynor) & CD1400_CAR_CHAN); switch (cmd) { #if 0 case TIOCSBRK: outb(iobase + com_cfcr, com->cfcr_image |= CFCR_SBREAK); break; case TIOCCBRK: outb(iobase + com_cfcr, com->cfcr_image &= ~CFCR_SBREAK); break; #endif /* 0 */ case TIOCSDTR: (void)commctl(com, TIOCM_DTR, DMBIS); break; case TIOCCDTR: (void)commctl(com, TIOCM_DTR, DMBIC); break; /* * XXX should disallow changing MCR_RTS if CS_RTS_IFLOW is set. The * changes get undone on the next call to comparam(). */ case TIOCMSET: (void)commctl(com, *(int *)data, DMSET); break; case TIOCMBIS: (void)commctl(com, *(int *)data, DMBIS); break; case TIOCMBIC: (void)commctl(com, *(int *)data, DMBIC); break; case TIOCMGET: *(int *)data = commctl(com, 0, DMGET); break; case TIOCMSDTRWAIT: /* must be root since the wait applies to following logins */ error = suser(p->p_ucred, &p->p_acflag); if (error != 0) { splx(s); return (error); } com->dtr_wait = *(int *)data * hz / 100; break; case TIOCMGDTRWAIT: *(int *)data = com->dtr_wait * 100 / hz; break; case TIOCTIMESTAMP: com->do_timestamp = TRUE; *(struct timeval *)data = com->timestamp; break; case TIOCDCDTIMESTAMP: com->do_dcd_timestamp = TRUE; *(struct timeval *)data = com->dcd_timestamp; break; default: splx(s); return (ENOTTY); } splx(s); return (0); } -void +static void siopoll() { int unit; #ifdef CyDebug ++cy_timeouts; #endif if (com_events == 0) return; repeat: for (unit = 0; unit < NSIO; ++unit) { u_char *buf; struct com_s *com; u_char *ibuf; cy_addr iobase; int incc; struct tty *tp; com = com_addr(unit); if (com == NULL) continue; tp = com->tp; if (tp == NULL) { /* * XXX forget any events related to closed devices * (actually never opened devices) so that we don't * loop. */ disable_intr(); incc = com->iptr - com->ibuf; com->iptr = com->ibuf; if (com->state & CS_CHECKMSR) { incc += LOTS_OF_EVENTS; com->state &= ~CS_CHECKMSR; } com_events -= incc; enable_intr(); if (incc != 0) log(LOG_DEBUG, "sio%d: %d events for device with no tp\n", unit, incc); continue; } /* switch the role of the low-level input buffers */ if (com->iptr == (ibuf = com->ibuf)) { buf = NULL; /* not used, but compiler can't tell */ incc = 0; } else { buf = ibuf; disable_intr(); incc = com->iptr - buf; com_events -= incc; if (ibuf == com->ibuf1) ibuf = com->ibuf2; else ibuf = com->ibuf1; com->ibufend = ibuf + RS_IBUFSIZE; com->ihighwater = ibuf + RS_IHIGHWATER; com->iptr = ibuf; /* * There is now room for another low-level buffer full * of input, so enable RTS if it is now disabled and * there is room in the high-level buffer. */ if ((com->state & CS_RTS_IFLOW) && !(com->mcr_image & MCR_RTS) && !(tp->t_state & TS_TBLOCK)) #if 0 outb(com->modem_ctl_port, com->mcr_image |= MCR_RTS); #else iobase = com->iobase, cd_outb(iobase, CD1400_CAR, com->cy_align, unit & CD1400_CAR_CHAN), cd_outb(iobase, CD1400_MSVR1, com->cy_align, com->mcr_image |= MCR_RTS); #endif enable_intr(); com->ibuf = ibuf; } if (com->state & CS_CHECKMSR) { u_char delta_modem_status; disable_intr(); delta_modem_status = com->last_modem_status ^ com->prev_modem_status; com->prev_modem_status = com->last_modem_status; com_events -= LOTS_OF_EVENTS; com->state &= ~CS_CHECKMSR; enable_intr(); if (delta_modem_status & MSR_DCD) (*linesw[tp->t_line].l_modem) (tp, com->prev_modem_status & MSR_DCD); } if (com->state & CS_ODONE) { disable_intr(); com_events -= LOTS_OF_EVENTS; com->state &= ~CS_ODONE; if (!(com->state & CS_BUSY)) com->tp->t_state &= ~TS_BUSY; enable_intr(); (*linesw[tp->t_line].l_start)(tp); } if (incc <= 0 || !(tp->t_state & TS_ISOPEN)) continue; /* * Avoid the grotesquely inefficient lineswitch routine * (ttyinput) in "raw" mode. It usually takes about 450 * instructions (that's without canonical processing or echo!). * slinput is reasonably fast (usually 40 instructions plus * call overhead). */ if (tp->t_state & TS_CAN_BYPASS_L_RINT) { if (tp->t_rawq.c_cc + incc >= RB_I_HIGH_WATER && (com->state & CS_RTS_IFLOW || tp->t_iflag & IXOFF) && !(tp->t_state & TS_TBLOCK)) ttyblock(tp); tk_nin += incc; tk_rawcc += incc; tp->t_rawcc += incc; com->delta_error_counts[CE_TTY_BUF_OVERFLOW] += b_to_q((char *)buf, incc, &tp->t_rawq); ttwakeup(tp); if (tp->t_state & TS_TTSTOP && (tp->t_iflag & IXANY || tp->t_cc[VSTART] == tp->t_cc[VSTOP])) { tp->t_state &= ~TS_TTSTOP; tp->t_lflag &= ~FLUSHO; comstart(tp); } } else { do { u_char line_status; int recv_data; line_status = (u_char) buf[CE_INPUT_OFFSET]; recv_data = (u_char) *buf++; if (line_status & (LSR_BI | LSR_FE | LSR_OE | LSR_PE)) { if (line_status & LSR_BI) recv_data |= TTY_BI; if (line_status & LSR_FE) recv_data |= TTY_FE; if (line_status & LSR_OE) recv_data |= TTY_OE; if (line_status & LSR_PE) recv_data |= TTY_PE; } (*linesw[tp->t_line].l_rint)(recv_data, tp); } while (--incc > 0); } if (com_events == 0) break; } if (com_events >= LOTS_OF_EVENTS) goto repeat; } static int comparam(tp, t) struct tty *tp; struct termios *t; { int bits; int cflag; struct com_s *com; u_char cor_change; int idivisor; int iflag; cy_addr iobase; int iprescaler; int itimeout; int odivisor; int oprescaler; u_char opt; int s; int unit; /* do historical conversions */ if (t->c_ispeed == 0) t->c_ispeed = t->c_ospeed; /* check requested parameters */ idivisor = comspeed(t->c_ispeed, &iprescaler); if (idivisor < 0) return (EINVAL); odivisor = comspeed(t->c_ospeed, &oprescaler); if (odivisor < 0) return (EINVAL); /* parameters are OK, convert them to the com struct and the device */ unit = DEV_TO_UNIT(tp->t_dev); com = com_addr(unit); iobase = com->iobase; s = spltty(); cd_outb(iobase, CD1400_CAR, com->cy_align, unit & CD1400_CAR_CHAN); if (odivisor == 0) (void)commctl(com, TIOCM_DTR, DMBIC); /* hang up line */ else (void)commctl(com, TIOCM_DTR, DMBIS); if (idivisor != 0) { cd_outb(iobase, CD1400_RBPR, com->cy_align, idivisor); cd_outb(iobase, CD1400_RCOR, com->cy_align, iprescaler); } if (odivisor != 0) { cd_outb(iobase, CD1400_TBPR, com->cy_align, odivisor); cd_outb(iobase, CD1400_TCOR, com->cy_align, oprescaler); } /* * channel control * receiver enable * transmitter enable (always set) */ cflag = t->c_cflag; opt = CD1400_CCR_CMDCHANCTL | CD1400_CCR_XMTEN | (cflag & CREAD ? CD1400_CCR_RCVEN : CD1400_CCR_RCVDIS); if (opt != com->channel_control) { com->channel_control = opt; cd1400_channel_cmd(iobase, opt, com->cy_align); } #ifdef Smarts /* set special chars */ /* XXX if one is _POSIX_VDISABLE, can't use some others */ if (t->c_cc[VSTOP] != _POSIX_VDISABLE) cd_outb(iobase, CD1400_SCHR1, com->cy_align, t->c_cc[VSTOP]); if (t->c_cc[VSTART] != _POSIX_VDISABLE) cd_outb(iobase, CD1400_SCHR2, com->cy_align, t->c_cc[VSTART]); if (t->c_cc[VINTR] != _POSIX_VDISABLE) cd_outb(iobase, CD1400_SCHR3, com->cy_align, t->c_cc[VINTR]); if (t->c_cc[VSUSP] != _POSIX_VDISABLE) cd_outb(iobase, CD1400_SCHR4, com->cy_align, t->c_cc[VSUSP]); #endif /* * set channel option register 1 - * parity mode * stop bits * char length */ opt = 0; /* parity */ if (cflag & PARENB) { if (cflag & PARODD) opt |= CD1400_COR1_PARODD; opt |= CD1400_COR1_PARNORMAL; } iflag = t->c_iflag; if (!(iflag & INPCK)) opt |= CD1400_COR1_NOINPCK; bits = 1 + 1; /* stop bits */ if (cflag & CSTOPB) { ++bits; opt |= CD1400_COR1_STOP2; } /* char length */ switch (cflag & CSIZE) { case CS5: bits += 5; opt |= CD1400_COR1_CS5; break; case CS6: bits += 6; opt |= CD1400_COR1_CS6; break; case CS7: bits += 7; opt |= CD1400_COR1_CS7; break; default: bits += 8; opt |= CD1400_COR1_CS8; break; } cor_change = 0; if (opt != com->cor[0]) { cor_change |= CD1400_CCR_COR1; cd_outb(iobase, CD1400_COR1, com->cy_align, com->cor[0] = opt); } /* * Set receive time-out period, normally to max(one char time, 5 ms). */ if (t->c_ispeed == 0) itimeout = cd_inb(iobase, CD1400_RTPR, com->cy_align); else { itimeout = (1000 * bits + t->c_ispeed - 1) / t->c_ispeed; #ifdef SOFT_HOTCHAR #define MIN_RTP 1 #else #define MIN_RTP 5 #endif if (itimeout < MIN_RTP) itimeout = MIN_RTP; } if (!(t->c_lflag & ICANON) && t->c_cc[VMIN] != 0 && t->c_cc[VTIME] != 0 && t->c_cc[VTIME] * 10 > itimeout) itimeout = t->c_cc[VTIME] * 10; if (itimeout > 255) itimeout = 255; cd_outb(iobase, CD1400_RTPR, com->cy_align, itimeout); /* * set channel option register 2 - * flow control */ opt = 0; #ifdef Smarts if (iflag & IXANY) opt |= CD1400_COR2_IXANY; if (iflag & IXOFF) opt |= CD1400_COR2_IXOFF; #endif #ifndef SOFT_CTS_OFLOW if (cflag & CCTS_OFLOW) opt |= CD1400_COR2_CCTS_OFLOW; #endif if (opt != com->cor[1]) { cor_change |= CD1400_CCR_COR2; cd_outb(iobase, CD1400_COR2, com->cy_align, com->cor[1] = opt); } /* * set channel option register 3 - * receiver FIFO interrupt threshold * flow control */ opt = RxFifoThreshold; #ifdef Smarts if (t->c_lflag & ICANON) opt |= CD1400_COR3_SCD34; /* detect INTR & SUSP chars */ if (iflag & IXOFF) /* detect and transparently handle START and STOP chars */ opt |= CD1400_COR3_FCT | CD1400_COR3_SCD12; #endif if (opt != com->cor[2]) { cor_change |= CD1400_CCR_COR3; cd_outb(iobase, CD1400_COR3, com->cy_align, com->cor[2] = opt); } /* notify the CD1400 if COR1-3 have changed */ if (cor_change) cd1400_channel_cmd(iobase, CD1400_CCR_CMDCORCHG | cor_change, com->cy_align); /* * set channel option register 4 - * CR/NL processing * break processing * received exception processing */ opt = 0; if (iflag & IGNCR) opt |= CD1400_COR4_IGNCR; #ifdef Smarts /* * we need a new ttyinput() for this, as we don't want to * have ICRNL && INLCR being done in both layers, or to have * synchronisation problems */ if (iflag & ICRNL) opt |= CD1400_COR4_ICRNL; if (iflag & INLCR) opt |= CD1400_COR4_INLCR; #endif if (iflag & IGNBRK) opt |= CD1400_COR4_IGNBRK; if (!(iflag & BRKINT)) opt |= CD1400_COR4_NOBRKINT; #if 0 /* XXX using this "intelligence" breaks reporting of overruns. */ if (iflag & IGNPAR) opt |= CD1400_COR4_PFO_DISCARD; else { if (iflag & PARMRK) opt |= CD1400_COR4_PFO_ESC; else opt |= CD1400_COR4_PFO_NUL; } #else opt |= CD1400_COR4_PFO_EXCEPTION; #endif cd_outb(iobase, CD1400_COR4, com->cy_align, opt); /* * set channel option register 5 - */ opt = 0; if (iflag & ISTRIP) opt |= CD1400_COR5_ISTRIP; if (t->c_iflag & IEXTEN) /* enable LNEXT (e.g. ctrl-v quoting) handling */ opt |= CD1400_COR5_LNEXT; #ifdef Smarts if (t->c_oflag & ONLCR) opt |= CD1400_COR5_ONLCR; if (t->c_oflag & OCRNL) opt |= CD1400_COR5_OCRNL; #endif cd_outb(iobase, CD1400_COR5, com->cy_align, opt); /* * We always generate modem status change interrupts for CD changes. * Among other things, this is necessary to track TS_CARR_ON for * pstat to print even when the driver doesn't care. CD changes * should be rare so interrupts for them are not worth extra code to * avoid. We avoid interrupts for other modem status changes (except * for CTS changes when SOFT_CTS_OFLOW is configured) since this is * simplest and best. */ /* * set modem change option register 1 * generate modem interrupts on which 1 -> 0 input transitions * also controls auto-DTR output flow-control, which we don't use */ opt = CD1400_MCOR1_CDzd; #ifdef SOFT_CTS_OFLOW if (cflag & CCTS_OFLOW) opt |= CD1400_MCOR1_CTSzd; #endif cd_outb(iobase, CD1400_MCOR1, com->cy_align, opt); /* * set modem change option register 2 * generate modem interrupts on specific 0 -> 1 input transitions */ opt = CD1400_MCOR2_CDod; #ifdef SOFT_CTS_OFLOW if (cflag & CCTS_OFLOW) opt |= CD1400_MCOR2_CTSod; #endif cd_outb(iobase, CD1400_MCOR2, com->cy_align, opt); /* * XXX should have done this long ago, but there is too much state * to change all atomically. */ disable_intr(); com->state &= ~CS_TTGO; if (!(tp->t_state & TS_TTSTOP)) com->state |= CS_TTGO; if (cflag & CRTS_IFLOW) { com->state |= CS_RTS_IFLOW; /* * If CS_RTS_IFLOW just changed from off to on, the change * needs to be propagated to MCR_RTS. This isn't urgent, * so do it later by calling comstart() instead of repeating * a lot of code from comstart() here. */ } else if (com->state & CS_RTS_IFLOW) { com->state &= ~CS_RTS_IFLOW; /* * CS_RTS_IFLOW just changed from on to off. Force MCR_RTS * on here, since comstart() won't do it later. */ #if 0 outb(com->modem_ctl_port, com->mcr_image |= MCR_RTS); #else cd_outb(iobase, CD1400_MSVR1, com->cy_align, com->mcr_image |= MCR_RTS); #endif } /* * Set up state to handle output flow control. * XXX - worth handling MDMBUF (DCD) flow control at the lowest level? * Now has 10+ msec latency, while CTS flow has 50- usec latency. */ com->state |= CS_ODEVREADY; #ifdef SOFT_CTS_OFLOW com->state &= ~CS_CTS_OFLOW; if (cflag & CCTS_OFLOW) { com->state |= CS_CTS_OFLOW; if (!(com->last_modem_status & MSR_CTS)) com->state &= ~CS_ODEVREADY; } #endif /* XXX shouldn't call functions while intrs are disabled. */ disc_optim(tp, t, com); #if 0 /* * Recover from fiddling with CS_TTGO. We used to call siointr1() * unconditionally, but that defeated the careful discarding of * stale input in sioopen(). */ if (com->state >= (CS_BUSY | CS_TTGO)) siointr1(com); #endif if (com->state >= (CS_BUSY | CS_TTGO | CS_ODEVREADY)) { if (!(com->intr_enable & CD1400_SRER_TXRDY)) cd_outb(iobase, CD1400_SRER, com->cy_align, com->intr_enable |= CD1400_SRER_TXRDY); } else { if (com->intr_enable & CD1400_SRER_TXRDY) cd_outb(iobase, CD1400_SRER, com->cy_align, com->intr_enable &= ~CD1400_SRER_TXRDY); } enable_intr(); splx(s); comstart(tp); return (0); } static void comstart(tp) struct tty *tp; { struct com_s *com; cy_addr iobase; int s; #ifdef CyDebug bool_t started; #endif int unit; unit = DEV_TO_UNIT(tp->t_dev); com = com_addr(unit); iobase = com->iobase; s = spltty(); #ifdef CyDebug ++com->start_count; started = FALSE; #endif disable_intr(); cd_outb(iobase, CD1400_CAR, com->cy_align, unit & CD1400_CAR_CHAN); if (tp->t_state & TS_TTSTOP) { com->state &= ~CS_TTGO; if (com->intr_enable & CD1400_SRER_TXRDY) cd_outb(iobase, CD1400_SRER, com->cy_align, com->intr_enable &= ~CD1400_SRER_TXRDY); } else { com->state |= CS_TTGO; if (com->state >= (CS_BUSY | CS_TTGO | CS_ODEVREADY) && !(com->intr_enable & CD1400_SRER_TXRDY)) cd_outb(iobase, CD1400_SRER, com->cy_align, com->intr_enable |= CD1400_SRER_TXRDY); } if (tp->t_state & TS_TBLOCK) { if (com->mcr_image & MCR_RTS && com->state & CS_RTS_IFLOW) #if 0 outb(com->modem_ctl_port, com->mcr_image &= ~MCR_RTS); #else cd_outb(iobase, CD1400_MSVR1, com->cy_align, com->mcr_image &= ~MCR_RTS); #endif } else { if (!(com->mcr_image & MCR_RTS) && com->iptr < com->ihighwater && com->state & CS_RTS_IFLOW) #if 0 outb(com->modem_ctl_port, com->mcr_image |= MCR_RTS); #else cd_outb(iobase, CD1400_MSVR1, com->cy_align, com->mcr_image |= MCR_RTS); #endif } enable_intr(); if (tp->t_state & (TS_TIMEOUT | TS_TTSTOP)) { ttwwakeup(tp); splx(s); return; } if (tp->t_outq.c_cc != 0) { struct lbq *qp; struct lbq *next; if (!com->obufs[0].l_queued) { #ifdef CyDebug started = TRUE; #endif com->obufs[0].l_tail = com->obuf1 + q_to_b(&tp->t_outq, com->obuf1, sizeof com->obuf1); com->obufs[0].l_next = NULL; com->obufs[0].l_queued = TRUE; disable_intr(); if (com->state & CS_BUSY) { qp = com->obufq.l_next; while ((next = qp->l_next) != NULL) qp = next; qp->l_next = &com->obufs[0]; } else { com->obufq.l_head = com->obufs[0].l_head; com->obufq.l_tail = com->obufs[0].l_tail; com->obufq.l_next = &com->obufs[0]; com->state |= CS_BUSY; if (com->state >= (CS_BUSY | CS_TTGO | CS_ODEVREADY)) cd_outb(iobase, CD1400_SRER, com->cy_align, com->intr_enable |= CD1400_SRER_TXRDY); } enable_intr(); } if (tp->t_outq.c_cc != 0 && !com->obufs[1].l_queued) { #ifdef CyDebug started = TRUE; #endif com->obufs[1].l_tail = com->obuf2 + q_to_b(&tp->t_outq, com->obuf2, sizeof com->obuf2); com->obufs[1].l_next = NULL; com->obufs[1].l_queued = TRUE; disable_intr(); if (com->state & CS_BUSY) { qp = com->obufq.l_next; while ((next = qp->l_next) != NULL) qp = next; qp->l_next = &com->obufs[1]; } else { com->obufq.l_head = com->obufs[1].l_head; com->obufq.l_tail = com->obufs[1].l_tail; com->obufq.l_next = &com->obufs[1]; com->state |= CS_BUSY; if (com->state >= (CS_BUSY | CS_TTGO | CS_ODEVREADY)) cd_outb(iobase, CD1400_SRER, com->cy_align, com->intr_enable |= CD1400_SRER_TXRDY); } enable_intr(); } tp->t_state |= TS_BUSY; } #ifdef CyDebug if (started) ++com->start_real; #endif #if 0 disable_intr(); if (com->state >= (CS_BUSY | CS_TTGO)) siointr1(com); /* fake interrupt to start output */ enable_intr(); #endif ttwwakeup(tp); splx(s); } static void siostop(tp, rw) struct tty *tp; int rw; { struct com_s *com; com = com_addr(DEV_TO_UNIT(tp->t_dev)); disable_intr(); if (rw & FWRITE) { com->obufs[0].l_queued = FALSE; com->obufs[1].l_queued = FALSE; if (com->state & CS_ODONE) com_events -= LOTS_OF_EVENTS; com->state &= ~(CS_ODONE | CS_BUSY); com->tp->t_state &= ~TS_BUSY; } if (rw & FREAD) { com_events -= (com->iptr - com->ibuf); com->iptr = com->ibuf; } enable_intr(); comstart(tp); /* XXX should clear h/w fifos too. */ } static struct tty * siodevtotty(dev) dev_t dev; { int mynor; int unit; mynor = minor(dev); if (mynor & CONTROL_MASK) return (NULL); unit = MINOR_TO_UNIT(mynor); if ((u_int) unit >= NSIO) return (NULL); return (&sio_tty[unit]); } static int commctl(com, bits, how) struct com_s *com; int bits; int how; { cy_addr iobase; int mcr; int msr; iobase = com->iobase; if (how == DMGET) { if (com->channel_control & CD1400_CCR_RCVEN) bits |= TIOCM_LE; mcr = com->mcr_image; if (mcr & MCR_DTR) bits |= TIOCM_DTR; if (mcr & MCR_RTS) /* XXX wired on for Cyclom-8Ys */ bits |= TIOCM_RTS; /* * We must read the modem status from the hardware because * we don't generate modem status change interrupts for all * changes, so com->prev_modem_status is not guaranteed to * be up to date. This is safe, unlike for sio, because * reading the status register doesn't clear pending modem * status change interrupts. */ msr = cd_inb(iobase, CD1400_MSVR2, com->cy_align); if (msr & MSR_CTS) bits |= TIOCM_CTS; if (msr & MSR_DCD) bits |= TIOCM_CD; if (msr & MSR_DSR) bits |= TIOCM_DSR; if (msr & MSR_RI) /* XXX not connected except for Cyclom-16Y? */ bits |= TIOCM_RI; return (bits); } mcr = 0; if (bits & TIOCM_DTR) mcr |= MCR_DTR; if (bits & TIOCM_RTS) mcr |= MCR_RTS; disable_intr(); switch (how) { case DMSET: com->mcr_image = mcr; cd_outb(iobase, CD1400_MSVR1, com->cy_align, mcr); cd_outb(iobase, CD1400_MSVR2, com->cy_align, mcr); break; case DMBIS: com->mcr_image = mcr = com->mcr_image | mcr; cd_outb(iobase, CD1400_MSVR1, com->cy_align, mcr); cd_outb(iobase, CD1400_MSVR2, com->cy_align, mcr); break; case DMBIC: com->mcr_image = mcr = com->mcr_image & ~mcr; cd_outb(iobase, CD1400_MSVR1, com->cy_align, mcr); cd_outb(iobase, CD1400_MSVR2, com->cy_align, mcr); break; } enable_intr(); return (0); } static void siosettimeout() { struct com_s *com; bool_t someopen; int unit; /* * Set our timeout period to 1 second if no polled devices are open. * Otherwise set it to max(1/200, 1/hz). * Enable timeouts iff some device is open. */ untimeout(comwakeup, (void *)NULL, sio_timeout_handle); sio_timeout = hz; someopen = FALSE; for (unit = 0; unit < NSIO; ++unit) { com = com_addr(unit); if (com != NULL && com->tp != NULL && com->tp->t_state & TS_ISOPEN) { someopen = TRUE; #if 0 if (com->poll || com->poll_output) { sio_timeout = hz > 200 ? hz / 200 : 1; break; } #endif } } if (someopen) { sio_timeouts_until_log = hz / sio_timeout; sio_timeout_handle = timeout(comwakeup, (void *)NULL, sio_timeout); } else { /* Flush error messages, if any. */ sio_timeouts_until_log = 1; comwakeup((void *)NULL); untimeout(comwakeup, (void *)NULL, sio_timeout_handle); } } static void comwakeup(chan) void *chan; { struct com_s *com; int unit; sio_timeout_handle = timeout(comwakeup, (void *)NULL, sio_timeout); #if 0 /* * Recover from lost output interrupts. * Poll any lines that don't use interrupts. */ for (unit = 0; unit < NSIO; ++unit) { com = com_addr(unit); if (com != NULL && (com->state >= (CS_BUSY | CS_TTGO) || com->poll)) { disable_intr(); siointr1(com); enable_intr(); } } #endif /* * Check for and log errors, but not too often. */ if (--sio_timeouts_until_log > 0) return; sio_timeouts_until_log = hz / sio_timeout; for (unit = 0; unit < NSIO; ++unit) { int errnum; com = com_addr(unit); if (com == NULL) continue; for (errnum = 0; errnum < CE_NTYPES; ++errnum) { u_int delta; u_long total; disable_intr(); delta = com->delta_error_counts[errnum]; com->delta_error_counts[errnum] = 0; enable_intr(); if (delta == 0) continue; total = com->error_counts[errnum] += delta; log(LOG_ERR, "cy%d: %u more %s%s (total %lu)\n", unit, delta, error_desc[errnum], delta == 1 ? "" : "s", total); } } } static void disc_optim(tp, t, com) struct tty *tp; struct termios *t; struct com_s *com; { #ifndef SOFT_HOTCHAR cy_addr iobase; u_char opt; #endif /* * XXX can skip a lot more cases if Smarts. Maybe * (IGNCR | ISTRIP | IXON) in c_iflag. But perhaps we * shouldn't skip if (TS_CNTTB | TS_LNCH) is set in t_state. */ if (!(t->c_iflag & (ICRNL | IGNCR | IMAXBEL | INLCR | ISTRIP | IXON)) && (!(t->c_iflag & BRKINT) || (t->c_iflag & IGNBRK)) && (!(t->c_iflag & PARMRK) || (t->c_iflag & (IGNPAR | IGNBRK)) == (IGNPAR | IGNBRK)) && !(t->c_lflag & (ECHO | ICANON | IEXTEN | ISIG | PENDIN)) && linesw[tp->t_line].l_rint == ttyinput) tp->t_state |= TS_CAN_BYPASS_L_RINT; else tp->t_state &= ~TS_CAN_BYPASS_L_RINT; com->hotchar = linesw[tp->t_line].l_hotchar; #ifndef SOFT_HOTCHAR iobase = com->iobase; cd_outb(iobase, CD1400_CAR, com->cy_align, com->unit & CD1400_CAR_CHAN); opt = com->cor[2] & ~CD1400_COR3_SCD34; if (com->hotchar != 0) { cd_outb(iobase, CD1400_SCHR3, com->cy_align, com->hotchar); cd_outb(iobase, CD1400_SCHR4, com->cy_align, com->hotchar); opt |= CD1400_COR3_SCD34; } if (opt != com->cor[2]) { cd_outb(iobase, CD1400_COR3, com->cy_align, com->cor[2] = opt); cd1400_channel_cmd(com->iobase, CD1400_CCR_CMDCORCHG | CD1400_CCR_COR3, com->cy_align); } #endif } #ifdef Smarts /* standard line discipline input routine */ int cyinput(c, tp) int c; struct tty *tp; { /* XXX duplicate ttyinput(), but without the IXOFF/IXON/ISTRIP/IPARMRK * bits, as they are done by the CD1400. Hardly worth the effort, * given that high-throughput sessions are raw anyhow. */ } #endif /* Smarts */ static int comspeed(speed, prescaler_io) speed_t speed; int *prescaler_io; { int actual; int error; int divider; int prescaler; int prescaler_unit; if (speed == 0) return (0); if (speed < 0 || speed > 150000) return (-1); /* determine which prescaler to use */ for (prescaler_unit = 4, prescaler = 2048; prescaler_unit; prescaler_unit--, prescaler >>= 2) { if (CY_CLOCK / prescaler / speed > 63) break; } divider = (CY_CLOCK / prescaler * 2 / speed + 1) / 2; /* round off */ if (divider > 255) divider = 255; actual = CY_CLOCK/prescaler/divider; /* 10 times error in percent: */ error = ((actual - (long)speed) * 2000 / (long)speed + 1) / 2; /* 3.0% max error tolerance */ if (error < -30 || error > 30) return (-1); #if 0 printf("prescaler = %d (%d)\n", prescaler, prescaler_unit); printf("divider = %d (%x)\n", divider, divider); printf("actual = %d\n", actual); printf("error = %d\n", error); #endif *prescaler_io = prescaler_unit; return (divider); } static void cd1400_channel_cmd(iobase, cmd, cy_align) cy_addr iobase; int cmd; int cy_align; { /* XXX hsu@clinet.fi: This is always more dependent on ISA bus speed, as the card is probed every round? Replaced delaycount with 8k. Either delaycount has to be implemented in FreeBSD or more sensible way of doing these should be implemented. DELAY isn't enough here. */ u_int maxwait = 5 * 8 * 1024; /* approx. 5 ms */ /* wait for processing of previous command to complete */ while (cd_inb(iobase, CD1400_CCR, cy_align) && maxwait--) ; if (!maxwait) log(LOG_ERR, "cy: channel command timeout (%d loops) - arrgh\n", 5 * 8 * 1024); cd_outb(iobase, CD1400_CCR, cy_align, cmd); } #ifdef CyDebug /* useful in ddb */ void cystatus(unit) int unit; { struct com_s *com; cy_addr iobase; u_int ocount; struct tty *tp; com = com_addr(unit); printf("info for channel %d\n", unit); printf("------------------\n"); printf("total cyclom service probes:\t%d\n", cy_svrr_probes); printf("calls to upper layer:\t\t%d\n", cy_timeouts); if (com == NULL) return; iobase = com->iobase; printf("\n"); printf("cd1400 base address:\\tt%p\n", iobase); cd_outb(iobase, CD1400_CAR, com->cy_align, unit & CD1400_CAR_CHAN); printf("saved channel_control:\t\t0x%02x\n", com->channel_control); printf("saved cor1-3:\t\t\t0x%02x 0x%02x 0x%02x\n", com->cor[0], com->cor[1], com->cor[2]); printf("service request enable reg:\t0x%02x (0x%02x cached)\n", cd_inb(iobase, CD1400_SRER, com->cy_align), com->intr_enable); printf("service request register:\t0x%02x\n", cd_inb(iobase, CD1400_SVRR, com->cy_align)); printf("modem status:\t\t\t0x%02x (0x%02x cached)\n", cd_inb(iobase, CD1400_MSVR2, com->cy_align), com->prev_modem_status); printf("rx/tx/mdm interrupt registers:\t0x%02x 0x%02x 0x%02x\n", cd_inb(iobase, CD1400_RIR, com->cy_align), cd_inb(iobase, CD1400_TIR, com->cy_align), cd_inb(iobase, CD1400_MIR, com->cy_align)); printf("\n"); printf("com state:\t\t\t0x%02x\n", com->state); printf("calls to comstart():\t\t%d (%d useful)\n", com->start_count, com->start_real); printf("rx buffer chars free:\t\t%d\n", com->iptr - com->ibuf); ocount = 0; if (com->obufs[0].l_queued) ocount += com->obufs[0].l_tail - com->obufs[0].l_head; if (com->obufs[1].l_queued) ocount += com->obufs[1].l_tail - com->obufs[1].l_head; printf("tx buffer chars:\t\t%u\n", ocount); printf("received chars:\t\t\t%d\n", com->bytes_in); printf("received exceptions:\t\t%d\n", com->recv_exception); printf("modem signal deltas:\t\t%d\n", com->mdm); printf("transmitted chars:\t\t%d\n", com->bytes_out); printf("\n"); tp = com->tp; if (tp != NULL) { printf("tty state:\t\t\t0x%08x\n", tp->t_state); printf( "upper layer queue lengths:\t%d raw, %d canon, %d output\n", tp->t_rawq.c_cc, tp->t_canq.c_cc, tp->t_outq.c_cc); } else printf("tty state:\t\t\tclosed\n"); } #endif /* CyDebug */ Index: head/sys/dev/cy/cy_isa.c =================================================================== --- head/sys/dev/cy/cy_isa.c (revision 38245) +++ head/sys/dev/cy/cy_isa.c (revision 38246) @@ -1,2589 +1,2585 @@ /*- * cyclades cyclom-y serial driver * Andrew Herbert , 17 August 1993 * * Copyright (c) 1993 Andrew Herbert. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. The name Andrew Herbert may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY ``AS IS'' AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN * NO EVENT SHALL I BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * - * $Id: cy.c,v 1.64 1998/07/15 12:18:12 bde Exp $ + * $Id: cy.c,v 1.65 1998/07/29 18:48:20 bde Exp $ */ #include "opt_compat.h" #include "opt_devfs.h" #include "cy.h" /* * TODO: * Implement BREAK. * Fix overflows when closing line. * Atomic COR change. * Consoles. */ /* * Temporary compile-time configuration options. */ #define RxFifoThreshold (CD1400_RX_FIFO_SIZE / 2) /* Number of chars in the receiver FIFO before an * an interrupt is generated. Should depend on * line speed. Needs to be about 6 on a 486DX33 * for 4 active ports at 115200 bps. Why doesn't * 10 work? */ #define PollMode /* Use polling-based irq service routine, not the * hardware svcack lines. Must be defined for * Cyclom-16Y boards. Less efficient for Cyclom-8Ys, * and stops 4 * 115200 bps from working. */ #undef Smarts /* Enable slightly more CD1400 intelligence. Mainly * the output CR/LF processing, plus we can avoid a * few checks usually done in ttyinput(). * * XXX not fully implemented, and not particularly * worthwhile. */ #undef CyDebug /* Include debugging code (not very expensive). */ /* These will go away. */ #undef SOFT_CTS_OFLOW #define SOFT_HOTCHAR #include #include #include #include #include #include #include +#include #include #include #include #ifdef DEVFS #include #endif #include +#include #include #include #include #ifdef SMP #define disable_intr() COM_DISABLE_INTR() #define enable_intr() COM_ENABLE_INTR() #endif /* SMP */ /* * Dictionary so that I can name everything *sio* or *com* to compare with * sio.c. There is also lots of ugly formatting and unnecessary ifdefs to * simplify the comparision. These will go away. */ #define LSR_BI CD1400_RDSR_BREAK #define LSR_FE CD1400_RDSR_FE #define LSR_OE CD1400_RDSR_OE #define LSR_PE CD1400_RDSR_PE #define MCR_DTR CD1400_MSVR2_DTR #define MCR_RTS CD1400_MSVR1_RTS #define MSR_CTS CD1400_MSVR2_CTS #define MSR_DCD CD1400_MSVR2_CD #define MSR_DSR CD1400_MSVR2_DSR #define MSR_RI CD1400_MSVR2_RI #define NSIO (NCY * CY_MAX_PORTS) #define comconsole cyconsole #define comdefaultrate cydefaultrate #define com_events cy_events #define comhardclose cyhardclose #define commctl cymctl #define comparam cyparam +#define siopoll_registered cypoll_registered #define comspeed cyspeed #define comstart cystart #define comwakeup cywakeup #define nsio_tty ncy_tty #define p_com_addr p_cy_addr #define sioattach cyattach #define sioclose cyclose #define siodevtotty cydevtotty #define siodriver cydriver #define siodtrwakeup cydtrwakeup #define sioioctl cyioctl #define siointr cyintr #define siointr1 cyintr1 #define sioopen cyopen #define siopoll cypoll #define sioprobe cyprobe #define sioread cyread #define siosettimeout cysettimeout #define siostop cystop #define siowrite cywrite #define sio_timeout cy_timeout #define sio_timeouts_until_log cy_timeouts_until_log #define sio_timeout_handle cy_timeout_handle #define sio_tty cy_tty #define CY_MAX_PORTS (CD1400_NO_OF_CHANNELS * CY_MAX_CD1400s) /* We encode the cyclom unit number (cyu) in spare bits in the IVR's. */ #define CD1400_xIVR_CHAN_SHIFT 3 #define CD1400_xIVR_CHAN 0x1F #define LOTS_OF_EVENTS 64 /* helps separate urgent events from input */ #define RB_I_HIGH_WATER (TTYHOG - 2 * RS_IBUFSIZE) #define RS_IBUFSIZE 256 #define CALLOUT_MASK 0x80 #define CONTROL_MASK 0x60 #define CONTROL_INIT_STATE 0x20 #define CONTROL_LOCK_STATE 0x40 #define DEV_TO_UNIT(dev) (MINOR_TO_UNIT(minor(dev))) #define MINOR_MAGIC_MASK (CALLOUT_MASK | CONTROL_MASK) #define MINOR_TO_UNIT(mynor) (((mynor) >> 16) * CY_MAX_PORTS \ | (((mynor) & 0xff) & ~MINOR_MAGIC_MASK)) /* * Input buffer watermarks. * The external device is asked to stop sending when the buffer exactly reaches * high water, or when the high level requests it. * The high level is notified immediately (rather than at a later clock tick) * when this watermark is reached. * The buffer size is chosen so the watermark should almost never be reached. * The low watermark is invisibly 0 since the buffer is always emptied all at * once. */ #define RS_IHIGHWATER (3 * RS_IBUFSIZE / 4) /* * com state bits. * (CS_BUSY | CS_TTGO) and (CS_BUSY | CS_TTGO | CS_ODEVREADY) must be higher * than the other bits so that they can be tested as a group without masking * off the low bits. * * The following com and tty flags correspond closely: * CS_BUSY = TS_BUSY (maintained by comstart(), siopoll() and * siostop()) * CS_TTGO = ~TS_TTSTOP (maintained by comparam() and comstart()) * CS_CTS_OFLOW = CCTS_OFLOW (maintained by comparam()) * CS_RTS_IFLOW = CRTS_IFLOW (maintained by comparam()) * TS_FLUSH is not used. * XXX I think TIOCSETA doesn't clear TS_TTSTOP when it clears IXON. * XXX CS_*FLOW should be CF_*FLOW in com->flags (control flags not state). */ #define CS_BUSY 0x80 /* output in progress */ #define CS_TTGO 0x40 /* output not stopped by XOFF */ #define CS_ODEVREADY 0x20 /* external device h/w ready (CTS) */ #define CS_CHECKMSR 1 /* check of MSR scheduled */ #define CS_CTS_OFLOW 2 /* use CTS output flow control */ #define CS_DTR_OFF 0x10 /* DTR held off */ #define CS_ODONE 4 /* output completed */ #define CS_RTS_IFLOW 8 /* use RTS input flow control */ static char const * const error_desc[] = { #define CE_OVERRUN 0 "silo overflow", #define CE_INTERRUPT_BUF_OVERFLOW 1 "interrupt-level buffer overflow", #define CE_TTY_BUF_OVERFLOW 2 "tty-level buffer overflow", }; #define CE_NTYPES 3 #define CE_RECORD(com, errnum) (++(com)->delta_error_counts[errnum]) /* types. XXX - should be elsewhere */ typedef u_char bool_t; /* boolean */ typedef u_char volatile *cy_addr; /* queue of linear buffers */ struct lbq { u_char *l_head; /* next char to process */ u_char *l_tail; /* one past the last char to process */ struct lbq *l_next; /* next in queue */ bool_t l_queued; /* nonzero if queued */ }; /* com device structure */ struct com_s { u_char state; /* miscellaneous flag bits */ bool_t active_out; /* nonzero if the callout device is open */ #if 0 u_char cfcr_image; /* copy of value written to CFCR */ u_char fifo_image; /* copy of value written to FIFO */ bool_t hasfifo; /* nonzero for 16550 UARTs */ bool_t loses_outints; /* nonzero if device loses output interrupts */ #endif u_char mcr_image; /* copy of value written to MCR */ #if 0 #ifdef COM_MULTIPORT bool_t multiport; /* is this unit part of a multiport device? */ #endif /* COM_MULTIPORT */ bool_t no_irq; /* nonzero if irq is not attached */ bool_t poll; /* nonzero if polling is required */ bool_t poll_output; /* nonzero if polling for output is required */ #endif int unit; /* unit number */ int dtr_wait; /* time to hold DTR down on close (* 1/hz) */ #if 0 u_int tx_fifo_size; #endif u_int wopeners; /* # processes waiting for DCD in open() */ /* * The high level of the driver never reads status registers directly * because there would be too many side effects to handle conveniently. * Instead, it reads copies of the registers stored here by the * interrupt handler. */ u_char last_modem_status; /* last MSR read by intr handler */ u_char prev_modem_status; /* last MSR handled by high level */ u_char hotchar; /* ldisc-specific char to be handled ASAP */ u_char *ibuf; /* start of input buffer */ u_char *ibufend; /* end of input buffer */ u_char *ihighwater; /* threshold in input buffer */ u_char *iptr; /* next free spot in input buffer */ struct lbq obufq; /* head of queue of output buffers */ struct lbq obufs[2]; /* output buffers */ int cy_align; /* index for register alignment */ cy_addr cy_iobase; /* base address of this port's cyclom */ cy_addr iobase; /* base address of this port's cd1400 */ struct tty *tp; /* cross reference */ /* Initial state. */ struct termios it_in; /* should be in struct tty */ struct termios it_out; /* Lock state. */ struct termios lt_in; /* should be in struct tty */ struct termios lt_out; bool_t do_timestamp; bool_t do_dcd_timestamp; struct timeval timestamp; struct timeval dcd_timestamp; u_long bytes_in; /* statistics */ u_long bytes_out; u_int delta_error_counts[CE_NTYPES]; u_long error_counts[CE_NTYPES]; u_int recv_exception; /* exception chars received */ u_int mdm; /* modem signal changes */ #ifdef CyDebug u_int start_count; /* no. of calls to comstart() */ u_int start_real; /* no. of calls that did something */ #endif u_char channel_control;/* CD1400 CCR control command shadow */ u_char cor[3]; /* CD1400 COR1-3 shadows */ u_char intr_enable; /* CD1400 SRER shadow */ /* * Ping-pong input buffers. The extra factor of 2 in the sizes is * to allow for an error byte for each input byte. */ #define CE_INPUT_OFFSET RS_IBUFSIZE u_char ibuf1[2 * RS_IBUFSIZE]; u_char ibuf2[2 * RS_IBUFSIZE]; /* * Data area for output buffers. Someday we should build the output * buffer queue without copying data. */ u_char obuf1[256]; u_char obuf2[256]; #ifdef DEVFS void *devfs_token_ttyd; void *devfs_token_ttyl; void *devfs_token_ttyi; void *devfs_token_cuaa; void *devfs_token_cual; void *devfs_token_cuai; #endif }; -/* - * XXX public functions in drivers should be declared in headers produced - * by `config', not here. - */ - -/* Interrupt handling entry point. */ -void siopoll __P((void)); - -/* Device switch entry points. */ -#define sioreset noreset -#define siommap nommap -#define siostrategy nostrategy - /* PCI driver entry point. */ int cyattach_common __P((cy_addr cy_iobase, int cy_align)); static int cy_units __P((cy_addr cy_iobase, int cy_align)); static int sioattach __P((struct isa_device *dev)); static void cd1400_channel_cmd __P((cy_addr iobase, int cmd, int cy_align)); static timeout_t siodtrwakeup; static void comhardclose __P((struct com_s *com)); #if 0 static void siointr1 __P((struct com_s *com)); #endif static int commctl __P((struct com_s *com, int bits, int how)); static int comparam __P((struct tty *tp, struct termios *t)); +static swihand_t siopoll; static int sioprobe __P((struct isa_device *dev)); static void siosettimeout __P((void)); static int comspeed __P((speed_t speed, int *prescaler_io)); static void comstart __P((struct tty *tp)); static timeout_t comwakeup; static void disc_optim __P((struct tty *tp, struct termios *t, struct com_s *com)); #ifdef CyDebug void cystatus __P((int unit)); #endif static char driver_name[] = "cy"; /* table and macro for fast conversion from a unit number to its com struct */ static struct com_s *p_com_addr[NSIO]; #define com_addr(unit) (p_com_addr[unit]) struct isa_driver siodriver = { sioprobe, sioattach, driver_name }; static d_open_t sioopen; static d_close_t sioclose; static d_read_t sioread; static d_write_t siowrite; static d_ioctl_t sioioctl; static d_stop_t siostop; static d_devtotty_t siodevtotty; #define CDEV_MAJOR 48 static struct cdevsw sio_cdevsw = { sioopen, sioclose, sioread, siowrite, sioioctl, siostop, noreset, siodevtotty, ttpoll, nommap, NULL, driver_name, NULL, -1, }; static int comconsole = -1; static speed_t comdefaultrate = TTYDEF_SPEED; static u_int com_events; /* input chars + weighted output completions */ +static bool_t siopoll_registered; static int sio_timeout; static int sio_timeouts_until_log; static struct callout_handle sio_timeout_handle = CALLOUT_HANDLE_INITIALIZER(&sio_timeout_handle); #if 0 /* XXX */ static struct tty *sio_tty[NSIO]; #else static struct tty sio_tty[NSIO]; #endif static const int nsio_tty = NSIO; #ifdef CyDebug static u_int cd_inbs; static u_int cy_inbs; static u_int cd_outbs; static u_int cy_outbs; static u_int cy_svrr_probes; static u_int cy_timeouts; #endif static int cy_chip_offset[] = { 0x0000, 0x0400, 0x0800, 0x0c00, 0x0200, 0x0600, 0x0a00, 0x0e00, }; static int cy_nr_cd1400s[NCY]; static int cy_total_devices; #undef RxFifoThreshold static int volatile RxFifoThreshold = (CD1400_RX_FIFO_SIZE / 2); static int sioprobe(dev) struct isa_device *dev; { cy_addr iobase; iobase = (cy_addr)dev->id_maddr; /* Cyclom-16Y hardware reset (Cyclom-8Ys don't care) */ cd_inb(iobase, CY16_RESET, 0); /* XXX? */ DELAY(500); /* wait for the board to get its act together */ /* this is needed to get the board out of reset */ cd_outb(iobase, CY_CLEAR_INTR, 0, 0); DELAY(500); return (cy_units(iobase, 0) == 0 ? 0 : -1); } static int cy_units(cy_iobase, cy_align) cy_addr cy_iobase; int cy_align; { int cyu; u_char firmware_version; int i; cy_addr iobase; for (cyu = 0; cyu < CY_MAX_CD1400s; ++cyu) { iobase = cy_iobase + (cy_chip_offset[cyu] << cy_align); /* wait for chip to become ready for new command */ for (i = 0; i < 10; i++) { DELAY(50); if (!cd_inb(iobase, CD1400_CCR, cy_align)) break; } /* clear the GFRCR register */ cd_outb(iobase, CD1400_GFRCR, cy_align, 0); /* issue a reset command */ cd_outb(iobase, CD1400_CCR, cy_align, CD1400_CCR_CMDRESET | CD1400_CCR_FULLRESET); /* wait for the CD1400 to initialize itself */ for (i = 0; i < 200; i++) { DELAY(50); /* retrieve firmware version */ firmware_version = cd_inb(iobase, CD1400_GFRCR, cy_align); if ((firmware_version & 0xf0) == 0x40) break; } /* * Anything in the 0x40-0x4F range is fine. * If one CD1400 is bad then we don't support higher * numbered good ones on this board. */ if ((firmware_version & 0xf0) != 0x40) break; } return (cyu); } static int sioattach(isdp) struct isa_device *isdp; { int adapter; adapter = cyattach_common((cy_addr) isdp->id_maddr, 0); if (adapter < 0) return (0); /* * XXX * This kludge is to allow ISA/PCI device specifications in the * kernel config file to be in any order. */ if (isdp->id_unit != adapter) { printf("cy%d: attached as cy%d\n", isdp->id_unit, adapter); isdp->id_unit = adapter; /* XXX */ } isdp->id_ri_flags |= RI_FAST; return (1); } int cyattach_common(cy_iobase, cy_align) cy_addr cy_iobase; int cy_align; { int adapter; int cyu; dev_t dev; cy_addr iobase; int ncyu; int unit; adapter = cy_total_devices; if ((u_int)adapter >= NCY) { printf( "cy%d: can't attach adapter: insufficient cy devices configured\n", adapter); return (-1); } ncyu = cy_units(cy_iobase, cy_align); if (ncyu == 0) return (-1); cy_nr_cd1400s[adapter] = ncyu; cy_total_devices++; unit = adapter * CY_MAX_PORTS; for (cyu = 0; cyu < ncyu; ++cyu) { int cdu; iobase = (cy_addr) (cy_iobase + (cy_chip_offset[cyu] << cy_align)); /* Set up a receive timeout period of than 1+ ms. */ cd_outb(iobase, CD1400_PPR, cy_align, howmany(CY_CLOCK / CD1400_PPR_PRESCALER, 1000)); for (cdu = 0; cdu < CD1400_NO_OF_CHANNELS; ++cdu, ++unit) { struct com_s *com; int s; com = malloc(sizeof *com, M_DEVBUF, M_NOWAIT); if (com == NULL) break; bzero(com, sizeof *com); com->unit = unit; com->dtr_wait = 3 * hz; com->iptr = com->ibuf = com->ibuf1; com->ibufend = com->ibuf1 + RS_IBUFSIZE; com->ihighwater = com->ibuf1 + RS_IHIGHWATER; com->obufs[0].l_head = com->obuf1; com->obufs[1].l_head = com->obuf2; com->cy_align = cy_align; com->cy_iobase = cy_iobase; com->iobase = iobase; /* * We don't use all the flags from since they * are only relevant for logins. It's important to have echo off * initially so that the line doesn't start blathering before the * echo flag can be turned off. */ com->it_in.c_iflag = 0; com->it_in.c_oflag = 0; com->it_in.c_cflag = TTYDEF_CFLAG; com->it_in.c_lflag = 0; if (unit == comconsole) { com->it_in.c_iflag = TTYDEF_IFLAG; com->it_in.c_oflag = TTYDEF_OFLAG; com->it_in.c_cflag = TTYDEF_CFLAG | CLOCAL; com->it_in.c_lflag = TTYDEF_LFLAG; com->lt_out.c_cflag = com->lt_in.c_cflag = CLOCAL; } termioschars(&com->it_in); com->it_in.c_ispeed = com->it_in.c_ospeed = comdefaultrate; com->it_out = com->it_in; s = spltty(); com_addr(unit) = com; splx(s); dev = makedev(CDEV_MAJOR, 0); cdevsw_add(&dev, &sio_cdevsw, NULL); #ifdef DEVFS com->devfs_token_ttyd = devfs_add_devswf(&sio_cdevsw, unit, DV_CHR, UID_ROOT, GID_WHEEL, 0600, "ttyc%r%r", adapter, unit % CY_MAX_PORTS); com->devfs_token_ttyi = devfs_add_devswf(&sio_cdevsw, unit | CONTROL_INIT_STATE, DV_CHR, UID_ROOT, GID_WHEEL, 0600, "ttyic%r%r", adapter, unit % CY_MAX_PORTS); com->devfs_token_ttyl = devfs_add_devswf(&sio_cdevsw, unit | CONTROL_LOCK_STATE, DV_CHR, UID_ROOT, GID_WHEEL, 0600, "ttylc%r%r", adapter, unit % CY_MAX_PORTS); com->devfs_token_cuaa = devfs_add_devswf(&sio_cdevsw, unit | CALLOUT_MASK, DV_CHR, UID_UUCP, GID_DIALER, 0660, "cuac%r%r", adapter, unit % CY_MAX_PORTS); com->devfs_token_cuai = devfs_add_devswf(&sio_cdevsw, unit | CALLOUT_MASK | CONTROL_INIT_STATE, DV_CHR, UID_UUCP, GID_DIALER, 0660, "cuaic%r%r", adapter, unit % CY_MAX_PORTS); com->devfs_token_cual = devfs_add_devswf(&sio_cdevsw, unit | CALLOUT_MASK | CONTROL_LOCK_STATE, DV_CHR, UID_UUCP, GID_DIALER, 0660, "cualc%r%r", adapter, unit % CY_MAX_PORTS); #endif } } + if (!siopoll_registered) { + register_swi(SWI_TTY, siopoll); + siopoll_registered = TRUE; + } /* ensure an edge for the next interrupt */ cd_outb(cy_iobase, CY_CLEAR_INTR, cy_align, 0); return (adapter); } static int sioopen(dev, flag, mode, p) dev_t dev; int flag; int mode; struct proc *p; { struct com_s *com; int error; cy_addr iobase; int mynor; int s; struct tty *tp; int unit; mynor = minor(dev); unit = MINOR_TO_UNIT(mynor); if ((u_int) unit >= NSIO || (com = com_addr(unit)) == NULL) return (ENXIO); if (mynor & CONTROL_MASK) return (0); #if 0 /* XXX */ tp = com->tp = sio_tty[unit] = ttymalloc(sio_tty[unit]); #else tp = com->tp = &sio_tty[unit]; #endif s = spltty(); /* * We jump to this label after all non-interrupted sleeps to pick * up any changes of the device state. */ open_top: while (com->state & CS_DTR_OFF) { error = tsleep(&com->dtr_wait, TTIPRI | PCATCH, "cydtr", 0); if (error != 0) goto out; } if (tp->t_state & TS_ISOPEN) { /* * The device is open, so everything has been initialized. * Handle conflicts. */ if (mynor & CALLOUT_MASK) { if (!com->active_out) { error = EBUSY; goto out; } } else { if (com->active_out) { if (flag & O_NONBLOCK) { error = EBUSY; goto out; } error = tsleep(&com->active_out, TTIPRI | PCATCH, "cybi", 0); if (error != 0) goto out; goto open_top; } } if (tp->t_state & TS_XCLUDE && p->p_ucred->cr_uid != 0) { error = EBUSY; goto out; } } else { /* * The device isn't open, so there are no conflicts. * Initialize it. Initialization is done twice in many * cases: to preempt sleeping callin opens if we are * callout, and to complete a callin open after DCD rises. */ tp->t_oproc = comstart; tp->t_param = comparam; tp->t_dev = dev; tp->t_termios = mynor & CALLOUT_MASK ? com->it_out : com->it_in; #if 0 (void)commctl(com, TIOCM_DTR | TIOCM_RTS, DMSET); com->poll = com->no_irq; com->poll_output = com->loses_outints; #endif ++com->wopeners; iobase = com->iobase; /* reset this channel */ cd_outb(iobase, CD1400_CAR, com->cy_align, unit & CD1400_CAR_CHAN); cd1400_channel_cmd(iobase, CD1400_CCR_CMDRESET, com->cy_align); /* * Resetting disables the transmitter and receiver as well as * flushing the fifos so some of our cached state becomes * invalid. The documentation suggests that all registers * for the current channel are reset to defaults, but * apparently none are. We wouldn't want DTR cleared. */ com->channel_control = 0; /* Encode per-board unit in LIVR for access in intr routines. */ cd_outb(iobase, CD1400_LIVR, com->cy_align, (unit & CD1400_xIVR_CHAN) << CD1400_xIVR_CHAN_SHIFT); /* * raise dtr and generally set things up correctly. this * has the side-effect of selecting the appropriate cd1400 * channel, to help us with subsequent channel control stuff */ error = comparam(tp, &tp->t_termios); --com->wopeners; if (error != 0) goto out; /* * XXX we should goto open_top if comparam() slept. */ ttsetwater(tp); #if 0 if (com->hasfifo) { /* * (Re)enable and drain fifos. * * Certain SMC chips cause problems if the fifos * are enabled while input is ready. Turn off the * fifo if necessary to clear the input. We test * the input ready bit after enabling the fifos * since we've already enabled them in comparam() * and to handle races between enabling and fresh * input. */ while (TRUE) { outb(iobase + com_fifo, FIFO_RCV_RST | FIFO_XMT_RST | com->fifo_image); DELAY(100); if (!(inb(com->line_status_port) & LSR_RXRDY)) break; outb(iobase + com_fifo, 0); DELAY(100); (void) inb(com->data_port); } } disable_intr(); (void) inb(com->line_status_port); (void) inb(com->data_port); com->prev_modem_status = com->last_modem_status = inb(com->modem_status_port); outb(iobase + com_ier, IER_ERXRDY | IER_ETXRDY | IER_ERLS | IER_EMSC); enable_intr(); #else /* !0 */ /* XXX raise RTS too */ (void)commctl(com, TIOCM_DTR | TIOCM_RTS, DMSET); disable_intr(); com->prev_modem_status = com->last_modem_status = cd_inb(iobase, CD1400_MSVR2, com->cy_align); cd_outb(iobase, CD1400_SRER, com->cy_align, com->intr_enable = CD1400_SRER_MDMCH | CD1400_SRER_RXDATA); enable_intr(); #endif /* 0 */ /* * Handle initial DCD. Callout devices get a fake initial * DCD (trapdoor DCD). If we are callout, then any sleeping * callin opens get woken up and resume sleeping on "cybi" * instead of "cydcd". */ /* * XXX `mynor & CALLOUT_MASK' should be * `tp->t_cflag & (SOFT_CARRIER | TRAPDOOR_CARRIER) where * TRAPDOOR_CARRIER is the default initial state for callout * devices and SOFT_CARRIER is like CLOCAL except it hides * the true carrier. */ if (com->prev_modem_status & MSR_DCD || mynor & CALLOUT_MASK) (*linesw[tp->t_line].l_modem)(tp, 1); } /* * Wait for DCD if necessary. */ if (!(tp->t_state & TS_CARR_ON) && !(mynor & CALLOUT_MASK) && !(tp->t_cflag & CLOCAL) && !(flag & O_NONBLOCK)) { ++com->wopeners; error = tsleep(TSA_CARR_ON(tp), TTIPRI | PCATCH, "cydcd", 0); --com->wopeners; if (error != 0) goto out; goto open_top; } error = (*linesw[tp->t_line].l_open)(dev, tp); disc_optim(tp, &tp->t_termios, com); if (tp->t_state & TS_ISOPEN && mynor & CALLOUT_MASK) com->active_out = TRUE; siosettimeout(); out: splx(s); if (!(tp->t_state & TS_ISOPEN) && com->wopeners == 0) comhardclose(com); return (error); } static int sioclose(dev, flag, mode, p) dev_t dev; int flag; int mode; struct proc *p; { struct com_s *com; int mynor; int s; struct tty *tp; mynor = minor(dev); if (mynor & CONTROL_MASK) return (0); com = com_addr(MINOR_TO_UNIT(mynor)); tp = com->tp; s = spltty(); (*linesw[tp->t_line].l_close)(tp, flag); disc_optim(tp, &tp->t_termios, com); siostop(tp, FREAD | FWRITE); comhardclose(com); ttyclose(tp); siosettimeout(); splx(s); #ifdef broken /* session holds a ref to the tty; can't deallocate */ ttyfree(tp); com->tp = sio_tty[unit] = NULL; #endif return (0); } static void comhardclose(com) struct com_s *com; { cy_addr iobase; int s; struct tty *tp; int unit; unit = com->unit; iobase = com->iobase; s = spltty(); #if 0 com->poll = FALSE; com->poll_output = FALSE; #endif com->do_timestamp = 0; cd_outb(iobase, CD1400_CAR, com->cy_align, unit & CD1400_CAR_CHAN); #if 0 outb(iobase + com_cfcr, com->cfcr_image &= ~CFCR_SBREAK); #endif { #if 0 outb(iobase + com_ier, 0); #else disable_intr(); cd_outb(iobase, CD1400_SRER, com->cy_align, com->intr_enable = 0); enable_intr(); #endif tp = com->tp; if (tp->t_cflag & HUPCL /* * XXX we will miss any carrier drop between here and the * next open. Perhaps we should watch DCD even when the * port is closed; it is not sufficient to check it at * the next open because it might go up and down while * we're not watching. */ || !com->active_out && !(com->prev_modem_status & MSR_DCD) && !(com->it_in.c_cflag & CLOCAL) || !(tp->t_state & TS_ISOPEN)) { (void)commctl(com, TIOCM_DTR, DMBIC); /* Disable receiver (leave transmitter enabled). */ com->channel_control = CD1400_CCR_CMDCHANCTL | CD1400_CCR_XMTEN | CD1400_CCR_RCVDIS; cd1400_channel_cmd(iobase, com->channel_control, com->cy_align); if (com->dtr_wait != 0) { timeout(siodtrwakeup, com, com->dtr_wait); com->state |= CS_DTR_OFF; } } } #if 0 if (com->hasfifo) { /* * Disable fifos so that they are off after controlled * reboots. Some BIOSes fail to detect 16550s when the * fifos are enabled. */ outb(iobase + com_fifo, 0); } #endif com->active_out = FALSE; wakeup(&com->active_out); wakeup(TSA_CARR_ON(tp)); /* restart any wopeners */ splx(s); } static int sioread(dev, uio, flag) dev_t dev; struct uio *uio; int flag; { int mynor; struct tty *tp; mynor = minor(dev); if (mynor & CONTROL_MASK) return (ENODEV); tp = com_addr(MINOR_TO_UNIT(mynor))->tp; return ((*linesw[tp->t_line].l_read)(tp, uio, flag)); } static int siowrite(dev, uio, flag) dev_t dev; struct uio *uio; int flag; { int mynor; struct tty *tp; int unit; mynor = minor(dev); if (mynor & CONTROL_MASK) return (ENODEV); unit = MINOR_TO_UNIT(mynor); tp = com_addr(unit)->tp; /* * (XXX) We disallow virtual consoles if the physical console is * a serial port. This is in case there is a display attached that * is not the console. In that situation we don't need/want the X * server taking over the console. */ if (constty != NULL && unit == comconsole) constty = NULL; #ifdef Smarts /* XXX duplicate ttwrite(), but without so much output processing on * CR & LF chars. Hardly worth the effort, given that high-throughput * sessions are raw anyhow. */ #else return ((*linesw[tp->t_line].l_write)(tp, uio, flag)); #endif } static void siodtrwakeup(chan) void *chan; { struct com_s *com; com = (struct com_s *)chan; com->state &= ~CS_DTR_OFF; wakeup(&com->dtr_wait); } void siointr(unit) int unit; { int baseu; int cy_align; cy_addr cy_iobase; int cyu; cy_addr iobase; u_char status; COM_LOCK(); /* XXX could this be placed down lower in the loop? */ baseu = unit * CY_MAX_PORTS; cy_align = com_addr(baseu)->cy_align; cy_iobase = com_addr(baseu)->cy_iobase; /* check each CD1400 in turn */ for (cyu = 0; cyu < cy_nr_cd1400s[unit]; ++cyu) { iobase = (cy_addr) (cy_iobase + (cy_chip_offset[cyu] << cy_align)); /* poll to see if it has any work */ status = cd_inb(iobase, CD1400_SVRR, cy_align); if (status == 0) continue; #ifdef CyDebug ++cy_svrr_probes; #endif /* service requests as appropriate, giving priority to RX */ if (status & CD1400_SVRR_RXRDY) { struct com_s *com; u_int count; u_char *ioptr; u_char line_status; u_char recv_data; u_char serv_type; #ifdef PollMode u_char save_car; u_char save_rir; #endif #ifdef PollMode save_rir = cd_inb(iobase, CD1400_RIR, cy_align); save_car = cd_inb(iobase, CD1400_CAR, cy_align); /* enter rx service */ cd_outb(iobase, CD1400_CAR, cy_align, save_rir); serv_type = cd_inb(iobase, CD1400_RIVR, cy_align); com = com_addr(baseu + ((serv_type >> CD1400_xIVR_CHAN_SHIFT) & CD1400_xIVR_CHAN)); #else /* ack receive service */ serv_type = cy_inb(iobase, CY8_SVCACKR); com = com_addr(baseu + + ((serv_type >> CD1400_xIVR_CHAN_SHIFT) & CD1400_xIVR_CHAN)); #endif if (serv_type & CD1400_RIVR_EXCEPTION) { ++com->recv_exception; line_status = cd_inb(iobase, CD1400_RDSR, cy_align); /* break/unnattached error bits or real input? */ recv_data = cd_inb(iobase, CD1400_RDSR, cy_align); #ifndef SOFT_HOTCHAR if (line_status & CD1400_RDSR_SPECIAL && com->hotchar != 0) setsofttty(); #endif #if 1 /* XXX "intelligent" PFO error handling would break O error handling */ if (line_status & (LSR_PE|LSR_FE|LSR_BI)) { /* Don't store PE if IGNPAR and BI if IGNBRK, this hack allows "raw" tty optimization works even if IGN* is set. */ if ( com->tp == NULL || !(com->tp->t_state & TS_ISOPEN) || (line_status & (LSR_PE|LSR_FE)) && (com->tp->t_iflag & IGNPAR) || (line_status & LSR_BI) && (com->tp->t_iflag & IGNBRK)) goto cont; if ( (line_status & (LSR_PE|LSR_FE)) && (com->tp->t_state & TS_CAN_BYPASS_L_RINT) && ((line_status & LSR_FE) || (line_status & LSR_PE) && (com->tp->t_iflag & INPCK))) recv_data = 0; } #endif /* 1 */ ++com->bytes_in; #ifdef SOFT_HOTCHAR if (com->hotchar != 0 && recv_data == com->hotchar) setsofttty(); #endif ioptr = com->iptr; if (ioptr >= com->ibufend) CE_RECORD(com, CE_INTERRUPT_BUF_OVERFLOW); else { if (com->do_timestamp) microtime(&com->timestamp); ++com_events; ioptr[0] = recv_data; ioptr[CE_INPUT_OFFSET] = line_status; com->iptr = ++ioptr; if (ioptr == com->ihighwater && com->state & CS_RTS_IFLOW) #if 0 outb(com->modem_ctl_port, com->mcr_image &= ~MCR_RTS); #else cd_outb(iobase, CD1400_MSVR1, cy_align, com->mcr_image &= ~MCR_RTS); #endif if (line_status & LSR_OE) CE_RECORD(com, CE_OVERRUN); } goto cont; } else { int ifree; count = cd_inb(iobase, CD1400_RDCR, cy_align); if (!count) goto cont; com->bytes_in += count; ioptr = com->iptr; ifree = com->ibufend - ioptr; if (count > ifree) { count -= ifree; com_events += ifree; if (ifree != 0) { if (com->do_timestamp) microtime(&com->timestamp); do { recv_data = cd_inb(iobase, CD1400_RDSR, cy_align); #ifdef SOFT_HOTCHAR if (com->hotchar != 0 && recv_data == com->hotchar) setsofttty(); #endif ioptr[0] = recv_data; ioptr[CE_INPUT_OFFSET] = 0; ++ioptr; } while (--ifree != 0); } com->delta_error_counts [CE_INTERRUPT_BUF_OVERFLOW] += count; do { recv_data = cd_inb(iobase, CD1400_RDSR, cy_align); #ifdef SOFT_HOTCHAR if (com->hotchar != 0 && recv_data == com->hotchar) setsofttty(); #endif } while (--count != 0); } else { if (com->do_timestamp) microtime(&com->timestamp); if (ioptr <= com->ihighwater && ioptr + count > com->ihighwater && com->state & CS_RTS_IFLOW) #if 0 outb(com->modem_ctl_port, com->mcr_image &= ~MCR_RTS); #else cd_outb(iobase, CD1400_MSVR1, cy_align, com->mcr_image &= ~MCR_RTS); #endif com_events += count; do { recv_data = cd_inb(iobase, CD1400_RDSR, cy_align); #ifdef SOFT_HOTCHAR if (com->hotchar != 0 && recv_data == com->hotchar) setsofttty(); #endif ioptr[0] = recv_data; ioptr[CE_INPUT_OFFSET] = 0; ++ioptr; } while (--count != 0); } com->iptr = ioptr; } cont: /* terminate service context */ #ifdef PollMode cd_outb(iobase, CD1400_RIR, cy_align, save_rir & ~(CD1400_RIR_RDIREQ | CD1400_RIR_RBUSY)); cd_outb(iobase, CD1400_CAR, cy_align, save_car); #else cd_outb(iobase, CD1400_EOSRR, cy_align, 0); #endif } if (status & CD1400_SVRR_MDMCH) { struct com_s *com; u_char modem_status; #ifdef PollMode u_char save_car; u_char save_mir; #else u_char vector; #endif #ifdef PollMode save_mir = cd_inb(iobase, CD1400_MIR, cy_align); save_car = cd_inb(iobase, CD1400_CAR, cy_align); /* enter modem service */ cd_outb(iobase, CD1400_CAR, cy_align, save_mir); com = com_addr(baseu + cyu * CD1400_NO_OF_CHANNELS + (save_mir & CD1400_MIR_CHAN)); #else /* ack modem service */ vector = cy_inb(iobase, CY8_SVCACKM); com = com_addr(baseu + ((vector >> CD1400_xIVR_CHAN_SHIFT) & CD1400_xIVR_CHAN)); #endif ++com->mdm; modem_status = cd_inb(iobase, CD1400_MSVR2, cy_align); if (modem_status != com->last_modem_status) { if (com->do_dcd_timestamp && !(com->last_modem_status & MSR_DCD) && modem_status & MSR_DCD) microtime(&com->dcd_timestamp); /* * Schedule high level to handle DCD changes. Note * that we don't use the delta bits anywhere. Some * UARTs mess them up, and it's easy to remember the * previous bits and calculate the delta. */ com->last_modem_status = modem_status; if (!(com->state & CS_CHECKMSR)) { com_events += LOTS_OF_EVENTS; com->state |= CS_CHECKMSR; setsofttty(); } #ifdef SOFT_CTS_OFLOW /* handle CTS change immediately for crisp flow ctl */ if (com->state & CS_CTS_OFLOW) { if (modem_status & MSR_CTS) { com->state |= CS_ODEVREADY; if (com->state >= (CS_BUSY | CS_TTGO | CS_ODEVREADY) && !(com->intr_enable & CD1400_SRER_TXRDY)) cd_outb(iobase, CD1400_SRER, cy_align, com->intr_enable |= CD1400_SRER_TXRDY); } else { com->state &= ~CS_ODEVREADY; if (com->intr_enable & CD1400_SRER_TXRDY) cd_outb(iobase, CD1400_SRER, cy_align, com->intr_enable &= ~CD1400_SRER_TXRDY); } } #endif } /* terminate service context */ #ifdef PollMode cd_outb(iobase, CD1400_MIR, cy_align, save_mir & ~(CD1400_MIR_RDIREQ | CD1400_MIR_RBUSY)); cd_outb(iobase, CD1400_CAR, cy_align, save_car); #else cd_outb(iobase, CD1400_EOSRR, cy_align, 0); #endif } if (status & CD1400_SVRR_TXRDY) { struct com_s *com; #ifdef PollMode u_char save_car; u_char save_tir; #else u_char vector; #endif #ifdef PollMode save_tir = cd_inb(iobase, CD1400_TIR, cy_align); save_car = cd_inb(iobase, CD1400_CAR, cy_align); /* enter tx service */ cd_outb(iobase, CD1400_CAR, cy_align, save_tir); com = com_addr(baseu + cyu * CD1400_NO_OF_CHANNELS + (save_tir & CD1400_TIR_CHAN)); #else /* ack transmit service */ vector = cy_inb(iobase, CY8_SVCACKT); com = com_addr(baseu + ((vector >> CD1400_xIVR_CHAN_SHIFT) & CD1400_xIVR_CHAN)); #endif if (com->state >= (CS_BUSY | CS_TTGO | CS_ODEVREADY)) { u_char *ioptr; u_int ocount; ioptr = com->obufq.l_head; ocount = com->obufq.l_tail - ioptr; if (ocount > CD1400_TX_FIFO_SIZE) ocount = CD1400_TX_FIFO_SIZE; com->bytes_out += ocount; do cd_outb(iobase, CD1400_TDR, cy_align, *ioptr++); while (--ocount != 0); com->obufq.l_head = ioptr; if (ioptr >= com->obufq.l_tail) { struct lbq *qp; qp = com->obufq.l_next; qp->l_queued = FALSE; qp = qp->l_next; if (qp != NULL) { com->obufq.l_head = qp->l_head; com->obufq.l_tail = qp->l_tail; com->obufq.l_next = qp; } else { /* output just completed */ com->state &= ~CS_BUSY; cd_outb(iobase, CD1400_SRER, cy_align, com->intr_enable &= ~CD1400_SRER_TXRDY); } if (!(com->state & CS_ODONE)) { com_events += LOTS_OF_EVENTS; com->state |= CS_ODONE; /* handle at high level ASAP */ setsofttty(); } } } /* terminate service context */ #ifdef PollMode cd_outb(iobase, CD1400_TIR, cy_align, save_tir & ~(CD1400_TIR_RDIREQ | CD1400_TIR_RBUSY)); cd_outb(iobase, CD1400_CAR, cy_align, save_car); #else cd_outb(iobase, CD1400_EOSRR, cy_align, 0); #endif } } /* ensure an edge for the next interrupt */ cd_outb(cy_iobase, CY_CLEAR_INTR, cy_align, 0); schedsofttty(); COM_UNLOCK(); } #if 0 static void siointr1(com) struct com_s *com; { } #endif static int sioioctl(dev, cmd, data, flag, p) dev_t dev; u_long cmd; caddr_t data; int flag; struct proc *p; { struct com_s *com; int error; cy_addr iobase; int mynor; int s; struct tty *tp; #if defined(COMPAT_43) || defined(COMPAT_SUNOS) int oldcmd; struct termios term; #endif mynor = minor(dev); com = com_addr(MINOR_TO_UNIT(mynor)); iobase = com->iobase; if (mynor & CONTROL_MASK) { struct termios *ct; switch (mynor & CONTROL_MASK) { case CONTROL_INIT_STATE: ct = mynor & CALLOUT_MASK ? &com->it_out : &com->it_in; break; case CONTROL_LOCK_STATE: ct = mynor & CALLOUT_MASK ? &com->lt_out : &com->lt_in; break; default: return (ENODEV); /* /dev/nodev */ } switch (cmd) { case TIOCSETA: error = suser(p->p_ucred, &p->p_acflag); if (error != 0) return (error); *ct = *(struct termios *)data; return (0); case TIOCGETA: *(struct termios *)data = *ct; return (0); case TIOCGETD: *(int *)data = TTYDISC; return (0); case TIOCGWINSZ: bzero(data, sizeof(struct winsize)); return (0); default: return (ENOTTY); } } tp = com->tp; #if defined(COMPAT_43) || defined(COMPAT_SUNOS) term = tp->t_termios; oldcmd = cmd; error = ttsetcompat(tp, &cmd, data, &term); if (error != 0) return (error); if (cmd != oldcmd) data = (caddr_t)&term; #endif if (cmd == TIOCSETA || cmd == TIOCSETAW || cmd == TIOCSETAF) { int cc; struct termios *dt = (struct termios *)data; struct termios *lt = mynor & CALLOUT_MASK ? &com->lt_out : &com->lt_in; dt->c_iflag = (tp->t_iflag & lt->c_iflag) | (dt->c_iflag & ~lt->c_iflag); dt->c_oflag = (tp->t_oflag & lt->c_oflag) | (dt->c_oflag & ~lt->c_oflag); dt->c_cflag = (tp->t_cflag & lt->c_cflag) | (dt->c_cflag & ~lt->c_cflag); dt->c_lflag = (tp->t_lflag & lt->c_lflag) | (dt->c_lflag & ~lt->c_lflag); for (cc = 0; cc < NCCS; ++cc) if (lt->c_cc[cc] != 0) dt->c_cc[cc] = tp->t_cc[cc]; if (lt->c_ispeed != 0) dt->c_ispeed = tp->t_ispeed; if (lt->c_ospeed != 0) dt->c_ospeed = tp->t_ospeed; } error = (*linesw[tp->t_line].l_ioctl)(tp, cmd, data, flag, p); if (error != ENOIOCTL) return (error); s = spltty(); error = ttioctl(tp, cmd, data, flag); disc_optim(tp, &tp->t_termios, com); if (error != ENOIOCTL) { splx(s); return (error); } cd_outb(iobase, CD1400_CAR, com->cy_align, MINOR_TO_UNIT(mynor) & CD1400_CAR_CHAN); switch (cmd) { #if 0 case TIOCSBRK: outb(iobase + com_cfcr, com->cfcr_image |= CFCR_SBREAK); break; case TIOCCBRK: outb(iobase + com_cfcr, com->cfcr_image &= ~CFCR_SBREAK); break; #endif /* 0 */ case TIOCSDTR: (void)commctl(com, TIOCM_DTR, DMBIS); break; case TIOCCDTR: (void)commctl(com, TIOCM_DTR, DMBIC); break; /* * XXX should disallow changing MCR_RTS if CS_RTS_IFLOW is set. The * changes get undone on the next call to comparam(). */ case TIOCMSET: (void)commctl(com, *(int *)data, DMSET); break; case TIOCMBIS: (void)commctl(com, *(int *)data, DMBIS); break; case TIOCMBIC: (void)commctl(com, *(int *)data, DMBIC); break; case TIOCMGET: *(int *)data = commctl(com, 0, DMGET); break; case TIOCMSDTRWAIT: /* must be root since the wait applies to following logins */ error = suser(p->p_ucred, &p->p_acflag); if (error != 0) { splx(s); return (error); } com->dtr_wait = *(int *)data * hz / 100; break; case TIOCMGDTRWAIT: *(int *)data = com->dtr_wait * 100 / hz; break; case TIOCTIMESTAMP: com->do_timestamp = TRUE; *(struct timeval *)data = com->timestamp; break; case TIOCDCDTIMESTAMP: com->do_dcd_timestamp = TRUE; *(struct timeval *)data = com->dcd_timestamp; break; default: splx(s); return (ENOTTY); } splx(s); return (0); } -void +static void siopoll() { int unit; #ifdef CyDebug ++cy_timeouts; #endif if (com_events == 0) return; repeat: for (unit = 0; unit < NSIO; ++unit) { u_char *buf; struct com_s *com; u_char *ibuf; cy_addr iobase; int incc; struct tty *tp; com = com_addr(unit); if (com == NULL) continue; tp = com->tp; if (tp == NULL) { /* * XXX forget any events related to closed devices * (actually never opened devices) so that we don't * loop. */ disable_intr(); incc = com->iptr - com->ibuf; com->iptr = com->ibuf; if (com->state & CS_CHECKMSR) { incc += LOTS_OF_EVENTS; com->state &= ~CS_CHECKMSR; } com_events -= incc; enable_intr(); if (incc != 0) log(LOG_DEBUG, "sio%d: %d events for device with no tp\n", unit, incc); continue; } /* switch the role of the low-level input buffers */ if (com->iptr == (ibuf = com->ibuf)) { buf = NULL; /* not used, but compiler can't tell */ incc = 0; } else { buf = ibuf; disable_intr(); incc = com->iptr - buf; com_events -= incc; if (ibuf == com->ibuf1) ibuf = com->ibuf2; else ibuf = com->ibuf1; com->ibufend = ibuf + RS_IBUFSIZE; com->ihighwater = ibuf + RS_IHIGHWATER; com->iptr = ibuf; /* * There is now room for another low-level buffer full * of input, so enable RTS if it is now disabled and * there is room in the high-level buffer. */ if ((com->state & CS_RTS_IFLOW) && !(com->mcr_image & MCR_RTS) && !(tp->t_state & TS_TBLOCK)) #if 0 outb(com->modem_ctl_port, com->mcr_image |= MCR_RTS); #else iobase = com->iobase, cd_outb(iobase, CD1400_CAR, com->cy_align, unit & CD1400_CAR_CHAN), cd_outb(iobase, CD1400_MSVR1, com->cy_align, com->mcr_image |= MCR_RTS); #endif enable_intr(); com->ibuf = ibuf; } if (com->state & CS_CHECKMSR) { u_char delta_modem_status; disable_intr(); delta_modem_status = com->last_modem_status ^ com->prev_modem_status; com->prev_modem_status = com->last_modem_status; com_events -= LOTS_OF_EVENTS; com->state &= ~CS_CHECKMSR; enable_intr(); if (delta_modem_status & MSR_DCD) (*linesw[tp->t_line].l_modem) (tp, com->prev_modem_status & MSR_DCD); } if (com->state & CS_ODONE) { disable_intr(); com_events -= LOTS_OF_EVENTS; com->state &= ~CS_ODONE; if (!(com->state & CS_BUSY)) com->tp->t_state &= ~TS_BUSY; enable_intr(); (*linesw[tp->t_line].l_start)(tp); } if (incc <= 0 || !(tp->t_state & TS_ISOPEN)) continue; /* * Avoid the grotesquely inefficient lineswitch routine * (ttyinput) in "raw" mode. It usually takes about 450 * instructions (that's without canonical processing or echo!). * slinput is reasonably fast (usually 40 instructions plus * call overhead). */ if (tp->t_state & TS_CAN_BYPASS_L_RINT) { if (tp->t_rawq.c_cc + incc >= RB_I_HIGH_WATER && (com->state & CS_RTS_IFLOW || tp->t_iflag & IXOFF) && !(tp->t_state & TS_TBLOCK)) ttyblock(tp); tk_nin += incc; tk_rawcc += incc; tp->t_rawcc += incc; com->delta_error_counts[CE_TTY_BUF_OVERFLOW] += b_to_q((char *)buf, incc, &tp->t_rawq); ttwakeup(tp); if (tp->t_state & TS_TTSTOP && (tp->t_iflag & IXANY || tp->t_cc[VSTART] == tp->t_cc[VSTOP])) { tp->t_state &= ~TS_TTSTOP; tp->t_lflag &= ~FLUSHO; comstart(tp); } } else { do { u_char line_status; int recv_data; line_status = (u_char) buf[CE_INPUT_OFFSET]; recv_data = (u_char) *buf++; if (line_status & (LSR_BI | LSR_FE | LSR_OE | LSR_PE)) { if (line_status & LSR_BI) recv_data |= TTY_BI; if (line_status & LSR_FE) recv_data |= TTY_FE; if (line_status & LSR_OE) recv_data |= TTY_OE; if (line_status & LSR_PE) recv_data |= TTY_PE; } (*linesw[tp->t_line].l_rint)(recv_data, tp); } while (--incc > 0); } if (com_events == 0) break; } if (com_events >= LOTS_OF_EVENTS) goto repeat; } static int comparam(tp, t) struct tty *tp; struct termios *t; { int bits; int cflag; struct com_s *com; u_char cor_change; int idivisor; int iflag; cy_addr iobase; int iprescaler; int itimeout; int odivisor; int oprescaler; u_char opt; int s; int unit; /* do historical conversions */ if (t->c_ispeed == 0) t->c_ispeed = t->c_ospeed; /* check requested parameters */ idivisor = comspeed(t->c_ispeed, &iprescaler); if (idivisor < 0) return (EINVAL); odivisor = comspeed(t->c_ospeed, &oprescaler); if (odivisor < 0) return (EINVAL); /* parameters are OK, convert them to the com struct and the device */ unit = DEV_TO_UNIT(tp->t_dev); com = com_addr(unit); iobase = com->iobase; s = spltty(); cd_outb(iobase, CD1400_CAR, com->cy_align, unit & CD1400_CAR_CHAN); if (odivisor == 0) (void)commctl(com, TIOCM_DTR, DMBIC); /* hang up line */ else (void)commctl(com, TIOCM_DTR, DMBIS); if (idivisor != 0) { cd_outb(iobase, CD1400_RBPR, com->cy_align, idivisor); cd_outb(iobase, CD1400_RCOR, com->cy_align, iprescaler); } if (odivisor != 0) { cd_outb(iobase, CD1400_TBPR, com->cy_align, odivisor); cd_outb(iobase, CD1400_TCOR, com->cy_align, oprescaler); } /* * channel control * receiver enable * transmitter enable (always set) */ cflag = t->c_cflag; opt = CD1400_CCR_CMDCHANCTL | CD1400_CCR_XMTEN | (cflag & CREAD ? CD1400_CCR_RCVEN : CD1400_CCR_RCVDIS); if (opt != com->channel_control) { com->channel_control = opt; cd1400_channel_cmd(iobase, opt, com->cy_align); } #ifdef Smarts /* set special chars */ /* XXX if one is _POSIX_VDISABLE, can't use some others */ if (t->c_cc[VSTOP] != _POSIX_VDISABLE) cd_outb(iobase, CD1400_SCHR1, com->cy_align, t->c_cc[VSTOP]); if (t->c_cc[VSTART] != _POSIX_VDISABLE) cd_outb(iobase, CD1400_SCHR2, com->cy_align, t->c_cc[VSTART]); if (t->c_cc[VINTR] != _POSIX_VDISABLE) cd_outb(iobase, CD1400_SCHR3, com->cy_align, t->c_cc[VINTR]); if (t->c_cc[VSUSP] != _POSIX_VDISABLE) cd_outb(iobase, CD1400_SCHR4, com->cy_align, t->c_cc[VSUSP]); #endif /* * set channel option register 1 - * parity mode * stop bits * char length */ opt = 0; /* parity */ if (cflag & PARENB) { if (cflag & PARODD) opt |= CD1400_COR1_PARODD; opt |= CD1400_COR1_PARNORMAL; } iflag = t->c_iflag; if (!(iflag & INPCK)) opt |= CD1400_COR1_NOINPCK; bits = 1 + 1; /* stop bits */ if (cflag & CSTOPB) { ++bits; opt |= CD1400_COR1_STOP2; } /* char length */ switch (cflag & CSIZE) { case CS5: bits += 5; opt |= CD1400_COR1_CS5; break; case CS6: bits += 6; opt |= CD1400_COR1_CS6; break; case CS7: bits += 7; opt |= CD1400_COR1_CS7; break; default: bits += 8; opt |= CD1400_COR1_CS8; break; } cor_change = 0; if (opt != com->cor[0]) { cor_change |= CD1400_CCR_COR1; cd_outb(iobase, CD1400_COR1, com->cy_align, com->cor[0] = opt); } /* * Set receive time-out period, normally to max(one char time, 5 ms). */ if (t->c_ispeed == 0) itimeout = cd_inb(iobase, CD1400_RTPR, com->cy_align); else { itimeout = (1000 * bits + t->c_ispeed - 1) / t->c_ispeed; #ifdef SOFT_HOTCHAR #define MIN_RTP 1 #else #define MIN_RTP 5 #endif if (itimeout < MIN_RTP) itimeout = MIN_RTP; } if (!(t->c_lflag & ICANON) && t->c_cc[VMIN] != 0 && t->c_cc[VTIME] != 0 && t->c_cc[VTIME] * 10 > itimeout) itimeout = t->c_cc[VTIME] * 10; if (itimeout > 255) itimeout = 255; cd_outb(iobase, CD1400_RTPR, com->cy_align, itimeout); /* * set channel option register 2 - * flow control */ opt = 0; #ifdef Smarts if (iflag & IXANY) opt |= CD1400_COR2_IXANY; if (iflag & IXOFF) opt |= CD1400_COR2_IXOFF; #endif #ifndef SOFT_CTS_OFLOW if (cflag & CCTS_OFLOW) opt |= CD1400_COR2_CCTS_OFLOW; #endif if (opt != com->cor[1]) { cor_change |= CD1400_CCR_COR2; cd_outb(iobase, CD1400_COR2, com->cy_align, com->cor[1] = opt); } /* * set channel option register 3 - * receiver FIFO interrupt threshold * flow control */ opt = RxFifoThreshold; #ifdef Smarts if (t->c_lflag & ICANON) opt |= CD1400_COR3_SCD34; /* detect INTR & SUSP chars */ if (iflag & IXOFF) /* detect and transparently handle START and STOP chars */ opt |= CD1400_COR3_FCT | CD1400_COR3_SCD12; #endif if (opt != com->cor[2]) { cor_change |= CD1400_CCR_COR3; cd_outb(iobase, CD1400_COR3, com->cy_align, com->cor[2] = opt); } /* notify the CD1400 if COR1-3 have changed */ if (cor_change) cd1400_channel_cmd(iobase, CD1400_CCR_CMDCORCHG | cor_change, com->cy_align); /* * set channel option register 4 - * CR/NL processing * break processing * received exception processing */ opt = 0; if (iflag & IGNCR) opt |= CD1400_COR4_IGNCR; #ifdef Smarts /* * we need a new ttyinput() for this, as we don't want to * have ICRNL && INLCR being done in both layers, or to have * synchronisation problems */ if (iflag & ICRNL) opt |= CD1400_COR4_ICRNL; if (iflag & INLCR) opt |= CD1400_COR4_INLCR; #endif if (iflag & IGNBRK) opt |= CD1400_COR4_IGNBRK; if (!(iflag & BRKINT)) opt |= CD1400_COR4_NOBRKINT; #if 0 /* XXX using this "intelligence" breaks reporting of overruns. */ if (iflag & IGNPAR) opt |= CD1400_COR4_PFO_DISCARD; else { if (iflag & PARMRK) opt |= CD1400_COR4_PFO_ESC; else opt |= CD1400_COR4_PFO_NUL; } #else opt |= CD1400_COR4_PFO_EXCEPTION; #endif cd_outb(iobase, CD1400_COR4, com->cy_align, opt); /* * set channel option register 5 - */ opt = 0; if (iflag & ISTRIP) opt |= CD1400_COR5_ISTRIP; if (t->c_iflag & IEXTEN) /* enable LNEXT (e.g. ctrl-v quoting) handling */ opt |= CD1400_COR5_LNEXT; #ifdef Smarts if (t->c_oflag & ONLCR) opt |= CD1400_COR5_ONLCR; if (t->c_oflag & OCRNL) opt |= CD1400_COR5_OCRNL; #endif cd_outb(iobase, CD1400_COR5, com->cy_align, opt); /* * We always generate modem status change interrupts for CD changes. * Among other things, this is necessary to track TS_CARR_ON for * pstat to print even when the driver doesn't care. CD changes * should be rare so interrupts for them are not worth extra code to * avoid. We avoid interrupts for other modem status changes (except * for CTS changes when SOFT_CTS_OFLOW is configured) since this is * simplest and best. */ /* * set modem change option register 1 * generate modem interrupts on which 1 -> 0 input transitions * also controls auto-DTR output flow-control, which we don't use */ opt = CD1400_MCOR1_CDzd; #ifdef SOFT_CTS_OFLOW if (cflag & CCTS_OFLOW) opt |= CD1400_MCOR1_CTSzd; #endif cd_outb(iobase, CD1400_MCOR1, com->cy_align, opt); /* * set modem change option register 2 * generate modem interrupts on specific 0 -> 1 input transitions */ opt = CD1400_MCOR2_CDod; #ifdef SOFT_CTS_OFLOW if (cflag & CCTS_OFLOW) opt |= CD1400_MCOR2_CTSod; #endif cd_outb(iobase, CD1400_MCOR2, com->cy_align, opt); /* * XXX should have done this long ago, but there is too much state * to change all atomically. */ disable_intr(); com->state &= ~CS_TTGO; if (!(tp->t_state & TS_TTSTOP)) com->state |= CS_TTGO; if (cflag & CRTS_IFLOW) { com->state |= CS_RTS_IFLOW; /* * If CS_RTS_IFLOW just changed from off to on, the change * needs to be propagated to MCR_RTS. This isn't urgent, * so do it later by calling comstart() instead of repeating * a lot of code from comstart() here. */ } else if (com->state & CS_RTS_IFLOW) { com->state &= ~CS_RTS_IFLOW; /* * CS_RTS_IFLOW just changed from on to off. Force MCR_RTS * on here, since comstart() won't do it later. */ #if 0 outb(com->modem_ctl_port, com->mcr_image |= MCR_RTS); #else cd_outb(iobase, CD1400_MSVR1, com->cy_align, com->mcr_image |= MCR_RTS); #endif } /* * Set up state to handle output flow control. * XXX - worth handling MDMBUF (DCD) flow control at the lowest level? * Now has 10+ msec latency, while CTS flow has 50- usec latency. */ com->state |= CS_ODEVREADY; #ifdef SOFT_CTS_OFLOW com->state &= ~CS_CTS_OFLOW; if (cflag & CCTS_OFLOW) { com->state |= CS_CTS_OFLOW; if (!(com->last_modem_status & MSR_CTS)) com->state &= ~CS_ODEVREADY; } #endif /* XXX shouldn't call functions while intrs are disabled. */ disc_optim(tp, t, com); #if 0 /* * Recover from fiddling with CS_TTGO. We used to call siointr1() * unconditionally, but that defeated the careful discarding of * stale input in sioopen(). */ if (com->state >= (CS_BUSY | CS_TTGO)) siointr1(com); #endif if (com->state >= (CS_BUSY | CS_TTGO | CS_ODEVREADY)) { if (!(com->intr_enable & CD1400_SRER_TXRDY)) cd_outb(iobase, CD1400_SRER, com->cy_align, com->intr_enable |= CD1400_SRER_TXRDY); } else { if (com->intr_enable & CD1400_SRER_TXRDY) cd_outb(iobase, CD1400_SRER, com->cy_align, com->intr_enable &= ~CD1400_SRER_TXRDY); } enable_intr(); splx(s); comstart(tp); return (0); } static void comstart(tp) struct tty *tp; { struct com_s *com; cy_addr iobase; int s; #ifdef CyDebug bool_t started; #endif int unit; unit = DEV_TO_UNIT(tp->t_dev); com = com_addr(unit); iobase = com->iobase; s = spltty(); #ifdef CyDebug ++com->start_count; started = FALSE; #endif disable_intr(); cd_outb(iobase, CD1400_CAR, com->cy_align, unit & CD1400_CAR_CHAN); if (tp->t_state & TS_TTSTOP) { com->state &= ~CS_TTGO; if (com->intr_enable & CD1400_SRER_TXRDY) cd_outb(iobase, CD1400_SRER, com->cy_align, com->intr_enable &= ~CD1400_SRER_TXRDY); } else { com->state |= CS_TTGO; if (com->state >= (CS_BUSY | CS_TTGO | CS_ODEVREADY) && !(com->intr_enable & CD1400_SRER_TXRDY)) cd_outb(iobase, CD1400_SRER, com->cy_align, com->intr_enable |= CD1400_SRER_TXRDY); } if (tp->t_state & TS_TBLOCK) { if (com->mcr_image & MCR_RTS && com->state & CS_RTS_IFLOW) #if 0 outb(com->modem_ctl_port, com->mcr_image &= ~MCR_RTS); #else cd_outb(iobase, CD1400_MSVR1, com->cy_align, com->mcr_image &= ~MCR_RTS); #endif } else { if (!(com->mcr_image & MCR_RTS) && com->iptr < com->ihighwater && com->state & CS_RTS_IFLOW) #if 0 outb(com->modem_ctl_port, com->mcr_image |= MCR_RTS); #else cd_outb(iobase, CD1400_MSVR1, com->cy_align, com->mcr_image |= MCR_RTS); #endif } enable_intr(); if (tp->t_state & (TS_TIMEOUT | TS_TTSTOP)) { ttwwakeup(tp); splx(s); return; } if (tp->t_outq.c_cc != 0) { struct lbq *qp; struct lbq *next; if (!com->obufs[0].l_queued) { #ifdef CyDebug started = TRUE; #endif com->obufs[0].l_tail = com->obuf1 + q_to_b(&tp->t_outq, com->obuf1, sizeof com->obuf1); com->obufs[0].l_next = NULL; com->obufs[0].l_queued = TRUE; disable_intr(); if (com->state & CS_BUSY) { qp = com->obufq.l_next; while ((next = qp->l_next) != NULL) qp = next; qp->l_next = &com->obufs[0]; } else { com->obufq.l_head = com->obufs[0].l_head; com->obufq.l_tail = com->obufs[0].l_tail; com->obufq.l_next = &com->obufs[0]; com->state |= CS_BUSY; if (com->state >= (CS_BUSY | CS_TTGO | CS_ODEVREADY)) cd_outb(iobase, CD1400_SRER, com->cy_align, com->intr_enable |= CD1400_SRER_TXRDY); } enable_intr(); } if (tp->t_outq.c_cc != 0 && !com->obufs[1].l_queued) { #ifdef CyDebug started = TRUE; #endif com->obufs[1].l_tail = com->obuf2 + q_to_b(&tp->t_outq, com->obuf2, sizeof com->obuf2); com->obufs[1].l_next = NULL; com->obufs[1].l_queued = TRUE; disable_intr(); if (com->state & CS_BUSY) { qp = com->obufq.l_next; while ((next = qp->l_next) != NULL) qp = next; qp->l_next = &com->obufs[1]; } else { com->obufq.l_head = com->obufs[1].l_head; com->obufq.l_tail = com->obufs[1].l_tail; com->obufq.l_next = &com->obufs[1]; com->state |= CS_BUSY; if (com->state >= (CS_BUSY | CS_TTGO | CS_ODEVREADY)) cd_outb(iobase, CD1400_SRER, com->cy_align, com->intr_enable |= CD1400_SRER_TXRDY); } enable_intr(); } tp->t_state |= TS_BUSY; } #ifdef CyDebug if (started) ++com->start_real; #endif #if 0 disable_intr(); if (com->state >= (CS_BUSY | CS_TTGO)) siointr1(com); /* fake interrupt to start output */ enable_intr(); #endif ttwwakeup(tp); splx(s); } static void siostop(tp, rw) struct tty *tp; int rw; { struct com_s *com; com = com_addr(DEV_TO_UNIT(tp->t_dev)); disable_intr(); if (rw & FWRITE) { com->obufs[0].l_queued = FALSE; com->obufs[1].l_queued = FALSE; if (com->state & CS_ODONE) com_events -= LOTS_OF_EVENTS; com->state &= ~(CS_ODONE | CS_BUSY); com->tp->t_state &= ~TS_BUSY; } if (rw & FREAD) { com_events -= (com->iptr - com->ibuf); com->iptr = com->ibuf; } enable_intr(); comstart(tp); /* XXX should clear h/w fifos too. */ } static struct tty * siodevtotty(dev) dev_t dev; { int mynor; int unit; mynor = minor(dev); if (mynor & CONTROL_MASK) return (NULL); unit = MINOR_TO_UNIT(mynor); if ((u_int) unit >= NSIO) return (NULL); return (&sio_tty[unit]); } static int commctl(com, bits, how) struct com_s *com; int bits; int how; { cy_addr iobase; int mcr; int msr; iobase = com->iobase; if (how == DMGET) { if (com->channel_control & CD1400_CCR_RCVEN) bits |= TIOCM_LE; mcr = com->mcr_image; if (mcr & MCR_DTR) bits |= TIOCM_DTR; if (mcr & MCR_RTS) /* XXX wired on for Cyclom-8Ys */ bits |= TIOCM_RTS; /* * We must read the modem status from the hardware because * we don't generate modem status change interrupts for all * changes, so com->prev_modem_status is not guaranteed to * be up to date. This is safe, unlike for sio, because * reading the status register doesn't clear pending modem * status change interrupts. */ msr = cd_inb(iobase, CD1400_MSVR2, com->cy_align); if (msr & MSR_CTS) bits |= TIOCM_CTS; if (msr & MSR_DCD) bits |= TIOCM_CD; if (msr & MSR_DSR) bits |= TIOCM_DSR; if (msr & MSR_RI) /* XXX not connected except for Cyclom-16Y? */ bits |= TIOCM_RI; return (bits); } mcr = 0; if (bits & TIOCM_DTR) mcr |= MCR_DTR; if (bits & TIOCM_RTS) mcr |= MCR_RTS; disable_intr(); switch (how) { case DMSET: com->mcr_image = mcr; cd_outb(iobase, CD1400_MSVR1, com->cy_align, mcr); cd_outb(iobase, CD1400_MSVR2, com->cy_align, mcr); break; case DMBIS: com->mcr_image = mcr = com->mcr_image | mcr; cd_outb(iobase, CD1400_MSVR1, com->cy_align, mcr); cd_outb(iobase, CD1400_MSVR2, com->cy_align, mcr); break; case DMBIC: com->mcr_image = mcr = com->mcr_image & ~mcr; cd_outb(iobase, CD1400_MSVR1, com->cy_align, mcr); cd_outb(iobase, CD1400_MSVR2, com->cy_align, mcr); break; } enable_intr(); return (0); } static void siosettimeout() { struct com_s *com; bool_t someopen; int unit; /* * Set our timeout period to 1 second if no polled devices are open. * Otherwise set it to max(1/200, 1/hz). * Enable timeouts iff some device is open. */ untimeout(comwakeup, (void *)NULL, sio_timeout_handle); sio_timeout = hz; someopen = FALSE; for (unit = 0; unit < NSIO; ++unit) { com = com_addr(unit); if (com != NULL && com->tp != NULL && com->tp->t_state & TS_ISOPEN) { someopen = TRUE; #if 0 if (com->poll || com->poll_output) { sio_timeout = hz > 200 ? hz / 200 : 1; break; } #endif } } if (someopen) { sio_timeouts_until_log = hz / sio_timeout; sio_timeout_handle = timeout(comwakeup, (void *)NULL, sio_timeout); } else { /* Flush error messages, if any. */ sio_timeouts_until_log = 1; comwakeup((void *)NULL); untimeout(comwakeup, (void *)NULL, sio_timeout_handle); } } static void comwakeup(chan) void *chan; { struct com_s *com; int unit; sio_timeout_handle = timeout(comwakeup, (void *)NULL, sio_timeout); #if 0 /* * Recover from lost output interrupts. * Poll any lines that don't use interrupts. */ for (unit = 0; unit < NSIO; ++unit) { com = com_addr(unit); if (com != NULL && (com->state >= (CS_BUSY | CS_TTGO) || com->poll)) { disable_intr(); siointr1(com); enable_intr(); } } #endif /* * Check for and log errors, but not too often. */ if (--sio_timeouts_until_log > 0) return; sio_timeouts_until_log = hz / sio_timeout; for (unit = 0; unit < NSIO; ++unit) { int errnum; com = com_addr(unit); if (com == NULL) continue; for (errnum = 0; errnum < CE_NTYPES; ++errnum) { u_int delta; u_long total; disable_intr(); delta = com->delta_error_counts[errnum]; com->delta_error_counts[errnum] = 0; enable_intr(); if (delta == 0) continue; total = com->error_counts[errnum] += delta; log(LOG_ERR, "cy%d: %u more %s%s (total %lu)\n", unit, delta, error_desc[errnum], delta == 1 ? "" : "s", total); } } } static void disc_optim(tp, t, com) struct tty *tp; struct termios *t; struct com_s *com; { #ifndef SOFT_HOTCHAR cy_addr iobase; u_char opt; #endif /* * XXX can skip a lot more cases if Smarts. Maybe * (IGNCR | ISTRIP | IXON) in c_iflag. But perhaps we * shouldn't skip if (TS_CNTTB | TS_LNCH) is set in t_state. */ if (!(t->c_iflag & (ICRNL | IGNCR | IMAXBEL | INLCR | ISTRIP | IXON)) && (!(t->c_iflag & BRKINT) || (t->c_iflag & IGNBRK)) && (!(t->c_iflag & PARMRK) || (t->c_iflag & (IGNPAR | IGNBRK)) == (IGNPAR | IGNBRK)) && !(t->c_lflag & (ECHO | ICANON | IEXTEN | ISIG | PENDIN)) && linesw[tp->t_line].l_rint == ttyinput) tp->t_state |= TS_CAN_BYPASS_L_RINT; else tp->t_state &= ~TS_CAN_BYPASS_L_RINT; com->hotchar = linesw[tp->t_line].l_hotchar; #ifndef SOFT_HOTCHAR iobase = com->iobase; cd_outb(iobase, CD1400_CAR, com->cy_align, com->unit & CD1400_CAR_CHAN); opt = com->cor[2] & ~CD1400_COR3_SCD34; if (com->hotchar != 0) { cd_outb(iobase, CD1400_SCHR3, com->cy_align, com->hotchar); cd_outb(iobase, CD1400_SCHR4, com->cy_align, com->hotchar); opt |= CD1400_COR3_SCD34; } if (opt != com->cor[2]) { cd_outb(iobase, CD1400_COR3, com->cy_align, com->cor[2] = opt); cd1400_channel_cmd(com->iobase, CD1400_CCR_CMDCORCHG | CD1400_CCR_COR3, com->cy_align); } #endif } #ifdef Smarts /* standard line discipline input routine */ int cyinput(c, tp) int c; struct tty *tp; { /* XXX duplicate ttyinput(), but without the IXOFF/IXON/ISTRIP/IPARMRK * bits, as they are done by the CD1400. Hardly worth the effort, * given that high-throughput sessions are raw anyhow. */ } #endif /* Smarts */ static int comspeed(speed, prescaler_io) speed_t speed; int *prescaler_io; { int actual; int error; int divider; int prescaler; int prescaler_unit; if (speed == 0) return (0); if (speed < 0 || speed > 150000) return (-1); /* determine which prescaler to use */ for (prescaler_unit = 4, prescaler = 2048; prescaler_unit; prescaler_unit--, prescaler >>= 2) { if (CY_CLOCK / prescaler / speed > 63) break; } divider = (CY_CLOCK / prescaler * 2 / speed + 1) / 2; /* round off */ if (divider > 255) divider = 255; actual = CY_CLOCK/prescaler/divider; /* 10 times error in percent: */ error = ((actual - (long)speed) * 2000 / (long)speed + 1) / 2; /* 3.0% max error tolerance */ if (error < -30 || error > 30) return (-1); #if 0 printf("prescaler = %d (%d)\n", prescaler, prescaler_unit); printf("divider = %d (%x)\n", divider, divider); printf("actual = %d\n", actual); printf("error = %d\n", error); #endif *prescaler_io = prescaler_unit; return (divider); } static void cd1400_channel_cmd(iobase, cmd, cy_align) cy_addr iobase; int cmd; int cy_align; { /* XXX hsu@clinet.fi: This is always more dependent on ISA bus speed, as the card is probed every round? Replaced delaycount with 8k. Either delaycount has to be implemented in FreeBSD or more sensible way of doing these should be implemented. DELAY isn't enough here. */ u_int maxwait = 5 * 8 * 1024; /* approx. 5 ms */ /* wait for processing of previous command to complete */ while (cd_inb(iobase, CD1400_CCR, cy_align) && maxwait--) ; if (!maxwait) log(LOG_ERR, "cy: channel command timeout (%d loops) - arrgh\n", 5 * 8 * 1024); cd_outb(iobase, CD1400_CCR, cy_align, cmd); } #ifdef CyDebug /* useful in ddb */ void cystatus(unit) int unit; { struct com_s *com; cy_addr iobase; u_int ocount; struct tty *tp; com = com_addr(unit); printf("info for channel %d\n", unit); printf("------------------\n"); printf("total cyclom service probes:\t%d\n", cy_svrr_probes); printf("calls to upper layer:\t\t%d\n", cy_timeouts); if (com == NULL) return; iobase = com->iobase; printf("\n"); printf("cd1400 base address:\\tt%p\n", iobase); cd_outb(iobase, CD1400_CAR, com->cy_align, unit & CD1400_CAR_CHAN); printf("saved channel_control:\t\t0x%02x\n", com->channel_control); printf("saved cor1-3:\t\t\t0x%02x 0x%02x 0x%02x\n", com->cor[0], com->cor[1], com->cor[2]); printf("service request enable reg:\t0x%02x (0x%02x cached)\n", cd_inb(iobase, CD1400_SRER, com->cy_align), com->intr_enable); printf("service request register:\t0x%02x\n", cd_inb(iobase, CD1400_SVRR, com->cy_align)); printf("modem status:\t\t\t0x%02x (0x%02x cached)\n", cd_inb(iobase, CD1400_MSVR2, com->cy_align), com->prev_modem_status); printf("rx/tx/mdm interrupt registers:\t0x%02x 0x%02x 0x%02x\n", cd_inb(iobase, CD1400_RIR, com->cy_align), cd_inb(iobase, CD1400_TIR, com->cy_align), cd_inb(iobase, CD1400_MIR, com->cy_align)); printf("\n"); printf("com state:\t\t\t0x%02x\n", com->state); printf("calls to comstart():\t\t%d (%d useful)\n", com->start_count, com->start_real); printf("rx buffer chars free:\t\t%d\n", com->iptr - com->ibuf); ocount = 0; if (com->obufs[0].l_queued) ocount += com->obufs[0].l_tail - com->obufs[0].l_head; if (com->obufs[1].l_queued) ocount += com->obufs[1].l_tail - com->obufs[1].l_head; printf("tx buffer chars:\t\t%u\n", ocount); printf("received chars:\t\t\t%d\n", com->bytes_in); printf("received exceptions:\t\t%d\n", com->recv_exception); printf("modem signal deltas:\t\t%d\n", com->mdm); printf("transmitted chars:\t\t%d\n", com->bytes_out); printf("\n"); tp = com->tp; if (tp != NULL) { printf("tty state:\t\t\t0x%08x\n", tp->t_state); printf( "upper layer queue lengths:\t%d raw, %d canon, %d output\n", tp->t_rawq.c_cc, tp->t_canq.c_cc, tp->t_outq.c_cc); } else printf("tty state:\t\t\tclosed\n"); } #endif /* CyDebug */ Index: head/sys/dev/rc/rc.c =================================================================== --- head/sys/dev/rc/rc.c (revision 38245) +++ head/sys/dev/rc/rc.c (revision 38246) @@ -1,1512 +1,1507 @@ /* * Copyright (C) 1995 by Pavel Antonov, Moscow, Russia. * Copyright (C) 1995 by Andrey A. Chernov, Moscow, Russia. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * SDL Communications Riscom/8 (based on Cirrus Logic CL-CD180) driver * */ #include "rc.h" #if NRC > 0 #include "opt_devfs.h" /*#define RCDEBUG*/ #include #include #include #include #include #include #include +#include #include #ifdef DEVFS #include #endif /*DEVFS*/ #include +#include #include #include #include /* Prototypes */ static int rcprobe __P((struct isa_device *)); static int rcattach __P((struct isa_device *)); -/*- - * This space intentionally left blank to stop __LINE__ from screwing up - * regression tests :-(. - * - * - * - */ -void rcpoll __P((void)); - #define rcin(port) RC_IN (nec, port) #define rcout(port,v) RC_OUT (nec, port, v) #define WAITFORCCR(u,c) rc_wait0(nec, (u), (c), __LINE__) #define CCRCMD(u,c,cmd) WAITFORCCR((u), (c)); rcout(CD180_CCR, (cmd)) #define RC_IBUFSIZE 256 #define RB_I_HIGH_WATER (TTYHOG - 2 * RC_IBUFSIZE) #define RC_OBUFSIZE 512 #define RC_IHIGHWATER (3 * RC_IBUFSIZE / 4) #define INPUT_FLAGS_SHIFT (2 * RC_IBUFSIZE) #define LOTS_OF_EVENTS 64 #define RC_FAKEID 0x10 #define RC_PROBED 1 #define RC_ATTACHED 2 #define GET_UNIT(dev) (minor(dev) & 0x3F) #define CALLOUT(dev) (minor(dev) & 0x80) /* For isa routines */ struct isa_driver rcdriver = { rcprobe, rcattach, "rc" }; static d_open_t rcopen; static d_close_t rcclose; static d_read_t rcread; static d_write_t rcwrite; static d_ioctl_t rcioctl; static d_stop_t rcstop; static d_devtotty_t rcdevtotty; #define CDEV_MAJOR 63 static struct cdevsw rc_cdevsw = { rcopen, rcclose, rcread, rcwrite, /*63*/ rcioctl, rcstop, noreset, rcdevtotty,/* rc */ ttpoll, nommap, NULL, "rc", NULL, -1 }; /* Per-board structure */ static struct rc_softc { u_int rcb_probed; /* 1 - probed, 2 - attached */ u_int rcb_addr; /* Base I/O addr */ u_int rcb_unit; /* unit # */ u_char rcb_dtr; /* DTR status */ struct rc_chans *rcb_baserc; /* base rc ptr */ } rc_softc[NRC]; /* Per-channel structure */ static struct rc_chans { struct rc_softc *rc_rcb; /* back ptr */ u_short rc_flags; /* Misc. flags */ int rc_chan; /* Channel # */ u_char rc_ier; /* intr. enable reg */ u_char rc_msvr; /* modem sig. status */ u_char rc_cor2; /* options reg */ u_char rc_pendcmd; /* special cmd pending */ u_int rc_dtrwait; /* dtr timeout */ u_int rc_dcdwaits; /* how many waits DCD in open */ u_char rc_hotchar; /* end packed optimize */ struct tty *rc_tp; /* tty struct */ u_char *rc_iptr; /* Chars input buffer */ u_char *rc_hiwat; /* hi-water mark */ u_char *rc_bufend; /* end of buffer */ u_char *rc_optr; /* ptr in output buf */ u_char *rc_obufend; /* end of output buf */ u_char rc_ibuf[4 * RC_IBUFSIZE]; /* input buffer */ u_char rc_obuf[RC_OBUFSIZE]; /* output buffer */ #ifdef DEVFS void *devfs_token; #endif } rc_chans[NRC * CD180_NCHAN]; static int rc_scheduled_event = 0; /* for pstat -t */ static struct tty rc_tty[NRC * CD180_NCHAN]; static const int nrc_tty = NRC * CD180_NCHAN; /* Flags */ #define RC_DTR_OFF 0x0001 /* DTR wait, for close/open */ #define RC_ACTOUT 0x0002 /* Dial-out port active */ #define RC_RTSFLOW 0x0004 /* RTS flow ctl enabled */ #define RC_CTSFLOW 0x0008 /* CTS flow ctl enabled */ #define RC_DORXFER 0x0010 /* RXFER event planned */ #define RC_DOXXFER 0x0020 /* XXFER event planned */ #define RC_MODCHG 0x0040 /* Modem status changed */ #define RC_OSUSP 0x0080 /* Output suspended */ #define RC_OSBUSY 0x0100 /* start() routine in progress */ #define RC_WAS_BUFOVFL 0x0200 /* low-level buffer ovferflow */ #define RC_WAS_SILOVFL 0x0400 /* silo buffer overflow */ #define RC_SEND_RDY 0x0800 /* ready to send */ /* Table for translation of RCSR status bits to internal form */ static int rc_rcsrt[16] = { 0, TTY_OE, TTY_FE, TTY_FE|TTY_OE, TTY_PE, TTY_PE|TTY_OE, TTY_PE|TTY_FE, TTY_PE|TTY_FE|TTY_OE, TTY_BI, TTY_BI|TTY_OE, TTY_BI|TTY_FE, TTY_BI|TTY_FE|TTY_OE, TTY_BI|TTY_PE, TTY_BI|TTY_PE|TTY_OE, TTY_BI|TTY_PE|TTY_FE, TTY_BI|TTY_PE|TTY_FE|TTY_OE }; /* Static prototypes */ static void rc_hwreset __P((int, int, unsigned int)); static int rc_test __P((int, int)); static void rc_discard_output __P((struct rc_chans *)); static void rc_hardclose __P((struct rc_chans *)); static int rc_modctl __P((struct rc_chans *, int, int)); static void rc_start __P((struct tty *)); static int rc_param __P((struct tty *, struct termios *)); +static swihand_t rcpoll; static void rc_reinit __P((struct rc_softc *)); #ifdef RCDEBUG static void printrcflags(); #endif static timeout_t rc_dtrwakeup; static timeout_t rc_wakeup; static void disc_optim __P((struct tty *tp, struct termios *t, struct rc_chans *)); static void rc_wait0 __P((int nec, int unit, int chan, int line)); /**********************************************/ /* Quick device probing */ static int rcprobe(dvp) struct isa_device *dvp; { int irq = ffs(dvp->id_irq) - 1; register int nec = dvp->id_iobase; if (dvp->id_unit > NRC) return 0; if (!RC_VALIDADDR(nec)) { printf("rc%d: illegal base address %x\n", dvp->id_unit, nec); return 0; } if (!RC_VALIDIRQ(irq)) { printf("rc%d: illegal IRQ value %d\n", dvp->id_unit, irq); return 0; } rcout(CD180_PPRL, 0x22); /* Random values to Prescale reg. */ rcout(CD180_PPRH, 0x11); if (rcin(CD180_PPRL) != 0x22 || rcin(CD180_PPRH) != 0x11) return 0; /* Now, test the board more thoroughly, with diagnostic */ if (rc_test(nec, dvp->id_unit)) return 0; rc_softc[dvp->id_unit].rcb_probed = RC_PROBED; return 0xF; } static int rcattach(dvp) struct isa_device *dvp; { register int chan, nec = dvp->id_iobase; struct rc_softc *rcb = &rc_softc[dvp->id_unit]; struct rc_chans *rc = &rc_chans[dvp->id_unit * CD180_NCHAN]; - static int rc_wakeup_started = 0; + static int rc_started = 0; struct tty *tp; /* Thorooughly test the device */ if (rcb->rcb_probed != RC_PROBED) return 0; rcb->rcb_addr = nec; rcb->rcb_dtr = 0; rcb->rcb_baserc = rc; /*rcb->rcb_chipid = 0x10 + dvp->id_unit;*/ printf("rc%d: %d chans, firmware rev. %c\n", dvp->id_unit, CD180_NCHAN, (rcin(CD180_GFRCR) & 0xF) + 'A'); for (chan = 0; chan < CD180_NCHAN; chan++, rc++) { rc->rc_rcb = rcb; rc->rc_chan = chan; rc->rc_iptr = rc->rc_ibuf; rc->rc_bufend = &rc->rc_ibuf[RC_IBUFSIZE]; rc->rc_hiwat = &rc->rc_ibuf[RC_IHIGHWATER]; rc->rc_flags = rc->rc_ier = rc->rc_msvr = 0; rc->rc_cor2 = rc->rc_pendcmd = 0; rc->rc_optr = rc->rc_obufend = rc->rc_obuf; rc->rc_dtrwait = 3 * hz; rc->rc_dcdwaits= 0; rc->rc_hotchar = 0; tp = rc->rc_tp = &rc_tty[chan]; ttychars(tp); tp->t_lflag = tp->t_iflag = tp->t_oflag = 0; tp->t_cflag = TTYDEF_CFLAG; tp->t_ispeed = tp->t_ospeed = TTYDEF_SPEED; #ifdef DEVFS /* FIX THIS to reflect real devices */ rc->devfs_token = devfs_add_devswf(&rc_cdevsw, (dvp->id_unit * CD180_NCHAN) + chan, DV_CHR, 0, 0, 0600, "rc%d.%d", dvp->id_unit, chan); #endif } rcb->rcb_probed = RC_ATTACHED; - if (!rc_wakeup_started) { + if (!rc_started) { + register_swi(SWI_TTY, rcpoll); rc_wakeup((void *)NULL); - rc_wakeup_started = 1; + rc_started = 0; } return 1; } /* RC interrupt handling */ void rcintr(unit) int unit; { register struct rc_softc *rcb = &rc_softc[unit]; register struct rc_chans *rc; register int nec, resid; register u_char val, iack, bsr, ucnt, *optr; int good_data, t_state; if (rcb->rcb_probed != RC_ATTACHED) { printf("rc%d: bogus interrupt\n", unit); return; } nec = rcb->rcb_addr; bsr = ~(rcin(RC_BSR)); if (!(bsr & (RC_BSR_TOUT|RC_BSR_RXINT|RC_BSR_TXINT|RC_BSR_MOINT))) { printf("rc%d: extra interrupt\n", unit); rcout(CD180_EOIR, 0); return; } while (bsr & (RC_BSR_TOUT|RC_BSR_RXINT|RC_BSR_TXINT|RC_BSR_MOINT)) { #ifdef RCDEBUG_DETAILED printf("rc%d: intr (%02x) %s%s%s%s\n", unit, bsr, (bsr & RC_BSR_TOUT)?"TOUT ":"", (bsr & RC_BSR_RXINT)?"RXINT ":"", (bsr & RC_BSR_TXINT)?"TXINT ":"", (bsr & RC_BSR_MOINT)?"MOINT":""); #endif if (bsr & RC_BSR_TOUT) { printf("rc%d: hardware failure, reset board\n", unit); rcout(RC_CTOUT, 0); rc_reinit(rcb); return; } if (bsr & RC_BSR_RXINT) { iack = rcin(RC_PILR_RX); good_data = (iack == (GIVR_IT_RGDI | RC_FAKEID)); if (!good_data && iack != (GIVR_IT_REI | RC_FAKEID)) { printf("rc%d: fake rxint: %02x\n", unit, iack); goto more_intrs; } rc = rcb->rcb_baserc + ((rcin(CD180_GICR) & GICR_CHAN) >> GICR_LSH); t_state = rc->rc_tp->t_state; /* Do RTS flow control stuff */ if ( (rc->rc_flags & RC_RTSFLOW) || !(t_state & TS_ISOPEN) ) { if ( ( !(t_state & TS_ISOPEN) || (t_state & TS_TBLOCK) ) && (rc->rc_msvr & MSVR_RTS) ) rcout(CD180_MSVR, rc->rc_msvr &= ~MSVR_RTS); else if (!(rc->rc_msvr & MSVR_RTS)) rcout(CD180_MSVR, rc->rc_msvr |= MSVR_RTS); } ucnt = rcin(CD180_RDCR) & 0xF; resid = 0; if (t_state & TS_ISOPEN) { /* check for input buffer overflow */ if ((rc->rc_iptr + ucnt) >= rc->rc_bufend) { resid = ucnt; ucnt = rc->rc_bufend - rc->rc_iptr; resid -= ucnt; if (!(rc->rc_flags & RC_WAS_BUFOVFL)) { rc->rc_flags |= RC_WAS_BUFOVFL; rc_scheduled_event++; } } optr = rc->rc_iptr; /* check foor good data */ if (good_data) { while (ucnt-- > 0) { val = rcin(CD180_RDR); optr[0] = val; optr[INPUT_FLAGS_SHIFT] = 0; optr++; rc_scheduled_event++; if (val != 0 && val == rc->rc_hotchar) setsofttty(); } } else { /* Store also status data */ while (ucnt-- > 0) { iack = rcin(CD180_RCSR); if (iack & RCSR_Timeout) break; if ( (iack & RCSR_OE) && !(rc->rc_flags & RC_WAS_SILOVFL)) { rc->rc_flags |= RC_WAS_SILOVFL; rc_scheduled_event++; } val = rcin(CD180_RDR); /* Don't store PE if IGNPAR and BREAK if IGNBRK, this hack allows "raw" tty optimization works even if IGN* is set. */ if ( !(iack & (RCSR_PE|RCSR_FE|RCSR_Break)) || (!(iack & (RCSR_PE|RCSR_FE)) || !(rc->rc_tp->t_iflag & IGNPAR)) && (!(iack & RCSR_Break) || !(rc->rc_tp->t_iflag & IGNBRK))) { if ( (iack & (RCSR_PE|RCSR_FE)) && (t_state & TS_CAN_BYPASS_L_RINT) && ((iack & RCSR_FE) || (iack & RCSR_PE) && (rc->rc_tp->t_iflag & INPCK))) val = 0; else if (val != 0 && val == rc->rc_hotchar) setsofttty(); optr[0] = val; optr[INPUT_FLAGS_SHIFT] = iack; optr++; rc_scheduled_event++; } } } rc->rc_iptr = optr; rc->rc_flags |= RC_DORXFER; } else resid = ucnt; /* Clear FIFO if necessary */ while (resid-- > 0) { if (!good_data) iack = rcin(CD180_RCSR); else iack = 0; if (iack & RCSR_Timeout) break; (void) rcin(CD180_RDR); } goto more_intrs; } if (bsr & RC_BSR_MOINT) { iack = rcin(RC_PILR_MODEM); if (iack != (GIVR_IT_MSCI | RC_FAKEID)) { printf("rc%d: fake moint: %02x\n", unit, iack); goto more_intrs; } rc = rcb->rcb_baserc + ((rcin(CD180_GICR) & GICR_CHAN) >> GICR_LSH); iack = rcin(CD180_MCR); rc->rc_msvr = rcin(CD180_MSVR); rcout(CD180_MCR, 0); #ifdef RCDEBUG printrcflags(rc, "moint"); #endif if (rc->rc_flags & RC_CTSFLOW) { if (rc->rc_msvr & MSVR_CTS) rc->rc_flags |= RC_SEND_RDY; else rc->rc_flags &= ~RC_SEND_RDY; } else rc->rc_flags |= RC_SEND_RDY; if ((iack & MCR_CDchg) && !(rc->rc_flags & RC_MODCHG)) { rc_scheduled_event += LOTS_OF_EVENTS; rc->rc_flags |= RC_MODCHG; setsofttty(); } goto more_intrs; } if (bsr & RC_BSR_TXINT) { iack = rcin(RC_PILR_TX); if (iack != (GIVR_IT_TDI | RC_FAKEID)) { printf("rc%d: fake txint: %02x\n", unit, iack); goto more_intrs; } rc = rcb->rcb_baserc + ((rcin(CD180_GICR) & GICR_CHAN) >> GICR_LSH); if ( (rc->rc_flags & RC_OSUSP) || !(rc->rc_flags & RC_SEND_RDY) ) goto more_intrs; /* Handle breaks and other stuff */ if (rc->rc_pendcmd) { rcout(CD180_COR2, rc->rc_cor2 |= COR2_ETC); rcout(CD180_TDR, CD180_C_ESC); rcout(CD180_TDR, rc->rc_pendcmd); rcout(CD180_COR2, rc->rc_cor2 &= ~COR2_ETC); rc->rc_pendcmd = 0; goto more_intrs; } optr = rc->rc_optr; resid = rc->rc_obufend - optr; if (resid > CD180_NFIFO) resid = CD180_NFIFO; while (resid-- > 0) rcout(CD180_TDR, *optr++); rc->rc_optr = optr; /* output completed? */ if (optr >= rc->rc_obufend) { rcout(CD180_IER, rc->rc_ier &= ~IER_TxRdy); #ifdef RCDEBUG printf("rc%d/%d: output completed\n", unit, rc->rc_chan); #endif if (!(rc->rc_flags & RC_DOXXFER)) { rc_scheduled_event += LOTS_OF_EVENTS; rc->rc_flags |= RC_DOXXFER; setsofttty(); } } } more_intrs: rcout(CD180_EOIR, 0); /* end of interrupt */ rcout(RC_CTOUT, 0); bsr = ~(rcin(RC_BSR)); } } /* Feed characters to output buffer */ static void rc_start(tp) register struct tty *tp; { register struct rc_chans *rc = &rc_chans[GET_UNIT(tp->t_dev)]; register int nec = rc->rc_rcb->rcb_addr, s; if (rc->rc_flags & RC_OSBUSY) return; s = spltty(); rc->rc_flags |= RC_OSBUSY; disable_intr(); if (tp->t_state & TS_TTSTOP) rc->rc_flags |= RC_OSUSP; else rc->rc_flags &= ~RC_OSUSP; /* Do RTS flow control stuff */ if ( (rc->rc_flags & RC_RTSFLOW) && (tp->t_state & TS_TBLOCK) && (rc->rc_msvr & MSVR_RTS) ) { rcout(CD180_CAR, rc->rc_chan); rcout(CD180_MSVR, rc->rc_msvr &= ~MSVR_RTS); } else if (!(rc->rc_msvr & MSVR_RTS)) { rcout(CD180_CAR, rc->rc_chan); rcout(CD180_MSVR, rc->rc_msvr |= MSVR_RTS); } enable_intr(); if (tp->t_state & (TS_TIMEOUT|TS_TTSTOP)) goto out; #ifdef RCDEBUG printrcflags(rc, "rcstart"); #endif ttwwakeup(tp); #ifdef RCDEBUG printf("rcstart: outq = %d obuf = %d\n", tp->t_outq.c_cc, rc->rc_obufend - rc->rc_optr); #endif if (tp->t_state & TS_BUSY) goto out; /* output still in progress ... */ if (tp->t_outq.c_cc > 0) { u_int ocnt; tp->t_state |= TS_BUSY; ocnt = q_to_b(&tp->t_outq, rc->rc_obuf, sizeof rc->rc_obuf); disable_intr(); rc->rc_optr = rc->rc_obuf; rc->rc_obufend = rc->rc_optr + ocnt; enable_intr(); if (!(rc->rc_ier & IER_TxRdy)) { #ifdef RCDEBUG printf("rc%d/%d: rcstart enable txint\n", rc->rc_rcb->rcb_unit, rc->rc_chan); #endif rcout(CD180_CAR, rc->rc_chan); rcout(CD180_IER, rc->rc_ier |= IER_TxRdy); } } out: rc->rc_flags &= ~RC_OSBUSY; (void) splx(s); } /* Handle delayed events. */ void rcpoll() { register struct rc_chans *rc; register struct rc_softc *rcb; register u_char *tptr, *eptr; register struct tty *tp; register int chan, icnt, nec, unit; if (rc_scheduled_event == 0) return; repeat: for (unit = 0; unit < NRC; unit++) { rcb = &rc_softc[unit]; rc = rcb->rcb_baserc; nec = rc->rc_rcb->rcb_addr; for (chan = 0; chan < CD180_NCHAN; rc++, chan++) { tp = rc->rc_tp; #ifdef RCDEBUG if (rc->rc_flags & (RC_DORXFER|RC_DOXXFER|RC_MODCHG| RC_WAS_BUFOVFL|RC_WAS_SILOVFL)) printrcflags(rc, "rcevent"); #endif if (rc->rc_flags & RC_WAS_BUFOVFL) { disable_intr(); rc->rc_flags &= ~RC_WAS_BUFOVFL; rc_scheduled_event--; enable_intr(); printf("rc%d/%d: interrupt-level buffer overflow\n", unit, chan); } if (rc->rc_flags & RC_WAS_SILOVFL) { disable_intr(); rc->rc_flags &= ~RC_WAS_SILOVFL; rc_scheduled_event--; enable_intr(); printf("rc%d/%d: silo overflow\n", unit, chan); } if (rc->rc_flags & RC_MODCHG) { disable_intr(); rc->rc_flags &= ~RC_MODCHG; rc_scheduled_event -= LOTS_OF_EVENTS; enable_intr(); (*linesw[tp->t_line].l_modem)(tp, !!(rc->rc_msvr & MSVR_CD)); } if (rc->rc_flags & RC_DORXFER) { disable_intr(); rc->rc_flags &= ~RC_DORXFER; eptr = rc->rc_iptr; if (rc->rc_bufend == &rc->rc_ibuf[2 * RC_IBUFSIZE]) tptr = &rc->rc_ibuf[RC_IBUFSIZE]; else tptr = rc->rc_ibuf; icnt = eptr - tptr; if (icnt > 0) { if (rc->rc_bufend == &rc->rc_ibuf[2 * RC_IBUFSIZE]) { rc->rc_iptr = rc->rc_ibuf; rc->rc_bufend = &rc->rc_ibuf[RC_IBUFSIZE]; rc->rc_hiwat = &rc->rc_ibuf[RC_IHIGHWATER]; } else { rc->rc_iptr = &rc->rc_ibuf[RC_IBUFSIZE]; rc->rc_bufend = &rc->rc_ibuf[2 * RC_IBUFSIZE]; rc->rc_hiwat = &rc->rc_ibuf[RC_IBUFSIZE + RC_IHIGHWATER]; } if ( (rc->rc_flags & RC_RTSFLOW) && (tp->t_state & TS_ISOPEN) && !(tp->t_state & TS_TBLOCK) && !(rc->rc_msvr & MSVR_RTS) ) { rcout(CD180_CAR, chan); rcout(CD180_MSVR, rc->rc_msvr |= MSVR_RTS); } rc_scheduled_event -= icnt; } enable_intr(); if (icnt <= 0 || !(tp->t_state & TS_ISOPEN)) goto done1; if ( (tp->t_state & TS_CAN_BYPASS_L_RINT) && !(tp->t_state & TS_LOCAL)) { if ((tp->t_rawq.c_cc + icnt) >= RB_I_HIGH_WATER && ((rc->rc_flags & RC_RTSFLOW) || (tp->t_iflag & IXOFF)) && !(tp->t_state & TS_TBLOCK)) ttyblock(tp); tk_nin += icnt; tk_rawcc += icnt; tp->t_rawcc += icnt; if (b_to_q(tptr, icnt, &tp->t_rawq)) printf("rc%d/%d: tty-level buffer overflow\n", unit, chan); ttwakeup(tp); if ((tp->t_state & TS_TTSTOP) && ((tp->t_iflag & IXANY) || (tp->t_cc[VSTART] == tp->t_cc[VSTOP]))) { tp->t_state &= ~TS_TTSTOP; tp->t_lflag &= ~FLUSHO; rc_start(tp); } } else { for (; tptr < eptr; tptr++) (*linesw[tp->t_line].l_rint) (tptr[0] | rc_rcsrt[tptr[INPUT_FLAGS_SHIFT] & 0xF], tp); } done1: ; } if (rc->rc_flags & RC_DOXXFER) { disable_intr(); rc_scheduled_event -= LOTS_OF_EVENTS; rc->rc_flags &= ~RC_DOXXFER; rc->rc_tp->t_state &= ~TS_BUSY; enable_intr(); (*linesw[tp->t_line].l_start)(tp); } } if (rc_scheduled_event == 0) break; } if (rc_scheduled_event >= LOTS_OF_EVENTS) goto repeat; } static void rcstop(tp, rw) register struct tty *tp; int rw; { register struct rc_chans *rc = &rc_chans[GET_UNIT(tp->t_dev)]; u_char *tptr, *eptr; #ifdef RCDEBUG printf("rc%d/%d: rcstop %s%s\n", rc->rc_rcb->rcb_unit, rc->rc_chan, (rw & FWRITE)?"FWRITE ":"", (rw & FREAD)?"FREAD":""); #endif if (rw & FWRITE) rc_discard_output(rc); disable_intr(); if (rw & FREAD) { rc->rc_flags &= ~RC_DORXFER; eptr = rc->rc_iptr; if (rc->rc_bufend == &rc->rc_ibuf[2 * RC_IBUFSIZE]) { tptr = &rc->rc_ibuf[RC_IBUFSIZE]; rc->rc_iptr = &rc->rc_ibuf[RC_IBUFSIZE]; } else { tptr = rc->rc_ibuf; rc->rc_iptr = rc->rc_ibuf; } rc_scheduled_event -= eptr - tptr; } if (tp->t_state & TS_TTSTOP) rc->rc_flags |= RC_OSUSP; else rc->rc_flags &= ~RC_OSUSP; enable_intr(); } static int rcopen(dev, flag, mode, p) dev_t dev; int flag, mode; struct proc *p; { register struct rc_chans *rc; register struct tty *tp; int unit, nec, s, error = 0; unit = GET_UNIT(dev); if (unit >= NRC * CD180_NCHAN) return ENXIO; if (rc_softc[unit / CD180_NCHAN].rcb_probed != RC_ATTACHED) return ENXIO; rc = &rc_chans[unit]; tp = rc->rc_tp; nec = rc->rc_rcb->rcb_addr; #ifdef RCDEBUG printf("rc%d/%d: rcopen: dev %x\n", rc->rc_rcb->rcb_unit, unit, dev); #endif s = spltty(); again: while (rc->rc_flags & RC_DTR_OFF) { error = tsleep(&(rc->rc_dtrwait), TTIPRI | PCATCH, "rcdtr", 0); if (error != 0) goto out; } if (tp->t_state & TS_ISOPEN) { if (CALLOUT(dev)) { if (!(rc->rc_flags & RC_ACTOUT)) { error = EBUSY; goto out; } } else { if (rc->rc_flags & RC_ACTOUT) { if (flag & O_NONBLOCK) { error = EBUSY; goto out; } if (error = tsleep(&rc->rc_rcb, TTIPRI|PCATCH, "rcbi", 0)) goto out; goto again; } } if (tp->t_state & TS_XCLUDE && p->p_ucred->cr_uid != 0) { error = EBUSY; goto out; } } else { tp->t_oproc = rc_start; tp->t_param = rc_param; tp->t_dev = dev; if (CALLOUT(dev)) tp->t_cflag |= CLOCAL; else tp->t_cflag &= ~CLOCAL; error = rc_param(tp, &tp->t_termios); if (error) goto out; (void) rc_modctl(rc, TIOCM_RTS|TIOCM_DTR, DMSET); ttsetwater(tp); if ((rc->rc_msvr & MSVR_CD) || CALLOUT(dev)) (*linesw[tp->t_line].l_modem)(tp, 1); } if (!(tp->t_state & TS_CARR_ON) && !CALLOUT(dev) && !(tp->t_cflag & CLOCAL) && !(flag & O_NONBLOCK)) { rc->rc_dcdwaits++; error = tsleep(TSA_CARR_ON(tp), TTIPRI | PCATCH, "rcdcd", 0); rc->rc_dcdwaits--; if (error != 0) goto out; goto again; } error = (*linesw[tp->t_line].l_open)(dev, tp); disc_optim(tp, &tp->t_termios, rc); if ((tp->t_state & TS_ISOPEN) && CALLOUT(dev)) rc->rc_flags |= RC_ACTOUT; out: (void) splx(s); if(rc->rc_dcdwaits == 0 && !(tp->t_state & TS_ISOPEN)) rc_hardclose(rc); return error; } static int rcclose(dev, flag, mode, p) dev_t dev; int flag, mode; struct proc *p; { register struct rc_chans *rc; register struct tty *tp; int s, unit = GET_UNIT(dev); if (unit >= NRC * CD180_NCHAN) return ENXIO; rc = &rc_chans[unit]; tp = rc->rc_tp; #ifdef RCDEBUG printf("rc%d/%d: rcclose dev %x\n", rc->rc_rcb->rcb_unit, unit, dev); #endif s = spltty(); (*linesw[tp->t_line].l_close)(tp, flag); disc_optim(tp, &tp->t_termios, rc); rcstop(tp, FREAD | FWRITE); rc_hardclose(rc); ttyclose(tp); splx(s); return 0; } static void rc_hardclose(rc) register struct rc_chans *rc; { register int s, nec = rc->rc_rcb->rcb_addr; register struct tty *tp = rc->rc_tp; s = spltty(); rcout(CD180_CAR, rc->rc_chan); /* Disable rx/tx intrs */ rcout(CD180_IER, rc->rc_ier = 0); if ( (tp->t_cflag & HUPCL) || !(rc->rc_flags & RC_ACTOUT) && !(rc->rc_msvr & MSVR_CD) && !(tp->t_cflag & CLOCAL) || !(tp->t_state & TS_ISOPEN) ) { CCRCMD(rc->rc_rcb->rcb_unit, rc->rc_chan, CCR_ResetChan); WAITFORCCR(rc->rc_rcb->rcb_unit, rc->rc_chan); (void) rc_modctl(rc, TIOCM_RTS, DMSET); if (rc->rc_dtrwait) { timeout(rc_dtrwakeup, rc, rc->rc_dtrwait); rc->rc_flags |= RC_DTR_OFF; } } rc->rc_flags &= ~RC_ACTOUT; wakeup((caddr_t) &rc->rc_rcb); /* wake bi */ wakeup(TSA_CARR_ON(tp)); (void) splx(s); } /* Read from line */ static int rcread(dev, uio, flag) dev_t dev; struct uio *uio; int flag; { struct tty *tp = rc_chans[GET_UNIT(dev)].rc_tp; return ((*linesw[tp->t_line].l_read)(tp, uio, flag)); } /* Write to line */ static int rcwrite(dev, uio, flag) dev_t dev; struct uio *uio; int flag; { struct tty *tp = rc_chans[GET_UNIT(dev)].rc_tp; return ((*linesw[tp->t_line].l_write)(tp, uio, flag)); } /* Reset the bastard */ static void rc_hwreset(unit, nec, chipid) register int unit, nec; unsigned int chipid; { CCRCMD(unit, -1, CCR_HWRESET); /* Hardware reset */ DELAY(20000); WAITFORCCR(unit, -1); rcout(RC_CTOUT, 0); /* Clear timeout */ rcout(CD180_GIVR, chipid); rcout(CD180_GICR, 0); /* Set Prescaler Registers (1 msec) */ rcout(CD180_PPRL, ((RC_OSCFREQ + 999) / 1000) & 0xFF); rcout(CD180_PPRH, ((RC_OSCFREQ + 999) / 1000) >> 8); /* Initialize Priority Interrupt Level Registers */ rcout(CD180_PILR1, RC_PILR_MODEM); rcout(CD180_PILR2, RC_PILR_TX); rcout(CD180_PILR3, RC_PILR_RX); /* Reset DTR */ rcout(RC_DTREG, ~0); } /* Set channel parameters */ static int rc_param(tp, ts) register struct tty *tp; struct termios *ts; { register struct rc_chans *rc = &rc_chans[GET_UNIT(tp->t_dev)]; register int nec = rc->rc_rcb->rcb_addr; int idivs, odivs, s, val, cflag, iflag, lflag, inpflow; if ( ts->c_ospeed < 0 || ts->c_ospeed > 76800 || ts->c_ispeed < 0 || ts->c_ispeed > 76800 ) return (EINVAL); if (ts->c_ispeed == 0) ts->c_ispeed = ts->c_ospeed; odivs = RC_BRD(ts->c_ospeed); idivs = RC_BRD(ts->c_ispeed); s = spltty(); /* Select channel */ rcout(CD180_CAR, rc->rc_chan); /* If speed == 0, hangup line */ if (ts->c_ospeed == 0) { CCRCMD(rc->rc_rcb->rcb_unit, rc->rc_chan, CCR_ResetChan); WAITFORCCR(rc->rc_rcb->rcb_unit, rc->rc_chan); (void) rc_modctl(rc, TIOCM_DTR, DMBIC); } tp->t_state &= ~TS_CAN_BYPASS_L_RINT; cflag = ts->c_cflag; iflag = ts->c_iflag; lflag = ts->c_lflag; if (idivs > 0) { rcout(CD180_RBPRL, idivs & 0xFF); rcout(CD180_RBPRH, idivs >> 8); } if (odivs > 0) { rcout(CD180_TBPRL, odivs & 0xFF); rcout(CD180_TBPRH, odivs >> 8); } /* set timeout value */ if (ts->c_ispeed > 0) { int itm = ts->c_ispeed > 2400 ? 5 : 10000 / ts->c_ispeed + 1; if ( !(lflag & ICANON) && ts->c_cc[VMIN] != 0 && ts->c_cc[VTIME] != 0 && ts->c_cc[VTIME] * 10 > itm) itm = ts->c_cc[VTIME] * 10; rcout(CD180_RTPR, itm <= 255 ? itm : 255); } switch (cflag & CSIZE) { case CS5: val = COR1_5BITS; break; case CS6: val = COR1_6BITS; break; case CS7: val = COR1_7BITS; break; default: case CS8: val = COR1_8BITS; break; } if (cflag & PARENB) { val |= COR1_NORMPAR; if (cflag & PARODD) val |= COR1_ODDP; if (!(cflag & INPCK)) val |= COR1_Ignore; } else val |= COR1_Ignore; if (cflag & CSTOPB) val |= COR1_2SB; rcout(CD180_COR1, val); /* Set FIFO threshold */ val = ts->c_ospeed <= 4800 ? 1 : CD180_NFIFO / 2; inpflow = 0; if ( (iflag & IXOFF) && ( ts->c_cc[VSTOP] != _POSIX_VDISABLE && ( ts->c_cc[VSTART] != _POSIX_VDISABLE || (iflag & IXANY) ) ) ) { inpflow = 1; val |= COR3_SCDE|COR3_FCT; } rcout(CD180_COR3, val); /* Initialize on-chip automatic flow control */ val = 0; rc->rc_flags &= ~(RC_CTSFLOW|RC_SEND_RDY); if (cflag & CCTS_OFLOW) { rc->rc_flags |= RC_CTSFLOW; val |= COR2_CtsAE; } else rc->rc_flags |= RC_SEND_RDY; if (tp->t_state & TS_TTSTOP) rc->rc_flags |= RC_OSUSP; else rc->rc_flags &= ~RC_OSUSP; if (cflag & CRTS_IFLOW) rc->rc_flags |= RC_RTSFLOW; else rc->rc_flags &= ~RC_RTSFLOW; if (inpflow) { if (ts->c_cc[VSTART] != _POSIX_VDISABLE) rcout(CD180_SCHR1, ts->c_cc[VSTART]); rcout(CD180_SCHR2, ts->c_cc[VSTOP]); val |= COR2_TxIBE; if (iflag & IXANY) val |= COR2_IXM; } rcout(CD180_COR2, rc->rc_cor2 = val); CCRCMD(rc->rc_rcb->rcb_unit, rc->rc_chan, CCR_CORCHG1 | CCR_CORCHG2 | CCR_CORCHG3); disc_optim(tp, ts, rc); /* modem ctl */ val = cflag & CLOCAL ? 0 : MCOR1_CDzd; if (cflag & CCTS_OFLOW) val |= MCOR1_CTSzd; rcout(CD180_MCOR1, val); val = cflag & CLOCAL ? 0 : MCOR2_CDod; if (cflag & CCTS_OFLOW) val |= MCOR2_CTSod; rcout(CD180_MCOR2, val); /* enable i/o and interrupts */ CCRCMD(rc->rc_rcb->rcb_unit, rc->rc_chan, CCR_XMTREN | ((cflag & CREAD) ? CCR_RCVREN : CCR_RCVRDIS)); WAITFORCCR(rc->rc_rcb->rcb_unit, rc->rc_chan); rc->rc_ier = cflag & CLOCAL ? 0 : IER_CD; if (cflag & CCTS_OFLOW) rc->rc_ier |= IER_CTS; if (cflag & CREAD) rc->rc_ier |= IER_RxData; if (tp->t_state & TS_BUSY) rc->rc_ier |= IER_TxRdy; if (ts->c_ospeed != 0) rc_modctl(rc, TIOCM_DTR, DMBIS); if ((cflag & CCTS_OFLOW) && (rc->rc_msvr & MSVR_CTS)) rc->rc_flags |= RC_SEND_RDY; rcout(CD180_IER, rc->rc_ier); (void) splx(s); return 0; } /* Re-initialize board after bogus interrupts */ static void rc_reinit(rcb) struct rc_softc *rcb; { register struct rc_chans *rc, *rce; register int nec; nec = rcb->rcb_addr; rc_hwreset(rcb->rcb_unit, nec, RC_FAKEID); rc = &rc_chans[rcb->rcb_unit * CD180_NCHAN]; rce = rc + CD180_NCHAN; for (; rc < rce; rc++) (void) rc_param(rc->rc_tp, &rc->rc_tp->t_termios); } static int rcioctl(dev, cmd, data, flag, p) dev_t dev; u_long cmd; int flag; caddr_t data; struct proc *p; { register struct rc_chans *rc = &rc_chans[GET_UNIT(dev)]; register int s, error; struct tty *tp = rc->rc_tp; error = (*linesw[tp->t_line].l_ioctl)(tp, cmd, data, flag, p); if (error != ENOIOCTL) return (error); error = ttioctl(tp, cmd, data, flag); disc_optim(tp, &tp->t_termios, rc); if (error != ENOIOCTL) return (error); s = spltty(); switch (cmd) { case TIOCSBRK: rc->rc_pendcmd = CD180_C_SBRK; break; case TIOCCBRK: rc->rc_pendcmd = CD180_C_EBRK; break; case TIOCSDTR: (void) rc_modctl(rc, TIOCM_DTR, DMBIS); break; case TIOCCDTR: (void) rc_modctl(rc, TIOCM_DTR, DMBIC); break; case TIOCMGET: *(int *) data = rc_modctl(rc, 0, DMGET); break; case TIOCMSET: (void) rc_modctl(rc, *(int *) data, DMSET); break; case TIOCMBIC: (void) rc_modctl(rc, *(int *) data, DMBIC); break; case TIOCMBIS: (void) rc_modctl(rc, *(int *) data, DMBIS); break; case TIOCMSDTRWAIT: error = suser(p->p_ucred, &p->p_acflag); if (error != 0) { splx(s); return (error); } rc->rc_dtrwait = *(int *)data * hz / 100; break; case TIOCMGDTRWAIT: *(int *)data = rc->rc_dtrwait * 100 / hz; break; default: (void) splx(s); return ENOTTY; } (void) splx(s); return 0; } /* Modem control routines */ static int rc_modctl(rc, bits, cmd) register struct rc_chans *rc; int bits, cmd; { register int nec = rc->rc_rcb->rcb_addr; u_char *dtr = &rc->rc_rcb->rcb_dtr, msvr; rcout(CD180_CAR, rc->rc_chan); switch (cmd) { case DMSET: rcout(RC_DTREG, (bits & TIOCM_DTR) ? ~(*dtr |= 1 << rc->rc_chan) : ~(*dtr &= ~(1 << rc->rc_chan))); msvr = rcin(CD180_MSVR); if (bits & TIOCM_RTS) msvr |= MSVR_RTS; else msvr &= ~MSVR_RTS; if (bits & TIOCM_DTR) msvr |= MSVR_DTR; else msvr &= ~MSVR_DTR; rcout(CD180_MSVR, msvr); break; case DMBIS: if (bits & TIOCM_DTR) rcout(RC_DTREG, ~(*dtr |= 1 << rc->rc_chan)); msvr = rcin(CD180_MSVR); if (bits & TIOCM_RTS) msvr |= MSVR_RTS; if (bits & TIOCM_DTR) msvr |= MSVR_DTR; rcout(CD180_MSVR, msvr); break; case DMGET: bits = TIOCM_LE; msvr = rc->rc_msvr = rcin(CD180_MSVR); if (msvr & MSVR_RTS) bits |= TIOCM_RTS; if (msvr & MSVR_CTS) bits |= TIOCM_CTS; if (msvr & MSVR_DSR) bits |= TIOCM_DSR; if (msvr & MSVR_DTR) bits |= TIOCM_DTR; if (msvr & MSVR_CD) bits |= TIOCM_CD; if (~rcin(RC_RIREG) & (1 << rc->rc_chan)) bits |= TIOCM_RI; return bits; case DMBIC: if (bits & TIOCM_DTR) rcout(RC_DTREG, ~(*dtr &= ~(1 << rc->rc_chan))); msvr = rcin(CD180_MSVR); if (bits & TIOCM_RTS) msvr &= ~MSVR_RTS; if (bits & TIOCM_DTR) msvr &= ~MSVR_DTR; rcout(CD180_MSVR, msvr); break; } rc->rc_msvr = rcin(CD180_MSVR); return 0; } /* Test the board. */ int rc_test(nec, unit) register int nec; int unit; { int chan = 0; int i = 0, rcnt, old_level; unsigned int iack, chipid; unsigned short divs; static u_char ctest[] = "\377\125\252\045\244\0\377"; #define CTLEN 8 #define ERR(s) { \ printf("rc%d: ", unit); printf s ; printf("\n"); \ (void) splx(old_level); return 1; } struct rtest { u_char txbuf[CD180_NFIFO]; /* TX buffer */ u_char rxbuf[CD180_NFIFO]; /* RX buffer */ int rxptr; /* RX pointer */ int txptr; /* TX pointer */ } tchans[CD180_NCHAN]; old_level = spltty(); chipid = RC_FAKEID; /* First, reset board to inital state */ rc_hwreset(unit, nec, chipid); divs = RC_BRD(19200); /* Initialize channels */ for (chan = 0; chan < CD180_NCHAN; chan++) { /* Select and reset channel */ rcout(CD180_CAR, chan); CCRCMD(unit, chan, CCR_ResetChan); WAITFORCCR(unit, chan); /* Set speed */ rcout(CD180_RBPRL, divs & 0xFF); rcout(CD180_RBPRH, divs >> 8); rcout(CD180_TBPRL, divs & 0xFF); rcout(CD180_TBPRH, divs >> 8); /* set timeout value */ rcout(CD180_RTPR, 0); /* Establish local loopback */ rcout(CD180_COR1, COR1_NOPAR | COR1_8BITS | COR1_1SB); rcout(CD180_COR2, COR2_LLM); rcout(CD180_COR3, CD180_NFIFO); CCRCMD(unit, chan, CCR_CORCHG1 | CCR_CORCHG2 | CCR_CORCHG3); CCRCMD(unit, chan, CCR_RCVREN | CCR_XMTREN); WAITFORCCR(unit, chan); rcout(CD180_MSVR, MSVR_RTS); /* Fill TXBUF with test data */ for (i = 0; i < CD180_NFIFO; i++) { tchans[chan].txbuf[i] = ctest[i]; tchans[chan].rxbuf[i] = 0; } tchans[chan].txptr = tchans[chan].rxptr = 0; /* Now, start transmit */ rcout(CD180_IER, IER_TxMpty|IER_RxData); } /* Pseudo-interrupt poll stuff */ for (rcnt = 10000; rcnt-- > 0; rcnt--) { i = ~(rcin(RC_BSR)); if (i & RC_BSR_TOUT) ERR(("BSR timeout bit set\n")) else if (i & RC_BSR_TXINT) { iack = rcin(RC_PILR_TX); if (iack != (GIVR_IT_TDI | chipid)) ERR(("Bad TX intr ack (%02x != %02x)\n", iack, GIVR_IT_TDI | chipid)); chan = (rcin(CD180_GICR) & GICR_CHAN) >> GICR_LSH; /* If no more data to transmit, disable TX intr */ if (tchans[chan].txptr >= CD180_NFIFO) { iack = rcin(CD180_IER); rcout(CD180_IER, iack & ~IER_TxMpty); } else { for (iack = tchans[chan].txptr; iack < CD180_NFIFO; iack++) rcout(CD180_TDR, tchans[chan].txbuf[iack]); tchans[chan].txptr = iack; } rcout(CD180_EOIR, 0); } else if (i & RC_BSR_RXINT) { u_char ucnt; iack = rcin(RC_PILR_RX); if (iack != (GIVR_IT_RGDI | chipid) && iack != (GIVR_IT_REI | chipid)) ERR(("Bad RX intr ack (%02x != %02x)\n", iack, GIVR_IT_RGDI | chipid)) chan = (rcin(CD180_GICR) & GICR_CHAN) >> GICR_LSH; ucnt = rcin(CD180_RDCR) & 0xF; while (ucnt-- > 0) { iack = rcin(CD180_RCSR); if (iack & RCSR_Timeout) break; if (iack & 0xF) ERR(("Bad char chan %d (RCSR = %02X)\n", chan, iack)) if (tchans[chan].rxptr > CD180_NFIFO) ERR(("Got extra chars chan %d\n", chan)) tchans[chan].rxbuf[tchans[chan].rxptr++] = rcin(CD180_RDR); } rcout(CD180_EOIR, 0); } rcout(RC_CTOUT, 0); for (iack = chan = 0; chan < CD180_NCHAN; chan++) if (tchans[chan].rxptr >= CD180_NFIFO) iack++; if (iack == CD180_NCHAN) break; } for (chan = 0; chan < CD180_NCHAN; chan++) { /* Select and reset channel */ rcout(CD180_CAR, chan); CCRCMD(unit, chan, CCR_ResetChan); } if (!rcnt) ERR(("looses characters during local loopback\n")) /* Now, check data */ for (chan = 0; chan < CD180_NCHAN; chan++) for (i = 0; i < CD180_NFIFO; i++) if (ctest[i] != tchans[chan].rxbuf[i]) ERR(("data mismatch chan %d ptr %d (%d != %d)\n", chan, i, ctest[i], tchans[chan].rxbuf[i])) (void) splx(old_level); return 0; } #ifdef RCDEBUG static void printrcflags(rc, comment) struct rc_chans *rc; char *comment; { u_short f = rc->rc_flags; register int nec = rc->rc_rcb->rcb_addr; printf("rc%d/%d: %s flags: %s%s%s%s%s%s%s%s%s%s%s%s\n", rc->rc_rcb->rcb_unit, rc->rc_chan, comment, (f & RC_DTR_OFF)?"DTR_OFF " :"", (f & RC_ACTOUT) ?"ACTOUT " :"", (f & RC_RTSFLOW)?"RTSFLOW " :"", (f & RC_CTSFLOW)?"CTSFLOW " :"", (f & RC_DORXFER)?"DORXFER " :"", (f & RC_DOXXFER)?"DOXXFER " :"", (f & RC_MODCHG) ?"MODCHG " :"", (f & RC_OSUSP) ?"OSUSP " :"", (f & RC_OSBUSY) ?"OSBUSY " :"", (f & RC_WAS_BUFOVFL) ?"BUFOVFL " :"", (f & RC_WAS_SILOVFL) ?"SILOVFL " :"", (f & RC_SEND_RDY) ?"SEND_RDY":""); rcout(CD180_CAR, rc->rc_chan); printf("rc%d/%d: msvr %02x ier %02x ccsr %02x\n", rc->rc_rcb->rcb_unit, rc->rc_chan, rcin(CD180_MSVR), rcin(CD180_IER), rcin(CD180_CCSR)); } #endif /* RCDEBUG */ static struct tty * rcdevtotty(dev) dev_t dev; { int unit; unit = GET_UNIT(dev); if (unit >= NRC * CD180_NCHAN) return NULL; return (&rc_tty[unit]); } static void rc_dtrwakeup(chan) void *chan; { struct rc_chans *rc; rc = (struct rc_chans *)chan; rc->rc_flags &= ~RC_DTR_OFF; wakeup(&rc->rc_dtrwait); } static void rc_discard_output(rc) struct rc_chans *rc; { disable_intr(); if (rc->rc_flags & RC_DOXXFER) { rc_scheduled_event -= LOTS_OF_EVENTS; rc->rc_flags &= ~RC_DOXXFER; } rc->rc_optr = rc->rc_obufend; rc->rc_tp->t_state &= ~TS_BUSY; enable_intr(); ttwwakeup(rc->rc_tp); } static void rc_wakeup(chan) void *chan; { timeout(rc_wakeup, (caddr_t)NULL, 1); if (rc_scheduled_event != 0) { int s; s = splsofttty(); rcpoll(); splx(s); } } static void disc_optim(tp, t, rc) struct tty *tp; struct termios *t; struct rc_chans *rc; { if (!(t->c_iflag & (ICRNL | IGNCR | IMAXBEL | INLCR | ISTRIP | IXON)) && (!(t->c_iflag & BRKINT) || (t->c_iflag & IGNBRK)) && (!(t->c_iflag & PARMRK) || (t->c_iflag & (IGNPAR | IGNBRK)) == (IGNPAR | IGNBRK)) && !(t->c_lflag & (ECHO | ICANON | IEXTEN | ISIG | PENDIN)) && linesw[tp->t_line].l_rint == ttyinput) tp->t_state |= TS_CAN_BYPASS_L_RINT; else tp->t_state &= ~TS_CAN_BYPASS_L_RINT; rc->rc_hotchar = linesw[tp->t_line].l_hotchar; } static void rc_wait0(nec, unit, chan, line) int nec, unit, chan, line; { int rcnt; for (rcnt = 50; rcnt && rcin(CD180_CCR); rcnt--) DELAY(30); if (rcnt == 0) printf("rc%d/%d: channel command timeout, rc.c line: %d\n", unit, chan, line); } static rc_devsw_installed = 0; static void rc_drvinit(void *unused) { dev_t dev; if( ! rc_devsw_installed ) { dev = makedev(CDEV_MAJOR, 0); cdevsw_add(&dev,&rc_cdevsw, NULL); rc_devsw_installed = 1; } } SYSINIT(rcdev,SI_SUB_DRIVERS,SI_ORDER_MIDDLE+CDEV_MAJOR,rc_drvinit,NULL) #endif /* NRC */ Index: head/sys/dev/sio/sio.c =================================================================== --- head/sys/dev/sio/sio.c (revision 38245) +++ head/sys/dev/sio/sio.c (revision 38246) @@ -1,3044 +1,3039 @@ /*- * Copyright (c) 1991 The Regents of the University of California. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * from: @(#)com.c 7.5 (Berkeley) 5/16/91 - * $Id: sio.c,v 1.208 1998/06/24 09:23:32 phk Exp $ + * $Id: sio.c,v 1.209 1998/07/15 12:18:14 bde Exp $ */ #include "opt_comconsole.h" #include "opt_compat.h" #include "opt_ddb.h" #include "opt_devfs.h" #include "opt_sio.h" #include "sio.h" #include "pnp.h" #ifndef EXTRA_SIO #if NPNP > 0 #define EXTRA_SIO 2 #else #define EXTRA_SIO 0 #endif #endif #define NSIOTOT (NSIO + EXTRA_SIO) /* * Serial driver, based on 386BSD-0.1 com driver. * Mostly rewritten to use pseudo-DMA. * Works for National Semiconductor NS8250-NS16550AF UARTs. * COM driver, based on HP dca driver. * * Changes for PC-Card integration: * - Added PC-Card driver table and handlers */ #include #include #include #include #include #include #include #include #include +#include #include #include #include #ifdef DEVFS #include #endif #include +#include #include #include #include #include #ifdef COM_ESP #include #endif #include #include "card.h" #if NCARD > 0 #include #include #endif #if NPNP > 0 #include #endif #ifdef SMP #define disable_intr() COM_DISABLE_INTR() #define enable_intr() COM_ENABLE_INTR() #endif /* SMP */ #define LOTS_OF_EVENTS 64 /* helps separate urgent events from input */ #define RB_I_HIGH_WATER (TTYHOG - 2 * RS_IBUFSIZE) #define RS_IBUFSIZE 256 #define CALLOUT_MASK 0x80 #define CONTROL_MASK 0x60 #define CONTROL_INIT_STATE 0x20 #define CONTROL_LOCK_STATE 0x40 #define DEV_TO_UNIT(dev) (MINOR_TO_UNIT(minor(dev))) #define MINOR_MAGIC_MASK (CALLOUT_MASK | CONTROL_MASK) #define MINOR_TO_UNIT(mynor) ((mynor) & ~MINOR_MAGIC_MASK) #ifdef COM_MULTIPORT /* checks in flags for multiport and which is multiport "master chip" * for a given card */ #define COM_ISMULTIPORT(dev) ((dev)->id_flags & 0x01) #define COM_MPMASTER(dev) (((dev)->id_flags >> 8) & 0x0ff) #define COM_NOTAST4(dev) ((dev)->id_flags & 0x04) #endif /* COM_MULTIPORT */ #define COM_CONSOLE(dev) ((dev)->id_flags & 0x10) #define COM_FORCECONSOLE(dev) ((dev)->id_flags & 0x20) #define COM_LLCONSOLE(dev) ((dev)->id_flags & 0x40) #define COM_LOSESOUTINTS(dev) ((dev)->id_flags & 0x08) #define COM_NOFIFO(dev) ((dev)->id_flags & 0x02) #define COM_ST16650A(dev) ((dev)->id_flags & 0x20000) #define COM_C_NOPROBE (0x40000) #define COM_NOPROBE(dev) ((dev)->id_flags & COM_C_NOPROBE) #define COM_C_IIR_TXRDYBUG (0x80000) #define COM_IIR_TXRDYBUG(dev) ((dev)->id_flags & COM_C_IIR_TXRDYBUG) #define COM_FIFOSIZE(dev) (((dev)->id_flags & 0xff000000) >> 24) #define com_scr 7 /* scratch register for 16450-16550 (R/W) */ /* * Input buffer watermarks. * The external device is asked to stop sending when the buffer exactly reaches * high water, or when the high level requests it. * The high level is notified immediately (rather than at a later clock tick) * when this watermark is reached. * The buffer size is chosen so the watermark should almost never be reached. * The low watermark is invisibly 0 since the buffer is always emptied all at * once. */ #define RS_IHIGHWATER (3 * RS_IBUFSIZE / 4) /* * com state bits. * (CS_BUSY | CS_TTGO) and (CS_BUSY | CS_TTGO | CS_ODEVREADY) must be higher * than the other bits so that they can be tested as a group without masking * off the low bits. * * The following com and tty flags correspond closely: * CS_BUSY = TS_BUSY (maintained by comstart(), siopoll() and * siostop()) * CS_TTGO = ~TS_TTSTOP (maintained by comparam() and comstart()) * CS_CTS_OFLOW = CCTS_OFLOW (maintained by comparam()) * CS_RTS_IFLOW = CRTS_IFLOW (maintained by comparam()) * TS_FLUSH is not used. * XXX I think TIOCSETA doesn't clear TS_TTSTOP when it clears IXON. * XXX CS_*FLOW should be CF_*FLOW in com->flags (control flags not state). */ #define CS_BUSY 0x80 /* output in progress */ #define CS_TTGO 0x40 /* output not stopped by XOFF */ #define CS_ODEVREADY 0x20 /* external device h/w ready (CTS) */ #define CS_CHECKMSR 1 /* check of MSR scheduled */ #define CS_CTS_OFLOW 2 /* use CTS output flow control */ #define CS_DTR_OFF 0x10 /* DTR held off */ #define CS_ODONE 4 /* output completed */ #define CS_RTS_IFLOW 8 /* use RTS input flow control */ #define CSE_BUSYCHECK 1 /* siobusycheck() scheduled */ static char const * const error_desc[] = { #define CE_OVERRUN 0 "silo overflow", #define CE_INTERRUPT_BUF_OVERFLOW 1 "interrupt-level buffer overflow", #define CE_TTY_BUF_OVERFLOW 2 "tty-level buffer overflow", }; #define CE_NTYPES 3 #define CE_RECORD(com, errnum) (++(com)->delta_error_counts[errnum]) /* types. XXX - should be elsewhere */ typedef u_int Port_t; /* hardware port */ typedef u_char bool_t; /* boolean */ /* queue of linear buffers */ struct lbq { u_char *l_head; /* next char to process */ u_char *l_tail; /* one past the last char to process */ struct lbq *l_next; /* next in queue */ bool_t l_queued; /* nonzero if queued */ }; /* com device structure */ struct com_s { u_int id_flags; /* Copy isa device falgas */ u_char state; /* miscellaneous flag bits */ bool_t active_out; /* nonzero if the callout device is open */ u_char cfcr_image; /* copy of value written to CFCR */ #ifdef COM_ESP bool_t esp; /* is this unit a hayes esp board? */ #endif u_char extra_state; /* more flag bits, separate for order trick */ u_char fifo_image; /* copy of value written to FIFO */ bool_t hasfifo; /* nonzero for 16550 UARTs */ bool_t st16650a; /* Is a Startech 16650A or RTS/CTS compat */ bool_t loses_outints; /* nonzero if device loses output interrupts */ u_char mcr_image; /* copy of value written to MCR */ #ifdef COM_MULTIPORT bool_t multiport; /* is this unit part of a multiport device? */ #endif /* COM_MULTIPORT */ bool_t no_irq; /* nonzero if irq is not attached */ bool_t gone; /* hardware disappeared */ bool_t poll; /* nonzero if polling is required */ bool_t poll_output; /* nonzero if polling for output is required */ int unit; /* unit number */ int dtr_wait; /* time to hold DTR down on close (* 1/hz) */ u_int tx_fifo_size; u_int wopeners; /* # processes waiting for DCD in open() */ /* * The high level of the driver never reads status registers directly * because there would be too many side effects to handle conveniently. * Instead, it reads copies of the registers stored here by the * interrupt handler. */ u_char last_modem_status; /* last MSR read by intr handler */ u_char prev_modem_status; /* last MSR handled by high level */ u_char hotchar; /* ldisc-specific char to be handled ASAP */ u_char *ibuf; /* start of input buffer */ u_char *ibufend; /* end of input buffer */ u_char *ihighwater; /* threshold in input buffer */ u_char *iptr; /* next free spot in input buffer */ struct lbq obufq; /* head of queue of output buffers */ struct lbq obufs[2]; /* output buffers */ Port_t data_port; /* i/o ports */ #ifdef COM_ESP Port_t esp_port; #endif Port_t int_id_port; Port_t iobase; Port_t modem_ctl_port; Port_t line_status_port; Port_t modem_status_port; Port_t intr_ctl_port; /* Ports of IIR register */ struct tty *tp; /* cross reference */ /* Initial state. */ struct termios it_in; /* should be in struct tty */ struct termios it_out; /* Lock state. */ struct termios lt_in; /* should be in struct tty */ struct termios lt_out; bool_t do_timestamp; bool_t do_dcd_timestamp; struct timeval timestamp; struct timeval dcd_timestamp; u_long bytes_in; /* statistics */ u_long bytes_out; u_int delta_error_counts[CE_NTYPES]; u_long error_counts[CE_NTYPES]; /* * Ping-pong input buffers. The extra factor of 2 in the sizes is * to allow for an error byte for each input byte. */ #define CE_INPUT_OFFSET RS_IBUFSIZE u_char ibuf1[2 * RS_IBUFSIZE]; u_char ibuf2[2 * RS_IBUFSIZE]; /* * Data area for output buffers. Someday we should build the output * buffer queue without copying data. */ u_char obuf1[256]; u_char obuf2[256]; #ifdef DEVFS void *devfs_token_ttyd; void *devfs_token_ttyl; void *devfs_token_ttyi; void *devfs_token_cuaa; void *devfs_token_cual; void *devfs_token_cuai; #endif }; -/* - * XXX public functions in drivers should be declared in headers produced - * by `config', not here. - */ - -/* Interrupt handling entry point. */ -void siopoll __P((void)); - -/* Device switch entry points. */ -#define sioreset noreset -#define siommap nommap -#define siostrategy nostrategy - #ifdef COM_ESP static int espattach __P((struct isa_device *isdp, struct com_s *com, Port_t esp_port)); #endif static int sioattach __P((struct isa_device *dev)); static timeout_t siobusycheck; static timeout_t siodtrwakeup; static void comhardclose __P((struct com_s *com)); static void siointr1 __P((struct com_s *com)); static int commctl __P((struct com_s *com, int bits, int how)); static int comparam __P((struct tty *tp, struct termios *t)); +static swihand_t siopoll; static int sioprobe __P((struct isa_device *dev)); static void siosettimeout __P((void)); static void comstart __P((struct tty *tp)); static timeout_t comwakeup; static void disc_optim __P((struct tty *tp, struct termios *t, struct com_s *com)); #ifdef DSI_SOFT_MODEM static int LoadSoftModem __P((int unit,int base_io, u_long size, u_char *ptr)); #endif /* DSI_SOFT_MODEM */ static char driver_name[] = "sio"; /* table and macro for fast conversion from a unit number to its com struct */ static struct com_s *p_com_addr[NSIOTOT]; #define com_addr(unit) (p_com_addr[unit]) struct isa_driver siodriver = { sioprobe, sioattach, driver_name }; static d_open_t sioopen; static d_close_t sioclose; static d_read_t sioread; static d_write_t siowrite; static d_ioctl_t sioioctl; static d_stop_t siostop; static d_devtotty_t siodevtotty; #define CDEV_MAJOR 28 static struct cdevsw sio_cdevsw = { sioopen, sioclose, sioread, siowrite, sioioctl, siostop, noreset, siodevtotty, ttpoll, nommap, NULL, driver_name, NULL, -1, }; static int comconsole = -1; static volatile speed_t comdefaultrate = CONSPEED; static u_int com_events; /* input chars + weighted output completions */ static Port_t siocniobase; +static bool_t siopoll_registered; static int sio_timeout; static int sio_timeouts_until_log; static struct callout_handle sio_timeout_handle = CALLOUT_HANDLE_INITIALIZER(&sio_timeout_handle); #if 0 /* XXX */ static struct tty *sio_tty[NSIOTOT]; #else static struct tty sio_tty[NSIOTOT]; #endif static const int nsio_tty = NSIOTOT; static struct speedtab comspeedtab[] = { { 0, 0 }, { 50, COMBRD(50) }, { 75, COMBRD(75) }, { 110, COMBRD(110) }, { 134, COMBRD(134) }, { 150, COMBRD(150) }, { 200, COMBRD(200) }, { 300, COMBRD(300) }, { 600, COMBRD(600) }, { 1200, COMBRD(1200) }, { 1800, COMBRD(1800) }, { 2400, COMBRD(2400) }, { 4800, COMBRD(4800) }, { 9600, COMBRD(9600) }, { 19200, COMBRD(19200) }, { 38400, COMBRD(38400) }, { 57600, COMBRD(57600) }, { 115200, COMBRD(115200) }, { -1, -1 } }; #ifdef COM_ESP /* XXX configure this properly. */ static Port_t likely_com_ports[] = { 0x3f8, 0x2f8, 0x3e8, 0x2e8, }; static Port_t likely_esp_ports[] = { 0x140, 0x180, 0x280, 0 }; #endif /* * handle sysctl read/write requests for console speed * * In addition to setting comdefaultrate for I/O through /dev/console, * also set the initial and lock values for the /dev/ttyXX device * if there is one associated with the console. Finally, if the /dev/tty * device has already been open, change the speed on the open running port * itself. */ static int sysctl_machdep_comdefaultrate SYSCTL_HANDLER_ARGS { int error, s; speed_t newspeed; struct com_s *com; struct tty *tp; newspeed = comdefaultrate; error = sysctl_handle_opaque(oidp, &newspeed, sizeof newspeed, req); if (error || !req->newptr) return (error); comdefaultrate = newspeed; if (comconsole < 0) /* serial console not selected? */ return (0); com = com_addr(comconsole); if (!com) return (ENXIO); /* * set the initial and lock rates for /dev/ttydXX and /dev/cuaXX * (note, the lock rates really are boolean -- if non-zero, disallow * speed changes) */ com->it_in.c_ispeed = com->it_in.c_ospeed = com->lt_in.c_ispeed = com->lt_in.c_ospeed = com->it_out.c_ispeed = com->it_out.c_ospeed = com->lt_out.c_ispeed = com->lt_out.c_ospeed = comdefaultrate; /* * if we're open, change the running rate too */ tp = com->tp; if (tp && (tp->t_state & TS_ISOPEN)) { tp->t_termios.c_ispeed = tp->t_termios.c_ospeed = comdefaultrate; s = spltty(); error = comparam(tp, &tp->t_termios); splx(s); } return error; } SYSCTL_PROC(_machdep, OID_AUTO, conspeed, CTLTYPE_INT | CTLFLAG_RW, 0, 0, sysctl_machdep_comdefaultrate, "I", ""); #if NCARD > 0 /* * PC-Card (PCMCIA) specific code. */ static int sioinit __P((struct pccard_devinfo *)); static void siounload __P((struct pccard_devinfo *)); static int card_intr __P((struct pccard_devinfo *)); static struct pccard_device sio_info = { driver_name, sioinit, siounload, card_intr, 0, /* Attributes - presently unused */ &tty_imask /* Interrupt mask for device */ /* XXX - Should this also include net_imask? */ }; DATA_SET(pccarddrv_set, sio_info); /* * Initialize the device - called from Slot manager. */ int sioinit(struct pccard_devinfo *devi) { /* validate unit number. */ if (devi->isahd.id_unit >= (NSIOTOT)) return(ENODEV); /* Make sure it isn't already probed. */ if (com_addr(devi->isahd.id_unit)) return(EBUSY); /* It's already probed as serial by Upper */ devi->isahd.id_flags |= COM_C_NOPROBE; /* * Probe the device. If a value is returned, the * device was found at the location. */ if (sioprobe(&devi->isahd) == 0) return(ENXIO); if (sioattach(&devi->isahd) == 0) return(ENXIO); return(0); } /* * siounload - unload the driver and clear the table. * XXX TODO: * This is usually called when the card is ejected, but * can be caused by a modunload of a controller driver. * The idea is to reset the driver's view of the device * and ensure that any driver entry points such as * read and write do not hang. */ static void siounload(struct pccard_devinfo *devi) { struct com_s *com; if (!devi) { printf("NULL devi in siounload\n"); return; } com = com_addr(devi->isahd.id_unit); if (!com) { printf("NULL com in siounload\n"); return; } if (!com->iobase) { printf("sio%d already unloaded!\n",devi->isahd.id_unit); return; } if (com->tp && (com->tp->t_state & TS_ISOPEN)) { com->gone = 1; printf("sio%d: unload\n", devi->isahd.id_unit); com->tp->t_gen++; ttyclose(com->tp); ttwakeup(com->tp); ttwwakeup(com->tp); } else { com_addr(com->unit) = NULL; bzero(com, sizeof *com); free(com,M_TTYS); printf("sio%d: unload,gone\n", devi->isahd.id_unit); } } /* * card_intr - Shared interrupt called from * front end of PC-Card handler. */ static int card_intr(struct pccard_devinfo *devi) { struct com_s *com; COM_LOCK(); com = com_addr(devi->isahd.id_unit); if (com && !com->gone) siointr1(com_addr(devi->isahd.id_unit)); COM_UNLOCK(); return(1); } #endif /* NCARD > 0 */ static int sioprobe(dev) struct isa_device *dev; { static bool_t already_init; bool_t failures[10]; int fn; struct isa_device *idev; Port_t iobase; intrmask_t irqmap[4]; intrmask_t irqs; u_char mcr_image; int result; struct isa_device *xdev; if (!already_init) { /* * Turn off MCR_IENABLE for all likely serial ports. An unused * port with its MCR_IENABLE gate open will inhibit interrupts * from any used port that shares the interrupt vector. * XXX the gate enable is elsewhere for some multiports. */ for (xdev = isa_devtab_tty; xdev->id_driver != NULL; xdev++) if (xdev->id_driver == &siodriver && xdev->id_enabled) outb(xdev->id_iobase + com_mcr, 0); already_init = TRUE; } if (COM_LLCONSOLE(dev)) { printf("sio%d: reserved for low-level i/o\n", dev->id_unit); return (0); } /* * If the device is on a multiport card and has an AST/4 * compatible interrupt control register, initialize this * register and prepare to leave MCR_IENABLE clear in the mcr. * Otherwise, prepare to set MCR_IENABLE in the mcr. * Point idev to the device struct giving the correct id_irq. * This is the struct for the master device if there is one. */ idev = dev; mcr_image = MCR_IENABLE; #ifdef COM_MULTIPORT if (COM_ISMULTIPORT(dev)) { idev = find_isadev(isa_devtab_tty, &siodriver, COM_MPMASTER(dev)); if (idev == NULL) { printf("sio%d: master device %d not configured\n", dev->id_unit, COM_MPMASTER(dev)); dev->id_irq = 0; idev = dev; } if (!COM_NOTAST4(dev)) { outb(idev->id_iobase + com_scr, idev->id_irq ? 0x80 : 0); mcr_image = 0; } } #endif /* COM_MULTIPORT */ if (idev->id_irq == 0) mcr_image = 0; bzero(failures, sizeof failures); iobase = dev->id_iobase; /* * We don't want to get actual interrupts, just masked ones. * Interrupts from this line should already be masked in the ICU, * but mask them in the processor as well in case there are some * (misconfigured) shared interrupts. */ disable_intr(); /* EXTRA DELAY? */ /* * Initialize the speed and the word size and wait long enough to * drain the maximum of 16 bytes of junk in device output queues. * The speed is undefined after a master reset and must be set * before relying on anything related to output. There may be * junk after a (very fast) soft reboot and (apparently) after * master reset. * XXX what about the UART bug avoided by waiting in comparam()? * We don't want to to wait long enough to drain at 2 bps. */ if (iobase == siocniobase) DELAY((16 + 1) * 1000000 / (comdefaultrate / 10)); else { outb(iobase + com_cfcr, CFCR_DLAB | CFCR_8BITS); outb(iobase + com_dlbl, COMBRD(SIO_TEST_SPEED) & 0xff); outb(iobase + com_dlbh, (u_int) COMBRD(SIO_TEST_SPEED) >> 8); outb(iobase + com_cfcr, CFCR_8BITS); DELAY((16 + 1) * 1000000 / (SIO_TEST_SPEED / 10)); } /* * Enable the interrupt gate and disable device interupts. This * should leave the device driving the interrupt line low and * guarantee an edge trigger if an interrupt can be generated. */ /* EXTRA DELAY? */ outb(iobase + com_mcr, mcr_image); outb(iobase + com_ier, 0); DELAY(1000); /* XXX */ irqmap[0] = isa_irq_pending(); /* * Attempt to set loopback mode so that we can send a null byte * without annoying any external device. */ /* EXTRA DELAY? */ outb(iobase + com_mcr, mcr_image | MCR_LOOPBACK); /* * Attempt to generate an output interrupt. On 8250's, setting * IER_ETXRDY generates an interrupt independent of the current * setting and independent of whether the THR is empty. On 16450's, * setting IER_ETXRDY generates an interrupt independent of the * current setting. On 16550A's, setting IER_ETXRDY only * generates an interrupt when IER_ETXRDY is not already set. */ outb(iobase + com_ier, IER_ETXRDY); /* * On some 16x50 incompatibles, setting IER_ETXRDY doesn't generate * an interrupt. They'd better generate one for actually doing * output. Loopback may be broken on the same incompatibles but * it's unlikely to do more than allow the null byte out. */ outb(iobase + com_data, 0); DELAY((1 + 2) * 1000000 / (SIO_TEST_SPEED / 10)); /* * Turn off loopback mode so that the interrupt gate works again * (MCR_IENABLE was hidden). This should leave the device driving * an interrupt line high. It doesn't matter if the interrupt * line oscillates while we are not looking at it, since interrupts * are disabled. */ /* EXTRA DELAY? */ outb(iobase + com_mcr, mcr_image); /* * It's a definitly Serial PCMCIA(16550A), but still be required * for IIR_TXRDY implementation ( Palido 321s, DC-1S... ) */ if ( COM_NOPROBE(dev) ) { /* Reading IIR register twice */ for ( fn = 0; fn < 2; fn ++ ) { DELAY(10000); failures[6] = inb(iobase + com_iir); } /* Check IIR_TXRDY clear ? */ result = IO_COMSIZE; if ( failures[6] & IIR_TXRDY ) { /* Nop, Double check with clearing IER */ outb(iobase + com_ier, 0); if ( inb(iobase + com_iir) & IIR_NOPEND ) { /* Ok. we're familia this gang */ dev->id_flags |= COM_C_IIR_TXRDYBUG; /* Set IIR_TXRDYBUG */ } else { /* Unknow, Just omit this chip.. XXX*/ result = 0; } } else { /* OK. this is well-known guys */ dev->id_flags &= ~COM_C_IIR_TXRDYBUG; /*Clear IIR_TXRDYBUG*/ } outb(iobase + com_cfcr, CFCR_8BITS); enable_intr(); return (iobase == siocniobase ? IO_COMSIZE : result); } /* * Check that * o the CFCR, IER and MCR in UART hold the values written to them * (the values happen to be all distinct - this is good for * avoiding false positive tests from bus echoes). * o an output interrupt is generated and its vector is correct. * o the interrupt goes away when the IIR in the UART is read. */ /* EXTRA DELAY? */ failures[0] = inb(iobase + com_cfcr) - CFCR_8BITS; failures[1] = inb(iobase + com_ier) - IER_ETXRDY; failures[2] = inb(iobase + com_mcr) - mcr_image; DELAY(10000); /* Some internal modems need this time */ irqmap[1] = isa_irq_pending(); failures[4] = (inb(iobase + com_iir) & IIR_IMASK) - IIR_TXRDY; DELAY(1000); /* XXX */ irqmap[2] = isa_irq_pending(); failures[6] = (inb(iobase + com_iir) & IIR_IMASK) - IIR_NOPEND; /* * Turn off all device interrupts and check that they go off properly. * Leave MCR_IENABLE alone. For ports without a master port, it gates * the OUT2 output of the UART to * the ICU input. Closing the gate would give a floating ICU input * (unless there is another device driving at) and spurious interrupts. * (On the system that this was first tested on, the input floats high * and gives a (masked) interrupt as soon as the gate is closed.) */ outb(iobase + com_ier, 0); outb(iobase + com_cfcr, CFCR_8BITS); /* dummy to avoid bus echo */ failures[7] = inb(iobase + com_ier); DELAY(1000); /* XXX */ irqmap[3] = isa_irq_pending(); failures[9] = (inb(iobase + com_iir) & IIR_IMASK) - IIR_NOPEND; enable_intr(); irqs = irqmap[1] & ~irqmap[0]; if (idev->id_irq != 0 && (idev->id_irq & irqs) == 0) printf( "sio%d: configured irq %d not in bitmap of probed irqs %#x\n", dev->id_unit, ffs(idev->id_irq) - 1, irqs); if (bootverbose) printf("sio%d: irq maps: %#x %#x %#x %#x\n", dev->id_unit, irqmap[0], irqmap[1], irqmap[2], irqmap[3]); result = IO_COMSIZE; for (fn = 0; fn < sizeof failures; ++fn) if (failures[fn]) { outb(iobase + com_mcr, 0); result = 0; if (bootverbose) { printf("sio%d: probe failed test(s):", dev->id_unit); for (fn = 0; fn < sizeof failures; ++fn) if (failures[fn]) printf(" %d", fn); printf("\n"); } break; } return (iobase == siocniobase ? IO_COMSIZE : result); } #ifdef COM_ESP static int espattach(isdp, com, esp_port) struct isa_device *isdp; struct com_s *com; Port_t esp_port; { u_char dips; u_char val; /* * Check the ESP-specific I/O port to see if we're an ESP * card. If not, return failure immediately. */ if ((inb(esp_port) & 0xf3) == 0) { printf(" port 0x%x is not an ESP board?\n", esp_port); return (0); } /* * We've got something that claims to be a Hayes ESP card. * Let's hope so. */ /* Get the dip-switch configuration */ outb(esp_port + ESP_CMD1, ESP_GETDIPS); dips = inb(esp_port + ESP_STATUS1); /* * Bits 0,1 of dips say which COM port we are. */ if (com->iobase == likely_com_ports[dips & 0x03]) printf(" : ESP"); else { printf(" esp_port has com %d\n", dips & 0x03); return (0); } /* * Check for ESP version 2.0 or later: bits 4,5,6 = 010. */ outb(esp_port + ESP_CMD1, ESP_GETTEST); val = inb(esp_port + ESP_STATUS1); /* clear reg 1 */ val = inb(esp_port + ESP_STATUS2); if ((val & 0x70) < 0x20) { printf("-old (%o)", val & 0x70); return (0); } /* * Check for ability to emulate 16550: bit 7 == 1 */ if ((dips & 0x80) == 0) { printf(" slave"); return (0); } /* * Okay, we seem to be a Hayes ESP card. Whee. */ com->esp = TRUE; com->esp_port = esp_port; return (1); } #endif /* COM_ESP */ static int sioattach(isdp) struct isa_device *isdp; { struct com_s *com; dev_t dev; #ifdef COM_ESP Port_t *espp; #endif Port_t iobase; int s; int unit; isdp->id_ri_flags |= RI_FAST; iobase = isdp->id_iobase; unit = isdp->id_unit; com = malloc(sizeof *com, M_TTYS, M_NOWAIT); if (com == NULL) return (0); /* * sioprobe() has initialized the device registers as follows: * o cfcr = CFCR_8BITS. * It is most important that CFCR_DLAB is off, so that the * data port is not hidden when we enable interrupts. * o ier = 0. * Interrupts are only enabled when the line is open. * o mcr = MCR_IENABLE, or 0 if the port has AST/4 compatible * interrupt control register or the config specifies no irq. * Keeping MCR_DTR and MCR_RTS off might stop the external * device from sending before we are ready. */ bzero(com, sizeof *com); com->unit = unit; com->cfcr_image = CFCR_8BITS; com->dtr_wait = 3 * hz; com->loses_outints = COM_LOSESOUTINTS(isdp) != 0; com->no_irq = isdp->id_irq == 0; com->tx_fifo_size = 1; com->iptr = com->ibuf = com->ibuf1; com->ibufend = com->ibuf1 + RS_IBUFSIZE; com->ihighwater = com->ibuf1 + RS_IHIGHWATER; com->obufs[0].l_head = com->obuf1; com->obufs[1].l_head = com->obuf2; com->iobase = iobase; com->data_port = iobase + com_data; com->int_id_port = iobase + com_iir; com->modem_ctl_port = iobase + com_mcr; com->mcr_image = inb(com->modem_ctl_port); com->line_status_port = iobase + com_lsr; com->modem_status_port = iobase + com_msr; com->intr_ctl_port = iobase + com_ier; /* * We don't use all the flags from since they * are only relevant for logins. It's important to have echo off * initially so that the line doesn't start blathering before the * echo flag can be turned off. */ com->it_in.c_iflag = 0; com->it_in.c_oflag = 0; com->it_in.c_cflag = TTYDEF_CFLAG; com->it_in.c_lflag = 0; if (unit == comconsole) { com->it_in.c_iflag = TTYDEF_IFLAG; com->it_in.c_oflag = TTYDEF_OFLAG; com->it_in.c_cflag = TTYDEF_CFLAG | CLOCAL; com->it_in.c_lflag = TTYDEF_LFLAG; com->lt_out.c_cflag = com->lt_in.c_cflag = CLOCAL; com->lt_out.c_ispeed = com->lt_out.c_ospeed = com->lt_in.c_ispeed = com->lt_in.c_ospeed = com->it_in.c_ispeed = com->it_in.c_ospeed = comdefaultrate; } else com->it_in.c_ispeed = com->it_in.c_ospeed = TTYDEF_SPEED; termioschars(&com->it_in); com->it_out = com->it_in; /* attempt to determine UART type */ printf("sio%d: type", unit); #ifdef DSI_SOFT_MODEM if((inb(iobase+7) ^ inb(iobase+7)) & 0x80) { printf(" Digicom Systems, Inc. SoftModem"); goto determined_type; } #endif /* DSI_SOFT_MODEM */ #ifdef COM_MULTIPORT if (!COM_ISMULTIPORT(isdp) && !COM_IIR_TXRDYBUG(isdp)) #else if (!COM_IIR_TXRDYBUG(isdp)) #endif { u_char scr; u_char scr1; u_char scr2; scr = inb(iobase + com_scr); outb(iobase + com_scr, 0xa5); scr1 = inb(iobase + com_scr); outb(iobase + com_scr, 0x5a); scr2 = inb(iobase + com_scr); outb(iobase + com_scr, scr); if (scr1 != 0xa5 || scr2 != 0x5a) { printf(" 8250"); goto determined_type; } } outb(iobase + com_fifo, FIFO_ENABLE | FIFO_RX_HIGH); DELAY(100); com->st16650a = 0; switch (inb(com->int_id_port) & IIR_FIFO_MASK) { case FIFO_RX_LOW: printf(" 16450"); break; case FIFO_RX_MEDL: printf(" 16450?"); break; case FIFO_RX_MEDH: printf(" 16550?"); break; case FIFO_RX_HIGH: if (COM_NOFIFO(isdp)) { printf(" 16550A fifo disabled"); } else { com->hasfifo = TRUE; if (COM_ST16650A(isdp)) { com->st16650a = 1; com->tx_fifo_size = 32; printf(" ST16650A"); } else { com->tx_fifo_size = COM_FIFOSIZE(isdp); printf(" 16550A"); } } #ifdef COM_ESP for (espp = likely_esp_ports; *espp != 0; espp++) if (espattach(isdp, com, *espp)) { com->tx_fifo_size = 1024; break; } #endif if (!com->st16650a) { if (!com->tx_fifo_size) com->tx_fifo_size = 16; else printf(" lookalike with %d bytes FIFO", com->tx_fifo_size); } break; } #ifdef COM_ESP if (com->esp) { /* * Set 16550 compatibility mode. * We don't use the ESP_MODE_SCALE bit to increase the * fifo trigger levels because we can't handle large * bursts of input. * XXX flow control should be set in comparam(), not here. */ outb(com->esp_port + ESP_CMD1, ESP_SETMODE); outb(com->esp_port + ESP_CMD2, ESP_MODE_RTS | ESP_MODE_FIFO); /* Set RTS/CTS flow control. */ outb(com->esp_port + ESP_CMD1, ESP_SETFLOWTYPE); outb(com->esp_port + ESP_CMD2, ESP_FLOW_RTS); outb(com->esp_port + ESP_CMD2, ESP_FLOW_CTS); /* Set flow-control levels. */ outb(com->esp_port + ESP_CMD1, ESP_SETRXFLOW); outb(com->esp_port + ESP_CMD2, HIBYTE(768)); outb(com->esp_port + ESP_CMD2, LOBYTE(768)); outb(com->esp_port + ESP_CMD2, HIBYTE(512)); outb(com->esp_port + ESP_CMD2, LOBYTE(512)); } #endif /* COM_ESP */ outb(iobase + com_fifo, 0); determined_type: ; #ifdef COM_MULTIPORT if (COM_ISMULTIPORT(isdp)) { com->multiport = TRUE; printf(" (multiport"); if (unit == COM_MPMASTER(isdp)) printf(" master"); printf(")"); com->no_irq = find_isadev(isa_devtab_tty, &siodriver, COM_MPMASTER(isdp))->id_irq == 0; } #endif /* COM_MULTIPORT */ if (unit == comconsole) printf(", console"); if ( COM_IIR_TXRDYBUG(isdp) ) printf(" with a bogus IIR_TXRDY register"); printf("\n"); s = spltty(); com_addr(unit) = com; splx(s); dev = makedev(CDEV_MAJOR, 0); cdevsw_add(&dev, &sio_cdevsw, NULL); #ifdef DEVFS com->devfs_token_ttyd = devfs_add_devswf(&sio_cdevsw, unit, DV_CHR, UID_ROOT, GID_WHEEL, 0600, "ttyd%r", unit); com->devfs_token_ttyi = devfs_add_devswf(&sio_cdevsw, unit | CONTROL_INIT_STATE, DV_CHR, UID_ROOT, GID_WHEEL, 0600, "ttyid%r", unit); com->devfs_token_ttyl = devfs_add_devswf(&sio_cdevsw, unit | CONTROL_LOCK_STATE, DV_CHR, UID_ROOT, GID_WHEEL, 0600, "ttyld%r", unit); com->devfs_token_cuaa = devfs_add_devswf(&sio_cdevsw, unit | CALLOUT_MASK, DV_CHR, UID_UUCP, GID_DIALER, 0660, "cuaa%r", unit); com->devfs_token_cuai = devfs_add_devswf(&sio_cdevsw, unit | CALLOUT_MASK | CONTROL_INIT_STATE, DV_CHR, UID_UUCP, GID_DIALER, 0660, "cuaia%r", unit); com->devfs_token_cual = devfs_add_devswf(&sio_cdevsw, unit | CALLOUT_MASK | CONTROL_LOCK_STATE, DV_CHR, UID_UUCP, GID_DIALER, 0660, "cuala%r", unit); #endif + if (!siopoll_registered) { + register_swi(SWI_TTY, siopoll); + siopoll_registered = TRUE; + } com->id_flags = isdp->id_flags; /* Heritate id_flags for later */ return (1); } static int sioopen(dev, flag, mode, p) dev_t dev; int flag; int mode; struct proc *p; { struct com_s *com; int error; Port_t iobase; int mynor; int s; struct tty *tp; int unit; mynor = minor(dev); unit = MINOR_TO_UNIT(mynor); if ((u_int) unit >= NSIOTOT || (com = com_addr(unit)) == NULL) return (ENXIO); if (com->gone) return (ENXIO); if (mynor & CONTROL_MASK) return (0); #if 0 /* XXX */ tp = com->tp = sio_tty[unit] = ttymalloc(sio_tty[unit]); #else tp = com->tp = &sio_tty[unit]; #endif s = spltty(); /* * We jump to this label after all non-interrupted sleeps to pick * up any changes of the device state. */ open_top: while (com->state & CS_DTR_OFF) { error = tsleep(&com->dtr_wait, TTIPRI | PCATCH, "siodtr", 0); if (com_addr(unit) == NULL) return (ENXIO); if (error != 0 || com->gone) goto out; } if (tp->t_state & TS_ISOPEN) { /* * The device is open, so everything has been initialized. * Handle conflicts. */ if (mynor & CALLOUT_MASK) { if (!com->active_out) { error = EBUSY; goto out; } } else { if (com->active_out) { if (flag & O_NONBLOCK) { error = EBUSY; goto out; } error = tsleep(&com->active_out, TTIPRI | PCATCH, "siobi", 0); if (com_addr(unit) == NULL) return (ENXIO); if (error != 0 || com->gone) goto out; goto open_top; } } if (tp->t_state & TS_XCLUDE && p->p_ucred->cr_uid != 0) { error = EBUSY; goto out; } } else { /* * The device isn't open, so there are no conflicts. * Initialize it. Initialization is done twice in many * cases: to preempt sleeping callin opens if we are * callout, and to complete a callin open after DCD rises. */ tp->t_oproc = comstart; tp->t_param = comparam; tp->t_dev = dev; tp->t_termios = mynor & CALLOUT_MASK ? com->it_out : com->it_in; (void)commctl(com, TIOCM_DTR | TIOCM_RTS, DMSET); com->poll = com->no_irq; com->poll_output = com->loses_outints; ++com->wopeners; error = comparam(tp, &tp->t_termios); --com->wopeners; if (error != 0) goto out; /* * XXX we should goto open_top if comparam() slept. */ ttsetwater(tp); iobase = com->iobase; if (com->hasfifo) { /* * (Re)enable and drain fifos. * * Certain SMC chips cause problems if the fifos * are enabled while input is ready. Turn off the * fifo if necessary to clear the input. We test * the input ready bit after enabling the fifos * since we've already enabled them in comparam() * and to handle races between enabling and fresh * input. */ while (TRUE) { outb(iobase + com_fifo, FIFO_RCV_RST | FIFO_XMT_RST | com->fifo_image); /* * XXX the delays are for superstitious * historical reasons. It must be less than * the character time at the maximum * supported speed (87 usec at 115200 bps * 8N1). Otherwise we might loop endlessly * if data is streaming in. We used to use * delays of 100. That usually worked * because DELAY(100) used to usually delay * for about 85 usec instead of 100. */ DELAY(50); if (!(inb(com->line_status_port) & LSR_RXRDY)) break; outb(iobase + com_fifo, 0); DELAY(50); (void) inb(com->data_port); } } disable_intr(); (void) inb(com->line_status_port); (void) inb(com->data_port); com->prev_modem_status = com->last_modem_status = inb(com->modem_status_port); if (COM_IIR_TXRDYBUG(com)) { outb(com->intr_ctl_port, IER_ERXRDY | IER_ERLS | IER_EMSC); } else { outb(com->intr_ctl_port, IER_ERXRDY | IER_ETXRDY | IER_ERLS | IER_EMSC); } enable_intr(); /* * Handle initial DCD. Callout devices get a fake initial * DCD (trapdoor DCD). If we are callout, then any sleeping * callin opens get woken up and resume sleeping on "siobi" * instead of "siodcd". */ /* * XXX `mynor & CALLOUT_MASK' should be * `tp->t_cflag & (SOFT_CARRIER | TRAPDOOR_CARRIER) where * TRAPDOOR_CARRIER is the default initial state for callout * devices and SOFT_CARRIER is like CLOCAL except it hides * the true carrier. */ if (com->prev_modem_status & MSR_DCD || mynor & CALLOUT_MASK) (*linesw[tp->t_line].l_modem)(tp, 1); } /* * Wait for DCD if necessary. */ if (!(tp->t_state & TS_CARR_ON) && !(mynor & CALLOUT_MASK) && !(tp->t_cflag & CLOCAL) && !(flag & O_NONBLOCK)) { ++com->wopeners; error = tsleep(TSA_CARR_ON(tp), TTIPRI | PCATCH, "siodcd", 0); if (com_addr(unit) == NULL) return (ENXIO); --com->wopeners; if (error != 0 || com->gone) goto out; goto open_top; } error = (*linesw[tp->t_line].l_open)(dev, tp); disc_optim(tp, &tp->t_termios, com); if (tp->t_state & TS_ISOPEN && mynor & CALLOUT_MASK) com->active_out = TRUE; siosettimeout(); out: splx(s); if (!(tp->t_state & TS_ISOPEN) && com->wopeners == 0) comhardclose(com); return (error); } static int sioclose(dev, flag, mode, p) dev_t dev; int flag; int mode; struct proc *p; { struct com_s *com; int mynor; int s; struct tty *tp; mynor = minor(dev); if (mynor & CONTROL_MASK) return (0); com = com_addr(MINOR_TO_UNIT(mynor)); tp = com->tp; s = spltty(); (*linesw[tp->t_line].l_close)(tp, flag); disc_optim(tp, &tp->t_termios, com); siostop(tp, FREAD | FWRITE); comhardclose(com); ttyclose(tp); siosettimeout(); splx(s); if (com->gone) { printf("sio%d: gone\n", com->unit); s = spltty(); com_addr(com->unit) = 0; bzero(tp,sizeof *tp); bzero(com,sizeof *com); free(com,M_TTYS); splx(s); } return (0); } static void comhardclose(com) struct com_s *com; { Port_t iobase; int s; struct tty *tp; int unit; unit = com->unit; iobase = com->iobase; s = spltty(); com->poll = FALSE; com->poll_output = FALSE; com->do_timestamp = FALSE; com->do_dcd_timestamp = FALSE; outb(iobase + com_cfcr, com->cfcr_image &= ~CFCR_SBREAK); { outb(iobase + com_ier, 0); tp = com->tp; if (tp->t_cflag & HUPCL /* * XXX we will miss any carrier drop between here and the * next open. Perhaps we should watch DCD even when the * port is closed; it is not sufficient to check it at * the next open because it might go up and down while * we're not watching. */ || !com->active_out && !(com->prev_modem_status & MSR_DCD) && !(com->it_in.c_cflag & CLOCAL) || !(tp->t_state & TS_ISOPEN)) { (void)commctl(com, TIOCM_DTR, DMBIC); if (com->dtr_wait != 0 && !(com->state & CS_DTR_OFF)) { timeout(siodtrwakeup, com, com->dtr_wait); com->state |= CS_DTR_OFF; } } } if (com->hasfifo) { /* * Disable fifos so that they are off after controlled * reboots. Some BIOSes fail to detect 16550s when the * fifos are enabled. */ outb(iobase + com_fifo, 0); } com->active_out = FALSE; wakeup(&com->active_out); wakeup(TSA_CARR_ON(tp)); /* restart any wopeners */ splx(s); } static int sioread(dev, uio, flag) dev_t dev; struct uio *uio; int flag; { int mynor; int unit; struct tty *tp; mynor = minor(dev); if (mynor & CONTROL_MASK) return (ENODEV); unit = MINOR_TO_UNIT(mynor); if (com_addr(unit)->gone) return (ENODEV); tp = com_addr(unit)->tp; return ((*linesw[tp->t_line].l_read)(tp, uio, flag)); } static int siowrite(dev, uio, flag) dev_t dev; struct uio *uio; int flag; { int mynor; struct tty *tp; int unit; mynor = minor(dev); if (mynor & CONTROL_MASK) return (ENODEV); unit = MINOR_TO_UNIT(mynor); if (com_addr(unit)->gone) return (ENODEV); tp = com_addr(unit)->tp; /* * (XXX) We disallow virtual consoles if the physical console is * a serial port. This is in case there is a display attached that * is not the console. In that situation we don't need/want the X * server taking over the console. */ if (constty != NULL && unit == comconsole) constty = NULL; return ((*linesw[tp->t_line].l_write)(tp, uio, flag)); } static void siobusycheck(chan) void *chan; { struct com_s *com; int s; com = (struct com_s *)chan; /* * Clear TS_BUSY if low-level output is complete. * spl locking is sufficient because siointr1() does not set CS_BUSY. * If siointr1() clears CS_BUSY after we look at it, then we'll get * called again. Reading the line status port outside of siointr1() * is safe because CS_BUSY is clear so there are no output interrupts * to lose. */ s = spltty(); if (com->state & CS_BUSY) com->extra_state &= ~CSE_BUSYCHECK; /* False alarm. */ else if ((inb(com->line_status_port) & (LSR_TSRE | LSR_TXRDY)) == (LSR_TSRE | LSR_TXRDY)) { com->tp->t_state &= ~TS_BUSY; ttwwakeup(com->tp); com->extra_state &= ~CSE_BUSYCHECK; } else timeout(siobusycheck, com, hz / 100); splx(s); } static void siodtrwakeup(chan) void *chan; { struct com_s *com; com = (struct com_s *)chan; com->state &= ~CS_DTR_OFF; wakeup(&com->dtr_wait); } void siointr(unit) int unit; { #ifndef COM_MULTIPORT COM_LOCK(); siointr1(com_addr(unit)); COM_UNLOCK(); #else /* COM_MULTIPORT */ struct com_s *com; bool_t possibly_more_intrs; /* * Loop until there is no activity on any port. This is necessary * to get an interrupt edge more than to avoid another interrupt. * If the IRQ signal is just an OR of the IRQ signals from several * devices, then the edge from one may be lost because another is * on. */ COM_LOCK(); do { possibly_more_intrs = FALSE; for (unit = 0; unit < NSIOTOT; ++unit) { com = com_addr(unit); /* * XXX COM_LOCK(); * would it work here, or be counter-productive? */ if (com != NULL && !com->gone && (inb(com->int_id_port) & IIR_IMASK) != IIR_NOPEND) { siointr1(com); possibly_more_intrs = TRUE; } /* XXX COM_UNLOCK(); */ } } while (possibly_more_intrs); COM_UNLOCK(); #endif /* COM_MULTIPORT */ } static void siointr1(com) struct com_s *com; { u_char line_status; u_char modem_status; u_char *ioptr; u_char recv_data; u_char int_ident; u_char int_ctl; u_char int_ctl_new; int_ctl = inb(com->intr_ctl_port); int_ctl_new = int_ctl; while (!com->gone) { line_status = inb(com->line_status_port); /* input event? (check first to help avoid overruns) */ while (line_status & LSR_RCV_MASK) { /* break/unnattached error bits or real input? */ if (!(line_status & LSR_RXRDY)) recv_data = 0; else recv_data = inb(com->data_port); if (line_status & (LSR_BI | LSR_FE | LSR_PE)) { /* * Don't store BI if IGNBRK or FE/PE if IGNPAR. * Otherwise, push the work to a higher level * (to handle PARMRK) if we're bypassing. * Otherwise, convert BI/FE and PE+INPCK to 0. * * This makes bypassing work right in the * usual "raw" case (IGNBRK set, and IGNPAR * and INPCK clear). * * Note: BI together with FE/PE means just BI. */ if (line_status & LSR_BI) { #if defined(DDB) && defined(BREAK_TO_DEBUGGER) if (com->unit == comconsole) { breakpoint(); goto cont; } #endif if (com->tp == NULL || com->tp->t_iflag & IGNBRK) goto cont; } else { if (com->tp == NULL || com->tp->t_iflag & IGNPAR) goto cont; } if (com->tp->t_state & TS_CAN_BYPASS_L_RINT && (line_status & (LSR_BI | LSR_FE) || com->tp->t_iflag & INPCK)) recv_data = 0; } ++com->bytes_in; if (com->hotchar != 0 && recv_data == com->hotchar) setsofttty(); ioptr = com->iptr; if (ioptr >= com->ibufend) CE_RECORD(com, CE_INTERRUPT_BUF_OVERFLOW); else { if (com->do_timestamp) microtime(&com->timestamp); ++com_events; schedsofttty(); #if 0 /* for testing input latency vs efficiency */ if (com->iptr - com->ibuf == 8) setsofttty(); #endif ioptr[0] = recv_data; ioptr[CE_INPUT_OFFSET] = line_status; com->iptr = ++ioptr; if (ioptr == com->ihighwater && com->state & CS_RTS_IFLOW) outb(com->modem_ctl_port, com->mcr_image &= ~MCR_RTS); if (line_status & LSR_OE) CE_RECORD(com, CE_OVERRUN); } cont: /* * "& 0x7F" is to avoid the gcc-1.40 generating a slow * jump from the top of the loop to here */ line_status = inb(com->line_status_port) & 0x7F; } /* modem status change? (always check before doing output) */ modem_status = inb(com->modem_status_port); if (modem_status != com->last_modem_status) { if (com->do_dcd_timestamp && !(com->last_modem_status & MSR_DCD) && modem_status & MSR_DCD) microtime(&com->dcd_timestamp); /* * Schedule high level to handle DCD changes. Note * that we don't use the delta bits anywhere. Some * UARTs mess them up, and it's easy to remember the * previous bits and calculate the delta. */ com->last_modem_status = modem_status; if (!(com->state & CS_CHECKMSR)) { com_events += LOTS_OF_EVENTS; com->state |= CS_CHECKMSR; setsofttty(); } /* handle CTS change immediately for crisp flow ctl */ if (com->state & CS_CTS_OFLOW) { if (modem_status & MSR_CTS) com->state |= CS_ODEVREADY; else com->state &= ~CS_ODEVREADY; } } /* output queued and everything ready? */ if (line_status & LSR_TXRDY && com->state >= (CS_BUSY | CS_TTGO | CS_ODEVREADY)) { ioptr = com->obufq.l_head; if (com->tx_fifo_size > 1) { u_int ocount; ocount = com->obufq.l_tail - ioptr; if (ocount > com->tx_fifo_size) ocount = com->tx_fifo_size; com->bytes_out += ocount; do outb(com->data_port, *ioptr++); while (--ocount != 0); } else { outb(com->data_port, *ioptr++); ++com->bytes_out; } com->obufq.l_head = ioptr; if (COM_IIR_TXRDYBUG(com)) { int_ctl_new = int_ctl | IER_ETXRDY; } if (ioptr >= com->obufq.l_tail) { struct lbq *qp; qp = com->obufq.l_next; qp->l_queued = FALSE; qp = qp->l_next; if (qp != NULL) { com->obufq.l_head = qp->l_head; com->obufq.l_tail = qp->l_tail; com->obufq.l_next = qp; } else { /* output just completed */ if ( COM_IIR_TXRDYBUG(com) ) { int_ctl_new = int_ctl & ~IER_ETXRDY; } com->state &= ~CS_BUSY; } if (!(com->state & CS_ODONE)) { com_events += LOTS_OF_EVENTS; com->state |= CS_ODONE; setsofttty(); /* handle at high level ASAP */ } } if ( COM_IIR_TXRDYBUG(com) && (int_ctl != int_ctl_new)) { outb(com->intr_ctl_port, int_ctl_new); } } /* finished? */ #ifndef COM_MULTIPORT if ((inb(com->int_id_port) & IIR_IMASK) == IIR_NOPEND) #endif /* COM_MULTIPORT */ return; } } static int sioioctl(dev, cmd, data, flag, p) dev_t dev; u_long cmd; caddr_t data; int flag; struct proc *p; { struct com_s *com; int error; Port_t iobase; int mynor; int s; struct tty *tp; #if defined(COMPAT_43) || defined(COMPAT_SUNOS) int oldcmd; struct termios term; #endif mynor = minor(dev); com = com_addr(MINOR_TO_UNIT(mynor)); if (com->gone) return (ENODEV); iobase = com->iobase; if (mynor & CONTROL_MASK) { struct termios *ct; switch (mynor & CONTROL_MASK) { case CONTROL_INIT_STATE: ct = mynor & CALLOUT_MASK ? &com->it_out : &com->it_in; break; case CONTROL_LOCK_STATE: ct = mynor & CALLOUT_MASK ? &com->lt_out : &com->lt_in; break; default: return (ENODEV); /* /dev/nodev */ } switch (cmd) { case TIOCSETA: error = suser(p->p_ucred, &p->p_acflag); if (error != 0) return (error); *ct = *(struct termios *)data; return (0); case TIOCGETA: *(struct termios *)data = *ct; return (0); case TIOCGETD: *(int *)data = TTYDISC; return (0); case TIOCGWINSZ: bzero(data, sizeof(struct winsize)); return (0); #ifdef DSI_SOFT_MODEM /* * Download micro-code to Digicom modem. */ case TIOCDSIMICROCODE: { u_long l; u_char *p,*pi; pi = (u_char*)(*(caddr_t*)data); error = copyin(pi,&l,sizeof l); if(error) {return error;}; pi += sizeof l; p = malloc(l,M_TEMP,M_NOWAIT); if(!p) {return ENOBUFS;} error = copyin(pi,p,l); if(error) {free(p,M_TEMP); return error;}; if(error = LoadSoftModem( MINOR_TO_UNIT(mynor),iobase,l,p)) {free(p,M_TEMP); return error;} free(p,M_TEMP); return(0); } #endif /* DSI_SOFT_MODEM */ default: return (ENOTTY); } } tp = com->tp; #if defined(COMPAT_43) || defined(COMPAT_SUNOS) term = tp->t_termios; oldcmd = cmd; error = ttsetcompat(tp, &cmd, data, &term); if (error != 0) return (error); if (cmd != oldcmd) data = (caddr_t)&term; #endif if (cmd == TIOCSETA || cmd == TIOCSETAW || cmd == TIOCSETAF) { int cc; struct termios *dt = (struct termios *)data; struct termios *lt = mynor & CALLOUT_MASK ? &com->lt_out : &com->lt_in; dt->c_iflag = (tp->t_iflag & lt->c_iflag) | (dt->c_iflag & ~lt->c_iflag); dt->c_oflag = (tp->t_oflag & lt->c_oflag) | (dt->c_oflag & ~lt->c_oflag); dt->c_cflag = (tp->t_cflag & lt->c_cflag) | (dt->c_cflag & ~lt->c_cflag); dt->c_lflag = (tp->t_lflag & lt->c_lflag) | (dt->c_lflag & ~lt->c_lflag); for (cc = 0; cc < NCCS; ++cc) if (lt->c_cc[cc] != 0) dt->c_cc[cc] = tp->t_cc[cc]; if (lt->c_ispeed != 0) dt->c_ispeed = tp->t_ispeed; if (lt->c_ospeed != 0) dt->c_ospeed = tp->t_ospeed; } error = (*linesw[tp->t_line].l_ioctl)(tp, cmd, data, flag, p); if (error != ENOIOCTL) return (error); s = spltty(); error = ttioctl(tp, cmd, data, flag); disc_optim(tp, &tp->t_termios, com); if (error != ENOIOCTL) { splx(s); return (error); } switch (cmd) { case TIOCSBRK: outb(iobase + com_cfcr, com->cfcr_image |= CFCR_SBREAK); break; case TIOCCBRK: outb(iobase + com_cfcr, com->cfcr_image &= ~CFCR_SBREAK); break; case TIOCSDTR: (void)commctl(com, TIOCM_DTR, DMBIS); break; case TIOCCDTR: (void)commctl(com, TIOCM_DTR, DMBIC); break; /* * XXX should disallow changing MCR_RTS if CS_RTS_IFLOW is set. The * changes get undone on the next call to comparam(). */ case TIOCMSET: (void)commctl(com, *(int *)data, DMSET); break; case TIOCMBIS: (void)commctl(com, *(int *)data, DMBIS); break; case TIOCMBIC: (void)commctl(com, *(int *)data, DMBIC); break; case TIOCMGET: *(int *)data = commctl(com, 0, DMGET); break; case TIOCMSDTRWAIT: /* must be root since the wait applies to following logins */ error = suser(p->p_ucred, &p->p_acflag); if (error != 0) { splx(s); return (error); } com->dtr_wait = *(int *)data * hz / 100; break; case TIOCMGDTRWAIT: *(int *)data = com->dtr_wait * 100 / hz; break; case TIOCTIMESTAMP: com->do_timestamp = TRUE; *(struct timeval *)data = com->timestamp; break; case TIOCDCDTIMESTAMP: com->do_dcd_timestamp = TRUE; *(struct timeval *)data = com->dcd_timestamp; break; default: splx(s); return (ENOTTY); } splx(s); return (0); } -void +static void siopoll() { int unit; if (com_events == 0) return; repeat: for (unit = 0; unit < NSIOTOT; ++unit) { u_char *buf; struct com_s *com; u_char *ibuf; int incc; struct tty *tp; com = com_addr(unit); if (com == NULL) continue; tp = com->tp; if (tp == NULL || com->gone) { /* * Discard any events related to never-opened or * going-away devices. */ disable_intr(); incc = com->iptr - com->ibuf; com->iptr = com->ibuf; if (com->state & CS_CHECKMSR) { incc += LOTS_OF_EVENTS; com->state &= ~CS_CHECKMSR; } com_events -= incc; enable_intr(); continue; } /* switch the role of the low-level input buffers */ if (com->iptr == (ibuf = com->ibuf)) { buf = NULL; /* not used, but compiler can't tell */ incc = 0; } else { buf = ibuf; disable_intr(); incc = com->iptr - buf; com_events -= incc; if (ibuf == com->ibuf1) ibuf = com->ibuf2; else ibuf = com->ibuf1; com->ibufend = ibuf + RS_IBUFSIZE; com->ihighwater = ibuf + RS_IHIGHWATER; com->iptr = ibuf; /* * There is now room for another low-level buffer full * of input, so enable RTS if it is now disabled and * there is room in the high-level buffer. */ if ((com->state & CS_RTS_IFLOW) && !(com->mcr_image & MCR_RTS) && !(tp->t_state & TS_TBLOCK)) outb(com->modem_ctl_port, com->mcr_image |= MCR_RTS); enable_intr(); com->ibuf = ibuf; } if (com->state & CS_CHECKMSR) { u_char delta_modem_status; disable_intr(); delta_modem_status = com->last_modem_status ^ com->prev_modem_status; com->prev_modem_status = com->last_modem_status; com_events -= LOTS_OF_EVENTS; com->state &= ~CS_CHECKMSR; enable_intr(); if (delta_modem_status & MSR_DCD) (*linesw[tp->t_line].l_modem) (tp, com->prev_modem_status & MSR_DCD); } if (com->state & CS_ODONE) { disable_intr(); com_events -= LOTS_OF_EVENTS; com->state &= ~CS_ODONE; enable_intr(); if (!(com->state & CS_BUSY) && !(com->extra_state & CSE_BUSYCHECK)) { timeout(siobusycheck, com, hz / 100); com->extra_state |= CSE_BUSYCHECK; } (*linesw[tp->t_line].l_start)(tp); } if (incc <= 0 || !(tp->t_state & TS_ISOPEN) || !(tp->t_cflag & CREAD)) continue; /* * Avoid the grotesquely inefficient lineswitch routine * (ttyinput) in "raw" mode. It usually takes about 450 * instructions (that's without canonical processing or echo!). * slinput is reasonably fast (usually 40 instructions plus * call overhead). */ if (tp->t_state & TS_CAN_BYPASS_L_RINT) { if (tp->t_rawq.c_cc + incc >= RB_I_HIGH_WATER && (com->state & CS_RTS_IFLOW || tp->t_iflag & IXOFF) && !(tp->t_state & TS_TBLOCK)) ttyblock(tp); tk_nin += incc; tk_rawcc += incc; tp->t_rawcc += incc; com->delta_error_counts[CE_TTY_BUF_OVERFLOW] += b_to_q((char *)buf, incc, &tp->t_rawq); ttwakeup(tp); if (tp->t_state & TS_TTSTOP && (tp->t_iflag & IXANY || tp->t_cc[VSTART] == tp->t_cc[VSTOP])) { tp->t_state &= ~TS_TTSTOP; tp->t_lflag &= ~FLUSHO; comstart(tp); } } else { do { u_char line_status; int recv_data; line_status = (u_char) buf[CE_INPUT_OFFSET]; recv_data = (u_char) *buf++; if (line_status & (LSR_BI | LSR_FE | LSR_OE | LSR_PE)) { if (line_status & LSR_BI) recv_data |= TTY_BI; if (line_status & LSR_FE) recv_data |= TTY_FE; if (line_status & LSR_OE) recv_data |= TTY_OE; if (line_status & LSR_PE) recv_data |= TTY_PE; } (*linesw[tp->t_line].l_rint)(recv_data, tp); } while (--incc > 0); } if (com_events == 0) break; } if (com_events >= LOTS_OF_EVENTS) goto repeat; } static int comparam(tp, t) struct tty *tp; struct termios *t; { u_int cfcr; int cflag; struct com_s *com; int divisor; u_char dlbh; u_char dlbl; int error; Port_t iobase; int s; int unit; int txtimeout; /* do historical conversions */ if (t->c_ispeed == 0) t->c_ispeed = t->c_ospeed; /* check requested parameters */ divisor = ttspeedtab(t->c_ospeed, comspeedtab); if (divisor < 0 || divisor > 0 && t->c_ispeed != t->c_ospeed) return (EINVAL); /* parameters are OK, convert them to the com struct and the device */ unit = DEV_TO_UNIT(tp->t_dev); com = com_addr(unit); iobase = com->iobase; s = spltty(); if (divisor == 0) (void)commctl(com, TIOCM_DTR, DMBIC); /* hang up line */ else (void)commctl(com, TIOCM_DTR, DMBIS); cflag = t->c_cflag; switch (cflag & CSIZE) { case CS5: cfcr = CFCR_5BITS; break; case CS6: cfcr = CFCR_6BITS; break; case CS7: cfcr = CFCR_7BITS; break; default: cfcr = CFCR_8BITS; break; } if (cflag & PARENB) { cfcr |= CFCR_PENAB; if (!(cflag & PARODD)) cfcr |= CFCR_PEVEN; } if (cflag & CSTOPB) cfcr |= CFCR_STOPB; if (com->hasfifo && divisor != 0) { /* * Use a fifo trigger level low enough so that the input * latency from the fifo is less than about 16 msec and * the total latency is less than about 30 msec. These * latencies are reasonable for humans. Serial comms * protocols shouldn't expect anything better since modem * latencies are larger. */ com->fifo_image = t->c_ospeed <= 4800 ? FIFO_ENABLE : FIFO_ENABLE | FIFO_RX_HIGH; #ifdef COM_ESP /* * The Hayes ESP card needs the fifo DMA mode bit set * in compatibility mode. If not, it will interrupt * for each character received. */ if (com->esp) com->fifo_image |= FIFO_DMA_MODE; #endif outb(iobase + com_fifo, com->fifo_image); } /* * Some UARTs lock up if the divisor latch registers are selected * while the UART is doing output (they refuse to transmit anything * more until given a hard reset). Fix this by stopping filling * the device buffers and waiting for them to drain. Reading the * line status port outside of siointr1() might lose some receiver * error bits, but that is acceptable here. */ disable_intr(); retry: com->state &= ~CS_TTGO; txtimeout = tp->t_timeout; enable_intr(); while ((inb(com->line_status_port) & (LSR_TSRE | LSR_TXRDY)) != (LSR_TSRE | LSR_TXRDY)) { tp->t_state |= TS_SO_OCOMPLETE; error = ttysleep(tp, TSA_OCOMPLETE(tp), TTIPRI | PCATCH, "siotx", hz / 100); if ( txtimeout != 0 && (!error || error == EAGAIN) && (txtimeout -= hz / 100) <= 0 ) error = EIO; if (com->gone) error = ENODEV; if (error != 0 && error != EAGAIN) { if (!(tp->t_state & TS_TTSTOP)) { disable_intr(); com->state |= CS_TTGO; enable_intr(); } splx(s); return (error); } } disable_intr(); /* very important while com_data is hidden */ /* * XXX - clearing CS_TTGO is not sufficient to stop further output, * because siopoll() calls comstart() which usually sets it again * because TS_TTSTOP is clear. Setting TS_TTSTOP would not be * sufficient, for similar reasons. */ if ((inb(com->line_status_port) & (LSR_TSRE | LSR_TXRDY)) != (LSR_TSRE | LSR_TXRDY)) goto retry; if (divisor != 0) { outb(iobase + com_cfcr, cfcr | CFCR_DLAB); /* * Only set the divisor registers if they would change, * since on some 16550 incompatibles (UMC8669F), setting * them while input is arriving them loses sync until * data stops arriving. */ dlbl = divisor & 0xFF; if (inb(iobase + com_dlbl) != dlbl) outb(iobase + com_dlbl, dlbl); dlbh = (u_int) divisor >> 8; if (inb(iobase + com_dlbh) != dlbh) outb(iobase + com_dlbh, dlbh); } outb(iobase + com_cfcr, com->cfcr_image = cfcr); if (!(tp->t_state & TS_TTSTOP)) com->state |= CS_TTGO; if (cflag & CRTS_IFLOW) { if (com->st16650a) { outb(iobase + com_cfcr, 0xbf); outb(iobase + com_fifo, inb(iobase + com_fifo) | 0x40); } com->state |= CS_RTS_IFLOW; /* * If CS_RTS_IFLOW just changed from off to on, the change * needs to be propagated to MCR_RTS. This isn't urgent, * so do it later by calling comstart() instead of repeating * a lot of code from comstart() here. */ } else if (com->state & CS_RTS_IFLOW) { com->state &= ~CS_RTS_IFLOW; /* * CS_RTS_IFLOW just changed from on to off. Force MCR_RTS * on here, since comstart() won't do it later. */ outb(com->modem_ctl_port, com->mcr_image |= MCR_RTS); if (com->st16650a) { outb(iobase + com_cfcr, 0xbf); outb(iobase + com_fifo, inb(iobase + com_fifo) & ~0x40); } } /* * Set up state to handle output flow control. * XXX - worth handling MDMBUF (DCD) flow control at the lowest level? * Now has 10+ msec latency, while CTS flow has 50- usec latency. */ com->state |= CS_ODEVREADY; com->state &= ~CS_CTS_OFLOW; if (cflag & CCTS_OFLOW) { com->state |= CS_CTS_OFLOW; if (!(com->last_modem_status & MSR_CTS)) com->state &= ~CS_ODEVREADY; if (com->st16650a) { outb(iobase + com_cfcr, 0xbf); outb(iobase + com_fifo, inb(iobase + com_fifo) | 0x80); } } else { if (com->st16650a) { outb(iobase + com_cfcr, 0xbf); outb(iobase + com_fifo, inb(iobase + com_fifo) & ~0x80); } } outb(iobase + com_cfcr, com->cfcr_image); /* XXX shouldn't call functions while intrs are disabled. */ disc_optim(tp, t, com); /* * Recover from fiddling with CS_TTGO. We used to call siointr1() * unconditionally, but that defeated the careful discarding of * stale input in sioopen(). */ if (com->state >= (CS_BUSY | CS_TTGO)) siointr1(com); enable_intr(); splx(s); comstart(tp); return (0); } static void comstart(tp) struct tty *tp; { struct com_s *com; int s; int unit; unit = DEV_TO_UNIT(tp->t_dev); com = com_addr(unit); s = spltty(); disable_intr(); if (tp->t_state & TS_TTSTOP) com->state &= ~CS_TTGO; else com->state |= CS_TTGO; if (tp->t_state & TS_TBLOCK) { if (com->mcr_image & MCR_RTS && com->state & CS_RTS_IFLOW) outb(com->modem_ctl_port, com->mcr_image &= ~MCR_RTS); } else { if (!(com->mcr_image & MCR_RTS) && com->iptr < com->ihighwater && com->state & CS_RTS_IFLOW) outb(com->modem_ctl_port, com->mcr_image |= MCR_RTS); } enable_intr(); if (tp->t_state & (TS_TIMEOUT | TS_TTSTOP)) { ttwwakeup(tp); splx(s); return; } if (tp->t_outq.c_cc != 0) { struct lbq *qp; struct lbq *next; if (!com->obufs[0].l_queued) { com->obufs[0].l_tail = com->obuf1 + q_to_b(&tp->t_outq, com->obuf1, sizeof com->obuf1); com->obufs[0].l_next = NULL; com->obufs[0].l_queued = TRUE; disable_intr(); if (com->state & CS_BUSY) { qp = com->obufq.l_next; while ((next = qp->l_next) != NULL) qp = next; qp->l_next = &com->obufs[0]; } else { com->obufq.l_head = com->obufs[0].l_head; com->obufq.l_tail = com->obufs[0].l_tail; com->obufq.l_next = &com->obufs[0]; com->state |= CS_BUSY; } enable_intr(); } if (tp->t_outq.c_cc != 0 && !com->obufs[1].l_queued) { com->obufs[1].l_tail = com->obuf2 + q_to_b(&tp->t_outq, com->obuf2, sizeof com->obuf2); com->obufs[1].l_next = NULL; com->obufs[1].l_queued = TRUE; disable_intr(); if (com->state & CS_BUSY) { qp = com->obufq.l_next; while ((next = qp->l_next) != NULL) qp = next; qp->l_next = &com->obufs[1]; } else { com->obufq.l_head = com->obufs[1].l_head; com->obufq.l_tail = com->obufs[1].l_tail; com->obufq.l_next = &com->obufs[1]; com->state |= CS_BUSY; } enable_intr(); } tp->t_state |= TS_BUSY; } disable_intr(); if (com->state >= (CS_BUSY | CS_TTGO)) siointr1(com); /* fake interrupt to start output */ enable_intr(); ttwwakeup(tp); splx(s); } static void siostop(tp, rw) struct tty *tp; int rw; { struct com_s *com; com = com_addr(DEV_TO_UNIT(tp->t_dev)); if (com->gone) return; disable_intr(); if (rw & FWRITE) { if (com->hasfifo) #ifdef COM_ESP /* XXX avoid h/w bug. */ if (!com->esp) #endif /* XXX does this flush everything? */ outb(com->iobase + com_fifo, FIFO_XMT_RST | com->fifo_image); com->obufs[0].l_queued = FALSE; com->obufs[1].l_queued = FALSE; if (com->state & CS_ODONE) com_events -= LOTS_OF_EVENTS; com->state &= ~(CS_ODONE | CS_BUSY); com->tp->t_state &= ~TS_BUSY; } if (rw & FREAD) { if (com->hasfifo) #ifdef COM_ESP /* XXX avoid h/w bug. */ if (!com->esp) #endif /* XXX does this flush everything? */ outb(com->iobase + com_fifo, FIFO_RCV_RST | com->fifo_image); com_events -= (com->iptr - com->ibuf); com->iptr = com->ibuf; } enable_intr(); comstart(tp); } static struct tty * siodevtotty(dev) dev_t dev; { int mynor; int unit; mynor = minor(dev); if (mynor & CONTROL_MASK) return (NULL); unit = MINOR_TO_UNIT(mynor); if ((u_int) unit >= NSIOTOT) return (NULL); return (&sio_tty[unit]); } static int commctl(com, bits, how) struct com_s *com; int bits; int how; { int mcr; int msr; if (how == DMGET) { bits = TIOCM_LE; /* XXX - always enabled while open */ mcr = com->mcr_image; if (mcr & MCR_DTR) bits |= TIOCM_DTR; if (mcr & MCR_RTS) bits |= TIOCM_RTS; msr = com->prev_modem_status; if (msr & MSR_CTS) bits |= TIOCM_CTS; if (msr & MSR_DCD) bits |= TIOCM_CD; if (msr & MSR_DSR) bits |= TIOCM_DSR; /* * XXX - MSR_RI is naturally volatile, and we make MSR_TERI * more volatile by reading the modem status a lot. Perhaps * we should latch both bits until the status is read here. */ if (msr & (MSR_RI | MSR_TERI)) bits |= TIOCM_RI; return (bits); } mcr = 0; if (bits & TIOCM_DTR) mcr |= MCR_DTR; if (bits & TIOCM_RTS) mcr |= MCR_RTS; if (com->gone) return(0); disable_intr(); switch (how) { case DMSET: outb(com->modem_ctl_port, com->mcr_image = mcr | (com->mcr_image & MCR_IENABLE)); break; case DMBIS: outb(com->modem_ctl_port, com->mcr_image |= mcr); break; case DMBIC: outb(com->modem_ctl_port, com->mcr_image &= ~mcr); break; } enable_intr(); return (0); } static void siosettimeout() { struct com_s *com; bool_t someopen; int unit; /* * Set our timeout period to 1 second if no polled devices are open. * Otherwise set it to max(1/200, 1/hz). * Enable timeouts iff some device is open. */ untimeout(comwakeup, (void *)NULL, sio_timeout_handle); sio_timeout = hz; someopen = FALSE; for (unit = 0; unit < NSIOTOT; ++unit) { com = com_addr(unit); if (com != NULL && com->tp != NULL && com->tp->t_state & TS_ISOPEN && !com->gone) { someopen = TRUE; if (com->poll || com->poll_output) { sio_timeout = hz > 200 ? hz / 200 : 1; break; } } } if (someopen) { sio_timeouts_until_log = hz / sio_timeout; sio_timeout_handle = timeout(comwakeup, (void *)NULL, sio_timeout); } else { /* Flush error messages, if any. */ sio_timeouts_until_log = 1; comwakeup((void *)NULL); untimeout(comwakeup, (void *)NULL, sio_timeout_handle); } } static void comwakeup(chan) void *chan; { struct com_s *com; int unit; sio_timeout_handle = timeout(comwakeup, (void *)NULL, sio_timeout); /* * Recover from lost output interrupts. * Poll any lines that don't use interrupts. */ for (unit = 0; unit < NSIOTOT; ++unit) { com = com_addr(unit); if (com != NULL && !com->gone && (com->state >= (CS_BUSY | CS_TTGO) || com->poll)) { disable_intr(); siointr1(com); enable_intr(); } } /* * Check for and log errors, but not too often. */ if (--sio_timeouts_until_log > 0) return; sio_timeouts_until_log = hz / sio_timeout; for (unit = 0; unit < NSIOTOT; ++unit) { int errnum; com = com_addr(unit); if (com == NULL) continue; if (com->gone) continue; for (errnum = 0; errnum < CE_NTYPES; ++errnum) { u_int delta; u_long total; disable_intr(); delta = com->delta_error_counts[errnum]; com->delta_error_counts[errnum] = 0; enable_intr(); if (delta == 0) continue; total = com->error_counts[errnum] += delta; log(LOG_ERR, "sio%d: %u more %s%s (total %lu)\n", unit, delta, error_desc[errnum], delta == 1 ? "" : "s", total); } } } static void disc_optim(tp, t, com) struct tty *tp; struct termios *t; struct com_s *com; { if (!(t->c_iflag & (ICRNL | IGNCR | IMAXBEL | INLCR | ISTRIP | IXON)) && (!(t->c_iflag & BRKINT) || (t->c_iflag & IGNBRK)) && (!(t->c_iflag & PARMRK) || (t->c_iflag & (IGNPAR | IGNBRK)) == (IGNPAR | IGNBRK)) && !(t->c_lflag & (ECHO | ICANON | IEXTEN | ISIG | PENDIN)) && linesw[tp->t_line].l_rint == ttyinput) tp->t_state |= TS_CAN_BYPASS_L_RINT; else tp->t_state &= ~TS_CAN_BYPASS_L_RINT; com->hotchar = linesw[tp->t_line].l_hotchar; } /* * Following are all routines needed for SIO to act as console */ #include struct siocnstate { u_char dlbl; u_char dlbh; u_char ier; u_char cfcr; u_char mcr; }; static speed_t siocngetspeed __P((Port_t, struct speedtab *)); static void siocnclose __P((struct siocnstate *sp)); static void siocnopen __P((struct siocnstate *sp)); static void siocntxwait __P((void)); static void siocntxwait() { int timo; /* * Wait for any pending transmission to finish. Required to avoid * the UART lockup bug when the speed is changed, and for normal * transmits. */ timo = 100000; while ((inb(siocniobase + com_lsr) & (LSR_TSRE | LSR_TXRDY)) != (LSR_TSRE | LSR_TXRDY) && --timo != 0) ; } /* * Read the serial port specified and try to figure out what speed * it's currently running at. We're assuming the serial port has * been initialized and is basicly idle. This routine is only intended * to be run at system startup. * * If the value read from the serial port doesn't make sense, return 0. */ static speed_t siocngetspeed(iobase, table) Port_t iobase; struct speedtab *table; { int code; u_char dlbh; u_char dlbl; u_char cfcr; cfcr = inb(iobase + com_cfcr); outb(iobase + com_cfcr, CFCR_DLAB | cfcr); dlbl = inb(iobase + com_dlbl); dlbh = inb(iobase + com_dlbh); outb(iobase + com_cfcr, cfcr); code = dlbh << 8 | dlbl; for ( ; table->sp_speed != -1; table++) if (table->sp_code == code) return (table->sp_speed); return 0; /* didn't match anything sane */ } static void siocnopen(sp) struct siocnstate *sp; { int divisor; u_char dlbh; u_char dlbl; Port_t iobase; /* * Save all the device control registers except the fifo register * and set our default ones (cs8 -parenb speed=comdefaultrate). * We can't save the fifo register since it is read-only. */ iobase = siocniobase; sp->ier = inb(iobase + com_ier); outb(iobase + com_ier, 0); /* spltty() doesn't stop siointr() */ siocntxwait(); sp->cfcr = inb(iobase + com_cfcr); outb(iobase + com_cfcr, CFCR_DLAB | CFCR_8BITS); sp->dlbl = inb(iobase + com_dlbl); sp->dlbh = inb(iobase + com_dlbh); /* * Only set the divisor registers if they would change, since on * some 16550 incompatibles (Startech), setting them clears the * data input register. This also reduces the effects of the * UMC8669F bug. */ divisor = ttspeedtab(comdefaultrate, comspeedtab); dlbl = divisor & 0xFF; if (sp->dlbl != dlbl) outb(iobase + com_dlbl, dlbl); dlbh = (u_int) divisor >> 8; if (sp->dlbh != dlbh) outb(iobase + com_dlbh, dlbh); outb(iobase + com_cfcr, CFCR_8BITS); sp->mcr = inb(iobase + com_mcr); /* * We don't want interrupts, but must be careful not to "disable" * them by clearing the MCR_IENABLE bit, since that might cause * an interrupt by floating the IRQ line. */ outb(iobase + com_mcr, (sp->mcr & MCR_IENABLE) | MCR_DTR | MCR_RTS); } static void siocnclose(sp) struct siocnstate *sp; { Port_t iobase; /* * Restore the device control registers. */ siocntxwait(); iobase = siocniobase; outb(iobase + com_cfcr, CFCR_DLAB | CFCR_8BITS); if (sp->dlbl != inb(iobase + com_dlbl)) outb(iobase + com_dlbl, sp->dlbl); if (sp->dlbh != inb(iobase + com_dlbh)) outb(iobase + com_dlbh, sp->dlbh); outb(iobase + com_cfcr, sp->cfcr); /* * XXX damp oscillations of MCR_DTR and MCR_RTS by not restoring them. */ outb(iobase + com_mcr, sp->mcr | MCR_DTR | MCR_RTS); outb(iobase + com_ier, sp->ier); } void siocnprobe(cp) struct consdev *cp; { speed_t boot_speed; u_char cfcr; struct isa_device *dvp; int s; struct siocnstate sp; /* * Find our first enabled console, if any. If it is a high-level * console device, then initialize it and return successfully. * If it is a low-level console device, then initialize it and * return unsuccessfully. It must be initialized in both cases * for early use by console drivers and debuggers. Initializing * the hardware is not necessary in all cases, since the i/o * routines initialize it on the fly, but it is necessary if * input might arrive while the hardware is switched back to an * uninitialized state. We can't handle multiple console devices * yet because our low-level routines don't take a device arg. * We trust the user to set the console flags properly so that we * don't need to probe. */ cp->cn_pri = CN_DEAD; for (dvp = isa_devtab_tty; dvp->id_driver != NULL; dvp++) if (dvp->id_driver == &siodriver && dvp->id_enabled && COM_CONSOLE(dvp)) { siocniobase = dvp->id_iobase; s = spltty(); if (boothowto & RB_SERIAL) { boot_speed = siocngetspeed(siocniobase, comspeedtab); if (boot_speed) comdefaultrate = boot_speed; } /* * Initialize the divisor latch. We can't rely on * siocnopen() to do this the first time, since it * avoids writing to the latch if the latch appears * to have the correct value. Also, if we didn't * just read the speed from the hardware, then we * need to set the speed in hardware so that * switching it later is null. */ cfcr = inb(siocniobase + com_cfcr); outb(siocniobase + com_cfcr, CFCR_DLAB | cfcr); outb(siocniobase + com_dlbl, COMBRD(comdefaultrate) & 0xff); outb(siocniobase + com_dlbh, (u_int) COMBRD(comdefaultrate) >> 8); outb(siocniobase + com_cfcr, cfcr); siocnopen(&sp); splx(s); if (!COM_LLCONSOLE(dvp)) { cp->cn_dev = makedev(CDEV_MAJOR, dvp->id_unit); cp->cn_pri = COM_FORCECONSOLE(dvp) || boothowto & RB_SERIAL ? CN_REMOTE : CN_NORMAL; } break; } } void siocninit(cp) struct consdev *cp; { comconsole = DEV_TO_UNIT(cp->cn_dev); } int siocncheckc(dev) dev_t dev; { int c; Port_t iobase; int s; struct siocnstate sp; iobase = siocniobase; s = spltty(); siocnopen(&sp); if (inb(iobase + com_lsr) & LSR_RXRDY) c = inb(iobase + com_data); else c = -1; siocnclose(&sp); splx(s); return (c); } int siocngetc(dev) dev_t dev; { int c; Port_t iobase; int s; struct siocnstate sp; iobase = siocniobase; s = spltty(); siocnopen(&sp); while (!(inb(iobase + com_lsr) & LSR_RXRDY)) ; c = inb(iobase + com_data); siocnclose(&sp); splx(s); return (c); } void siocnputc(dev, c) dev_t dev; int c; { int s; struct siocnstate sp; s = spltty(); siocnopen(&sp); siocntxwait(); outb(siocniobase + com_data, c); siocnclose(&sp); splx(s); } #ifdef DSI_SOFT_MODEM /* * The magic code to download microcode to a "Connection 14.4+Fax" * modem from Digicom Systems Inc. Very magic. */ #define DSI_ERROR(str) { ptr = str; goto error; } static int LoadSoftModem(int unit, int base_io, u_long size, u_char *ptr) { int int_c,int_k; int data_0188, data_0187; /* * First see if it is a DSI SoftModem */ if(!((inb(base_io+7) ^ inb(base_io+7)) & 0x80)) return ENODEV; data_0188 = inb(base_io+4); data_0187 = inb(base_io+3); outb(base_io+3,0x80); outb(base_io+4,0x0C); outb(base_io+0,0x31); outb(base_io+1,0x8C); outb(base_io+7,0x10); outb(base_io+7,0x19); if(0x18 != (inb(base_io+7) & 0x1A)) DSI_ERROR("dsp bus not granted"); if(0x01 != (inb(base_io+7) & 0x01)) { outb(base_io+7,0x18); outb(base_io+7,0x19); if(0x01 != (inb(base_io+7) & 0x01)) DSI_ERROR("program mem not granted"); } int_c = 0; while(1) { if(int_c >= 7 || size <= 0x1800) break; for(int_k = 0 ; int_k < 0x800; int_k++) { outb(base_io+0,*ptr++); outb(base_io+1,*ptr++); outb(base_io+2,*ptr++); } size -= 0x1800; int_c++; } if(size > 0x1800) { outb(base_io+7,0x18); outb(base_io+7,0x19); if(0x00 != (inb(base_io+7) & 0x01)) DSI_ERROR("program data not granted"); for(int_k = 0 ; int_k < 0x800; int_k++) { outb(base_io+1,*ptr++); outb(base_io+2,0); outb(base_io+1,*ptr++); outb(base_io+2,*ptr++); } size -= 0x1800; while(size > 0x1800) { for(int_k = 0 ; int_k < 0xC00; int_k++) { outb(base_io+1,*ptr++); outb(base_io+2,*ptr++); } size -= 0x1800; } if(size < 0x1800) { for(int_k=0;int_k 0) { if(int_c == 7) { outb(base_io+7,0x18); outb(base_io+7,0x19); if(0x00 != (inb(base_io+7) & 0x01)) DSI_ERROR("program data not granted"); for(int_k = 0 ; int_k < size/3; int_k++) { outb(base_io+1,*ptr++); outb(base_io+2,0); outb(base_io+1,*ptr++); outb(base_io+2,*ptr++); } } else { for(int_k = 0 ; int_k < size/3; int_k++) { outb(base_io+0,*ptr++); outb(base_io+1,*ptr++); outb(base_io+2,*ptr++); } } } outb(base_io+7,0x11); outb(base_io+7,3); outb(base_io+4,data_0188 & 0xfb); outb(base_io+3,data_0187); return 0; error: printf("sio%d: DSI SoftModem microcode load failed: <%s>\n",unit,ptr); outb(base_io+7,0x00); \ outb(base_io+3,data_0187); \ outb(base_io+4,data_0188); \ return EIO; } #endif /* DSI_SOFT_MODEM */ /* * support PnP cards if we are using 'em */ #if NPNP > 0 static struct siopnp_ids { u_long vend_id; char *id_str; } siopnp_ids[] = { { 0x5015f435, "MOT1550"}, { 0x8113b04e, "Supra1381"}, { 0x9012b04e, "Supra1290"}, { 0x11007256, "USR0011"}, { 0x30207256, "USR2030"}, { 0 } }; static char *siopnp_probe(u_long csn, u_long vend_id); static void siopnp_attach(u_long csn, u_long vend_id, char *name, struct isa_device *dev); static u_long nsiopnp = NSIO; static struct pnp_device siopnp = { "siopnp", siopnp_probe, siopnp_attach, &nsiopnp, &tty_imask }; DATA_SET (pnpdevice_set, siopnp); static char * siopnp_probe(u_long csn, u_long vend_id) { struct siopnp_ids *ids; char *s = NULL; for(ids = siopnp_ids; ids->vend_id != 0; ids++) { if (vend_id == ids->vend_id) { s = ids->id_str; break; } } if (s) { struct pnp_cinfo d; read_pnp_parms(&d, 0); if (d.enable == 0 || d.flags & 1) { printf("CSN %d is disabled.\n", csn); return (NULL); } } return (s); } static void siopnp_attach(u_long csn, u_long vend_id, char *name, struct isa_device *dev) { struct pnp_cinfo d; struct isa_device *dvp; if (dev->id_unit >= NSIOTOT) return; if (read_pnp_parms(&d, 0) == 0) { printf("failed to read pnp parms\n"); return; } write_pnp_parms(&d, 0); enable_pnp_card(); dev->id_iobase = d.port[0]; dev->id_irq = (1 << d.irq[0]); dev->id_intr = siointr; dev->id_ri_flags = RI_FAST; dev->id_drq = -1; if (dev->id_driver == NULL) { dev->id_driver = &siodriver; dvp = find_isadev(isa_devtab_tty, &siodriver, 0); if (dvp != NULL) dev->id_id = dvp->id_id; } if ((dev->id_alive = sioprobe(dev)) != 0) sioattach(dev); else printf("sio%d: probe failed\n", dev->id_unit); } #endif Index: head/sys/i386/i386/apic_vector.s =================================================================== --- head/sys/i386/i386/apic_vector.s (revision 38245) +++ head/sys/i386/i386/apic_vector.s (revision 38246) @@ -1,1047 +1,1047 @@ /* * from: vector.s, 386BSD 0.1 unknown origin - * $Id: apic_vector.s,v 1.30 1998/05/17 22:12:04 tegge Exp $ + * $Id: apic_vector.s,v 1.31 1998/08/11 15:08:12 bde Exp $ */ #include #include #include "i386/isa/intr_machdep.h" #ifdef FAST_SIMPLELOCK #define GET_FAST_INTR_LOCK \ pushl $_fast_intr_lock ; /* address of lock */ \ call _s_lock ; /* MP-safe */ \ addl $4,%esp #define REL_FAST_INTR_LOCK \ pushl $_fast_intr_lock ; /* address of lock */ \ call _s_unlock ; /* MP-safe */ \ addl $4,%esp #else /* FAST_SIMPLELOCK */ #define GET_FAST_INTR_LOCK \ call _get_isrlock #define REL_FAST_INTR_LOCK \ pushl $_mp_lock ; /* GIANT_LOCK */ \ call _MPrellock ; \ add $4, %esp #endif /* FAST_SIMPLELOCK */ /* convert an absolute IRQ# into a bitmask */ #define IRQ_BIT(irq_num) (1 << (irq_num)) /* make an index into the IO APIC from the IRQ# */ #define REDTBL_IDX(irq_num) (0x10 + ((irq_num) * 2)) /* * Macros for interrupt interrupt entry, call to handler, and exit. */ #ifdef FAST_WITHOUTCPL /* */ #define FAST_INTR(irq_num, vec_name) \ .text ; \ SUPERALIGN_TEXT ; \ IDTVEC(vec_name) ; \ pushl %eax ; /* save only call-used registers */ \ pushl %ecx ; \ pushl %edx ; \ pushl %ds ; \ MAYBE_PUSHL_ES ; \ movl $KDSEL,%eax ; \ movl %ax,%ds ; \ MAYBE_MOVW_AX_ES ; \ FAKE_MCOUNT((4+ACTUALLY_PUSHED)*4(%esp)) ; \ pushl _intr_unit + (irq_num) * 4 ; \ GET_FAST_INTR_LOCK ; \ call *_intr_handler + (irq_num) * 4 ; /* do the work ASAP */ \ REL_FAST_INTR_LOCK ; \ addl $4, %esp ; \ movl $0, lapic_eoi ; \ lock ; \ incl _cnt+V_INTR ; /* book-keeping can wait */ \ movl _intr_countp + (irq_num) * 4, %eax ; \ lock ; \ incl (%eax) ; \ MEXITCOUNT ; \ MAYBE_POPL_ES ; \ popl %ds ; \ popl %edx ; \ popl %ecx ; \ popl %eax ; \ iret #else /* FAST_WITHOUTCPL */ #define FAST_INTR(irq_num, vec_name) \ .text ; \ SUPERALIGN_TEXT ; \ IDTVEC(vec_name) ; \ pushl %eax ; /* save only call-used registers */ \ pushl %ecx ; \ pushl %edx ; \ pushl %ds ; \ MAYBE_PUSHL_ES ; \ movl $KDSEL, %eax ; \ movl %ax, %ds ; \ MAYBE_MOVW_AX_ES ; \ FAKE_MCOUNT((4+ACTUALLY_PUSHED)*4(%esp)) ; \ GET_FAST_INTR_LOCK ; \ pushl _intr_unit + (irq_num) * 4 ; \ call *_intr_handler + (irq_num) * 4 ; /* do the work ASAP */ \ addl $4, %esp ; \ movl $0, lapic_eoi ; \ lock ; \ incl _cnt+V_INTR ; /* book-keeping can wait */ \ movl _intr_countp + (irq_num) * 4,%eax ; \ lock ; \ incl (%eax) ; \ movl _cpl, %eax ; /* unmasking pending HWIs or SWIs? */ \ notl %eax ; \ andl _ipending, %eax ; \ jne 2f ; /* yes, maybe handle them */ \ 1: ; \ MEXITCOUNT ; \ REL_FAST_INTR_LOCK ; \ MAYBE_POPL_ES ; \ popl %ds ; \ popl %edx ; \ popl %ecx ; \ popl %eax ; \ iret ; \ ; \ ALIGN_TEXT ; \ 2: ; \ cmpb $3, _intr_nesting_level ; /* enough stack? */ \ jae 1b ; /* no, return */ \ movl _cpl, %eax ; \ /* XXX next line is probably unnecessary now. */ \ movl $HWI_MASK|SWI_MASK, _cpl ; /* limit nesting ... */ \ lock ; \ incb _intr_nesting_level ; /* ... really limit it ... */ \ sti ; /* to do this as early as possible */ \ MAYBE_POPL_ES ; /* discard most of thin frame ... */ \ popl %ecx ; /* ... original %ds ... */ \ popl %edx ; \ xchgl %eax, 4(%esp) ; /* orig %eax; save cpl */ \ pushal ; /* build fat frame (grrr) ... */ \ pushl %ecx ; /* ... actually %ds ... */ \ pushl %es ; \ movl $KDSEL, %eax ; \ movl %ax, %es ; \ movl (2+8+0)*4(%esp), %ecx ; /* %ecx from thin frame ... */ \ movl %ecx, (2+6)*4(%esp) ; /* ... to fat frame ... */ \ movl (2+8+1)*4(%esp), %eax ; /* ... cpl from thin frame */ \ pushl %eax ; \ subl $4, %esp ; /* junk for unit number */ \ MEXITCOUNT ; \ jmp _doreti #endif /** FAST_WITHOUTCPL */ /* * */ #define PUSH_FRAME \ pushl $0 ; /* dummy error code */ \ pushl $0 ; /* dummy trap type */ \ pushal ; \ pushl %ds ; /* save data and extra segments ... */ \ pushl %es #define POP_FRAME \ popl %es ; \ popl %ds ; \ popal ; \ addl $4+4,%esp #define MASK_IRQ(irq_num) \ IMASK_LOCK ; /* into critical reg */ \ testl $IRQ_BIT(irq_num), _apic_imen ; \ jne 7f ; /* masked, don't mask */ \ orl $IRQ_BIT(irq_num), _apic_imen ; /* set the mask bit */ \ movl _ioapic, %ecx ; /* ioapic[0] addr */ \ movl $REDTBL_IDX(irq_num), (%ecx) ; /* write the index */ \ movl IOAPIC_WINDOW(%ecx), %eax ; /* current value */ \ orl $IOART_INTMASK, %eax ; /* set the mask */ \ movl %eax, IOAPIC_WINDOW(%ecx) ; /* new value */ \ 7: ; /* already masked */ \ IMASK_UNLOCK /* * Test to see whether we are handling an edge or level triggered INT. * Level-triggered INTs must still be masked as we don't clear the source, * and the EOI cycle would cause redundant INTs to occur. */ #define MASK_LEVEL_IRQ(irq_num) \ testl $IRQ_BIT(irq_num), _apic_pin_trigger ; \ jz 9f ; /* edge, don't mask */ \ MASK_IRQ(irq_num) ; \ 9: #ifdef APIC_INTR_REORDER #define EOI_IRQ(irq_num) \ movl _apic_isrbit_location + 8 * (irq_num), %eax ; \ movl (%eax), %eax ; \ testl _apic_isrbit_location + 4 + 8 * (irq_num), %eax ; \ jz 9f ; /* not active */ \ movl $0, lapic_eoi ; \ APIC_ITRACE(apic_itrace_eoi, irq_num, APIC_ITRACE_EOI) ; \ 9: #else #define EOI_IRQ(irq_num) \ testl $IRQ_BIT(irq_num), lapic_isr1; \ jz 9f ; /* not active */ \ movl $0, lapic_eoi; \ APIC_ITRACE(apic_itrace_eoi, irq_num, APIC_ITRACE_EOI) ; \ 9: #endif /* * Test to see if the source is currntly masked, clear if so. */ #define UNMASK_IRQ(irq_num) \ IMASK_LOCK ; /* into critical reg */ \ testl $IRQ_BIT(irq_num), _apic_imen ; \ je 7f ; /* bit clear, not masked */ \ andl $~IRQ_BIT(irq_num), _apic_imen ;/* clear mask bit */ \ movl _ioapic,%ecx ; /* ioapic[0]addr */ \ movl $REDTBL_IDX(irq_num),(%ecx) ; /* write the index */ \ movl IOAPIC_WINDOW(%ecx),%eax ; /* current value */ \ andl $~IOART_INTMASK,%eax ; /* clear the mask */ \ movl %eax,IOAPIC_WINDOW(%ecx) ; /* new value */ \ 7: ; \ IMASK_UNLOCK #ifdef INTR_SIMPLELOCK #define ENLOCK #define DELOCK #define LATELOCK call _get_isrlock #else #define ENLOCK \ ISR_TRYLOCK ; /* XXX this is going away... */ \ testl %eax, %eax ; /* did we get it? */ \ jz 3f #define DELOCK ISR_RELLOCK #define LATELOCK #endif #ifdef APIC_INTR_DIAGNOSTIC #ifdef APIC_INTR_DIAGNOSTIC_IRQ log_intr_event: pushf cli pushl $CNAME(apic_itrace_debuglock) call _s_lock_np addl $4, %esp movl CNAME(apic_itrace_debugbuffer_idx), %ecx andl $32767, %ecx movl _cpuid, %eax shll $8, %eax orl 8(%esp), %eax movw %ax, CNAME(apic_itrace_debugbuffer)(,%ecx,2) incl %ecx andl $32767, %ecx movl %ecx, CNAME(apic_itrace_debugbuffer_idx) pushl $CNAME(apic_itrace_debuglock) call _s_unlock_np addl $4, %esp popf ret #define APIC_ITRACE(name, irq_num, id) \ lock ; /* MP-safe */ \ incl CNAME(name) + (irq_num) * 4 ; \ pushl %eax ; \ pushl %ecx ; \ pushl %edx ; \ movl $(irq_num), %eax ; \ cmpl $APIC_INTR_DIAGNOSTIC_IRQ, %eax ; \ jne 7f ; \ pushl $id ; \ call log_intr_event ; \ addl $4, %esp ; \ 7: ; \ popl %edx ; \ popl %ecx ; \ popl %eax #else #define APIC_ITRACE(name, irq_num, id) \ lock ; /* MP-safe */ \ incl CNAME(name) + (irq_num) * 4 #endif #define APIC_ITRACE_ENTER 1 #define APIC_ITRACE_EOI 2 #define APIC_ITRACE_TRYISRLOCK 3 #define APIC_ITRACE_GOTISRLOCK 4 #define APIC_ITRACE_ENTER2 5 #define APIC_ITRACE_LEAVE 6 #define APIC_ITRACE_UNMASK 7 #define APIC_ITRACE_ACTIVE 8 #define APIC_ITRACE_MASKED 9 #define APIC_ITRACE_NOISRLOCK 10 #define APIC_ITRACE_MASKED2 11 #define APIC_ITRACE_SPLZ 12 #define APIC_ITRACE_DORETI 13 #else #define APIC_ITRACE(name, irq_num, id) #endif #ifdef CPL_AND_CML #define INTR(irq_num, vec_name) \ .text ; \ SUPERALIGN_TEXT ; \ /* _XintrNN: entry point used by IDT/HWIs & splz_unpend via _vec[]. */ \ IDTVEC(vec_name) ; \ PUSH_FRAME ; \ movl $KDSEL, %eax ; /* reload with kernel's data segment */ \ movl %ax, %ds ; \ movl %ax, %es ; \ ; \ APIC_ITRACE(apic_itrace_enter, irq_num, APIC_ITRACE_ENTER) ; \ lock ; /* MP-safe */ \ btsl $(irq_num), iactive ; /* lazy masking */ \ jc 1f ; /* already active */ \ ; \ MASK_LEVEL_IRQ(irq_num) ; \ EOI_IRQ(irq_num) ; \ 0: ; \ APIC_ITRACE(apic_itrace_tryisrlock, irq_num, APIC_ITRACE_TRYISRLOCK) ;\ ENLOCK ; \ ; \ APIC_ITRACE(apic_itrace_gotisrlock, irq_num, APIC_ITRACE_GOTISRLOCK) ;\ AVCPL_LOCK ; /* MP-safe */ \ testl $IRQ_BIT(irq_num), _cpl ; \ jne 2f ; /* this INT masked */ \ testl $IRQ_BIT(irq_num), _cml ; \ jne 2f ; /* this INT masked */ \ orl $IRQ_BIT(irq_num), _cil ; \ AVCPL_UNLOCK ; \ ; \ incb _intr_nesting_level ; \ ; \ /* entry point used by doreti_unpend for HWIs. */ \ __CONCAT(Xresume,irq_num): ; \ FAKE_MCOUNT(12*4(%esp)) ; /* XXX avoid dbl cnt */ \ lock ; incl _cnt+V_INTR ; /* tally interrupts */ \ movl _intr_countp + (irq_num) * 4, %eax ; \ lock ; incl (%eax) ; \ ; \ AVCPL_LOCK ; /* MP-safe */ \ movl _cml, %eax ; \ pushl %eax ; \ orl _intr_mask + (irq_num) * 4, %eax ; \ movl %eax, _cml ; \ AVCPL_UNLOCK ; \ ; \ pushl _intr_unit + (irq_num) * 4 ; \ incl _inside_intr ; \ APIC_ITRACE(apic_itrace_enter2, irq_num, APIC_ITRACE_ENTER2) ; \ sti ; \ call *_intr_handler + (irq_num) * 4 ; \ cli ; \ APIC_ITRACE(apic_itrace_leave, irq_num, APIC_ITRACE_LEAVE) ; \ decl _inside_intr ; \ ; \ lock ; andl $~IRQ_BIT(irq_num), iactive ; \ lock ; andl $~IRQ_BIT(irq_num), _cil ; \ UNMASK_IRQ(irq_num) ; \ APIC_ITRACE(apic_itrace_unmask, irq_num, APIC_ITRACE_UNMASK) ; \ sti ; /* doreti repeats cli/sti */ \ MEXITCOUNT ; \ LATELOCK ; \ jmp _doreti ; \ ; \ ALIGN_TEXT ; \ 1: ; /* active */ \ APIC_ITRACE(apic_itrace_active, irq_num, APIC_ITRACE_ACTIVE) ; \ MASK_IRQ(irq_num) ; \ EOI_IRQ(irq_num) ; \ AVCPL_LOCK ; /* MP-safe */ \ orl $IRQ_BIT(irq_num), _ipending ; \ AVCPL_UNLOCK ; \ lock ; \ btsl $(irq_num), iactive ; /* still active */ \ jnc 0b ; /* retry */ \ POP_FRAME ; \ iret ; \ ; \ ALIGN_TEXT ; \ 2: ; /* masked by cpl|cml */ \ APIC_ITRACE(apic_itrace_masked, irq_num, APIC_ITRACE_MASKED) ; \ orl $IRQ_BIT(irq_num), _ipending ; \ AVCPL_UNLOCK ; \ DELOCK ; /* XXX this is going away... */ \ POP_FRAME ; \ iret ; \ ALIGN_TEXT ; \ 3: ; /* other cpu has isr lock */ \ APIC_ITRACE(apic_itrace_noisrlock, irq_num, APIC_ITRACE_NOISRLOCK) ;\ AVCPL_LOCK ; /* MP-safe */ \ orl $IRQ_BIT(irq_num), _ipending ; \ testl $IRQ_BIT(irq_num), _cpl ; \ jne 4f ; /* this INT masked */ \ testl $IRQ_BIT(irq_num), _cml ; \ jne 4f ; /* this INT masked */ \ orl $IRQ_BIT(irq_num), _cil ; \ AVCPL_UNLOCK ; \ call forward_irq ; /* forward irq to lock holder */ \ POP_FRAME ; /* and return */ \ iret ; \ ALIGN_TEXT ; \ 4: ; /* blocked */ \ APIC_ITRACE(apic_itrace_masked2, irq_num, APIC_ITRACE_MASKED2) ;\ AVCPL_UNLOCK ; \ POP_FRAME ; /* and return */ \ iret #else /* CPL_AND_CML */ #define INTR(irq_num, vec_name) \ .text ; \ SUPERALIGN_TEXT ; \ /* _XintrNN: entry point used by IDT/HWIs & splz_unpend via _vec[]. */ \ IDTVEC(vec_name) ; \ PUSH_FRAME ; \ movl $KDSEL, %eax ; /* reload with kernel's data segment */ \ movl %ax, %ds ; \ movl %ax, %es ; \ ; \ APIC_ITRACE(apic_itrace_enter, irq_num, APIC_ITRACE_ENTER) ; \ lock ; /* MP-safe */ \ btsl $(irq_num), iactive ; /* lazy masking */ \ jc 1f ; /* already active */ \ ; \ MASK_LEVEL_IRQ(irq_num) ; \ EOI_IRQ(irq_num) ; \ 0: ; \ APIC_ITRACE(apic_itrace_tryisrlock, irq_num, APIC_ITRACE_TRYISRLOCK) ;\ ISR_TRYLOCK ; /* XXX this is going away... */ \ testl %eax, %eax ; /* did we get it? */ \ jz 3f ; /* no */ \ ; \ APIC_ITRACE(apic_itrace_gotisrlock, irq_num, APIC_ITRACE_GOTISRLOCK) ;\ AVCPL_LOCK ; /* MP-safe */ \ testl $IRQ_BIT(irq_num), _cpl ; \ jne 2f ; /* this INT masked */ \ AVCPL_UNLOCK ; \ ; \ incb _intr_nesting_level ; \ ; \ /* entry point used by doreti_unpend for HWIs. */ \ __CONCAT(Xresume,irq_num): ; \ FAKE_MCOUNT(12*4(%esp)) ; /* XXX avoid dbl cnt */ \ lock ; incl _cnt+V_INTR ; /* tally interrupts */ \ movl _intr_countp + (irq_num) * 4, %eax ; \ lock ; incl (%eax) ; \ ; \ AVCPL_LOCK ; /* MP-safe */ \ movl _cpl, %eax ; \ pushl %eax ; \ orl _intr_mask + (irq_num) * 4, %eax ; \ movl %eax, _cpl ; \ andl $~IRQ_BIT(irq_num), _ipending ; \ AVCPL_UNLOCK ; \ ; \ pushl _intr_unit + (irq_num) * 4 ; \ APIC_ITRACE(apic_itrace_enter2, irq_num, APIC_ITRACE_ENTER2) ; \ sti ; \ call *_intr_handler + (irq_num) * 4 ; \ cli ; \ APIC_ITRACE(apic_itrace_leave, irq_num, APIC_ITRACE_LEAVE) ; \ ; \ lock ; andl $~IRQ_BIT(irq_num), iactive ; \ UNMASK_IRQ(irq_num) ; \ APIC_ITRACE(apic_itrace_unmask, irq_num, APIC_ITRACE_UNMASK) ; \ sti ; /* doreti repeats cli/sti */ \ MEXITCOUNT ; \ jmp _doreti ; \ ; \ ALIGN_TEXT ; \ 1: ; /* active */ \ APIC_ITRACE(apic_itrace_active, irq_num, APIC_ITRACE_ACTIVE) ; \ MASK_IRQ(irq_num) ; \ EOI_IRQ(irq_num) ; \ AVCPL_LOCK ; /* MP-safe */ \ orl $IRQ_BIT(irq_num), _ipending ; \ AVCPL_UNLOCK ; \ lock ; \ btsl $(irq_num), iactive ; /* still active */ \ jnc 0b ; /* retry */ \ POP_FRAME ; \ iret ; /* XXX: iactive bit might be 0 now */ \ ALIGN_TEXT ; \ 2: ; /* masked by cpl, leave iactive set */ \ APIC_ITRACE(apic_itrace_masked, irq_num, APIC_ITRACE_MASKED) ; \ orl $IRQ_BIT(irq_num), _ipending ; \ AVCPL_UNLOCK ; \ ISR_RELLOCK ; /* XXX this is going away... */ \ POP_FRAME ; \ iret ; \ ALIGN_TEXT ; \ 3: ; /* other cpu has isr lock */ \ APIC_ITRACE(apic_itrace_noisrlock, irq_num, APIC_ITRACE_NOISRLOCK) ;\ AVCPL_LOCK ; /* MP-safe */ \ orl $IRQ_BIT(irq_num), _ipending ; \ testl $IRQ_BIT(irq_num), _cpl ; \ jne 4f ; /* this INT masked */ \ AVCPL_UNLOCK ; \ call forward_irq ; /* forward irq to lock holder */ \ POP_FRAME ; /* and return */ \ iret ; \ ALIGN_TEXT ; \ 4: ; /* blocked */ \ APIC_ITRACE(apic_itrace_masked2, irq_num, APIC_ITRACE_MASKED2) ;\ AVCPL_UNLOCK ; \ POP_FRAME ; /* and return */ \ iret #endif /* CPL_AND_CML */ /* * Handle "spurious INTerrupts". * Notes: * This is different than the "spurious INTerrupt" generated by an * 8259 PIC for missing INTs. See the APIC documentation for details. * This routine should NOT do an 'EOI' cycle. */ .text SUPERALIGN_TEXT .globl _Xspuriousint _Xspuriousint: /* No EOI cycle used here */ iret /* * Handle TLB shootdowns. */ .text SUPERALIGN_TEXT .globl _Xinvltlb _Xinvltlb: pushl %eax #ifdef COUNT_XINVLTLB_HITS ss movl _cpuid, %eax ss incl _xhits(,%eax,4) #endif /* COUNT_XINVLTLB_HITS */ movl %cr3, %eax /* invalidate the TLB */ movl %eax, %cr3 ss /* stack segment, avoid %ds load */ movl $0, lapic_eoi /* End Of Interrupt to APIC */ popl %eax iret #ifdef BETTER_CLOCK /* * Executed by a CPU when it receives an Xcpucheckstate IPI from another CPU, * * - Stores current cpu state in checkstate_cpustate[cpuid] * 0 == user, 1 == sys, 2 == intr * - Stores current process in checkstate_curproc[cpuid] * * - Signals its receipt by setting bit cpuid in checkstate_probed_cpus. * * stack: 0 -> ds, 4 -> ebx, 8 -> eax, 12 -> eip, 16 -> cs, 20 -> eflags */ .text SUPERALIGN_TEXT .globl _Xcpucheckstate .globl _checkstate_cpustate .globl _checkstate_curproc .globl _checkstate_pc _Xcpucheckstate: pushl %eax pushl %ebx pushl %ds /* save current data segment */ movl $KDSEL, %eax movl %ax, %ds /* use KERNEL data segment */ movl $0, lapic_eoi /* End Of Interrupt to APIC */ movl $0, %ebx movl 16(%esp), %eax andl $3, %eax cmpl $3, %eax je 1f #ifdef VM86 testl $PSL_VM, 20(%esp) jne 1f #endif incl %ebx /* system or interrupt */ #ifdef CPL_AND_CML cmpl $0, _inside_intr je 1f incl %ebx /* interrupt */ #endif 1: movl _cpuid, %eax movl %ebx, _checkstate_cpustate(,%eax,4) movl _curproc, %ebx movl %ebx, _checkstate_curproc(,%eax,4) movl 12(%esp), %ebx movl %ebx, _checkstate_pc(,%eax,4) lock /* checkstate_probed_cpus |= (1< #include #include "i386/isa/intr_machdep.h" #ifdef FAST_SIMPLELOCK #define GET_FAST_INTR_LOCK \ pushl $_fast_intr_lock ; /* address of lock */ \ call _s_lock ; /* MP-safe */ \ addl $4,%esp #define REL_FAST_INTR_LOCK \ pushl $_fast_intr_lock ; /* address of lock */ \ call _s_unlock ; /* MP-safe */ \ addl $4,%esp #else /* FAST_SIMPLELOCK */ #define GET_FAST_INTR_LOCK \ call _get_isrlock #define REL_FAST_INTR_LOCK \ pushl $_mp_lock ; /* GIANT_LOCK */ \ call _MPrellock ; \ add $4, %esp #endif /* FAST_SIMPLELOCK */ /* convert an absolute IRQ# into a bitmask */ #define IRQ_BIT(irq_num) (1 << (irq_num)) /* make an index into the IO APIC from the IRQ# */ #define REDTBL_IDX(irq_num) (0x10 + ((irq_num) * 2)) /* * Macros for interrupt interrupt entry, call to handler, and exit. */ #ifdef FAST_WITHOUTCPL /* */ #define FAST_INTR(irq_num, vec_name) \ .text ; \ SUPERALIGN_TEXT ; \ IDTVEC(vec_name) ; \ pushl %eax ; /* save only call-used registers */ \ pushl %ecx ; \ pushl %edx ; \ pushl %ds ; \ MAYBE_PUSHL_ES ; \ movl $KDSEL,%eax ; \ movl %ax,%ds ; \ MAYBE_MOVW_AX_ES ; \ FAKE_MCOUNT((4+ACTUALLY_PUSHED)*4(%esp)) ; \ pushl _intr_unit + (irq_num) * 4 ; \ GET_FAST_INTR_LOCK ; \ call *_intr_handler + (irq_num) * 4 ; /* do the work ASAP */ \ REL_FAST_INTR_LOCK ; \ addl $4, %esp ; \ movl $0, lapic_eoi ; \ lock ; \ incl _cnt+V_INTR ; /* book-keeping can wait */ \ movl _intr_countp + (irq_num) * 4, %eax ; \ lock ; \ incl (%eax) ; \ MEXITCOUNT ; \ MAYBE_POPL_ES ; \ popl %ds ; \ popl %edx ; \ popl %ecx ; \ popl %eax ; \ iret #else /* FAST_WITHOUTCPL */ #define FAST_INTR(irq_num, vec_name) \ .text ; \ SUPERALIGN_TEXT ; \ IDTVEC(vec_name) ; \ pushl %eax ; /* save only call-used registers */ \ pushl %ecx ; \ pushl %edx ; \ pushl %ds ; \ MAYBE_PUSHL_ES ; \ movl $KDSEL, %eax ; \ movl %ax, %ds ; \ MAYBE_MOVW_AX_ES ; \ FAKE_MCOUNT((4+ACTUALLY_PUSHED)*4(%esp)) ; \ GET_FAST_INTR_LOCK ; \ pushl _intr_unit + (irq_num) * 4 ; \ call *_intr_handler + (irq_num) * 4 ; /* do the work ASAP */ \ addl $4, %esp ; \ movl $0, lapic_eoi ; \ lock ; \ incl _cnt+V_INTR ; /* book-keeping can wait */ \ movl _intr_countp + (irq_num) * 4,%eax ; \ lock ; \ incl (%eax) ; \ movl _cpl, %eax ; /* unmasking pending HWIs or SWIs? */ \ notl %eax ; \ andl _ipending, %eax ; \ jne 2f ; /* yes, maybe handle them */ \ 1: ; \ MEXITCOUNT ; \ REL_FAST_INTR_LOCK ; \ MAYBE_POPL_ES ; \ popl %ds ; \ popl %edx ; \ popl %ecx ; \ popl %eax ; \ iret ; \ ; \ ALIGN_TEXT ; \ 2: ; \ cmpb $3, _intr_nesting_level ; /* enough stack? */ \ jae 1b ; /* no, return */ \ movl _cpl, %eax ; \ /* XXX next line is probably unnecessary now. */ \ movl $HWI_MASK|SWI_MASK, _cpl ; /* limit nesting ... */ \ lock ; \ incb _intr_nesting_level ; /* ... really limit it ... */ \ sti ; /* to do this as early as possible */ \ MAYBE_POPL_ES ; /* discard most of thin frame ... */ \ popl %ecx ; /* ... original %ds ... */ \ popl %edx ; \ xchgl %eax, 4(%esp) ; /* orig %eax; save cpl */ \ pushal ; /* build fat frame (grrr) ... */ \ pushl %ecx ; /* ... actually %ds ... */ \ pushl %es ; \ movl $KDSEL, %eax ; \ movl %ax, %es ; \ movl (2+8+0)*4(%esp), %ecx ; /* %ecx from thin frame ... */ \ movl %ecx, (2+6)*4(%esp) ; /* ... to fat frame ... */ \ movl (2+8+1)*4(%esp), %eax ; /* ... cpl from thin frame */ \ pushl %eax ; \ subl $4, %esp ; /* junk for unit number */ \ MEXITCOUNT ; \ jmp _doreti #endif /** FAST_WITHOUTCPL */ /* * */ #define PUSH_FRAME \ pushl $0 ; /* dummy error code */ \ pushl $0 ; /* dummy trap type */ \ pushal ; \ pushl %ds ; /* save data and extra segments ... */ \ pushl %es #define POP_FRAME \ popl %es ; \ popl %ds ; \ popal ; \ addl $4+4,%esp #define MASK_IRQ(irq_num) \ IMASK_LOCK ; /* into critical reg */ \ testl $IRQ_BIT(irq_num), _apic_imen ; \ jne 7f ; /* masked, don't mask */ \ orl $IRQ_BIT(irq_num), _apic_imen ; /* set the mask bit */ \ movl _ioapic, %ecx ; /* ioapic[0] addr */ \ movl $REDTBL_IDX(irq_num), (%ecx) ; /* write the index */ \ movl IOAPIC_WINDOW(%ecx), %eax ; /* current value */ \ orl $IOART_INTMASK, %eax ; /* set the mask */ \ movl %eax, IOAPIC_WINDOW(%ecx) ; /* new value */ \ 7: ; /* already masked */ \ IMASK_UNLOCK /* * Test to see whether we are handling an edge or level triggered INT. * Level-triggered INTs must still be masked as we don't clear the source, * and the EOI cycle would cause redundant INTs to occur. */ #define MASK_LEVEL_IRQ(irq_num) \ testl $IRQ_BIT(irq_num), _apic_pin_trigger ; \ jz 9f ; /* edge, don't mask */ \ MASK_IRQ(irq_num) ; \ 9: #ifdef APIC_INTR_REORDER #define EOI_IRQ(irq_num) \ movl _apic_isrbit_location + 8 * (irq_num), %eax ; \ movl (%eax), %eax ; \ testl _apic_isrbit_location + 4 + 8 * (irq_num), %eax ; \ jz 9f ; /* not active */ \ movl $0, lapic_eoi ; \ APIC_ITRACE(apic_itrace_eoi, irq_num, APIC_ITRACE_EOI) ; \ 9: #else #define EOI_IRQ(irq_num) \ testl $IRQ_BIT(irq_num), lapic_isr1; \ jz 9f ; /* not active */ \ movl $0, lapic_eoi; \ APIC_ITRACE(apic_itrace_eoi, irq_num, APIC_ITRACE_EOI) ; \ 9: #endif /* * Test to see if the source is currntly masked, clear if so. */ #define UNMASK_IRQ(irq_num) \ IMASK_LOCK ; /* into critical reg */ \ testl $IRQ_BIT(irq_num), _apic_imen ; \ je 7f ; /* bit clear, not masked */ \ andl $~IRQ_BIT(irq_num), _apic_imen ;/* clear mask bit */ \ movl _ioapic,%ecx ; /* ioapic[0]addr */ \ movl $REDTBL_IDX(irq_num),(%ecx) ; /* write the index */ \ movl IOAPIC_WINDOW(%ecx),%eax ; /* current value */ \ andl $~IOART_INTMASK,%eax ; /* clear the mask */ \ movl %eax,IOAPIC_WINDOW(%ecx) ; /* new value */ \ 7: ; \ IMASK_UNLOCK #ifdef INTR_SIMPLELOCK #define ENLOCK #define DELOCK #define LATELOCK call _get_isrlock #else #define ENLOCK \ ISR_TRYLOCK ; /* XXX this is going away... */ \ testl %eax, %eax ; /* did we get it? */ \ jz 3f #define DELOCK ISR_RELLOCK #define LATELOCK #endif #ifdef APIC_INTR_DIAGNOSTIC #ifdef APIC_INTR_DIAGNOSTIC_IRQ log_intr_event: pushf cli pushl $CNAME(apic_itrace_debuglock) call _s_lock_np addl $4, %esp movl CNAME(apic_itrace_debugbuffer_idx), %ecx andl $32767, %ecx movl _cpuid, %eax shll $8, %eax orl 8(%esp), %eax movw %ax, CNAME(apic_itrace_debugbuffer)(,%ecx,2) incl %ecx andl $32767, %ecx movl %ecx, CNAME(apic_itrace_debugbuffer_idx) pushl $CNAME(apic_itrace_debuglock) call _s_unlock_np addl $4, %esp popf ret #define APIC_ITRACE(name, irq_num, id) \ lock ; /* MP-safe */ \ incl CNAME(name) + (irq_num) * 4 ; \ pushl %eax ; \ pushl %ecx ; \ pushl %edx ; \ movl $(irq_num), %eax ; \ cmpl $APIC_INTR_DIAGNOSTIC_IRQ, %eax ; \ jne 7f ; \ pushl $id ; \ call log_intr_event ; \ addl $4, %esp ; \ 7: ; \ popl %edx ; \ popl %ecx ; \ popl %eax #else #define APIC_ITRACE(name, irq_num, id) \ lock ; /* MP-safe */ \ incl CNAME(name) + (irq_num) * 4 #endif #define APIC_ITRACE_ENTER 1 #define APIC_ITRACE_EOI 2 #define APIC_ITRACE_TRYISRLOCK 3 #define APIC_ITRACE_GOTISRLOCK 4 #define APIC_ITRACE_ENTER2 5 #define APIC_ITRACE_LEAVE 6 #define APIC_ITRACE_UNMASK 7 #define APIC_ITRACE_ACTIVE 8 #define APIC_ITRACE_MASKED 9 #define APIC_ITRACE_NOISRLOCK 10 #define APIC_ITRACE_MASKED2 11 #define APIC_ITRACE_SPLZ 12 #define APIC_ITRACE_DORETI 13 #else #define APIC_ITRACE(name, irq_num, id) #endif #ifdef CPL_AND_CML #define INTR(irq_num, vec_name) \ .text ; \ SUPERALIGN_TEXT ; \ /* _XintrNN: entry point used by IDT/HWIs & splz_unpend via _vec[]. */ \ IDTVEC(vec_name) ; \ PUSH_FRAME ; \ movl $KDSEL, %eax ; /* reload with kernel's data segment */ \ movl %ax, %ds ; \ movl %ax, %es ; \ ; \ APIC_ITRACE(apic_itrace_enter, irq_num, APIC_ITRACE_ENTER) ; \ lock ; /* MP-safe */ \ btsl $(irq_num), iactive ; /* lazy masking */ \ jc 1f ; /* already active */ \ ; \ MASK_LEVEL_IRQ(irq_num) ; \ EOI_IRQ(irq_num) ; \ 0: ; \ APIC_ITRACE(apic_itrace_tryisrlock, irq_num, APIC_ITRACE_TRYISRLOCK) ;\ ENLOCK ; \ ; \ APIC_ITRACE(apic_itrace_gotisrlock, irq_num, APIC_ITRACE_GOTISRLOCK) ;\ AVCPL_LOCK ; /* MP-safe */ \ testl $IRQ_BIT(irq_num), _cpl ; \ jne 2f ; /* this INT masked */ \ testl $IRQ_BIT(irq_num), _cml ; \ jne 2f ; /* this INT masked */ \ orl $IRQ_BIT(irq_num), _cil ; \ AVCPL_UNLOCK ; \ ; \ incb _intr_nesting_level ; \ ; \ /* entry point used by doreti_unpend for HWIs. */ \ __CONCAT(Xresume,irq_num): ; \ FAKE_MCOUNT(12*4(%esp)) ; /* XXX avoid dbl cnt */ \ lock ; incl _cnt+V_INTR ; /* tally interrupts */ \ movl _intr_countp + (irq_num) * 4, %eax ; \ lock ; incl (%eax) ; \ ; \ AVCPL_LOCK ; /* MP-safe */ \ movl _cml, %eax ; \ pushl %eax ; \ orl _intr_mask + (irq_num) * 4, %eax ; \ movl %eax, _cml ; \ AVCPL_UNLOCK ; \ ; \ pushl _intr_unit + (irq_num) * 4 ; \ incl _inside_intr ; \ APIC_ITRACE(apic_itrace_enter2, irq_num, APIC_ITRACE_ENTER2) ; \ sti ; \ call *_intr_handler + (irq_num) * 4 ; \ cli ; \ APIC_ITRACE(apic_itrace_leave, irq_num, APIC_ITRACE_LEAVE) ; \ decl _inside_intr ; \ ; \ lock ; andl $~IRQ_BIT(irq_num), iactive ; \ lock ; andl $~IRQ_BIT(irq_num), _cil ; \ UNMASK_IRQ(irq_num) ; \ APIC_ITRACE(apic_itrace_unmask, irq_num, APIC_ITRACE_UNMASK) ; \ sti ; /* doreti repeats cli/sti */ \ MEXITCOUNT ; \ LATELOCK ; \ jmp _doreti ; \ ; \ ALIGN_TEXT ; \ 1: ; /* active */ \ APIC_ITRACE(apic_itrace_active, irq_num, APIC_ITRACE_ACTIVE) ; \ MASK_IRQ(irq_num) ; \ EOI_IRQ(irq_num) ; \ AVCPL_LOCK ; /* MP-safe */ \ orl $IRQ_BIT(irq_num), _ipending ; \ AVCPL_UNLOCK ; \ lock ; \ btsl $(irq_num), iactive ; /* still active */ \ jnc 0b ; /* retry */ \ POP_FRAME ; \ iret ; \ ; \ ALIGN_TEXT ; \ 2: ; /* masked by cpl|cml */ \ APIC_ITRACE(apic_itrace_masked, irq_num, APIC_ITRACE_MASKED) ; \ orl $IRQ_BIT(irq_num), _ipending ; \ AVCPL_UNLOCK ; \ DELOCK ; /* XXX this is going away... */ \ POP_FRAME ; \ iret ; \ ALIGN_TEXT ; \ 3: ; /* other cpu has isr lock */ \ APIC_ITRACE(apic_itrace_noisrlock, irq_num, APIC_ITRACE_NOISRLOCK) ;\ AVCPL_LOCK ; /* MP-safe */ \ orl $IRQ_BIT(irq_num), _ipending ; \ testl $IRQ_BIT(irq_num), _cpl ; \ jne 4f ; /* this INT masked */ \ testl $IRQ_BIT(irq_num), _cml ; \ jne 4f ; /* this INT masked */ \ orl $IRQ_BIT(irq_num), _cil ; \ AVCPL_UNLOCK ; \ call forward_irq ; /* forward irq to lock holder */ \ POP_FRAME ; /* and return */ \ iret ; \ ALIGN_TEXT ; \ 4: ; /* blocked */ \ APIC_ITRACE(apic_itrace_masked2, irq_num, APIC_ITRACE_MASKED2) ;\ AVCPL_UNLOCK ; \ POP_FRAME ; /* and return */ \ iret #else /* CPL_AND_CML */ #define INTR(irq_num, vec_name) \ .text ; \ SUPERALIGN_TEXT ; \ /* _XintrNN: entry point used by IDT/HWIs & splz_unpend via _vec[]. */ \ IDTVEC(vec_name) ; \ PUSH_FRAME ; \ movl $KDSEL, %eax ; /* reload with kernel's data segment */ \ movl %ax, %ds ; \ movl %ax, %es ; \ ; \ APIC_ITRACE(apic_itrace_enter, irq_num, APIC_ITRACE_ENTER) ; \ lock ; /* MP-safe */ \ btsl $(irq_num), iactive ; /* lazy masking */ \ jc 1f ; /* already active */ \ ; \ MASK_LEVEL_IRQ(irq_num) ; \ EOI_IRQ(irq_num) ; \ 0: ; \ APIC_ITRACE(apic_itrace_tryisrlock, irq_num, APIC_ITRACE_TRYISRLOCK) ;\ ISR_TRYLOCK ; /* XXX this is going away... */ \ testl %eax, %eax ; /* did we get it? */ \ jz 3f ; /* no */ \ ; \ APIC_ITRACE(apic_itrace_gotisrlock, irq_num, APIC_ITRACE_GOTISRLOCK) ;\ AVCPL_LOCK ; /* MP-safe */ \ testl $IRQ_BIT(irq_num), _cpl ; \ jne 2f ; /* this INT masked */ \ AVCPL_UNLOCK ; \ ; \ incb _intr_nesting_level ; \ ; \ /* entry point used by doreti_unpend for HWIs. */ \ __CONCAT(Xresume,irq_num): ; \ FAKE_MCOUNT(12*4(%esp)) ; /* XXX avoid dbl cnt */ \ lock ; incl _cnt+V_INTR ; /* tally interrupts */ \ movl _intr_countp + (irq_num) * 4, %eax ; \ lock ; incl (%eax) ; \ ; \ AVCPL_LOCK ; /* MP-safe */ \ movl _cpl, %eax ; \ pushl %eax ; \ orl _intr_mask + (irq_num) * 4, %eax ; \ movl %eax, _cpl ; \ andl $~IRQ_BIT(irq_num), _ipending ; \ AVCPL_UNLOCK ; \ ; \ pushl _intr_unit + (irq_num) * 4 ; \ APIC_ITRACE(apic_itrace_enter2, irq_num, APIC_ITRACE_ENTER2) ; \ sti ; \ call *_intr_handler + (irq_num) * 4 ; \ cli ; \ APIC_ITRACE(apic_itrace_leave, irq_num, APIC_ITRACE_LEAVE) ; \ ; \ lock ; andl $~IRQ_BIT(irq_num), iactive ; \ UNMASK_IRQ(irq_num) ; \ APIC_ITRACE(apic_itrace_unmask, irq_num, APIC_ITRACE_UNMASK) ; \ sti ; /* doreti repeats cli/sti */ \ MEXITCOUNT ; \ jmp _doreti ; \ ; \ ALIGN_TEXT ; \ 1: ; /* active */ \ APIC_ITRACE(apic_itrace_active, irq_num, APIC_ITRACE_ACTIVE) ; \ MASK_IRQ(irq_num) ; \ EOI_IRQ(irq_num) ; \ AVCPL_LOCK ; /* MP-safe */ \ orl $IRQ_BIT(irq_num), _ipending ; \ AVCPL_UNLOCK ; \ lock ; \ btsl $(irq_num), iactive ; /* still active */ \ jnc 0b ; /* retry */ \ POP_FRAME ; \ iret ; /* XXX: iactive bit might be 0 now */ \ ALIGN_TEXT ; \ 2: ; /* masked by cpl, leave iactive set */ \ APIC_ITRACE(apic_itrace_masked, irq_num, APIC_ITRACE_MASKED) ; \ orl $IRQ_BIT(irq_num), _ipending ; \ AVCPL_UNLOCK ; \ ISR_RELLOCK ; /* XXX this is going away... */ \ POP_FRAME ; \ iret ; \ ALIGN_TEXT ; \ 3: ; /* other cpu has isr lock */ \ APIC_ITRACE(apic_itrace_noisrlock, irq_num, APIC_ITRACE_NOISRLOCK) ;\ AVCPL_LOCK ; /* MP-safe */ \ orl $IRQ_BIT(irq_num), _ipending ; \ testl $IRQ_BIT(irq_num), _cpl ; \ jne 4f ; /* this INT masked */ \ AVCPL_UNLOCK ; \ call forward_irq ; /* forward irq to lock holder */ \ POP_FRAME ; /* and return */ \ iret ; \ ALIGN_TEXT ; \ 4: ; /* blocked */ \ APIC_ITRACE(apic_itrace_masked2, irq_num, APIC_ITRACE_MASKED2) ;\ AVCPL_UNLOCK ; \ POP_FRAME ; /* and return */ \ iret #endif /* CPL_AND_CML */ /* * Handle "spurious INTerrupts". * Notes: * This is different than the "spurious INTerrupt" generated by an * 8259 PIC for missing INTs. See the APIC documentation for details. * This routine should NOT do an 'EOI' cycle. */ .text SUPERALIGN_TEXT .globl _Xspuriousint _Xspuriousint: /* No EOI cycle used here */ iret /* * Handle TLB shootdowns. */ .text SUPERALIGN_TEXT .globl _Xinvltlb _Xinvltlb: pushl %eax #ifdef COUNT_XINVLTLB_HITS ss movl _cpuid, %eax ss incl _xhits(,%eax,4) #endif /* COUNT_XINVLTLB_HITS */ movl %cr3, %eax /* invalidate the TLB */ movl %eax, %cr3 ss /* stack segment, avoid %ds load */ movl $0, lapic_eoi /* End Of Interrupt to APIC */ popl %eax iret #ifdef BETTER_CLOCK /* * Executed by a CPU when it receives an Xcpucheckstate IPI from another CPU, * * - Stores current cpu state in checkstate_cpustate[cpuid] * 0 == user, 1 == sys, 2 == intr * - Stores current process in checkstate_curproc[cpuid] * * - Signals its receipt by setting bit cpuid in checkstate_probed_cpus. * * stack: 0 -> ds, 4 -> ebx, 8 -> eax, 12 -> eip, 16 -> cs, 20 -> eflags */ .text SUPERALIGN_TEXT .globl _Xcpucheckstate .globl _checkstate_cpustate .globl _checkstate_curproc .globl _checkstate_pc _Xcpucheckstate: pushl %eax pushl %ebx pushl %ds /* save current data segment */ movl $KDSEL, %eax movl %ax, %ds /* use KERNEL data segment */ movl $0, lapic_eoi /* End Of Interrupt to APIC */ movl $0, %ebx movl 16(%esp), %eax andl $3, %eax cmpl $3, %eax je 1f #ifdef VM86 testl $PSL_VM, 20(%esp) jne 1f #endif incl %ebx /* system or interrupt */ #ifdef CPL_AND_CML cmpl $0, _inside_intr je 1f incl %ebx /* interrupt */ #endif 1: movl _cpuid, %eax movl %ebx, _checkstate_cpustate(,%eax,4) movl _curproc, %ebx movl %ebx, _checkstate_curproc(,%eax,4) movl 12(%esp), %ebx movl %ebx, _checkstate_pc(,%eax,4) lock /* checkstate_probed_cpus |= (1<> 3) #ifdef AUTO_EOI_1 #define ENABLE_ICU1 /* use auto-EOI to reduce i/o */ #define OUTB_ICU1 #else #define ENABLE_ICU1 \ movb $ICU_EOI,%al ; /* as soon as possible send EOI ... */ \ OUTB_ICU1 /* ... to clear in service bit */ #define OUTB_ICU1 \ outb %al,$IO_ICU1 #endif #ifdef AUTO_EOI_2 /* * The data sheet says no auto-EOI on slave, but it sometimes works. */ #define ENABLE_ICU1_AND_2 ENABLE_ICU1 #else #define ENABLE_ICU1_AND_2 \ movb $ICU_EOI,%al ; /* as above */ \ outb %al,$IO_ICU2 ; /* but do second icu first ... */ \ OUTB_ICU1 /* ... then first icu (if !AUTO_EOI_1) */ #endif /* * Macros for interrupt interrupt entry, call to handler, and exit. */ #define FAST_INTR(irq_num, vec_name, enable_icus) \ .text ; \ SUPERALIGN_TEXT ; \ IDTVEC(vec_name) ; \ pushl %eax ; /* save only call-used registers */ \ pushl %ecx ; \ pushl %edx ; \ pushl %ds ; \ MAYBE_PUSHL_ES ; \ movl $KDSEL,%eax ; \ movl %ax,%ds ; \ MAYBE_MOVW_AX_ES ; \ FAKE_MCOUNT((4+ACTUALLY_PUSHED)*4(%esp)) ; \ pushl _intr_unit + (irq_num) * 4 ; \ call *_intr_handler + (irq_num) * 4 ; /* do the work ASAP */ \ enable_icus ; /* (re)enable ASAP (helps edge trigger?) */ \ addl $4,%esp ; \ incl _cnt+V_INTR ; /* book-keeping can wait */ \ movl _intr_countp + (irq_num) * 4,%eax ; \ incl (%eax) ; \ movl _cpl,%eax ; /* are we unmasking pending HWIs or SWIs? */ \ notl %eax ; \ andl _ipending,%eax ; \ jne 2f ; /* yes, maybe handle them */ \ 1: ; \ MEXITCOUNT ; \ MAYBE_POPL_ES ; \ popl %ds ; \ popl %edx ; \ popl %ecx ; \ popl %eax ; \ iret ; \ ; \ ALIGN_TEXT ; \ 2: ; \ cmpb $3,_intr_nesting_level ; /* is there enough stack? */ \ jae 1b ; /* no, return */ \ movl _cpl,%eax ; \ /* XXX next line is probably unnecessary now. */ \ movl $HWI_MASK|SWI_MASK,_cpl ; /* limit nesting ... */ \ incb _intr_nesting_level ; /* ... really limit it ... */ \ sti ; /* ... to do this as early as possible */ \ MAYBE_POPL_ES ; /* discard most of thin frame ... */ \ popl %ecx ; /* ... original %ds ... */ \ popl %edx ; \ xchgl %eax,4(%esp) ; /* orig %eax; save cpl */ \ pushal ; /* build fat frame (grrr) ... */ \ pushl %ecx ; /* ... actually %ds ... */ \ pushl %es ; \ movl $KDSEL,%eax ; \ movl %ax,%es ; \ movl (2+8+0)*4(%esp),%ecx ; /* ... %ecx from thin frame ... */ \ movl %ecx,(2+6)*4(%esp) ; /* ... to fat frame ... */ \ movl (2+8+1)*4(%esp),%eax ; /* ... cpl from thin frame */ \ pushl %eax ; \ subl $4,%esp ; /* junk for unit number */ \ MEXITCOUNT ; \ jmp _doreti #define INTR(irq_num, vec_name, icu, enable_icus, reg) \ .text ; \ SUPERALIGN_TEXT ; \ IDTVEC(vec_name) ; \ pushl $0 ; /* dummy error code */ \ pushl $0 ; /* dummy trap type */ \ pushal ; \ pushl %ds ; /* save our data and extra segments ... */ \ pushl %es ; \ movl $KDSEL,%eax ; /* ... and reload with kernel's own ... */ \ movl %ax,%ds ; /* ... early for obsolete reasons */ \ movl %ax,%es ; \ movb _imen + IRQ_BYTE(irq_num),%al ; \ orb $IRQ_BIT(irq_num),%al ; \ movb %al,_imen + IRQ_BYTE(irq_num) ; \ outb %al,$icu+ICU_IMR_OFFSET ; \ enable_icus ; \ movl _cpl,%eax ; \ testb $IRQ_BIT(irq_num),%reg ; \ jne 2f ; \ incb _intr_nesting_level ; \ __CONCAT(Xresume,irq_num): ; \ FAKE_MCOUNT(12*4(%esp)) ; /* XXX late to avoid double count */ \ incl _cnt+V_INTR ; /* tally interrupts */ \ movl _intr_countp + (irq_num) * 4,%eax ; \ incl (%eax) ; \ movl _cpl,%eax ; \ pushl %eax ; \ pushl _intr_unit + (irq_num) * 4 ; \ orl _intr_mask + (irq_num) * 4,%eax ; \ movl %eax,_cpl ; \ sti ; \ call *_intr_handler + (irq_num) * 4 ; \ cli ; /* must unmask _imen and icu atomically */ \ movb _imen + IRQ_BYTE(irq_num),%al ; \ andb $~IRQ_BIT(irq_num),%al ; \ movb %al,_imen + IRQ_BYTE(irq_num) ; \ outb %al,$icu+ICU_IMR_OFFSET ; \ sti ; /* XXX _doreti repeats the cli/sti */ \ MEXITCOUNT ; \ /* We could usually avoid the following jmp by inlining some of */ \ /* _doreti, but it's probably better to use less cache. */ \ jmp _doreti ; \ ; \ ALIGN_TEXT ; \ 2: ; \ /* XXX skip mcounting here to avoid double count */ \ orb $IRQ_BIT(irq_num),_ipending + IRQ_BYTE(irq_num) ; \ popl %es ; \ popl %ds ; \ popal ; \ addl $4+4,%esp ; \ iret MCOUNT_LABEL(bintr) FAST_INTR(0,fastintr0, ENABLE_ICU1) FAST_INTR(1,fastintr1, ENABLE_ICU1) FAST_INTR(2,fastintr2, ENABLE_ICU1) FAST_INTR(3,fastintr3, ENABLE_ICU1) FAST_INTR(4,fastintr4, ENABLE_ICU1) FAST_INTR(5,fastintr5, ENABLE_ICU1) FAST_INTR(6,fastintr6, ENABLE_ICU1) FAST_INTR(7,fastintr7, ENABLE_ICU1) FAST_INTR(8,fastintr8, ENABLE_ICU1_AND_2) FAST_INTR(9,fastintr9, ENABLE_ICU1_AND_2) FAST_INTR(10,fastintr10, ENABLE_ICU1_AND_2) FAST_INTR(11,fastintr11, ENABLE_ICU1_AND_2) FAST_INTR(12,fastintr12, ENABLE_ICU1_AND_2) FAST_INTR(13,fastintr13, ENABLE_ICU1_AND_2) FAST_INTR(14,fastintr14, ENABLE_ICU1_AND_2) FAST_INTR(15,fastintr15, ENABLE_ICU1_AND_2) INTR(0,intr0, IO_ICU1, ENABLE_ICU1, al) INTR(1,intr1, IO_ICU1, ENABLE_ICU1, al) INTR(2,intr2, IO_ICU1, ENABLE_ICU1, al) INTR(3,intr3, IO_ICU1, ENABLE_ICU1, al) INTR(4,intr4, IO_ICU1, ENABLE_ICU1, al) INTR(5,intr5, IO_ICU1, ENABLE_ICU1, al) INTR(6,intr6, IO_ICU1, ENABLE_ICU1, al) INTR(7,intr7, IO_ICU1, ENABLE_ICU1, al) INTR(8,intr8, IO_ICU2, ENABLE_ICU1_AND_2, ah) INTR(9,intr9, IO_ICU2, ENABLE_ICU1_AND_2, ah) INTR(10,intr10, IO_ICU2, ENABLE_ICU1_AND_2, ah) INTR(11,intr11, IO_ICU2, ENABLE_ICU1_AND_2, ah) INTR(12,intr12, IO_ICU2, ENABLE_ICU1_AND_2, ah) INTR(13,intr13, IO_ICU2, ENABLE_ICU1_AND_2, ah) INTR(14,intr14, IO_ICU2, ENABLE_ICU1_AND_2, ah) INTR(15,intr15, IO_ICU2, ENABLE_ICU1_AND_2, ah) MCOUNT_LABEL(eintr) .data .globl _ihandlers _ihandlers: /* addresses of interrupt handlers */ /* actually resumption addresses for HWI's */ .long Xresume0, Xresume1, Xresume2, Xresume3 .long Xresume4, Xresume5, Xresume6, Xresume7 .long Xresume8, Xresume9, Xresume10, Xresume11 .long Xresume12, Xresume13, Xresume14, Xresume15 - .long swi_tty, swi_net, dummycamisr, dummycamisr + .long _swi_null, swi_net, _swi_null, _swi_null .long _swi_vm, _swi_null, _swi_null, _swi_null .long _swi_null, _swi_null, _swi_null, _swi_null .long _swi_null, _swi_null, _softclock, swi_ast imasks: /* masks for interrupt handlers */ .space NHWI*4 /* padding; HWI masks are elsewhere */ .long SWI_TTY_MASK, SWI_NET_MASK, SWI_CAMNET_MASK, SWI_CAMBIO_MASK .long SWI_VM_MASK, 0, 0, 0 .long 0, 0, 0, 0 .long 0, 0, SWI_CLOCK_MASK, SWI_AST_MASK /* * Interrupt counters and names. The format of these and the label names * must agree with what vmstat expects. The tables are indexed by device * ids so that we don't have to move the names around as devices are * attached. */ #include "vector.h" .globl _intrcnt, _eintrcnt _intrcnt: .space (NR_DEVICES + ICU_LEN) * 4 _eintrcnt: .globl _intrnames, _eintrnames _intrnames: .ascii DEVICE_NAMES .asciz "stray irq0" .asciz "stray irq1" .asciz "stray irq2" .asciz "stray irq3" .asciz "stray irq4" .asciz "stray irq5" .asciz "stray irq6" .asciz "stray irq7" .asciz "stray irq8" .asciz "stray irq9" .asciz "stray irq10" .asciz "stray irq11" .asciz "stray irq12" .asciz "stray irq13" .asciz "stray irq14" .asciz "stray irq15" _eintrnames: .text Index: head/sys/i386/isa/cy.c =================================================================== --- head/sys/i386/isa/cy.c (revision 38245) +++ head/sys/i386/isa/cy.c (revision 38246) @@ -1,2589 +1,2585 @@ /*- * cyclades cyclom-y serial driver * Andrew Herbert , 17 August 1993 * * Copyright (c) 1993 Andrew Herbert. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. The name Andrew Herbert may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY ``AS IS'' AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN * NO EVENT SHALL I BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * - * $Id: cy.c,v 1.64 1998/07/15 12:18:12 bde Exp $ + * $Id: cy.c,v 1.65 1998/07/29 18:48:20 bde Exp $ */ #include "opt_compat.h" #include "opt_devfs.h" #include "cy.h" /* * TODO: * Implement BREAK. * Fix overflows when closing line. * Atomic COR change. * Consoles. */ /* * Temporary compile-time configuration options. */ #define RxFifoThreshold (CD1400_RX_FIFO_SIZE / 2) /* Number of chars in the receiver FIFO before an * an interrupt is generated. Should depend on * line speed. Needs to be about 6 on a 486DX33 * for 4 active ports at 115200 bps. Why doesn't * 10 work? */ #define PollMode /* Use polling-based irq service routine, not the * hardware svcack lines. Must be defined for * Cyclom-16Y boards. Less efficient for Cyclom-8Ys, * and stops 4 * 115200 bps from working. */ #undef Smarts /* Enable slightly more CD1400 intelligence. Mainly * the output CR/LF processing, plus we can avoid a * few checks usually done in ttyinput(). * * XXX not fully implemented, and not particularly * worthwhile. */ #undef CyDebug /* Include debugging code (not very expensive). */ /* These will go away. */ #undef SOFT_CTS_OFLOW #define SOFT_HOTCHAR #include #include #include #include #include #include #include +#include #include #include #include #ifdef DEVFS #include #endif #include +#include #include #include #include #ifdef SMP #define disable_intr() COM_DISABLE_INTR() #define enable_intr() COM_ENABLE_INTR() #endif /* SMP */ /* * Dictionary so that I can name everything *sio* or *com* to compare with * sio.c. There is also lots of ugly formatting and unnecessary ifdefs to * simplify the comparision. These will go away. */ #define LSR_BI CD1400_RDSR_BREAK #define LSR_FE CD1400_RDSR_FE #define LSR_OE CD1400_RDSR_OE #define LSR_PE CD1400_RDSR_PE #define MCR_DTR CD1400_MSVR2_DTR #define MCR_RTS CD1400_MSVR1_RTS #define MSR_CTS CD1400_MSVR2_CTS #define MSR_DCD CD1400_MSVR2_CD #define MSR_DSR CD1400_MSVR2_DSR #define MSR_RI CD1400_MSVR2_RI #define NSIO (NCY * CY_MAX_PORTS) #define comconsole cyconsole #define comdefaultrate cydefaultrate #define com_events cy_events #define comhardclose cyhardclose #define commctl cymctl #define comparam cyparam +#define siopoll_registered cypoll_registered #define comspeed cyspeed #define comstart cystart #define comwakeup cywakeup #define nsio_tty ncy_tty #define p_com_addr p_cy_addr #define sioattach cyattach #define sioclose cyclose #define siodevtotty cydevtotty #define siodriver cydriver #define siodtrwakeup cydtrwakeup #define sioioctl cyioctl #define siointr cyintr #define siointr1 cyintr1 #define sioopen cyopen #define siopoll cypoll #define sioprobe cyprobe #define sioread cyread #define siosettimeout cysettimeout #define siostop cystop #define siowrite cywrite #define sio_timeout cy_timeout #define sio_timeouts_until_log cy_timeouts_until_log #define sio_timeout_handle cy_timeout_handle #define sio_tty cy_tty #define CY_MAX_PORTS (CD1400_NO_OF_CHANNELS * CY_MAX_CD1400s) /* We encode the cyclom unit number (cyu) in spare bits in the IVR's. */ #define CD1400_xIVR_CHAN_SHIFT 3 #define CD1400_xIVR_CHAN 0x1F #define LOTS_OF_EVENTS 64 /* helps separate urgent events from input */ #define RB_I_HIGH_WATER (TTYHOG - 2 * RS_IBUFSIZE) #define RS_IBUFSIZE 256 #define CALLOUT_MASK 0x80 #define CONTROL_MASK 0x60 #define CONTROL_INIT_STATE 0x20 #define CONTROL_LOCK_STATE 0x40 #define DEV_TO_UNIT(dev) (MINOR_TO_UNIT(minor(dev))) #define MINOR_MAGIC_MASK (CALLOUT_MASK | CONTROL_MASK) #define MINOR_TO_UNIT(mynor) (((mynor) >> 16) * CY_MAX_PORTS \ | (((mynor) & 0xff) & ~MINOR_MAGIC_MASK)) /* * Input buffer watermarks. * The external device is asked to stop sending when the buffer exactly reaches * high water, or when the high level requests it. * The high level is notified immediately (rather than at a later clock tick) * when this watermark is reached. * The buffer size is chosen so the watermark should almost never be reached. * The low watermark is invisibly 0 since the buffer is always emptied all at * once. */ #define RS_IHIGHWATER (3 * RS_IBUFSIZE / 4) /* * com state bits. * (CS_BUSY | CS_TTGO) and (CS_BUSY | CS_TTGO | CS_ODEVREADY) must be higher * than the other bits so that they can be tested as a group without masking * off the low bits. * * The following com and tty flags correspond closely: * CS_BUSY = TS_BUSY (maintained by comstart(), siopoll() and * siostop()) * CS_TTGO = ~TS_TTSTOP (maintained by comparam() and comstart()) * CS_CTS_OFLOW = CCTS_OFLOW (maintained by comparam()) * CS_RTS_IFLOW = CRTS_IFLOW (maintained by comparam()) * TS_FLUSH is not used. * XXX I think TIOCSETA doesn't clear TS_TTSTOP when it clears IXON. * XXX CS_*FLOW should be CF_*FLOW in com->flags (control flags not state). */ #define CS_BUSY 0x80 /* output in progress */ #define CS_TTGO 0x40 /* output not stopped by XOFF */ #define CS_ODEVREADY 0x20 /* external device h/w ready (CTS) */ #define CS_CHECKMSR 1 /* check of MSR scheduled */ #define CS_CTS_OFLOW 2 /* use CTS output flow control */ #define CS_DTR_OFF 0x10 /* DTR held off */ #define CS_ODONE 4 /* output completed */ #define CS_RTS_IFLOW 8 /* use RTS input flow control */ static char const * const error_desc[] = { #define CE_OVERRUN 0 "silo overflow", #define CE_INTERRUPT_BUF_OVERFLOW 1 "interrupt-level buffer overflow", #define CE_TTY_BUF_OVERFLOW 2 "tty-level buffer overflow", }; #define CE_NTYPES 3 #define CE_RECORD(com, errnum) (++(com)->delta_error_counts[errnum]) /* types. XXX - should be elsewhere */ typedef u_char bool_t; /* boolean */ typedef u_char volatile *cy_addr; /* queue of linear buffers */ struct lbq { u_char *l_head; /* next char to process */ u_char *l_tail; /* one past the last char to process */ struct lbq *l_next; /* next in queue */ bool_t l_queued; /* nonzero if queued */ }; /* com device structure */ struct com_s { u_char state; /* miscellaneous flag bits */ bool_t active_out; /* nonzero if the callout device is open */ #if 0 u_char cfcr_image; /* copy of value written to CFCR */ u_char fifo_image; /* copy of value written to FIFO */ bool_t hasfifo; /* nonzero for 16550 UARTs */ bool_t loses_outints; /* nonzero if device loses output interrupts */ #endif u_char mcr_image; /* copy of value written to MCR */ #if 0 #ifdef COM_MULTIPORT bool_t multiport; /* is this unit part of a multiport device? */ #endif /* COM_MULTIPORT */ bool_t no_irq; /* nonzero if irq is not attached */ bool_t poll; /* nonzero if polling is required */ bool_t poll_output; /* nonzero if polling for output is required */ #endif int unit; /* unit number */ int dtr_wait; /* time to hold DTR down on close (* 1/hz) */ #if 0 u_int tx_fifo_size; #endif u_int wopeners; /* # processes waiting for DCD in open() */ /* * The high level of the driver never reads status registers directly * because there would be too many side effects to handle conveniently. * Instead, it reads copies of the registers stored here by the * interrupt handler. */ u_char last_modem_status; /* last MSR read by intr handler */ u_char prev_modem_status; /* last MSR handled by high level */ u_char hotchar; /* ldisc-specific char to be handled ASAP */ u_char *ibuf; /* start of input buffer */ u_char *ibufend; /* end of input buffer */ u_char *ihighwater; /* threshold in input buffer */ u_char *iptr; /* next free spot in input buffer */ struct lbq obufq; /* head of queue of output buffers */ struct lbq obufs[2]; /* output buffers */ int cy_align; /* index for register alignment */ cy_addr cy_iobase; /* base address of this port's cyclom */ cy_addr iobase; /* base address of this port's cd1400 */ struct tty *tp; /* cross reference */ /* Initial state. */ struct termios it_in; /* should be in struct tty */ struct termios it_out; /* Lock state. */ struct termios lt_in; /* should be in struct tty */ struct termios lt_out; bool_t do_timestamp; bool_t do_dcd_timestamp; struct timeval timestamp; struct timeval dcd_timestamp; u_long bytes_in; /* statistics */ u_long bytes_out; u_int delta_error_counts[CE_NTYPES]; u_long error_counts[CE_NTYPES]; u_int recv_exception; /* exception chars received */ u_int mdm; /* modem signal changes */ #ifdef CyDebug u_int start_count; /* no. of calls to comstart() */ u_int start_real; /* no. of calls that did something */ #endif u_char channel_control;/* CD1400 CCR control command shadow */ u_char cor[3]; /* CD1400 COR1-3 shadows */ u_char intr_enable; /* CD1400 SRER shadow */ /* * Ping-pong input buffers. The extra factor of 2 in the sizes is * to allow for an error byte for each input byte. */ #define CE_INPUT_OFFSET RS_IBUFSIZE u_char ibuf1[2 * RS_IBUFSIZE]; u_char ibuf2[2 * RS_IBUFSIZE]; /* * Data area for output buffers. Someday we should build the output * buffer queue without copying data. */ u_char obuf1[256]; u_char obuf2[256]; #ifdef DEVFS void *devfs_token_ttyd; void *devfs_token_ttyl; void *devfs_token_ttyi; void *devfs_token_cuaa; void *devfs_token_cual; void *devfs_token_cuai; #endif }; -/* - * XXX public functions in drivers should be declared in headers produced - * by `config', not here. - */ - -/* Interrupt handling entry point. */ -void siopoll __P((void)); - -/* Device switch entry points. */ -#define sioreset noreset -#define siommap nommap -#define siostrategy nostrategy - /* PCI driver entry point. */ int cyattach_common __P((cy_addr cy_iobase, int cy_align)); static int cy_units __P((cy_addr cy_iobase, int cy_align)); static int sioattach __P((struct isa_device *dev)); static void cd1400_channel_cmd __P((cy_addr iobase, int cmd, int cy_align)); static timeout_t siodtrwakeup; static void comhardclose __P((struct com_s *com)); #if 0 static void siointr1 __P((struct com_s *com)); #endif static int commctl __P((struct com_s *com, int bits, int how)); static int comparam __P((struct tty *tp, struct termios *t)); +static swihand_t siopoll; static int sioprobe __P((struct isa_device *dev)); static void siosettimeout __P((void)); static int comspeed __P((speed_t speed, int *prescaler_io)); static void comstart __P((struct tty *tp)); static timeout_t comwakeup; static void disc_optim __P((struct tty *tp, struct termios *t, struct com_s *com)); #ifdef CyDebug void cystatus __P((int unit)); #endif static char driver_name[] = "cy"; /* table and macro for fast conversion from a unit number to its com struct */ static struct com_s *p_com_addr[NSIO]; #define com_addr(unit) (p_com_addr[unit]) struct isa_driver siodriver = { sioprobe, sioattach, driver_name }; static d_open_t sioopen; static d_close_t sioclose; static d_read_t sioread; static d_write_t siowrite; static d_ioctl_t sioioctl; static d_stop_t siostop; static d_devtotty_t siodevtotty; #define CDEV_MAJOR 48 static struct cdevsw sio_cdevsw = { sioopen, sioclose, sioread, siowrite, sioioctl, siostop, noreset, siodevtotty, ttpoll, nommap, NULL, driver_name, NULL, -1, }; static int comconsole = -1; static speed_t comdefaultrate = TTYDEF_SPEED; static u_int com_events; /* input chars + weighted output completions */ +static bool_t siopoll_registered; static int sio_timeout; static int sio_timeouts_until_log; static struct callout_handle sio_timeout_handle = CALLOUT_HANDLE_INITIALIZER(&sio_timeout_handle); #if 0 /* XXX */ static struct tty *sio_tty[NSIO]; #else static struct tty sio_tty[NSIO]; #endif static const int nsio_tty = NSIO; #ifdef CyDebug static u_int cd_inbs; static u_int cy_inbs; static u_int cd_outbs; static u_int cy_outbs; static u_int cy_svrr_probes; static u_int cy_timeouts; #endif static int cy_chip_offset[] = { 0x0000, 0x0400, 0x0800, 0x0c00, 0x0200, 0x0600, 0x0a00, 0x0e00, }; static int cy_nr_cd1400s[NCY]; static int cy_total_devices; #undef RxFifoThreshold static int volatile RxFifoThreshold = (CD1400_RX_FIFO_SIZE / 2); static int sioprobe(dev) struct isa_device *dev; { cy_addr iobase; iobase = (cy_addr)dev->id_maddr; /* Cyclom-16Y hardware reset (Cyclom-8Ys don't care) */ cd_inb(iobase, CY16_RESET, 0); /* XXX? */ DELAY(500); /* wait for the board to get its act together */ /* this is needed to get the board out of reset */ cd_outb(iobase, CY_CLEAR_INTR, 0, 0); DELAY(500); return (cy_units(iobase, 0) == 0 ? 0 : -1); } static int cy_units(cy_iobase, cy_align) cy_addr cy_iobase; int cy_align; { int cyu; u_char firmware_version; int i; cy_addr iobase; for (cyu = 0; cyu < CY_MAX_CD1400s; ++cyu) { iobase = cy_iobase + (cy_chip_offset[cyu] << cy_align); /* wait for chip to become ready for new command */ for (i = 0; i < 10; i++) { DELAY(50); if (!cd_inb(iobase, CD1400_CCR, cy_align)) break; } /* clear the GFRCR register */ cd_outb(iobase, CD1400_GFRCR, cy_align, 0); /* issue a reset command */ cd_outb(iobase, CD1400_CCR, cy_align, CD1400_CCR_CMDRESET | CD1400_CCR_FULLRESET); /* wait for the CD1400 to initialize itself */ for (i = 0; i < 200; i++) { DELAY(50); /* retrieve firmware version */ firmware_version = cd_inb(iobase, CD1400_GFRCR, cy_align); if ((firmware_version & 0xf0) == 0x40) break; } /* * Anything in the 0x40-0x4F range is fine. * If one CD1400 is bad then we don't support higher * numbered good ones on this board. */ if ((firmware_version & 0xf0) != 0x40) break; } return (cyu); } static int sioattach(isdp) struct isa_device *isdp; { int adapter; adapter = cyattach_common((cy_addr) isdp->id_maddr, 0); if (adapter < 0) return (0); /* * XXX * This kludge is to allow ISA/PCI device specifications in the * kernel config file to be in any order. */ if (isdp->id_unit != adapter) { printf("cy%d: attached as cy%d\n", isdp->id_unit, adapter); isdp->id_unit = adapter; /* XXX */ } isdp->id_ri_flags |= RI_FAST; return (1); } int cyattach_common(cy_iobase, cy_align) cy_addr cy_iobase; int cy_align; { int adapter; int cyu; dev_t dev; cy_addr iobase; int ncyu; int unit; adapter = cy_total_devices; if ((u_int)adapter >= NCY) { printf( "cy%d: can't attach adapter: insufficient cy devices configured\n", adapter); return (-1); } ncyu = cy_units(cy_iobase, cy_align); if (ncyu == 0) return (-1); cy_nr_cd1400s[adapter] = ncyu; cy_total_devices++; unit = adapter * CY_MAX_PORTS; for (cyu = 0; cyu < ncyu; ++cyu) { int cdu; iobase = (cy_addr) (cy_iobase + (cy_chip_offset[cyu] << cy_align)); /* Set up a receive timeout period of than 1+ ms. */ cd_outb(iobase, CD1400_PPR, cy_align, howmany(CY_CLOCK / CD1400_PPR_PRESCALER, 1000)); for (cdu = 0; cdu < CD1400_NO_OF_CHANNELS; ++cdu, ++unit) { struct com_s *com; int s; com = malloc(sizeof *com, M_DEVBUF, M_NOWAIT); if (com == NULL) break; bzero(com, sizeof *com); com->unit = unit; com->dtr_wait = 3 * hz; com->iptr = com->ibuf = com->ibuf1; com->ibufend = com->ibuf1 + RS_IBUFSIZE; com->ihighwater = com->ibuf1 + RS_IHIGHWATER; com->obufs[0].l_head = com->obuf1; com->obufs[1].l_head = com->obuf2; com->cy_align = cy_align; com->cy_iobase = cy_iobase; com->iobase = iobase; /* * We don't use all the flags from since they * are only relevant for logins. It's important to have echo off * initially so that the line doesn't start blathering before the * echo flag can be turned off. */ com->it_in.c_iflag = 0; com->it_in.c_oflag = 0; com->it_in.c_cflag = TTYDEF_CFLAG; com->it_in.c_lflag = 0; if (unit == comconsole) { com->it_in.c_iflag = TTYDEF_IFLAG; com->it_in.c_oflag = TTYDEF_OFLAG; com->it_in.c_cflag = TTYDEF_CFLAG | CLOCAL; com->it_in.c_lflag = TTYDEF_LFLAG; com->lt_out.c_cflag = com->lt_in.c_cflag = CLOCAL; } termioschars(&com->it_in); com->it_in.c_ispeed = com->it_in.c_ospeed = comdefaultrate; com->it_out = com->it_in; s = spltty(); com_addr(unit) = com; splx(s); dev = makedev(CDEV_MAJOR, 0); cdevsw_add(&dev, &sio_cdevsw, NULL); #ifdef DEVFS com->devfs_token_ttyd = devfs_add_devswf(&sio_cdevsw, unit, DV_CHR, UID_ROOT, GID_WHEEL, 0600, "ttyc%r%r", adapter, unit % CY_MAX_PORTS); com->devfs_token_ttyi = devfs_add_devswf(&sio_cdevsw, unit | CONTROL_INIT_STATE, DV_CHR, UID_ROOT, GID_WHEEL, 0600, "ttyic%r%r", adapter, unit % CY_MAX_PORTS); com->devfs_token_ttyl = devfs_add_devswf(&sio_cdevsw, unit | CONTROL_LOCK_STATE, DV_CHR, UID_ROOT, GID_WHEEL, 0600, "ttylc%r%r", adapter, unit % CY_MAX_PORTS); com->devfs_token_cuaa = devfs_add_devswf(&sio_cdevsw, unit | CALLOUT_MASK, DV_CHR, UID_UUCP, GID_DIALER, 0660, "cuac%r%r", adapter, unit % CY_MAX_PORTS); com->devfs_token_cuai = devfs_add_devswf(&sio_cdevsw, unit | CALLOUT_MASK | CONTROL_INIT_STATE, DV_CHR, UID_UUCP, GID_DIALER, 0660, "cuaic%r%r", adapter, unit % CY_MAX_PORTS); com->devfs_token_cual = devfs_add_devswf(&sio_cdevsw, unit | CALLOUT_MASK | CONTROL_LOCK_STATE, DV_CHR, UID_UUCP, GID_DIALER, 0660, "cualc%r%r", adapter, unit % CY_MAX_PORTS); #endif } } + if (!siopoll_registered) { + register_swi(SWI_TTY, siopoll); + siopoll_registered = TRUE; + } /* ensure an edge for the next interrupt */ cd_outb(cy_iobase, CY_CLEAR_INTR, cy_align, 0); return (adapter); } static int sioopen(dev, flag, mode, p) dev_t dev; int flag; int mode; struct proc *p; { struct com_s *com; int error; cy_addr iobase; int mynor; int s; struct tty *tp; int unit; mynor = minor(dev); unit = MINOR_TO_UNIT(mynor); if ((u_int) unit >= NSIO || (com = com_addr(unit)) == NULL) return (ENXIO); if (mynor & CONTROL_MASK) return (0); #if 0 /* XXX */ tp = com->tp = sio_tty[unit] = ttymalloc(sio_tty[unit]); #else tp = com->tp = &sio_tty[unit]; #endif s = spltty(); /* * We jump to this label after all non-interrupted sleeps to pick * up any changes of the device state. */ open_top: while (com->state & CS_DTR_OFF) { error = tsleep(&com->dtr_wait, TTIPRI | PCATCH, "cydtr", 0); if (error != 0) goto out; } if (tp->t_state & TS_ISOPEN) { /* * The device is open, so everything has been initialized. * Handle conflicts. */ if (mynor & CALLOUT_MASK) { if (!com->active_out) { error = EBUSY; goto out; } } else { if (com->active_out) { if (flag & O_NONBLOCK) { error = EBUSY; goto out; } error = tsleep(&com->active_out, TTIPRI | PCATCH, "cybi", 0); if (error != 0) goto out; goto open_top; } } if (tp->t_state & TS_XCLUDE && p->p_ucred->cr_uid != 0) { error = EBUSY; goto out; } } else { /* * The device isn't open, so there are no conflicts. * Initialize it. Initialization is done twice in many * cases: to preempt sleeping callin opens if we are * callout, and to complete a callin open after DCD rises. */ tp->t_oproc = comstart; tp->t_param = comparam; tp->t_dev = dev; tp->t_termios = mynor & CALLOUT_MASK ? com->it_out : com->it_in; #if 0 (void)commctl(com, TIOCM_DTR | TIOCM_RTS, DMSET); com->poll = com->no_irq; com->poll_output = com->loses_outints; #endif ++com->wopeners; iobase = com->iobase; /* reset this channel */ cd_outb(iobase, CD1400_CAR, com->cy_align, unit & CD1400_CAR_CHAN); cd1400_channel_cmd(iobase, CD1400_CCR_CMDRESET, com->cy_align); /* * Resetting disables the transmitter and receiver as well as * flushing the fifos so some of our cached state becomes * invalid. The documentation suggests that all registers * for the current channel are reset to defaults, but * apparently none are. We wouldn't want DTR cleared. */ com->channel_control = 0; /* Encode per-board unit in LIVR for access in intr routines. */ cd_outb(iobase, CD1400_LIVR, com->cy_align, (unit & CD1400_xIVR_CHAN) << CD1400_xIVR_CHAN_SHIFT); /* * raise dtr and generally set things up correctly. this * has the side-effect of selecting the appropriate cd1400 * channel, to help us with subsequent channel control stuff */ error = comparam(tp, &tp->t_termios); --com->wopeners; if (error != 0) goto out; /* * XXX we should goto open_top if comparam() slept. */ ttsetwater(tp); #if 0 if (com->hasfifo) { /* * (Re)enable and drain fifos. * * Certain SMC chips cause problems if the fifos * are enabled while input is ready. Turn off the * fifo if necessary to clear the input. We test * the input ready bit after enabling the fifos * since we've already enabled them in comparam() * and to handle races between enabling and fresh * input. */ while (TRUE) { outb(iobase + com_fifo, FIFO_RCV_RST | FIFO_XMT_RST | com->fifo_image); DELAY(100); if (!(inb(com->line_status_port) & LSR_RXRDY)) break; outb(iobase + com_fifo, 0); DELAY(100); (void) inb(com->data_port); } } disable_intr(); (void) inb(com->line_status_port); (void) inb(com->data_port); com->prev_modem_status = com->last_modem_status = inb(com->modem_status_port); outb(iobase + com_ier, IER_ERXRDY | IER_ETXRDY | IER_ERLS | IER_EMSC); enable_intr(); #else /* !0 */ /* XXX raise RTS too */ (void)commctl(com, TIOCM_DTR | TIOCM_RTS, DMSET); disable_intr(); com->prev_modem_status = com->last_modem_status = cd_inb(iobase, CD1400_MSVR2, com->cy_align); cd_outb(iobase, CD1400_SRER, com->cy_align, com->intr_enable = CD1400_SRER_MDMCH | CD1400_SRER_RXDATA); enable_intr(); #endif /* 0 */ /* * Handle initial DCD. Callout devices get a fake initial * DCD (trapdoor DCD). If we are callout, then any sleeping * callin opens get woken up and resume sleeping on "cybi" * instead of "cydcd". */ /* * XXX `mynor & CALLOUT_MASK' should be * `tp->t_cflag & (SOFT_CARRIER | TRAPDOOR_CARRIER) where * TRAPDOOR_CARRIER is the default initial state for callout * devices and SOFT_CARRIER is like CLOCAL except it hides * the true carrier. */ if (com->prev_modem_status & MSR_DCD || mynor & CALLOUT_MASK) (*linesw[tp->t_line].l_modem)(tp, 1); } /* * Wait for DCD if necessary. */ if (!(tp->t_state & TS_CARR_ON) && !(mynor & CALLOUT_MASK) && !(tp->t_cflag & CLOCAL) && !(flag & O_NONBLOCK)) { ++com->wopeners; error = tsleep(TSA_CARR_ON(tp), TTIPRI | PCATCH, "cydcd", 0); --com->wopeners; if (error != 0) goto out; goto open_top; } error = (*linesw[tp->t_line].l_open)(dev, tp); disc_optim(tp, &tp->t_termios, com); if (tp->t_state & TS_ISOPEN && mynor & CALLOUT_MASK) com->active_out = TRUE; siosettimeout(); out: splx(s); if (!(tp->t_state & TS_ISOPEN) && com->wopeners == 0) comhardclose(com); return (error); } static int sioclose(dev, flag, mode, p) dev_t dev; int flag; int mode; struct proc *p; { struct com_s *com; int mynor; int s; struct tty *tp; mynor = minor(dev); if (mynor & CONTROL_MASK) return (0); com = com_addr(MINOR_TO_UNIT(mynor)); tp = com->tp; s = spltty(); (*linesw[tp->t_line].l_close)(tp, flag); disc_optim(tp, &tp->t_termios, com); siostop(tp, FREAD | FWRITE); comhardclose(com); ttyclose(tp); siosettimeout(); splx(s); #ifdef broken /* session holds a ref to the tty; can't deallocate */ ttyfree(tp); com->tp = sio_tty[unit] = NULL; #endif return (0); } static void comhardclose(com) struct com_s *com; { cy_addr iobase; int s; struct tty *tp; int unit; unit = com->unit; iobase = com->iobase; s = spltty(); #if 0 com->poll = FALSE; com->poll_output = FALSE; #endif com->do_timestamp = 0; cd_outb(iobase, CD1400_CAR, com->cy_align, unit & CD1400_CAR_CHAN); #if 0 outb(iobase + com_cfcr, com->cfcr_image &= ~CFCR_SBREAK); #endif { #if 0 outb(iobase + com_ier, 0); #else disable_intr(); cd_outb(iobase, CD1400_SRER, com->cy_align, com->intr_enable = 0); enable_intr(); #endif tp = com->tp; if (tp->t_cflag & HUPCL /* * XXX we will miss any carrier drop between here and the * next open. Perhaps we should watch DCD even when the * port is closed; it is not sufficient to check it at * the next open because it might go up and down while * we're not watching. */ || !com->active_out && !(com->prev_modem_status & MSR_DCD) && !(com->it_in.c_cflag & CLOCAL) || !(tp->t_state & TS_ISOPEN)) { (void)commctl(com, TIOCM_DTR, DMBIC); /* Disable receiver (leave transmitter enabled). */ com->channel_control = CD1400_CCR_CMDCHANCTL | CD1400_CCR_XMTEN | CD1400_CCR_RCVDIS; cd1400_channel_cmd(iobase, com->channel_control, com->cy_align); if (com->dtr_wait != 0) { timeout(siodtrwakeup, com, com->dtr_wait); com->state |= CS_DTR_OFF; } } } #if 0 if (com->hasfifo) { /* * Disable fifos so that they are off after controlled * reboots. Some BIOSes fail to detect 16550s when the * fifos are enabled. */ outb(iobase + com_fifo, 0); } #endif com->active_out = FALSE; wakeup(&com->active_out); wakeup(TSA_CARR_ON(tp)); /* restart any wopeners */ splx(s); } static int sioread(dev, uio, flag) dev_t dev; struct uio *uio; int flag; { int mynor; struct tty *tp; mynor = minor(dev); if (mynor & CONTROL_MASK) return (ENODEV); tp = com_addr(MINOR_TO_UNIT(mynor))->tp; return ((*linesw[tp->t_line].l_read)(tp, uio, flag)); } static int siowrite(dev, uio, flag) dev_t dev; struct uio *uio; int flag; { int mynor; struct tty *tp; int unit; mynor = minor(dev); if (mynor & CONTROL_MASK) return (ENODEV); unit = MINOR_TO_UNIT(mynor); tp = com_addr(unit)->tp; /* * (XXX) We disallow virtual consoles if the physical console is * a serial port. This is in case there is a display attached that * is not the console. In that situation we don't need/want the X * server taking over the console. */ if (constty != NULL && unit == comconsole) constty = NULL; #ifdef Smarts /* XXX duplicate ttwrite(), but without so much output processing on * CR & LF chars. Hardly worth the effort, given that high-throughput * sessions are raw anyhow. */ #else return ((*linesw[tp->t_line].l_write)(tp, uio, flag)); #endif } static void siodtrwakeup(chan) void *chan; { struct com_s *com; com = (struct com_s *)chan; com->state &= ~CS_DTR_OFF; wakeup(&com->dtr_wait); } void siointr(unit) int unit; { int baseu; int cy_align; cy_addr cy_iobase; int cyu; cy_addr iobase; u_char status; COM_LOCK(); /* XXX could this be placed down lower in the loop? */ baseu = unit * CY_MAX_PORTS; cy_align = com_addr(baseu)->cy_align; cy_iobase = com_addr(baseu)->cy_iobase; /* check each CD1400 in turn */ for (cyu = 0; cyu < cy_nr_cd1400s[unit]; ++cyu) { iobase = (cy_addr) (cy_iobase + (cy_chip_offset[cyu] << cy_align)); /* poll to see if it has any work */ status = cd_inb(iobase, CD1400_SVRR, cy_align); if (status == 0) continue; #ifdef CyDebug ++cy_svrr_probes; #endif /* service requests as appropriate, giving priority to RX */ if (status & CD1400_SVRR_RXRDY) { struct com_s *com; u_int count; u_char *ioptr; u_char line_status; u_char recv_data; u_char serv_type; #ifdef PollMode u_char save_car; u_char save_rir; #endif #ifdef PollMode save_rir = cd_inb(iobase, CD1400_RIR, cy_align); save_car = cd_inb(iobase, CD1400_CAR, cy_align); /* enter rx service */ cd_outb(iobase, CD1400_CAR, cy_align, save_rir); serv_type = cd_inb(iobase, CD1400_RIVR, cy_align); com = com_addr(baseu + ((serv_type >> CD1400_xIVR_CHAN_SHIFT) & CD1400_xIVR_CHAN)); #else /* ack receive service */ serv_type = cy_inb(iobase, CY8_SVCACKR); com = com_addr(baseu + + ((serv_type >> CD1400_xIVR_CHAN_SHIFT) & CD1400_xIVR_CHAN)); #endif if (serv_type & CD1400_RIVR_EXCEPTION) { ++com->recv_exception; line_status = cd_inb(iobase, CD1400_RDSR, cy_align); /* break/unnattached error bits or real input? */ recv_data = cd_inb(iobase, CD1400_RDSR, cy_align); #ifndef SOFT_HOTCHAR if (line_status & CD1400_RDSR_SPECIAL && com->hotchar != 0) setsofttty(); #endif #if 1 /* XXX "intelligent" PFO error handling would break O error handling */ if (line_status & (LSR_PE|LSR_FE|LSR_BI)) { /* Don't store PE if IGNPAR and BI if IGNBRK, this hack allows "raw" tty optimization works even if IGN* is set. */ if ( com->tp == NULL || !(com->tp->t_state & TS_ISOPEN) || (line_status & (LSR_PE|LSR_FE)) && (com->tp->t_iflag & IGNPAR) || (line_status & LSR_BI) && (com->tp->t_iflag & IGNBRK)) goto cont; if ( (line_status & (LSR_PE|LSR_FE)) && (com->tp->t_state & TS_CAN_BYPASS_L_RINT) && ((line_status & LSR_FE) || (line_status & LSR_PE) && (com->tp->t_iflag & INPCK))) recv_data = 0; } #endif /* 1 */ ++com->bytes_in; #ifdef SOFT_HOTCHAR if (com->hotchar != 0 && recv_data == com->hotchar) setsofttty(); #endif ioptr = com->iptr; if (ioptr >= com->ibufend) CE_RECORD(com, CE_INTERRUPT_BUF_OVERFLOW); else { if (com->do_timestamp) microtime(&com->timestamp); ++com_events; ioptr[0] = recv_data; ioptr[CE_INPUT_OFFSET] = line_status; com->iptr = ++ioptr; if (ioptr == com->ihighwater && com->state & CS_RTS_IFLOW) #if 0 outb(com->modem_ctl_port, com->mcr_image &= ~MCR_RTS); #else cd_outb(iobase, CD1400_MSVR1, cy_align, com->mcr_image &= ~MCR_RTS); #endif if (line_status & LSR_OE) CE_RECORD(com, CE_OVERRUN); } goto cont; } else { int ifree; count = cd_inb(iobase, CD1400_RDCR, cy_align); if (!count) goto cont; com->bytes_in += count; ioptr = com->iptr; ifree = com->ibufend - ioptr; if (count > ifree) { count -= ifree; com_events += ifree; if (ifree != 0) { if (com->do_timestamp) microtime(&com->timestamp); do { recv_data = cd_inb(iobase, CD1400_RDSR, cy_align); #ifdef SOFT_HOTCHAR if (com->hotchar != 0 && recv_data == com->hotchar) setsofttty(); #endif ioptr[0] = recv_data; ioptr[CE_INPUT_OFFSET] = 0; ++ioptr; } while (--ifree != 0); } com->delta_error_counts [CE_INTERRUPT_BUF_OVERFLOW] += count; do { recv_data = cd_inb(iobase, CD1400_RDSR, cy_align); #ifdef SOFT_HOTCHAR if (com->hotchar != 0 && recv_data == com->hotchar) setsofttty(); #endif } while (--count != 0); } else { if (com->do_timestamp) microtime(&com->timestamp); if (ioptr <= com->ihighwater && ioptr + count > com->ihighwater && com->state & CS_RTS_IFLOW) #if 0 outb(com->modem_ctl_port, com->mcr_image &= ~MCR_RTS); #else cd_outb(iobase, CD1400_MSVR1, cy_align, com->mcr_image &= ~MCR_RTS); #endif com_events += count; do { recv_data = cd_inb(iobase, CD1400_RDSR, cy_align); #ifdef SOFT_HOTCHAR if (com->hotchar != 0 && recv_data == com->hotchar) setsofttty(); #endif ioptr[0] = recv_data; ioptr[CE_INPUT_OFFSET] = 0; ++ioptr; } while (--count != 0); } com->iptr = ioptr; } cont: /* terminate service context */ #ifdef PollMode cd_outb(iobase, CD1400_RIR, cy_align, save_rir & ~(CD1400_RIR_RDIREQ | CD1400_RIR_RBUSY)); cd_outb(iobase, CD1400_CAR, cy_align, save_car); #else cd_outb(iobase, CD1400_EOSRR, cy_align, 0); #endif } if (status & CD1400_SVRR_MDMCH) { struct com_s *com; u_char modem_status; #ifdef PollMode u_char save_car; u_char save_mir; #else u_char vector; #endif #ifdef PollMode save_mir = cd_inb(iobase, CD1400_MIR, cy_align); save_car = cd_inb(iobase, CD1400_CAR, cy_align); /* enter modem service */ cd_outb(iobase, CD1400_CAR, cy_align, save_mir); com = com_addr(baseu + cyu * CD1400_NO_OF_CHANNELS + (save_mir & CD1400_MIR_CHAN)); #else /* ack modem service */ vector = cy_inb(iobase, CY8_SVCACKM); com = com_addr(baseu + ((vector >> CD1400_xIVR_CHAN_SHIFT) & CD1400_xIVR_CHAN)); #endif ++com->mdm; modem_status = cd_inb(iobase, CD1400_MSVR2, cy_align); if (modem_status != com->last_modem_status) { if (com->do_dcd_timestamp && !(com->last_modem_status & MSR_DCD) && modem_status & MSR_DCD) microtime(&com->dcd_timestamp); /* * Schedule high level to handle DCD changes. Note * that we don't use the delta bits anywhere. Some * UARTs mess them up, and it's easy to remember the * previous bits and calculate the delta. */ com->last_modem_status = modem_status; if (!(com->state & CS_CHECKMSR)) { com_events += LOTS_OF_EVENTS; com->state |= CS_CHECKMSR; setsofttty(); } #ifdef SOFT_CTS_OFLOW /* handle CTS change immediately for crisp flow ctl */ if (com->state & CS_CTS_OFLOW) { if (modem_status & MSR_CTS) { com->state |= CS_ODEVREADY; if (com->state >= (CS_BUSY | CS_TTGO | CS_ODEVREADY) && !(com->intr_enable & CD1400_SRER_TXRDY)) cd_outb(iobase, CD1400_SRER, cy_align, com->intr_enable |= CD1400_SRER_TXRDY); } else { com->state &= ~CS_ODEVREADY; if (com->intr_enable & CD1400_SRER_TXRDY) cd_outb(iobase, CD1400_SRER, cy_align, com->intr_enable &= ~CD1400_SRER_TXRDY); } } #endif } /* terminate service context */ #ifdef PollMode cd_outb(iobase, CD1400_MIR, cy_align, save_mir & ~(CD1400_MIR_RDIREQ | CD1400_MIR_RBUSY)); cd_outb(iobase, CD1400_CAR, cy_align, save_car); #else cd_outb(iobase, CD1400_EOSRR, cy_align, 0); #endif } if (status & CD1400_SVRR_TXRDY) { struct com_s *com; #ifdef PollMode u_char save_car; u_char save_tir; #else u_char vector; #endif #ifdef PollMode save_tir = cd_inb(iobase, CD1400_TIR, cy_align); save_car = cd_inb(iobase, CD1400_CAR, cy_align); /* enter tx service */ cd_outb(iobase, CD1400_CAR, cy_align, save_tir); com = com_addr(baseu + cyu * CD1400_NO_OF_CHANNELS + (save_tir & CD1400_TIR_CHAN)); #else /* ack transmit service */ vector = cy_inb(iobase, CY8_SVCACKT); com = com_addr(baseu + ((vector >> CD1400_xIVR_CHAN_SHIFT) & CD1400_xIVR_CHAN)); #endif if (com->state >= (CS_BUSY | CS_TTGO | CS_ODEVREADY)) { u_char *ioptr; u_int ocount; ioptr = com->obufq.l_head; ocount = com->obufq.l_tail - ioptr; if (ocount > CD1400_TX_FIFO_SIZE) ocount = CD1400_TX_FIFO_SIZE; com->bytes_out += ocount; do cd_outb(iobase, CD1400_TDR, cy_align, *ioptr++); while (--ocount != 0); com->obufq.l_head = ioptr; if (ioptr >= com->obufq.l_tail) { struct lbq *qp; qp = com->obufq.l_next; qp->l_queued = FALSE; qp = qp->l_next; if (qp != NULL) { com->obufq.l_head = qp->l_head; com->obufq.l_tail = qp->l_tail; com->obufq.l_next = qp; } else { /* output just completed */ com->state &= ~CS_BUSY; cd_outb(iobase, CD1400_SRER, cy_align, com->intr_enable &= ~CD1400_SRER_TXRDY); } if (!(com->state & CS_ODONE)) { com_events += LOTS_OF_EVENTS; com->state |= CS_ODONE; /* handle at high level ASAP */ setsofttty(); } } } /* terminate service context */ #ifdef PollMode cd_outb(iobase, CD1400_TIR, cy_align, save_tir & ~(CD1400_TIR_RDIREQ | CD1400_TIR_RBUSY)); cd_outb(iobase, CD1400_CAR, cy_align, save_car); #else cd_outb(iobase, CD1400_EOSRR, cy_align, 0); #endif } } /* ensure an edge for the next interrupt */ cd_outb(cy_iobase, CY_CLEAR_INTR, cy_align, 0); schedsofttty(); COM_UNLOCK(); } #if 0 static void siointr1(com) struct com_s *com; { } #endif static int sioioctl(dev, cmd, data, flag, p) dev_t dev; u_long cmd; caddr_t data; int flag; struct proc *p; { struct com_s *com; int error; cy_addr iobase; int mynor; int s; struct tty *tp; #if defined(COMPAT_43) || defined(COMPAT_SUNOS) int oldcmd; struct termios term; #endif mynor = minor(dev); com = com_addr(MINOR_TO_UNIT(mynor)); iobase = com->iobase; if (mynor & CONTROL_MASK) { struct termios *ct; switch (mynor & CONTROL_MASK) { case CONTROL_INIT_STATE: ct = mynor & CALLOUT_MASK ? &com->it_out : &com->it_in; break; case CONTROL_LOCK_STATE: ct = mynor & CALLOUT_MASK ? &com->lt_out : &com->lt_in; break; default: return (ENODEV); /* /dev/nodev */ } switch (cmd) { case TIOCSETA: error = suser(p->p_ucred, &p->p_acflag); if (error != 0) return (error); *ct = *(struct termios *)data; return (0); case TIOCGETA: *(struct termios *)data = *ct; return (0); case TIOCGETD: *(int *)data = TTYDISC; return (0); case TIOCGWINSZ: bzero(data, sizeof(struct winsize)); return (0); default: return (ENOTTY); } } tp = com->tp; #if defined(COMPAT_43) || defined(COMPAT_SUNOS) term = tp->t_termios; oldcmd = cmd; error = ttsetcompat(tp, &cmd, data, &term); if (error != 0) return (error); if (cmd != oldcmd) data = (caddr_t)&term; #endif if (cmd == TIOCSETA || cmd == TIOCSETAW || cmd == TIOCSETAF) { int cc; struct termios *dt = (struct termios *)data; struct termios *lt = mynor & CALLOUT_MASK ? &com->lt_out : &com->lt_in; dt->c_iflag = (tp->t_iflag & lt->c_iflag) | (dt->c_iflag & ~lt->c_iflag); dt->c_oflag = (tp->t_oflag & lt->c_oflag) | (dt->c_oflag & ~lt->c_oflag); dt->c_cflag = (tp->t_cflag & lt->c_cflag) | (dt->c_cflag & ~lt->c_cflag); dt->c_lflag = (tp->t_lflag & lt->c_lflag) | (dt->c_lflag & ~lt->c_lflag); for (cc = 0; cc < NCCS; ++cc) if (lt->c_cc[cc] != 0) dt->c_cc[cc] = tp->t_cc[cc]; if (lt->c_ispeed != 0) dt->c_ispeed = tp->t_ispeed; if (lt->c_ospeed != 0) dt->c_ospeed = tp->t_ospeed; } error = (*linesw[tp->t_line].l_ioctl)(tp, cmd, data, flag, p); if (error != ENOIOCTL) return (error); s = spltty(); error = ttioctl(tp, cmd, data, flag); disc_optim(tp, &tp->t_termios, com); if (error != ENOIOCTL) { splx(s); return (error); } cd_outb(iobase, CD1400_CAR, com->cy_align, MINOR_TO_UNIT(mynor) & CD1400_CAR_CHAN); switch (cmd) { #if 0 case TIOCSBRK: outb(iobase + com_cfcr, com->cfcr_image |= CFCR_SBREAK); break; case TIOCCBRK: outb(iobase + com_cfcr, com->cfcr_image &= ~CFCR_SBREAK); break; #endif /* 0 */ case TIOCSDTR: (void)commctl(com, TIOCM_DTR, DMBIS); break; case TIOCCDTR: (void)commctl(com, TIOCM_DTR, DMBIC); break; /* * XXX should disallow changing MCR_RTS if CS_RTS_IFLOW is set. The * changes get undone on the next call to comparam(). */ case TIOCMSET: (void)commctl(com, *(int *)data, DMSET); break; case TIOCMBIS: (void)commctl(com, *(int *)data, DMBIS); break; case TIOCMBIC: (void)commctl(com, *(int *)data, DMBIC); break; case TIOCMGET: *(int *)data = commctl(com, 0, DMGET); break; case TIOCMSDTRWAIT: /* must be root since the wait applies to following logins */ error = suser(p->p_ucred, &p->p_acflag); if (error != 0) { splx(s); return (error); } com->dtr_wait = *(int *)data * hz / 100; break; case TIOCMGDTRWAIT: *(int *)data = com->dtr_wait * 100 / hz; break; case TIOCTIMESTAMP: com->do_timestamp = TRUE; *(struct timeval *)data = com->timestamp; break; case TIOCDCDTIMESTAMP: com->do_dcd_timestamp = TRUE; *(struct timeval *)data = com->dcd_timestamp; break; default: splx(s); return (ENOTTY); } splx(s); return (0); } -void +static void siopoll() { int unit; #ifdef CyDebug ++cy_timeouts; #endif if (com_events == 0) return; repeat: for (unit = 0; unit < NSIO; ++unit) { u_char *buf; struct com_s *com; u_char *ibuf; cy_addr iobase; int incc; struct tty *tp; com = com_addr(unit); if (com == NULL) continue; tp = com->tp; if (tp == NULL) { /* * XXX forget any events related to closed devices * (actually never opened devices) so that we don't * loop. */ disable_intr(); incc = com->iptr - com->ibuf; com->iptr = com->ibuf; if (com->state & CS_CHECKMSR) { incc += LOTS_OF_EVENTS; com->state &= ~CS_CHECKMSR; } com_events -= incc; enable_intr(); if (incc != 0) log(LOG_DEBUG, "sio%d: %d events for device with no tp\n", unit, incc); continue; } /* switch the role of the low-level input buffers */ if (com->iptr == (ibuf = com->ibuf)) { buf = NULL; /* not used, but compiler can't tell */ incc = 0; } else { buf = ibuf; disable_intr(); incc = com->iptr - buf; com_events -= incc; if (ibuf == com->ibuf1) ibuf = com->ibuf2; else ibuf = com->ibuf1; com->ibufend = ibuf + RS_IBUFSIZE; com->ihighwater = ibuf + RS_IHIGHWATER; com->iptr = ibuf; /* * There is now room for another low-level buffer full * of input, so enable RTS if it is now disabled and * there is room in the high-level buffer. */ if ((com->state & CS_RTS_IFLOW) && !(com->mcr_image & MCR_RTS) && !(tp->t_state & TS_TBLOCK)) #if 0 outb(com->modem_ctl_port, com->mcr_image |= MCR_RTS); #else iobase = com->iobase, cd_outb(iobase, CD1400_CAR, com->cy_align, unit & CD1400_CAR_CHAN), cd_outb(iobase, CD1400_MSVR1, com->cy_align, com->mcr_image |= MCR_RTS); #endif enable_intr(); com->ibuf = ibuf; } if (com->state & CS_CHECKMSR) { u_char delta_modem_status; disable_intr(); delta_modem_status = com->last_modem_status ^ com->prev_modem_status; com->prev_modem_status = com->last_modem_status; com_events -= LOTS_OF_EVENTS; com->state &= ~CS_CHECKMSR; enable_intr(); if (delta_modem_status & MSR_DCD) (*linesw[tp->t_line].l_modem) (tp, com->prev_modem_status & MSR_DCD); } if (com->state & CS_ODONE) { disable_intr(); com_events -= LOTS_OF_EVENTS; com->state &= ~CS_ODONE; if (!(com->state & CS_BUSY)) com->tp->t_state &= ~TS_BUSY; enable_intr(); (*linesw[tp->t_line].l_start)(tp); } if (incc <= 0 || !(tp->t_state & TS_ISOPEN)) continue; /* * Avoid the grotesquely inefficient lineswitch routine * (ttyinput) in "raw" mode. It usually takes about 450 * instructions (that's without canonical processing or echo!). * slinput is reasonably fast (usually 40 instructions plus * call overhead). */ if (tp->t_state & TS_CAN_BYPASS_L_RINT) { if (tp->t_rawq.c_cc + incc >= RB_I_HIGH_WATER && (com->state & CS_RTS_IFLOW || tp->t_iflag & IXOFF) && !(tp->t_state & TS_TBLOCK)) ttyblock(tp); tk_nin += incc; tk_rawcc += incc; tp->t_rawcc += incc; com->delta_error_counts[CE_TTY_BUF_OVERFLOW] += b_to_q((char *)buf, incc, &tp->t_rawq); ttwakeup(tp); if (tp->t_state & TS_TTSTOP && (tp->t_iflag & IXANY || tp->t_cc[VSTART] == tp->t_cc[VSTOP])) { tp->t_state &= ~TS_TTSTOP; tp->t_lflag &= ~FLUSHO; comstart(tp); } } else { do { u_char line_status; int recv_data; line_status = (u_char) buf[CE_INPUT_OFFSET]; recv_data = (u_char) *buf++; if (line_status & (LSR_BI | LSR_FE | LSR_OE | LSR_PE)) { if (line_status & LSR_BI) recv_data |= TTY_BI; if (line_status & LSR_FE) recv_data |= TTY_FE; if (line_status & LSR_OE) recv_data |= TTY_OE; if (line_status & LSR_PE) recv_data |= TTY_PE; } (*linesw[tp->t_line].l_rint)(recv_data, tp); } while (--incc > 0); } if (com_events == 0) break; } if (com_events >= LOTS_OF_EVENTS) goto repeat; } static int comparam(tp, t) struct tty *tp; struct termios *t; { int bits; int cflag; struct com_s *com; u_char cor_change; int idivisor; int iflag; cy_addr iobase; int iprescaler; int itimeout; int odivisor; int oprescaler; u_char opt; int s; int unit; /* do historical conversions */ if (t->c_ispeed == 0) t->c_ispeed = t->c_ospeed; /* check requested parameters */ idivisor = comspeed(t->c_ispeed, &iprescaler); if (idivisor < 0) return (EINVAL); odivisor = comspeed(t->c_ospeed, &oprescaler); if (odivisor < 0) return (EINVAL); /* parameters are OK, convert them to the com struct and the device */ unit = DEV_TO_UNIT(tp->t_dev); com = com_addr(unit); iobase = com->iobase; s = spltty(); cd_outb(iobase, CD1400_CAR, com->cy_align, unit & CD1400_CAR_CHAN); if (odivisor == 0) (void)commctl(com, TIOCM_DTR, DMBIC); /* hang up line */ else (void)commctl(com, TIOCM_DTR, DMBIS); if (idivisor != 0) { cd_outb(iobase, CD1400_RBPR, com->cy_align, idivisor); cd_outb(iobase, CD1400_RCOR, com->cy_align, iprescaler); } if (odivisor != 0) { cd_outb(iobase, CD1400_TBPR, com->cy_align, odivisor); cd_outb(iobase, CD1400_TCOR, com->cy_align, oprescaler); } /* * channel control * receiver enable * transmitter enable (always set) */ cflag = t->c_cflag; opt = CD1400_CCR_CMDCHANCTL | CD1400_CCR_XMTEN | (cflag & CREAD ? CD1400_CCR_RCVEN : CD1400_CCR_RCVDIS); if (opt != com->channel_control) { com->channel_control = opt; cd1400_channel_cmd(iobase, opt, com->cy_align); } #ifdef Smarts /* set special chars */ /* XXX if one is _POSIX_VDISABLE, can't use some others */ if (t->c_cc[VSTOP] != _POSIX_VDISABLE) cd_outb(iobase, CD1400_SCHR1, com->cy_align, t->c_cc[VSTOP]); if (t->c_cc[VSTART] != _POSIX_VDISABLE) cd_outb(iobase, CD1400_SCHR2, com->cy_align, t->c_cc[VSTART]); if (t->c_cc[VINTR] != _POSIX_VDISABLE) cd_outb(iobase, CD1400_SCHR3, com->cy_align, t->c_cc[VINTR]); if (t->c_cc[VSUSP] != _POSIX_VDISABLE) cd_outb(iobase, CD1400_SCHR4, com->cy_align, t->c_cc[VSUSP]); #endif /* * set channel option register 1 - * parity mode * stop bits * char length */ opt = 0; /* parity */ if (cflag & PARENB) { if (cflag & PARODD) opt |= CD1400_COR1_PARODD; opt |= CD1400_COR1_PARNORMAL; } iflag = t->c_iflag; if (!(iflag & INPCK)) opt |= CD1400_COR1_NOINPCK; bits = 1 + 1; /* stop bits */ if (cflag & CSTOPB) { ++bits; opt |= CD1400_COR1_STOP2; } /* char length */ switch (cflag & CSIZE) { case CS5: bits += 5; opt |= CD1400_COR1_CS5; break; case CS6: bits += 6; opt |= CD1400_COR1_CS6; break; case CS7: bits += 7; opt |= CD1400_COR1_CS7; break; default: bits += 8; opt |= CD1400_COR1_CS8; break; } cor_change = 0; if (opt != com->cor[0]) { cor_change |= CD1400_CCR_COR1; cd_outb(iobase, CD1400_COR1, com->cy_align, com->cor[0] = opt); } /* * Set receive time-out period, normally to max(one char time, 5 ms). */ if (t->c_ispeed == 0) itimeout = cd_inb(iobase, CD1400_RTPR, com->cy_align); else { itimeout = (1000 * bits + t->c_ispeed - 1) / t->c_ispeed; #ifdef SOFT_HOTCHAR #define MIN_RTP 1 #else #define MIN_RTP 5 #endif if (itimeout < MIN_RTP) itimeout = MIN_RTP; } if (!(t->c_lflag & ICANON) && t->c_cc[VMIN] != 0 && t->c_cc[VTIME] != 0 && t->c_cc[VTIME] * 10 > itimeout) itimeout = t->c_cc[VTIME] * 10; if (itimeout > 255) itimeout = 255; cd_outb(iobase, CD1400_RTPR, com->cy_align, itimeout); /* * set channel option register 2 - * flow control */ opt = 0; #ifdef Smarts if (iflag & IXANY) opt |= CD1400_COR2_IXANY; if (iflag & IXOFF) opt |= CD1400_COR2_IXOFF; #endif #ifndef SOFT_CTS_OFLOW if (cflag & CCTS_OFLOW) opt |= CD1400_COR2_CCTS_OFLOW; #endif if (opt != com->cor[1]) { cor_change |= CD1400_CCR_COR2; cd_outb(iobase, CD1400_COR2, com->cy_align, com->cor[1] = opt); } /* * set channel option register 3 - * receiver FIFO interrupt threshold * flow control */ opt = RxFifoThreshold; #ifdef Smarts if (t->c_lflag & ICANON) opt |= CD1400_COR3_SCD34; /* detect INTR & SUSP chars */ if (iflag & IXOFF) /* detect and transparently handle START and STOP chars */ opt |= CD1400_COR3_FCT | CD1400_COR3_SCD12; #endif if (opt != com->cor[2]) { cor_change |= CD1400_CCR_COR3; cd_outb(iobase, CD1400_COR3, com->cy_align, com->cor[2] = opt); } /* notify the CD1400 if COR1-3 have changed */ if (cor_change) cd1400_channel_cmd(iobase, CD1400_CCR_CMDCORCHG | cor_change, com->cy_align); /* * set channel option register 4 - * CR/NL processing * break processing * received exception processing */ opt = 0; if (iflag & IGNCR) opt |= CD1400_COR4_IGNCR; #ifdef Smarts /* * we need a new ttyinput() for this, as we don't want to * have ICRNL && INLCR being done in both layers, or to have * synchronisation problems */ if (iflag & ICRNL) opt |= CD1400_COR4_ICRNL; if (iflag & INLCR) opt |= CD1400_COR4_INLCR; #endif if (iflag & IGNBRK) opt |= CD1400_COR4_IGNBRK; if (!(iflag & BRKINT)) opt |= CD1400_COR4_NOBRKINT; #if 0 /* XXX using this "intelligence" breaks reporting of overruns. */ if (iflag & IGNPAR) opt |= CD1400_COR4_PFO_DISCARD; else { if (iflag & PARMRK) opt |= CD1400_COR4_PFO_ESC; else opt |= CD1400_COR4_PFO_NUL; } #else opt |= CD1400_COR4_PFO_EXCEPTION; #endif cd_outb(iobase, CD1400_COR4, com->cy_align, opt); /* * set channel option register 5 - */ opt = 0; if (iflag & ISTRIP) opt |= CD1400_COR5_ISTRIP; if (t->c_iflag & IEXTEN) /* enable LNEXT (e.g. ctrl-v quoting) handling */ opt |= CD1400_COR5_LNEXT; #ifdef Smarts if (t->c_oflag & ONLCR) opt |= CD1400_COR5_ONLCR; if (t->c_oflag & OCRNL) opt |= CD1400_COR5_OCRNL; #endif cd_outb(iobase, CD1400_COR5, com->cy_align, opt); /* * We always generate modem status change interrupts for CD changes. * Among other things, this is necessary to track TS_CARR_ON for * pstat to print even when the driver doesn't care. CD changes * should be rare so interrupts for them are not worth extra code to * avoid. We avoid interrupts for other modem status changes (except * for CTS changes when SOFT_CTS_OFLOW is configured) since this is * simplest and best. */ /* * set modem change option register 1 * generate modem interrupts on which 1 -> 0 input transitions * also controls auto-DTR output flow-control, which we don't use */ opt = CD1400_MCOR1_CDzd; #ifdef SOFT_CTS_OFLOW if (cflag & CCTS_OFLOW) opt |= CD1400_MCOR1_CTSzd; #endif cd_outb(iobase, CD1400_MCOR1, com->cy_align, opt); /* * set modem change option register 2 * generate modem interrupts on specific 0 -> 1 input transitions */ opt = CD1400_MCOR2_CDod; #ifdef SOFT_CTS_OFLOW if (cflag & CCTS_OFLOW) opt |= CD1400_MCOR2_CTSod; #endif cd_outb(iobase, CD1400_MCOR2, com->cy_align, opt); /* * XXX should have done this long ago, but there is too much state * to change all atomically. */ disable_intr(); com->state &= ~CS_TTGO; if (!(tp->t_state & TS_TTSTOP)) com->state |= CS_TTGO; if (cflag & CRTS_IFLOW) { com->state |= CS_RTS_IFLOW; /* * If CS_RTS_IFLOW just changed from off to on, the change * needs to be propagated to MCR_RTS. This isn't urgent, * so do it later by calling comstart() instead of repeating * a lot of code from comstart() here. */ } else if (com->state & CS_RTS_IFLOW) { com->state &= ~CS_RTS_IFLOW; /* * CS_RTS_IFLOW just changed from on to off. Force MCR_RTS * on here, since comstart() won't do it later. */ #if 0 outb(com->modem_ctl_port, com->mcr_image |= MCR_RTS); #else cd_outb(iobase, CD1400_MSVR1, com->cy_align, com->mcr_image |= MCR_RTS); #endif } /* * Set up state to handle output flow control. * XXX - worth handling MDMBUF (DCD) flow control at the lowest level? * Now has 10+ msec latency, while CTS flow has 50- usec latency. */ com->state |= CS_ODEVREADY; #ifdef SOFT_CTS_OFLOW com->state &= ~CS_CTS_OFLOW; if (cflag & CCTS_OFLOW) { com->state |= CS_CTS_OFLOW; if (!(com->last_modem_status & MSR_CTS)) com->state &= ~CS_ODEVREADY; } #endif /* XXX shouldn't call functions while intrs are disabled. */ disc_optim(tp, t, com); #if 0 /* * Recover from fiddling with CS_TTGO. We used to call siointr1() * unconditionally, but that defeated the careful discarding of * stale input in sioopen(). */ if (com->state >= (CS_BUSY | CS_TTGO)) siointr1(com); #endif if (com->state >= (CS_BUSY | CS_TTGO | CS_ODEVREADY)) { if (!(com->intr_enable & CD1400_SRER_TXRDY)) cd_outb(iobase, CD1400_SRER, com->cy_align, com->intr_enable |= CD1400_SRER_TXRDY); } else { if (com->intr_enable & CD1400_SRER_TXRDY) cd_outb(iobase, CD1400_SRER, com->cy_align, com->intr_enable &= ~CD1400_SRER_TXRDY); } enable_intr(); splx(s); comstart(tp); return (0); } static void comstart(tp) struct tty *tp; { struct com_s *com; cy_addr iobase; int s; #ifdef CyDebug bool_t started; #endif int unit; unit = DEV_TO_UNIT(tp->t_dev); com = com_addr(unit); iobase = com->iobase; s = spltty(); #ifdef CyDebug ++com->start_count; started = FALSE; #endif disable_intr(); cd_outb(iobase, CD1400_CAR, com->cy_align, unit & CD1400_CAR_CHAN); if (tp->t_state & TS_TTSTOP) { com->state &= ~CS_TTGO; if (com->intr_enable & CD1400_SRER_TXRDY) cd_outb(iobase, CD1400_SRER, com->cy_align, com->intr_enable &= ~CD1400_SRER_TXRDY); } else { com->state |= CS_TTGO; if (com->state >= (CS_BUSY | CS_TTGO | CS_ODEVREADY) && !(com->intr_enable & CD1400_SRER_TXRDY)) cd_outb(iobase, CD1400_SRER, com->cy_align, com->intr_enable |= CD1400_SRER_TXRDY); } if (tp->t_state & TS_TBLOCK) { if (com->mcr_image & MCR_RTS && com->state & CS_RTS_IFLOW) #if 0 outb(com->modem_ctl_port, com->mcr_image &= ~MCR_RTS); #else cd_outb(iobase, CD1400_MSVR1, com->cy_align, com->mcr_image &= ~MCR_RTS); #endif } else { if (!(com->mcr_image & MCR_RTS) && com->iptr < com->ihighwater && com->state & CS_RTS_IFLOW) #if 0 outb(com->modem_ctl_port, com->mcr_image |= MCR_RTS); #else cd_outb(iobase, CD1400_MSVR1, com->cy_align, com->mcr_image |= MCR_RTS); #endif } enable_intr(); if (tp->t_state & (TS_TIMEOUT | TS_TTSTOP)) { ttwwakeup(tp); splx(s); return; } if (tp->t_outq.c_cc != 0) { struct lbq *qp; struct lbq *next; if (!com->obufs[0].l_queued) { #ifdef CyDebug started = TRUE; #endif com->obufs[0].l_tail = com->obuf1 + q_to_b(&tp->t_outq, com->obuf1, sizeof com->obuf1); com->obufs[0].l_next = NULL; com->obufs[0].l_queued = TRUE; disable_intr(); if (com->state & CS_BUSY) { qp = com->obufq.l_next; while ((next = qp->l_next) != NULL) qp = next; qp->l_next = &com->obufs[0]; } else { com->obufq.l_head = com->obufs[0].l_head; com->obufq.l_tail = com->obufs[0].l_tail; com->obufq.l_next = &com->obufs[0]; com->state |= CS_BUSY; if (com->state >= (CS_BUSY | CS_TTGO | CS_ODEVREADY)) cd_outb(iobase, CD1400_SRER, com->cy_align, com->intr_enable |= CD1400_SRER_TXRDY); } enable_intr(); } if (tp->t_outq.c_cc != 0 && !com->obufs[1].l_queued) { #ifdef CyDebug started = TRUE; #endif com->obufs[1].l_tail = com->obuf2 + q_to_b(&tp->t_outq, com->obuf2, sizeof com->obuf2); com->obufs[1].l_next = NULL; com->obufs[1].l_queued = TRUE; disable_intr(); if (com->state & CS_BUSY) { qp = com->obufq.l_next; while ((next = qp->l_next) != NULL) qp = next; qp->l_next = &com->obufs[1]; } else { com->obufq.l_head = com->obufs[1].l_head; com->obufq.l_tail = com->obufs[1].l_tail; com->obufq.l_next = &com->obufs[1]; com->state |= CS_BUSY; if (com->state >= (CS_BUSY | CS_TTGO | CS_ODEVREADY)) cd_outb(iobase, CD1400_SRER, com->cy_align, com->intr_enable |= CD1400_SRER_TXRDY); } enable_intr(); } tp->t_state |= TS_BUSY; } #ifdef CyDebug if (started) ++com->start_real; #endif #if 0 disable_intr(); if (com->state >= (CS_BUSY | CS_TTGO)) siointr1(com); /* fake interrupt to start output */ enable_intr(); #endif ttwwakeup(tp); splx(s); } static void siostop(tp, rw) struct tty *tp; int rw; { struct com_s *com; com = com_addr(DEV_TO_UNIT(tp->t_dev)); disable_intr(); if (rw & FWRITE) { com->obufs[0].l_queued = FALSE; com->obufs[1].l_queued = FALSE; if (com->state & CS_ODONE) com_events -= LOTS_OF_EVENTS; com->state &= ~(CS_ODONE | CS_BUSY); com->tp->t_state &= ~TS_BUSY; } if (rw & FREAD) { com_events -= (com->iptr - com->ibuf); com->iptr = com->ibuf; } enable_intr(); comstart(tp); /* XXX should clear h/w fifos too. */ } static struct tty * siodevtotty(dev) dev_t dev; { int mynor; int unit; mynor = minor(dev); if (mynor & CONTROL_MASK) return (NULL); unit = MINOR_TO_UNIT(mynor); if ((u_int) unit >= NSIO) return (NULL); return (&sio_tty[unit]); } static int commctl(com, bits, how) struct com_s *com; int bits; int how; { cy_addr iobase; int mcr; int msr; iobase = com->iobase; if (how == DMGET) { if (com->channel_control & CD1400_CCR_RCVEN) bits |= TIOCM_LE; mcr = com->mcr_image; if (mcr & MCR_DTR) bits |= TIOCM_DTR; if (mcr & MCR_RTS) /* XXX wired on for Cyclom-8Ys */ bits |= TIOCM_RTS; /* * We must read the modem status from the hardware because * we don't generate modem status change interrupts for all * changes, so com->prev_modem_status is not guaranteed to * be up to date. This is safe, unlike for sio, because * reading the status register doesn't clear pending modem * status change interrupts. */ msr = cd_inb(iobase, CD1400_MSVR2, com->cy_align); if (msr & MSR_CTS) bits |= TIOCM_CTS; if (msr & MSR_DCD) bits |= TIOCM_CD; if (msr & MSR_DSR) bits |= TIOCM_DSR; if (msr & MSR_RI) /* XXX not connected except for Cyclom-16Y? */ bits |= TIOCM_RI; return (bits); } mcr = 0; if (bits & TIOCM_DTR) mcr |= MCR_DTR; if (bits & TIOCM_RTS) mcr |= MCR_RTS; disable_intr(); switch (how) { case DMSET: com->mcr_image = mcr; cd_outb(iobase, CD1400_MSVR1, com->cy_align, mcr); cd_outb(iobase, CD1400_MSVR2, com->cy_align, mcr); break; case DMBIS: com->mcr_image = mcr = com->mcr_image | mcr; cd_outb(iobase, CD1400_MSVR1, com->cy_align, mcr); cd_outb(iobase, CD1400_MSVR2, com->cy_align, mcr); break; case DMBIC: com->mcr_image = mcr = com->mcr_image & ~mcr; cd_outb(iobase, CD1400_MSVR1, com->cy_align, mcr); cd_outb(iobase, CD1400_MSVR2, com->cy_align, mcr); break; } enable_intr(); return (0); } static void siosettimeout() { struct com_s *com; bool_t someopen; int unit; /* * Set our timeout period to 1 second if no polled devices are open. * Otherwise set it to max(1/200, 1/hz). * Enable timeouts iff some device is open. */ untimeout(comwakeup, (void *)NULL, sio_timeout_handle); sio_timeout = hz; someopen = FALSE; for (unit = 0; unit < NSIO; ++unit) { com = com_addr(unit); if (com != NULL && com->tp != NULL && com->tp->t_state & TS_ISOPEN) { someopen = TRUE; #if 0 if (com->poll || com->poll_output) { sio_timeout = hz > 200 ? hz / 200 : 1; break; } #endif } } if (someopen) { sio_timeouts_until_log = hz / sio_timeout; sio_timeout_handle = timeout(comwakeup, (void *)NULL, sio_timeout); } else { /* Flush error messages, if any. */ sio_timeouts_until_log = 1; comwakeup((void *)NULL); untimeout(comwakeup, (void *)NULL, sio_timeout_handle); } } static void comwakeup(chan) void *chan; { struct com_s *com; int unit; sio_timeout_handle = timeout(comwakeup, (void *)NULL, sio_timeout); #if 0 /* * Recover from lost output interrupts. * Poll any lines that don't use interrupts. */ for (unit = 0; unit < NSIO; ++unit) { com = com_addr(unit); if (com != NULL && (com->state >= (CS_BUSY | CS_TTGO) || com->poll)) { disable_intr(); siointr1(com); enable_intr(); } } #endif /* * Check for and log errors, but not too often. */ if (--sio_timeouts_until_log > 0) return; sio_timeouts_until_log = hz / sio_timeout; for (unit = 0; unit < NSIO; ++unit) { int errnum; com = com_addr(unit); if (com == NULL) continue; for (errnum = 0; errnum < CE_NTYPES; ++errnum) { u_int delta; u_long total; disable_intr(); delta = com->delta_error_counts[errnum]; com->delta_error_counts[errnum] = 0; enable_intr(); if (delta == 0) continue; total = com->error_counts[errnum] += delta; log(LOG_ERR, "cy%d: %u more %s%s (total %lu)\n", unit, delta, error_desc[errnum], delta == 1 ? "" : "s", total); } } } static void disc_optim(tp, t, com) struct tty *tp; struct termios *t; struct com_s *com; { #ifndef SOFT_HOTCHAR cy_addr iobase; u_char opt; #endif /* * XXX can skip a lot more cases if Smarts. Maybe * (IGNCR | ISTRIP | IXON) in c_iflag. But perhaps we * shouldn't skip if (TS_CNTTB | TS_LNCH) is set in t_state. */ if (!(t->c_iflag & (ICRNL | IGNCR | IMAXBEL | INLCR | ISTRIP | IXON)) && (!(t->c_iflag & BRKINT) || (t->c_iflag & IGNBRK)) && (!(t->c_iflag & PARMRK) || (t->c_iflag & (IGNPAR | IGNBRK)) == (IGNPAR | IGNBRK)) && !(t->c_lflag & (ECHO | ICANON | IEXTEN | ISIG | PENDIN)) && linesw[tp->t_line].l_rint == ttyinput) tp->t_state |= TS_CAN_BYPASS_L_RINT; else tp->t_state &= ~TS_CAN_BYPASS_L_RINT; com->hotchar = linesw[tp->t_line].l_hotchar; #ifndef SOFT_HOTCHAR iobase = com->iobase; cd_outb(iobase, CD1400_CAR, com->cy_align, com->unit & CD1400_CAR_CHAN); opt = com->cor[2] & ~CD1400_COR3_SCD34; if (com->hotchar != 0) { cd_outb(iobase, CD1400_SCHR3, com->cy_align, com->hotchar); cd_outb(iobase, CD1400_SCHR4, com->cy_align, com->hotchar); opt |= CD1400_COR3_SCD34; } if (opt != com->cor[2]) { cd_outb(iobase, CD1400_COR3, com->cy_align, com->cor[2] = opt); cd1400_channel_cmd(com->iobase, CD1400_CCR_CMDCORCHG | CD1400_CCR_COR3, com->cy_align); } #endif } #ifdef Smarts /* standard line discipline input routine */ int cyinput(c, tp) int c; struct tty *tp; { /* XXX duplicate ttyinput(), but without the IXOFF/IXON/ISTRIP/IPARMRK * bits, as they are done by the CD1400. Hardly worth the effort, * given that high-throughput sessions are raw anyhow. */ } #endif /* Smarts */ static int comspeed(speed, prescaler_io) speed_t speed; int *prescaler_io; { int actual; int error; int divider; int prescaler; int prescaler_unit; if (speed == 0) return (0); if (speed < 0 || speed > 150000) return (-1); /* determine which prescaler to use */ for (prescaler_unit = 4, prescaler = 2048; prescaler_unit; prescaler_unit--, prescaler >>= 2) { if (CY_CLOCK / prescaler / speed > 63) break; } divider = (CY_CLOCK / prescaler * 2 / speed + 1) / 2; /* round off */ if (divider > 255) divider = 255; actual = CY_CLOCK/prescaler/divider; /* 10 times error in percent: */ error = ((actual - (long)speed) * 2000 / (long)speed + 1) / 2; /* 3.0% max error tolerance */ if (error < -30 || error > 30) return (-1); #if 0 printf("prescaler = %d (%d)\n", prescaler, prescaler_unit); printf("divider = %d (%x)\n", divider, divider); printf("actual = %d\n", actual); printf("error = %d\n", error); #endif *prescaler_io = prescaler_unit; return (divider); } static void cd1400_channel_cmd(iobase, cmd, cy_align) cy_addr iobase; int cmd; int cy_align; { /* XXX hsu@clinet.fi: This is always more dependent on ISA bus speed, as the card is probed every round? Replaced delaycount with 8k. Either delaycount has to be implemented in FreeBSD or more sensible way of doing these should be implemented. DELAY isn't enough here. */ u_int maxwait = 5 * 8 * 1024; /* approx. 5 ms */ /* wait for processing of previous command to complete */ while (cd_inb(iobase, CD1400_CCR, cy_align) && maxwait--) ; if (!maxwait) log(LOG_ERR, "cy: channel command timeout (%d loops) - arrgh\n", 5 * 8 * 1024); cd_outb(iobase, CD1400_CCR, cy_align, cmd); } #ifdef CyDebug /* useful in ddb */ void cystatus(unit) int unit; { struct com_s *com; cy_addr iobase; u_int ocount; struct tty *tp; com = com_addr(unit); printf("info for channel %d\n", unit); printf("------------------\n"); printf("total cyclom service probes:\t%d\n", cy_svrr_probes); printf("calls to upper layer:\t\t%d\n", cy_timeouts); if (com == NULL) return; iobase = com->iobase; printf("\n"); printf("cd1400 base address:\\tt%p\n", iobase); cd_outb(iobase, CD1400_CAR, com->cy_align, unit & CD1400_CAR_CHAN); printf("saved channel_control:\t\t0x%02x\n", com->channel_control); printf("saved cor1-3:\t\t\t0x%02x 0x%02x 0x%02x\n", com->cor[0], com->cor[1], com->cor[2]); printf("service request enable reg:\t0x%02x (0x%02x cached)\n", cd_inb(iobase, CD1400_SRER, com->cy_align), com->intr_enable); printf("service request register:\t0x%02x\n", cd_inb(iobase, CD1400_SVRR, com->cy_align)); printf("modem status:\t\t\t0x%02x (0x%02x cached)\n", cd_inb(iobase, CD1400_MSVR2, com->cy_align), com->prev_modem_status); printf("rx/tx/mdm interrupt registers:\t0x%02x 0x%02x 0x%02x\n", cd_inb(iobase, CD1400_RIR, com->cy_align), cd_inb(iobase, CD1400_TIR, com->cy_align), cd_inb(iobase, CD1400_MIR, com->cy_align)); printf("\n"); printf("com state:\t\t\t0x%02x\n", com->state); printf("calls to comstart():\t\t%d (%d useful)\n", com->start_count, com->start_real); printf("rx buffer chars free:\t\t%d\n", com->iptr - com->ibuf); ocount = 0; if (com->obufs[0].l_queued) ocount += com->obufs[0].l_tail - com->obufs[0].l_head; if (com->obufs[1].l_queued) ocount += com->obufs[1].l_tail - com->obufs[1].l_head; printf("tx buffer chars:\t\t%u\n", ocount); printf("received chars:\t\t\t%d\n", com->bytes_in); printf("received exceptions:\t\t%d\n", com->recv_exception); printf("modem signal deltas:\t\t%d\n", com->mdm); printf("transmitted chars:\t\t%d\n", com->bytes_out); printf("\n"); tp = com->tp; if (tp != NULL) { printf("tty state:\t\t\t0x%08x\n", tp->t_state); printf( "upper layer queue lengths:\t%d raw, %d canon, %d output\n", tp->t_rawq.c_cc, tp->t_canq.c_cc, tp->t_outq.c_cc); } else printf("tty state:\t\t\tclosed\n"); } #endif /* CyDebug */ Index: head/sys/i386/isa/icu_vector.s =================================================================== --- head/sys/i386/isa/icu_vector.s (revision 38245) +++ head/sys/i386/isa/icu_vector.s (revision 38246) @@ -1,250 +1,250 @@ /* * from: vector.s, 386BSD 0.1 unknown origin - * $Id: icu_vector.s,v 1.7 1998/01/15 07:33:59 gibbs Exp $ + * $Id: icu_vector.s,v 1.8 1998/08/11 15:08:12 bde Exp $ */ /* * modified for PC98 by Kakefuda */ #ifdef PC98 #define ICU_IMR_OFFSET 2 /* IO_ICU{1,2} + 2 */ #else #define ICU_IMR_OFFSET 1 /* IO_ICU{1,2} + 1 */ #endif #define ICU_EOI 0x20 /* XXX - define elsewhere */ #define IRQ_BIT(irq_num) (1 << ((irq_num) % 8)) #define IRQ_BYTE(irq_num) ((irq_num) >> 3) #ifdef AUTO_EOI_1 #define ENABLE_ICU1 /* use auto-EOI to reduce i/o */ #define OUTB_ICU1 #else #define ENABLE_ICU1 \ movb $ICU_EOI,%al ; /* as soon as possible send EOI ... */ \ OUTB_ICU1 /* ... to clear in service bit */ #define OUTB_ICU1 \ outb %al,$IO_ICU1 #endif #ifdef AUTO_EOI_2 /* * The data sheet says no auto-EOI on slave, but it sometimes works. */ #define ENABLE_ICU1_AND_2 ENABLE_ICU1 #else #define ENABLE_ICU1_AND_2 \ movb $ICU_EOI,%al ; /* as above */ \ outb %al,$IO_ICU2 ; /* but do second icu first ... */ \ OUTB_ICU1 /* ... then first icu (if !AUTO_EOI_1) */ #endif /* * Macros for interrupt interrupt entry, call to handler, and exit. */ #define FAST_INTR(irq_num, vec_name, enable_icus) \ .text ; \ SUPERALIGN_TEXT ; \ IDTVEC(vec_name) ; \ pushl %eax ; /* save only call-used registers */ \ pushl %ecx ; \ pushl %edx ; \ pushl %ds ; \ MAYBE_PUSHL_ES ; \ movl $KDSEL,%eax ; \ movl %ax,%ds ; \ MAYBE_MOVW_AX_ES ; \ FAKE_MCOUNT((4+ACTUALLY_PUSHED)*4(%esp)) ; \ pushl _intr_unit + (irq_num) * 4 ; \ call *_intr_handler + (irq_num) * 4 ; /* do the work ASAP */ \ enable_icus ; /* (re)enable ASAP (helps edge trigger?) */ \ addl $4,%esp ; \ incl _cnt+V_INTR ; /* book-keeping can wait */ \ movl _intr_countp + (irq_num) * 4,%eax ; \ incl (%eax) ; \ movl _cpl,%eax ; /* are we unmasking pending HWIs or SWIs? */ \ notl %eax ; \ andl _ipending,%eax ; \ jne 2f ; /* yes, maybe handle them */ \ 1: ; \ MEXITCOUNT ; \ MAYBE_POPL_ES ; \ popl %ds ; \ popl %edx ; \ popl %ecx ; \ popl %eax ; \ iret ; \ ; \ ALIGN_TEXT ; \ 2: ; \ cmpb $3,_intr_nesting_level ; /* is there enough stack? */ \ jae 1b ; /* no, return */ \ movl _cpl,%eax ; \ /* XXX next line is probably unnecessary now. */ \ movl $HWI_MASK|SWI_MASK,_cpl ; /* limit nesting ... */ \ incb _intr_nesting_level ; /* ... really limit it ... */ \ sti ; /* ... to do this as early as possible */ \ MAYBE_POPL_ES ; /* discard most of thin frame ... */ \ popl %ecx ; /* ... original %ds ... */ \ popl %edx ; \ xchgl %eax,4(%esp) ; /* orig %eax; save cpl */ \ pushal ; /* build fat frame (grrr) ... */ \ pushl %ecx ; /* ... actually %ds ... */ \ pushl %es ; \ movl $KDSEL,%eax ; \ movl %ax,%es ; \ movl (2+8+0)*4(%esp),%ecx ; /* ... %ecx from thin frame ... */ \ movl %ecx,(2+6)*4(%esp) ; /* ... to fat frame ... */ \ movl (2+8+1)*4(%esp),%eax ; /* ... cpl from thin frame */ \ pushl %eax ; \ subl $4,%esp ; /* junk for unit number */ \ MEXITCOUNT ; \ jmp _doreti #define INTR(irq_num, vec_name, icu, enable_icus, reg) \ .text ; \ SUPERALIGN_TEXT ; \ IDTVEC(vec_name) ; \ pushl $0 ; /* dummy error code */ \ pushl $0 ; /* dummy trap type */ \ pushal ; \ pushl %ds ; /* save our data and extra segments ... */ \ pushl %es ; \ movl $KDSEL,%eax ; /* ... and reload with kernel's own ... */ \ movl %ax,%ds ; /* ... early for obsolete reasons */ \ movl %ax,%es ; \ movb _imen + IRQ_BYTE(irq_num),%al ; \ orb $IRQ_BIT(irq_num),%al ; \ movb %al,_imen + IRQ_BYTE(irq_num) ; \ outb %al,$icu+ICU_IMR_OFFSET ; \ enable_icus ; \ movl _cpl,%eax ; \ testb $IRQ_BIT(irq_num),%reg ; \ jne 2f ; \ incb _intr_nesting_level ; \ __CONCAT(Xresume,irq_num): ; \ FAKE_MCOUNT(12*4(%esp)) ; /* XXX late to avoid double count */ \ incl _cnt+V_INTR ; /* tally interrupts */ \ movl _intr_countp + (irq_num) * 4,%eax ; \ incl (%eax) ; \ movl _cpl,%eax ; \ pushl %eax ; \ pushl _intr_unit + (irq_num) * 4 ; \ orl _intr_mask + (irq_num) * 4,%eax ; \ movl %eax,_cpl ; \ sti ; \ call *_intr_handler + (irq_num) * 4 ; \ cli ; /* must unmask _imen and icu atomically */ \ movb _imen + IRQ_BYTE(irq_num),%al ; \ andb $~IRQ_BIT(irq_num),%al ; \ movb %al,_imen + IRQ_BYTE(irq_num) ; \ outb %al,$icu+ICU_IMR_OFFSET ; \ sti ; /* XXX _doreti repeats the cli/sti */ \ MEXITCOUNT ; \ /* We could usually avoid the following jmp by inlining some of */ \ /* _doreti, but it's probably better to use less cache. */ \ jmp _doreti ; \ ; \ ALIGN_TEXT ; \ 2: ; \ /* XXX skip mcounting here to avoid double count */ \ orb $IRQ_BIT(irq_num),_ipending + IRQ_BYTE(irq_num) ; \ popl %es ; \ popl %ds ; \ popal ; \ addl $4+4,%esp ; \ iret MCOUNT_LABEL(bintr) FAST_INTR(0,fastintr0, ENABLE_ICU1) FAST_INTR(1,fastintr1, ENABLE_ICU1) FAST_INTR(2,fastintr2, ENABLE_ICU1) FAST_INTR(3,fastintr3, ENABLE_ICU1) FAST_INTR(4,fastintr4, ENABLE_ICU1) FAST_INTR(5,fastintr5, ENABLE_ICU1) FAST_INTR(6,fastintr6, ENABLE_ICU1) FAST_INTR(7,fastintr7, ENABLE_ICU1) FAST_INTR(8,fastintr8, ENABLE_ICU1_AND_2) FAST_INTR(9,fastintr9, ENABLE_ICU1_AND_2) FAST_INTR(10,fastintr10, ENABLE_ICU1_AND_2) FAST_INTR(11,fastintr11, ENABLE_ICU1_AND_2) FAST_INTR(12,fastintr12, ENABLE_ICU1_AND_2) FAST_INTR(13,fastintr13, ENABLE_ICU1_AND_2) FAST_INTR(14,fastintr14, ENABLE_ICU1_AND_2) FAST_INTR(15,fastintr15, ENABLE_ICU1_AND_2) INTR(0,intr0, IO_ICU1, ENABLE_ICU1, al) INTR(1,intr1, IO_ICU1, ENABLE_ICU1, al) INTR(2,intr2, IO_ICU1, ENABLE_ICU1, al) INTR(3,intr3, IO_ICU1, ENABLE_ICU1, al) INTR(4,intr4, IO_ICU1, ENABLE_ICU1, al) INTR(5,intr5, IO_ICU1, ENABLE_ICU1, al) INTR(6,intr6, IO_ICU1, ENABLE_ICU1, al) INTR(7,intr7, IO_ICU1, ENABLE_ICU1, al) INTR(8,intr8, IO_ICU2, ENABLE_ICU1_AND_2, ah) INTR(9,intr9, IO_ICU2, ENABLE_ICU1_AND_2, ah) INTR(10,intr10, IO_ICU2, ENABLE_ICU1_AND_2, ah) INTR(11,intr11, IO_ICU2, ENABLE_ICU1_AND_2, ah) INTR(12,intr12, IO_ICU2, ENABLE_ICU1_AND_2, ah) INTR(13,intr13, IO_ICU2, ENABLE_ICU1_AND_2, ah) INTR(14,intr14, IO_ICU2, ENABLE_ICU1_AND_2, ah) INTR(15,intr15, IO_ICU2, ENABLE_ICU1_AND_2, ah) MCOUNT_LABEL(eintr) .data .globl _ihandlers _ihandlers: /* addresses of interrupt handlers */ /* actually resumption addresses for HWI's */ .long Xresume0, Xresume1, Xresume2, Xresume3 .long Xresume4, Xresume5, Xresume6, Xresume7 .long Xresume8, Xresume9, Xresume10, Xresume11 .long Xresume12, Xresume13, Xresume14, Xresume15 - .long swi_tty, swi_net, dummycamisr, dummycamisr + .long _swi_null, swi_net, _swi_null, _swi_null .long _swi_vm, _swi_null, _swi_null, _swi_null .long _swi_null, _swi_null, _swi_null, _swi_null .long _swi_null, _swi_null, _softclock, swi_ast imasks: /* masks for interrupt handlers */ .space NHWI*4 /* padding; HWI masks are elsewhere */ .long SWI_TTY_MASK, SWI_NET_MASK, SWI_CAMNET_MASK, SWI_CAMBIO_MASK .long SWI_VM_MASK, 0, 0, 0 .long 0, 0, 0, 0 .long 0, 0, SWI_CLOCK_MASK, SWI_AST_MASK /* * Interrupt counters and names. The format of these and the label names * must agree with what vmstat expects. The tables are indexed by device * ids so that we don't have to move the names around as devices are * attached. */ #include "vector.h" .globl _intrcnt, _eintrcnt _intrcnt: .space (NR_DEVICES + ICU_LEN) * 4 _eintrcnt: .globl _intrnames, _eintrnames _intrnames: .ascii DEVICE_NAMES .asciz "stray irq0" .asciz "stray irq1" .asciz "stray irq2" .asciz "stray irq3" .asciz "stray irq4" .asciz "stray irq5" .asciz "stray irq6" .asciz "stray irq7" .asciz "stray irq8" .asciz "stray irq9" .asciz "stray irq10" .asciz "stray irq11" .asciz "stray irq12" .asciz "stray irq13" .asciz "stray irq14" .asciz "stray irq15" _eintrnames: .text Index: head/sys/i386/isa/ipl.s =================================================================== --- head/sys/i386/isa/ipl.s (revision 38245) +++ head/sys/i386/isa/ipl.s (revision 38246) @@ -1,455 +1,428 @@ /*- * Copyright (c) 1989, 1990 William F. Jolitz. * Copyright (c) 1990 The Regents of the University of California. * All rights reserved. * * This code is derived from software contributed to Berkeley by * William Jolitz. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)ipl.s * - * $Id: ipl.s,v 1.22 1998/07/27 16:51:33 jlemon Exp $ + * $Id: ipl.s,v 1.23 1998/08/11 15:08:12 bde Exp $ */ /* * AT/386 * Vector interrupt control section */ .data ALIGN_DATA /* current priority (all off) */ .globl _cpl _cpl: .long HWI_MASK | SWI_MASK .globl _tty_imask _tty_imask: .long 0 .globl _bio_imask _bio_imask: .long SWI_CAMBIO_MASK .globl _cam_imask _cam_imask: .long SWI_CAMBIO_MASK | SWI_CAMNET_MASK .globl _net_imask _net_imask: .long SWI_CAMNET_MASK .globl _soft_imask _soft_imask: .long SWI_MASK .globl _softnet_imask _softnet_imask: .long SWI_NET_MASK .globl _softtty_imask _softtty_imask: .long SWI_TTY_MASK /* pending interrupts blocked by splxxx() */ .globl _ipending _ipending: .long 0 /* set with bits for which queue to service */ .globl _netisr _netisr: .long 0 .globl _netisrs _netisrs: .long dummynetisr, dummynetisr, dummynetisr, dummynetisr .long dummynetisr, dummynetisr, dummynetisr, dummynetisr .long dummynetisr, dummynetisr, dummynetisr, dummynetisr .long dummynetisr, dummynetisr, dummynetisr, dummynetisr .long dummynetisr, dummynetisr, dummynetisr, dummynetisr .long dummynetisr, dummynetisr, dummynetisr, dummynetisr .long dummynetisr, dummynetisr, dummynetisr, dummynetisr .long dummynetisr, dummynetisr, dummynetisr, dummynetisr .text #ifdef SMP #ifdef notnow #define TEST_CIL \ cmpl $0x0100, _cil ; \ jne 1f ; \ cmpl $0, _inside_intr ; \ jne 1f ; \ int $3 ; \ 1: #else #define TEST_CIL #endif #endif /* * Handle return from interrupts, traps and syscalls. */ SUPERALIGN_TEXT _doreti: #ifdef SMP TEST_CIL #endif FAKE_MCOUNT(_bintr) /* init "from" _bintr -> _doreti */ addl $4,%esp /* discard unit number */ popl %eax /* cpl or cml to restore */ doreti_next: /* * Check for pending HWIs and SWIs atomically with restoring cpl * and exiting. The check has to be atomic with exiting to stop * (ipending & ~cpl) changing from zero to nonzero while we're * looking at it (this wouldn't be fatal but it would increase * interrupt latency). Restoring cpl has to be atomic with exiting * so that the stack cannot pile up (the nesting level of interrupt * handlers is limited by the number of bits in cpl). */ #ifdef SMP TEST_CIL cli /* early to prevent INT deadlock */ pushl %eax /* preserve cpl while getting lock */ ICPL_LOCK popl %eax doreti_next2: #endif movl %eax,%ecx #ifdef CPL_AND_CML orl _cpl, %ecx /* add cpl to cml */ #endif notl %ecx /* set bit = unmasked level */ #ifndef SMP cli #endif andl _ipending,%ecx /* set bit = unmasked pending INT */ jne doreti_unpend doreti_exit: #ifdef SMP TEST_CIL #endif #ifdef CPL_AND_CML movl %eax, _cml #else movl %eax,_cpl #endif FAST_ICPL_UNLOCK /* preserves %eax */ MPLOCKED decb _intr_nesting_level MEXITCOUNT #ifdef VM86 #ifdef CPL_AND_CML /* XXX CPL_AND_CML needs work */ #error not ready for vm86 #endif cmpl $1,_in_vm86call je 1f /* want cpl == SWI_AST_PENDING */ /* * XXX * Sometimes when attempting to return to vm86 mode, cpl is not * being reset to 0, so here we force it to 0 before returning to * vm86 mode. doreti_stop is a convenient place to set a breakpoint. * When the cpl problem is solved, this code can disappear. */ ICPL_LOCK cmpl $0,_cpl /* cpl == 0, skip it */ je 1f testl $PSL_VM,TF_EFLAGS(%esp) /* going to VM86 mode? */ jne doreti_stop testb $SEL_RPL_MASK,TRAPF_CS_OFF(%esp) /* to user mode? */ je 1f doreti_stop: movl $0,_cpl nop 1: FAST_ICPL_UNLOCK /* preserves %eax */ #endif /* VM86 */ #ifdef SMP #ifdef INTR_SIMPLELOCK #error code needed here to decide which lock to release, INTR or giant #endif /* release the kernel lock */ pushl $_mp_lock /* GIANT_LOCK */ call _MPrellock add $4, %esp #endif /* SMP */ .globl doreti_popl_es doreti_popl_es: popl %es .globl doreti_popl_ds doreti_popl_ds: popl %ds popal addl $8,%esp .globl doreti_iret doreti_iret: iret ALIGN_TEXT .globl doreti_iret_fault doreti_iret_fault: subl $8,%esp pushal pushl %ds .globl doreti_popl_ds_fault doreti_popl_ds_fault: pushl %es .globl doreti_popl_es_fault doreti_popl_es_fault: movl $0,4+4+32+4(%esp) /* XXX should be the error code */ movl $T_PROTFLT,4+4+32+0(%esp) jmp alltraps_with_regs_pushed ALIGN_TEXT doreti_unpend: /* * Enabling interrupts is safe because we haven't restored cpl yet. * The locking from the "btrl" test is probably no longer necessary. * We won't miss any new pending interrupts because we will check * for them again. */ #ifdef SMP TEST_CIL /* we enter with cpl locked */ bsfl %ecx, %ecx /* slow, but not worth optimizing */ btrl %ecx, _ipending jnc doreti_next2 /* some intr cleared memory copy */ cmpl $NHWI, %ecx jae 1f btsl %ecx, _cil 1: FAST_ICPL_UNLOCK /* preserves %eax */ sti /* late to prevent INT deadlock */ #else sti bsfl %ecx,%ecx /* slow, but not worth optimizing */ btrl %ecx,_ipending jnc doreti_next /* some intr cleared memory copy */ #endif /* SMP */ /* * Set up JUMP to _ihandlers[%ecx] for HWIs. * Set up CALL of _ihandlers[%ecx] for SWIs. * This is a bit early for the SMP case - we have to push %ecx and * %edx, but could push only %ecx and load %edx later. */ movl _ihandlers(,%ecx,4),%edx cmpl $NHWI,%ecx jae doreti_swi cli #ifdef SMP pushl %edx /* preserve %edx */ #ifdef APIC_INTR_DIAGNOSTIC pushl %ecx #endif pushl %eax /* preserve %eax */ ICPL_LOCK #ifdef CPL_AND_CML popl _cml #else popl _cpl #endif FAST_ICPL_UNLOCK #ifdef APIC_INTR_DIAGNOSTIC popl %ecx #endif popl %edx #else movl %eax,_cpl #endif MEXITCOUNT #ifdef APIC_INTR_DIAGNOSTIC lock incl CNAME(apic_itrace_doreti)(,%ecx,4) #ifdef APIC_INTR_DIAGNOSTIC_IRQ cmpl $APIC_INTR_DIAGNOSTIC_IRQ,%ecx jne 9f pushl %eax pushl %ecx pushl %edx pushl $APIC_ITRACE_DORETI call log_intr_event addl $4,%esp popl %edx popl %ecx popl %eax 9: #endif #endif jmp %edx ALIGN_TEXT doreti_swi: #ifdef SMP TEST_CIL #endif pushl %eax /* * The SWI_AST handler has to run at cpl = SWI_AST_MASK and the * SWI_CLOCK handler at cpl = SWI_CLOCK_MASK, so we have to restore * all the h/w bits in cpl now and have to worry about stack growth. * The worst case is currently (30 Jan 1994) 2 SWI handlers nested * in dying interrupt frames and about 12 HWIs nested in active * interrupt frames. There are only 4 different SWIs and the HWI * and SWI masks limit the nesting further. */ #ifdef SMP orl imasks(,%ecx,4), %eax pushl %ecx /* preserve for use by _swi_generic */ pushl %edx /* save handler entry point */ cli /* prevent INT deadlock */ pushl %eax /* save cpl|cml */ ICPL_LOCK #ifdef CPL_AND_CML popl _cml /* restore cml */ #else popl _cpl /* restore cpl */ #endif FAST_ICPL_UNLOCK sti popl %edx /* restore handler entry point */ popl %ecx #else orl imasks(,%ecx,4),%eax movl %eax,_cpl #endif call %edx popl %eax jmp doreti_next ALIGN_TEXT swi_ast: addl $8,%esp /* discard raddr & cpl to get trap frame */ #ifdef VM86 cmpl $1,_in_vm86call je 1f /* stay in kernel mode */ #endif testb $SEL_RPL_MASK,TRAPF_CS_OFF(%esp) je swi_ast_phantom swi_ast_user: movl $T_ASTFLT,(2+8+0)*4(%esp) movb $0,_intr_nesting_level /* finish becoming a trap handler */ call _trap subl %eax,%eax /* recover cpl|cml */ #ifdef CPL_AND_CML movl %eax, _cpl #endif movb $1,_intr_nesting_level /* for doreti_next to decrement */ jmp doreti_next ALIGN_TEXT swi_ast_phantom: #ifdef VM86 /* * check for ast from vm86 mode. Placed down here so the jumps do * not get taken for mainline code. */ testl $PSL_VM,TF_EFLAGS(%esp) jne swi_ast_user 1: #endif /* VM86 */ /* * These happen when there is an interrupt in a trap handler before * ASTs can be masked or in an lcall handler before they can be * masked or after they are unmasked. They could be avoided for * trap entries by using interrupt gates, and for lcall exits by * using by using cli, but they are unavoidable for lcall entries. */ cli ICPL_LOCK orl $SWI_AST_PENDING, _ipending /* cpl is unlocked in doreti_exit */ subl %eax,%eax #ifdef CPL_AND_CML movl %eax, _cpl #endif jmp doreti_exit /* SWI_AST is highest so we must be done */ ALIGN_TEXT swi_net: MCOUNT bsfl _netisr,%eax je swi_net_done swi_net_more: btrl %eax,_netisr jnc swi_net_next call *_netisrs(,%eax,4) swi_net_next: bsfl _netisr,%eax jne swi_net_more swi_net_done: ret ALIGN_TEXT dummynetisr: MCOUNT ret - - ALIGN_TEXT -dummycamisr: - MCOUNT - ret - -/* - * This function will go away soon when register_swi() is used to register - * the poll functions. - */ - ALIGN_TEXT -swi_tty: - MCOUNT -#include "cy.h" -#if NCY > 0 - call _cypoll -#endif -#include "rc.h" -#if NRC > 0 - call _rcpoll -#endif -#include "sio.h" -#if NSIO > 0 - jmp _siopoll -#else - ret -#endif /* * The arg is in a nonstandard place, so swi_dispatcher() can't be called * directly and swi_generic() can't use ENTRY() or MCOUNT. */ ALIGN_TEXT .globl _swi_generic _swi_generic: pushl %ecx FAKE_MCOUNT(4(%esp)) call _swi_dispatcher popl %ecx ret ENTRY(swi_null) ret #ifdef APIC_IO #include "i386/isa/apic_ipl.s" #else #include "i386/isa/icu_ipl.s" #endif /* APIC_IO */ Index: head/sys/i386/isa/rc.c =================================================================== --- head/sys/i386/isa/rc.c (revision 38245) +++ head/sys/i386/isa/rc.c (revision 38246) @@ -1,1512 +1,1507 @@ /* * Copyright (C) 1995 by Pavel Antonov, Moscow, Russia. * Copyright (C) 1995 by Andrey A. Chernov, Moscow, Russia. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * SDL Communications Riscom/8 (based on Cirrus Logic CL-CD180) driver * */ #include "rc.h" #if NRC > 0 #include "opt_devfs.h" /*#define RCDEBUG*/ #include #include #include #include #include #include #include +#include #include #ifdef DEVFS #include #endif /*DEVFS*/ #include +#include #include #include #include /* Prototypes */ static int rcprobe __P((struct isa_device *)); static int rcattach __P((struct isa_device *)); -/*- - * This space intentionally left blank to stop __LINE__ from screwing up - * regression tests :-(. - * - * - * - */ -void rcpoll __P((void)); - #define rcin(port) RC_IN (nec, port) #define rcout(port,v) RC_OUT (nec, port, v) #define WAITFORCCR(u,c) rc_wait0(nec, (u), (c), __LINE__) #define CCRCMD(u,c,cmd) WAITFORCCR((u), (c)); rcout(CD180_CCR, (cmd)) #define RC_IBUFSIZE 256 #define RB_I_HIGH_WATER (TTYHOG - 2 * RC_IBUFSIZE) #define RC_OBUFSIZE 512 #define RC_IHIGHWATER (3 * RC_IBUFSIZE / 4) #define INPUT_FLAGS_SHIFT (2 * RC_IBUFSIZE) #define LOTS_OF_EVENTS 64 #define RC_FAKEID 0x10 #define RC_PROBED 1 #define RC_ATTACHED 2 #define GET_UNIT(dev) (minor(dev) & 0x3F) #define CALLOUT(dev) (minor(dev) & 0x80) /* For isa routines */ struct isa_driver rcdriver = { rcprobe, rcattach, "rc" }; static d_open_t rcopen; static d_close_t rcclose; static d_read_t rcread; static d_write_t rcwrite; static d_ioctl_t rcioctl; static d_stop_t rcstop; static d_devtotty_t rcdevtotty; #define CDEV_MAJOR 63 static struct cdevsw rc_cdevsw = { rcopen, rcclose, rcread, rcwrite, /*63*/ rcioctl, rcstop, noreset, rcdevtotty,/* rc */ ttpoll, nommap, NULL, "rc", NULL, -1 }; /* Per-board structure */ static struct rc_softc { u_int rcb_probed; /* 1 - probed, 2 - attached */ u_int rcb_addr; /* Base I/O addr */ u_int rcb_unit; /* unit # */ u_char rcb_dtr; /* DTR status */ struct rc_chans *rcb_baserc; /* base rc ptr */ } rc_softc[NRC]; /* Per-channel structure */ static struct rc_chans { struct rc_softc *rc_rcb; /* back ptr */ u_short rc_flags; /* Misc. flags */ int rc_chan; /* Channel # */ u_char rc_ier; /* intr. enable reg */ u_char rc_msvr; /* modem sig. status */ u_char rc_cor2; /* options reg */ u_char rc_pendcmd; /* special cmd pending */ u_int rc_dtrwait; /* dtr timeout */ u_int rc_dcdwaits; /* how many waits DCD in open */ u_char rc_hotchar; /* end packed optimize */ struct tty *rc_tp; /* tty struct */ u_char *rc_iptr; /* Chars input buffer */ u_char *rc_hiwat; /* hi-water mark */ u_char *rc_bufend; /* end of buffer */ u_char *rc_optr; /* ptr in output buf */ u_char *rc_obufend; /* end of output buf */ u_char rc_ibuf[4 * RC_IBUFSIZE]; /* input buffer */ u_char rc_obuf[RC_OBUFSIZE]; /* output buffer */ #ifdef DEVFS void *devfs_token; #endif } rc_chans[NRC * CD180_NCHAN]; static int rc_scheduled_event = 0; /* for pstat -t */ static struct tty rc_tty[NRC * CD180_NCHAN]; static const int nrc_tty = NRC * CD180_NCHAN; /* Flags */ #define RC_DTR_OFF 0x0001 /* DTR wait, for close/open */ #define RC_ACTOUT 0x0002 /* Dial-out port active */ #define RC_RTSFLOW 0x0004 /* RTS flow ctl enabled */ #define RC_CTSFLOW 0x0008 /* CTS flow ctl enabled */ #define RC_DORXFER 0x0010 /* RXFER event planned */ #define RC_DOXXFER 0x0020 /* XXFER event planned */ #define RC_MODCHG 0x0040 /* Modem status changed */ #define RC_OSUSP 0x0080 /* Output suspended */ #define RC_OSBUSY 0x0100 /* start() routine in progress */ #define RC_WAS_BUFOVFL 0x0200 /* low-level buffer ovferflow */ #define RC_WAS_SILOVFL 0x0400 /* silo buffer overflow */ #define RC_SEND_RDY 0x0800 /* ready to send */ /* Table for translation of RCSR status bits to internal form */ static int rc_rcsrt[16] = { 0, TTY_OE, TTY_FE, TTY_FE|TTY_OE, TTY_PE, TTY_PE|TTY_OE, TTY_PE|TTY_FE, TTY_PE|TTY_FE|TTY_OE, TTY_BI, TTY_BI|TTY_OE, TTY_BI|TTY_FE, TTY_BI|TTY_FE|TTY_OE, TTY_BI|TTY_PE, TTY_BI|TTY_PE|TTY_OE, TTY_BI|TTY_PE|TTY_FE, TTY_BI|TTY_PE|TTY_FE|TTY_OE }; /* Static prototypes */ static void rc_hwreset __P((int, int, unsigned int)); static int rc_test __P((int, int)); static void rc_discard_output __P((struct rc_chans *)); static void rc_hardclose __P((struct rc_chans *)); static int rc_modctl __P((struct rc_chans *, int, int)); static void rc_start __P((struct tty *)); static int rc_param __P((struct tty *, struct termios *)); +static swihand_t rcpoll; static void rc_reinit __P((struct rc_softc *)); #ifdef RCDEBUG static void printrcflags(); #endif static timeout_t rc_dtrwakeup; static timeout_t rc_wakeup; static void disc_optim __P((struct tty *tp, struct termios *t, struct rc_chans *)); static void rc_wait0 __P((int nec, int unit, int chan, int line)); /**********************************************/ /* Quick device probing */ static int rcprobe(dvp) struct isa_device *dvp; { int irq = ffs(dvp->id_irq) - 1; register int nec = dvp->id_iobase; if (dvp->id_unit > NRC) return 0; if (!RC_VALIDADDR(nec)) { printf("rc%d: illegal base address %x\n", dvp->id_unit, nec); return 0; } if (!RC_VALIDIRQ(irq)) { printf("rc%d: illegal IRQ value %d\n", dvp->id_unit, irq); return 0; } rcout(CD180_PPRL, 0x22); /* Random values to Prescale reg. */ rcout(CD180_PPRH, 0x11); if (rcin(CD180_PPRL) != 0x22 || rcin(CD180_PPRH) != 0x11) return 0; /* Now, test the board more thoroughly, with diagnostic */ if (rc_test(nec, dvp->id_unit)) return 0; rc_softc[dvp->id_unit].rcb_probed = RC_PROBED; return 0xF; } static int rcattach(dvp) struct isa_device *dvp; { register int chan, nec = dvp->id_iobase; struct rc_softc *rcb = &rc_softc[dvp->id_unit]; struct rc_chans *rc = &rc_chans[dvp->id_unit * CD180_NCHAN]; - static int rc_wakeup_started = 0; + static int rc_started = 0; struct tty *tp; /* Thorooughly test the device */ if (rcb->rcb_probed != RC_PROBED) return 0; rcb->rcb_addr = nec; rcb->rcb_dtr = 0; rcb->rcb_baserc = rc; /*rcb->rcb_chipid = 0x10 + dvp->id_unit;*/ printf("rc%d: %d chans, firmware rev. %c\n", dvp->id_unit, CD180_NCHAN, (rcin(CD180_GFRCR) & 0xF) + 'A'); for (chan = 0; chan < CD180_NCHAN; chan++, rc++) { rc->rc_rcb = rcb; rc->rc_chan = chan; rc->rc_iptr = rc->rc_ibuf; rc->rc_bufend = &rc->rc_ibuf[RC_IBUFSIZE]; rc->rc_hiwat = &rc->rc_ibuf[RC_IHIGHWATER]; rc->rc_flags = rc->rc_ier = rc->rc_msvr = 0; rc->rc_cor2 = rc->rc_pendcmd = 0; rc->rc_optr = rc->rc_obufend = rc->rc_obuf; rc->rc_dtrwait = 3 * hz; rc->rc_dcdwaits= 0; rc->rc_hotchar = 0; tp = rc->rc_tp = &rc_tty[chan]; ttychars(tp); tp->t_lflag = tp->t_iflag = tp->t_oflag = 0; tp->t_cflag = TTYDEF_CFLAG; tp->t_ispeed = tp->t_ospeed = TTYDEF_SPEED; #ifdef DEVFS /* FIX THIS to reflect real devices */ rc->devfs_token = devfs_add_devswf(&rc_cdevsw, (dvp->id_unit * CD180_NCHAN) + chan, DV_CHR, 0, 0, 0600, "rc%d.%d", dvp->id_unit, chan); #endif } rcb->rcb_probed = RC_ATTACHED; - if (!rc_wakeup_started) { + if (!rc_started) { + register_swi(SWI_TTY, rcpoll); rc_wakeup((void *)NULL); - rc_wakeup_started = 1; + rc_started = 0; } return 1; } /* RC interrupt handling */ void rcintr(unit) int unit; { register struct rc_softc *rcb = &rc_softc[unit]; register struct rc_chans *rc; register int nec, resid; register u_char val, iack, bsr, ucnt, *optr; int good_data, t_state; if (rcb->rcb_probed != RC_ATTACHED) { printf("rc%d: bogus interrupt\n", unit); return; } nec = rcb->rcb_addr; bsr = ~(rcin(RC_BSR)); if (!(bsr & (RC_BSR_TOUT|RC_BSR_RXINT|RC_BSR_TXINT|RC_BSR_MOINT))) { printf("rc%d: extra interrupt\n", unit); rcout(CD180_EOIR, 0); return; } while (bsr & (RC_BSR_TOUT|RC_BSR_RXINT|RC_BSR_TXINT|RC_BSR_MOINT)) { #ifdef RCDEBUG_DETAILED printf("rc%d: intr (%02x) %s%s%s%s\n", unit, bsr, (bsr & RC_BSR_TOUT)?"TOUT ":"", (bsr & RC_BSR_RXINT)?"RXINT ":"", (bsr & RC_BSR_TXINT)?"TXINT ":"", (bsr & RC_BSR_MOINT)?"MOINT":""); #endif if (bsr & RC_BSR_TOUT) { printf("rc%d: hardware failure, reset board\n", unit); rcout(RC_CTOUT, 0); rc_reinit(rcb); return; } if (bsr & RC_BSR_RXINT) { iack = rcin(RC_PILR_RX); good_data = (iack == (GIVR_IT_RGDI | RC_FAKEID)); if (!good_data && iack != (GIVR_IT_REI | RC_FAKEID)) { printf("rc%d: fake rxint: %02x\n", unit, iack); goto more_intrs; } rc = rcb->rcb_baserc + ((rcin(CD180_GICR) & GICR_CHAN) >> GICR_LSH); t_state = rc->rc_tp->t_state; /* Do RTS flow control stuff */ if ( (rc->rc_flags & RC_RTSFLOW) || !(t_state & TS_ISOPEN) ) { if ( ( !(t_state & TS_ISOPEN) || (t_state & TS_TBLOCK) ) && (rc->rc_msvr & MSVR_RTS) ) rcout(CD180_MSVR, rc->rc_msvr &= ~MSVR_RTS); else if (!(rc->rc_msvr & MSVR_RTS)) rcout(CD180_MSVR, rc->rc_msvr |= MSVR_RTS); } ucnt = rcin(CD180_RDCR) & 0xF; resid = 0; if (t_state & TS_ISOPEN) { /* check for input buffer overflow */ if ((rc->rc_iptr + ucnt) >= rc->rc_bufend) { resid = ucnt; ucnt = rc->rc_bufend - rc->rc_iptr; resid -= ucnt; if (!(rc->rc_flags & RC_WAS_BUFOVFL)) { rc->rc_flags |= RC_WAS_BUFOVFL; rc_scheduled_event++; } } optr = rc->rc_iptr; /* check foor good data */ if (good_data) { while (ucnt-- > 0) { val = rcin(CD180_RDR); optr[0] = val; optr[INPUT_FLAGS_SHIFT] = 0; optr++; rc_scheduled_event++; if (val != 0 && val == rc->rc_hotchar) setsofttty(); } } else { /* Store also status data */ while (ucnt-- > 0) { iack = rcin(CD180_RCSR); if (iack & RCSR_Timeout) break; if ( (iack & RCSR_OE) && !(rc->rc_flags & RC_WAS_SILOVFL)) { rc->rc_flags |= RC_WAS_SILOVFL; rc_scheduled_event++; } val = rcin(CD180_RDR); /* Don't store PE if IGNPAR and BREAK if IGNBRK, this hack allows "raw" tty optimization works even if IGN* is set. */ if ( !(iack & (RCSR_PE|RCSR_FE|RCSR_Break)) || (!(iack & (RCSR_PE|RCSR_FE)) || !(rc->rc_tp->t_iflag & IGNPAR)) && (!(iack & RCSR_Break) || !(rc->rc_tp->t_iflag & IGNBRK))) { if ( (iack & (RCSR_PE|RCSR_FE)) && (t_state & TS_CAN_BYPASS_L_RINT) && ((iack & RCSR_FE) || (iack & RCSR_PE) && (rc->rc_tp->t_iflag & INPCK))) val = 0; else if (val != 0 && val == rc->rc_hotchar) setsofttty(); optr[0] = val; optr[INPUT_FLAGS_SHIFT] = iack; optr++; rc_scheduled_event++; } } } rc->rc_iptr = optr; rc->rc_flags |= RC_DORXFER; } else resid = ucnt; /* Clear FIFO if necessary */ while (resid-- > 0) { if (!good_data) iack = rcin(CD180_RCSR); else iack = 0; if (iack & RCSR_Timeout) break; (void) rcin(CD180_RDR); } goto more_intrs; } if (bsr & RC_BSR_MOINT) { iack = rcin(RC_PILR_MODEM); if (iack != (GIVR_IT_MSCI | RC_FAKEID)) { printf("rc%d: fake moint: %02x\n", unit, iack); goto more_intrs; } rc = rcb->rcb_baserc + ((rcin(CD180_GICR) & GICR_CHAN) >> GICR_LSH); iack = rcin(CD180_MCR); rc->rc_msvr = rcin(CD180_MSVR); rcout(CD180_MCR, 0); #ifdef RCDEBUG printrcflags(rc, "moint"); #endif if (rc->rc_flags & RC_CTSFLOW) { if (rc->rc_msvr & MSVR_CTS) rc->rc_flags |= RC_SEND_RDY; else rc->rc_flags &= ~RC_SEND_RDY; } else rc->rc_flags |= RC_SEND_RDY; if ((iack & MCR_CDchg) && !(rc->rc_flags & RC_MODCHG)) { rc_scheduled_event += LOTS_OF_EVENTS; rc->rc_flags |= RC_MODCHG; setsofttty(); } goto more_intrs; } if (bsr & RC_BSR_TXINT) { iack = rcin(RC_PILR_TX); if (iack != (GIVR_IT_TDI | RC_FAKEID)) { printf("rc%d: fake txint: %02x\n", unit, iack); goto more_intrs; } rc = rcb->rcb_baserc + ((rcin(CD180_GICR) & GICR_CHAN) >> GICR_LSH); if ( (rc->rc_flags & RC_OSUSP) || !(rc->rc_flags & RC_SEND_RDY) ) goto more_intrs; /* Handle breaks and other stuff */ if (rc->rc_pendcmd) { rcout(CD180_COR2, rc->rc_cor2 |= COR2_ETC); rcout(CD180_TDR, CD180_C_ESC); rcout(CD180_TDR, rc->rc_pendcmd); rcout(CD180_COR2, rc->rc_cor2 &= ~COR2_ETC); rc->rc_pendcmd = 0; goto more_intrs; } optr = rc->rc_optr; resid = rc->rc_obufend - optr; if (resid > CD180_NFIFO) resid = CD180_NFIFO; while (resid-- > 0) rcout(CD180_TDR, *optr++); rc->rc_optr = optr; /* output completed? */ if (optr >= rc->rc_obufend) { rcout(CD180_IER, rc->rc_ier &= ~IER_TxRdy); #ifdef RCDEBUG printf("rc%d/%d: output completed\n", unit, rc->rc_chan); #endif if (!(rc->rc_flags & RC_DOXXFER)) { rc_scheduled_event += LOTS_OF_EVENTS; rc->rc_flags |= RC_DOXXFER; setsofttty(); } } } more_intrs: rcout(CD180_EOIR, 0); /* end of interrupt */ rcout(RC_CTOUT, 0); bsr = ~(rcin(RC_BSR)); } } /* Feed characters to output buffer */ static void rc_start(tp) register struct tty *tp; { register struct rc_chans *rc = &rc_chans[GET_UNIT(tp->t_dev)]; register int nec = rc->rc_rcb->rcb_addr, s; if (rc->rc_flags & RC_OSBUSY) return; s = spltty(); rc->rc_flags |= RC_OSBUSY; disable_intr(); if (tp->t_state & TS_TTSTOP) rc->rc_flags |= RC_OSUSP; else rc->rc_flags &= ~RC_OSUSP; /* Do RTS flow control stuff */ if ( (rc->rc_flags & RC_RTSFLOW) && (tp->t_state & TS_TBLOCK) && (rc->rc_msvr & MSVR_RTS) ) { rcout(CD180_CAR, rc->rc_chan); rcout(CD180_MSVR, rc->rc_msvr &= ~MSVR_RTS); } else if (!(rc->rc_msvr & MSVR_RTS)) { rcout(CD180_CAR, rc->rc_chan); rcout(CD180_MSVR, rc->rc_msvr |= MSVR_RTS); } enable_intr(); if (tp->t_state & (TS_TIMEOUT|TS_TTSTOP)) goto out; #ifdef RCDEBUG printrcflags(rc, "rcstart"); #endif ttwwakeup(tp); #ifdef RCDEBUG printf("rcstart: outq = %d obuf = %d\n", tp->t_outq.c_cc, rc->rc_obufend - rc->rc_optr); #endif if (tp->t_state & TS_BUSY) goto out; /* output still in progress ... */ if (tp->t_outq.c_cc > 0) { u_int ocnt; tp->t_state |= TS_BUSY; ocnt = q_to_b(&tp->t_outq, rc->rc_obuf, sizeof rc->rc_obuf); disable_intr(); rc->rc_optr = rc->rc_obuf; rc->rc_obufend = rc->rc_optr + ocnt; enable_intr(); if (!(rc->rc_ier & IER_TxRdy)) { #ifdef RCDEBUG printf("rc%d/%d: rcstart enable txint\n", rc->rc_rcb->rcb_unit, rc->rc_chan); #endif rcout(CD180_CAR, rc->rc_chan); rcout(CD180_IER, rc->rc_ier |= IER_TxRdy); } } out: rc->rc_flags &= ~RC_OSBUSY; (void) splx(s); } /* Handle delayed events. */ void rcpoll() { register struct rc_chans *rc; register struct rc_softc *rcb; register u_char *tptr, *eptr; register struct tty *tp; register int chan, icnt, nec, unit; if (rc_scheduled_event == 0) return; repeat: for (unit = 0; unit < NRC; unit++) { rcb = &rc_softc[unit]; rc = rcb->rcb_baserc; nec = rc->rc_rcb->rcb_addr; for (chan = 0; chan < CD180_NCHAN; rc++, chan++) { tp = rc->rc_tp; #ifdef RCDEBUG if (rc->rc_flags & (RC_DORXFER|RC_DOXXFER|RC_MODCHG| RC_WAS_BUFOVFL|RC_WAS_SILOVFL)) printrcflags(rc, "rcevent"); #endif if (rc->rc_flags & RC_WAS_BUFOVFL) { disable_intr(); rc->rc_flags &= ~RC_WAS_BUFOVFL; rc_scheduled_event--; enable_intr(); printf("rc%d/%d: interrupt-level buffer overflow\n", unit, chan); } if (rc->rc_flags & RC_WAS_SILOVFL) { disable_intr(); rc->rc_flags &= ~RC_WAS_SILOVFL; rc_scheduled_event--; enable_intr(); printf("rc%d/%d: silo overflow\n", unit, chan); } if (rc->rc_flags & RC_MODCHG) { disable_intr(); rc->rc_flags &= ~RC_MODCHG; rc_scheduled_event -= LOTS_OF_EVENTS; enable_intr(); (*linesw[tp->t_line].l_modem)(tp, !!(rc->rc_msvr & MSVR_CD)); } if (rc->rc_flags & RC_DORXFER) { disable_intr(); rc->rc_flags &= ~RC_DORXFER; eptr = rc->rc_iptr; if (rc->rc_bufend == &rc->rc_ibuf[2 * RC_IBUFSIZE]) tptr = &rc->rc_ibuf[RC_IBUFSIZE]; else tptr = rc->rc_ibuf; icnt = eptr - tptr; if (icnt > 0) { if (rc->rc_bufend == &rc->rc_ibuf[2 * RC_IBUFSIZE]) { rc->rc_iptr = rc->rc_ibuf; rc->rc_bufend = &rc->rc_ibuf[RC_IBUFSIZE]; rc->rc_hiwat = &rc->rc_ibuf[RC_IHIGHWATER]; } else { rc->rc_iptr = &rc->rc_ibuf[RC_IBUFSIZE]; rc->rc_bufend = &rc->rc_ibuf[2 * RC_IBUFSIZE]; rc->rc_hiwat = &rc->rc_ibuf[RC_IBUFSIZE + RC_IHIGHWATER]; } if ( (rc->rc_flags & RC_RTSFLOW) && (tp->t_state & TS_ISOPEN) && !(tp->t_state & TS_TBLOCK) && !(rc->rc_msvr & MSVR_RTS) ) { rcout(CD180_CAR, chan); rcout(CD180_MSVR, rc->rc_msvr |= MSVR_RTS); } rc_scheduled_event -= icnt; } enable_intr(); if (icnt <= 0 || !(tp->t_state & TS_ISOPEN)) goto done1; if ( (tp->t_state & TS_CAN_BYPASS_L_RINT) && !(tp->t_state & TS_LOCAL)) { if ((tp->t_rawq.c_cc + icnt) >= RB_I_HIGH_WATER && ((rc->rc_flags & RC_RTSFLOW) || (tp->t_iflag & IXOFF)) && !(tp->t_state & TS_TBLOCK)) ttyblock(tp); tk_nin += icnt; tk_rawcc += icnt; tp->t_rawcc += icnt; if (b_to_q(tptr, icnt, &tp->t_rawq)) printf("rc%d/%d: tty-level buffer overflow\n", unit, chan); ttwakeup(tp); if ((tp->t_state & TS_TTSTOP) && ((tp->t_iflag & IXANY) || (tp->t_cc[VSTART] == tp->t_cc[VSTOP]))) { tp->t_state &= ~TS_TTSTOP; tp->t_lflag &= ~FLUSHO; rc_start(tp); } } else { for (; tptr < eptr; tptr++) (*linesw[tp->t_line].l_rint) (tptr[0] | rc_rcsrt[tptr[INPUT_FLAGS_SHIFT] & 0xF], tp); } done1: ; } if (rc->rc_flags & RC_DOXXFER) { disable_intr(); rc_scheduled_event -= LOTS_OF_EVENTS; rc->rc_flags &= ~RC_DOXXFER; rc->rc_tp->t_state &= ~TS_BUSY; enable_intr(); (*linesw[tp->t_line].l_start)(tp); } } if (rc_scheduled_event == 0) break; } if (rc_scheduled_event >= LOTS_OF_EVENTS) goto repeat; } static void rcstop(tp, rw) register struct tty *tp; int rw; { register struct rc_chans *rc = &rc_chans[GET_UNIT(tp->t_dev)]; u_char *tptr, *eptr; #ifdef RCDEBUG printf("rc%d/%d: rcstop %s%s\n", rc->rc_rcb->rcb_unit, rc->rc_chan, (rw & FWRITE)?"FWRITE ":"", (rw & FREAD)?"FREAD":""); #endif if (rw & FWRITE) rc_discard_output(rc); disable_intr(); if (rw & FREAD) { rc->rc_flags &= ~RC_DORXFER; eptr = rc->rc_iptr; if (rc->rc_bufend == &rc->rc_ibuf[2 * RC_IBUFSIZE]) { tptr = &rc->rc_ibuf[RC_IBUFSIZE]; rc->rc_iptr = &rc->rc_ibuf[RC_IBUFSIZE]; } else { tptr = rc->rc_ibuf; rc->rc_iptr = rc->rc_ibuf; } rc_scheduled_event -= eptr - tptr; } if (tp->t_state & TS_TTSTOP) rc->rc_flags |= RC_OSUSP; else rc->rc_flags &= ~RC_OSUSP; enable_intr(); } static int rcopen(dev, flag, mode, p) dev_t dev; int flag, mode; struct proc *p; { register struct rc_chans *rc; register struct tty *tp; int unit, nec, s, error = 0; unit = GET_UNIT(dev); if (unit >= NRC * CD180_NCHAN) return ENXIO; if (rc_softc[unit / CD180_NCHAN].rcb_probed != RC_ATTACHED) return ENXIO; rc = &rc_chans[unit]; tp = rc->rc_tp; nec = rc->rc_rcb->rcb_addr; #ifdef RCDEBUG printf("rc%d/%d: rcopen: dev %x\n", rc->rc_rcb->rcb_unit, unit, dev); #endif s = spltty(); again: while (rc->rc_flags & RC_DTR_OFF) { error = tsleep(&(rc->rc_dtrwait), TTIPRI | PCATCH, "rcdtr", 0); if (error != 0) goto out; } if (tp->t_state & TS_ISOPEN) { if (CALLOUT(dev)) { if (!(rc->rc_flags & RC_ACTOUT)) { error = EBUSY; goto out; } } else { if (rc->rc_flags & RC_ACTOUT) { if (flag & O_NONBLOCK) { error = EBUSY; goto out; } if (error = tsleep(&rc->rc_rcb, TTIPRI|PCATCH, "rcbi", 0)) goto out; goto again; } } if (tp->t_state & TS_XCLUDE && p->p_ucred->cr_uid != 0) { error = EBUSY; goto out; } } else { tp->t_oproc = rc_start; tp->t_param = rc_param; tp->t_dev = dev; if (CALLOUT(dev)) tp->t_cflag |= CLOCAL; else tp->t_cflag &= ~CLOCAL; error = rc_param(tp, &tp->t_termios); if (error) goto out; (void) rc_modctl(rc, TIOCM_RTS|TIOCM_DTR, DMSET); ttsetwater(tp); if ((rc->rc_msvr & MSVR_CD) || CALLOUT(dev)) (*linesw[tp->t_line].l_modem)(tp, 1); } if (!(tp->t_state & TS_CARR_ON) && !CALLOUT(dev) && !(tp->t_cflag & CLOCAL) && !(flag & O_NONBLOCK)) { rc->rc_dcdwaits++; error = tsleep(TSA_CARR_ON(tp), TTIPRI | PCATCH, "rcdcd", 0); rc->rc_dcdwaits--; if (error != 0) goto out; goto again; } error = (*linesw[tp->t_line].l_open)(dev, tp); disc_optim(tp, &tp->t_termios, rc); if ((tp->t_state & TS_ISOPEN) && CALLOUT(dev)) rc->rc_flags |= RC_ACTOUT; out: (void) splx(s); if(rc->rc_dcdwaits == 0 && !(tp->t_state & TS_ISOPEN)) rc_hardclose(rc); return error; } static int rcclose(dev, flag, mode, p) dev_t dev; int flag, mode; struct proc *p; { register struct rc_chans *rc; register struct tty *tp; int s, unit = GET_UNIT(dev); if (unit >= NRC * CD180_NCHAN) return ENXIO; rc = &rc_chans[unit]; tp = rc->rc_tp; #ifdef RCDEBUG printf("rc%d/%d: rcclose dev %x\n", rc->rc_rcb->rcb_unit, unit, dev); #endif s = spltty(); (*linesw[tp->t_line].l_close)(tp, flag); disc_optim(tp, &tp->t_termios, rc); rcstop(tp, FREAD | FWRITE); rc_hardclose(rc); ttyclose(tp); splx(s); return 0; } static void rc_hardclose(rc) register struct rc_chans *rc; { register int s, nec = rc->rc_rcb->rcb_addr; register struct tty *tp = rc->rc_tp; s = spltty(); rcout(CD180_CAR, rc->rc_chan); /* Disable rx/tx intrs */ rcout(CD180_IER, rc->rc_ier = 0); if ( (tp->t_cflag & HUPCL) || !(rc->rc_flags & RC_ACTOUT) && !(rc->rc_msvr & MSVR_CD) && !(tp->t_cflag & CLOCAL) || !(tp->t_state & TS_ISOPEN) ) { CCRCMD(rc->rc_rcb->rcb_unit, rc->rc_chan, CCR_ResetChan); WAITFORCCR(rc->rc_rcb->rcb_unit, rc->rc_chan); (void) rc_modctl(rc, TIOCM_RTS, DMSET); if (rc->rc_dtrwait) { timeout(rc_dtrwakeup, rc, rc->rc_dtrwait); rc->rc_flags |= RC_DTR_OFF; } } rc->rc_flags &= ~RC_ACTOUT; wakeup((caddr_t) &rc->rc_rcb); /* wake bi */ wakeup(TSA_CARR_ON(tp)); (void) splx(s); } /* Read from line */ static int rcread(dev, uio, flag) dev_t dev; struct uio *uio; int flag; { struct tty *tp = rc_chans[GET_UNIT(dev)].rc_tp; return ((*linesw[tp->t_line].l_read)(tp, uio, flag)); } /* Write to line */ static int rcwrite(dev, uio, flag) dev_t dev; struct uio *uio; int flag; { struct tty *tp = rc_chans[GET_UNIT(dev)].rc_tp; return ((*linesw[tp->t_line].l_write)(tp, uio, flag)); } /* Reset the bastard */ static void rc_hwreset(unit, nec, chipid) register int unit, nec; unsigned int chipid; { CCRCMD(unit, -1, CCR_HWRESET); /* Hardware reset */ DELAY(20000); WAITFORCCR(unit, -1); rcout(RC_CTOUT, 0); /* Clear timeout */ rcout(CD180_GIVR, chipid); rcout(CD180_GICR, 0); /* Set Prescaler Registers (1 msec) */ rcout(CD180_PPRL, ((RC_OSCFREQ + 999) / 1000) & 0xFF); rcout(CD180_PPRH, ((RC_OSCFREQ + 999) / 1000) >> 8); /* Initialize Priority Interrupt Level Registers */ rcout(CD180_PILR1, RC_PILR_MODEM); rcout(CD180_PILR2, RC_PILR_TX); rcout(CD180_PILR3, RC_PILR_RX); /* Reset DTR */ rcout(RC_DTREG, ~0); } /* Set channel parameters */ static int rc_param(tp, ts) register struct tty *tp; struct termios *ts; { register struct rc_chans *rc = &rc_chans[GET_UNIT(tp->t_dev)]; register int nec = rc->rc_rcb->rcb_addr; int idivs, odivs, s, val, cflag, iflag, lflag, inpflow; if ( ts->c_ospeed < 0 || ts->c_ospeed > 76800 || ts->c_ispeed < 0 || ts->c_ispeed > 76800 ) return (EINVAL); if (ts->c_ispeed == 0) ts->c_ispeed = ts->c_ospeed; odivs = RC_BRD(ts->c_ospeed); idivs = RC_BRD(ts->c_ispeed); s = spltty(); /* Select channel */ rcout(CD180_CAR, rc->rc_chan); /* If speed == 0, hangup line */ if (ts->c_ospeed == 0) { CCRCMD(rc->rc_rcb->rcb_unit, rc->rc_chan, CCR_ResetChan); WAITFORCCR(rc->rc_rcb->rcb_unit, rc->rc_chan); (void) rc_modctl(rc, TIOCM_DTR, DMBIC); } tp->t_state &= ~TS_CAN_BYPASS_L_RINT; cflag = ts->c_cflag; iflag = ts->c_iflag; lflag = ts->c_lflag; if (idivs > 0) { rcout(CD180_RBPRL, idivs & 0xFF); rcout(CD180_RBPRH, idivs >> 8); } if (odivs > 0) { rcout(CD180_TBPRL, odivs & 0xFF); rcout(CD180_TBPRH, odivs >> 8); } /* set timeout value */ if (ts->c_ispeed > 0) { int itm = ts->c_ispeed > 2400 ? 5 : 10000 / ts->c_ispeed + 1; if ( !(lflag & ICANON) && ts->c_cc[VMIN] != 0 && ts->c_cc[VTIME] != 0 && ts->c_cc[VTIME] * 10 > itm) itm = ts->c_cc[VTIME] * 10; rcout(CD180_RTPR, itm <= 255 ? itm : 255); } switch (cflag & CSIZE) { case CS5: val = COR1_5BITS; break; case CS6: val = COR1_6BITS; break; case CS7: val = COR1_7BITS; break; default: case CS8: val = COR1_8BITS; break; } if (cflag & PARENB) { val |= COR1_NORMPAR; if (cflag & PARODD) val |= COR1_ODDP; if (!(cflag & INPCK)) val |= COR1_Ignore; } else val |= COR1_Ignore; if (cflag & CSTOPB) val |= COR1_2SB; rcout(CD180_COR1, val); /* Set FIFO threshold */ val = ts->c_ospeed <= 4800 ? 1 : CD180_NFIFO / 2; inpflow = 0; if ( (iflag & IXOFF) && ( ts->c_cc[VSTOP] != _POSIX_VDISABLE && ( ts->c_cc[VSTART] != _POSIX_VDISABLE || (iflag & IXANY) ) ) ) { inpflow = 1; val |= COR3_SCDE|COR3_FCT; } rcout(CD180_COR3, val); /* Initialize on-chip automatic flow control */ val = 0; rc->rc_flags &= ~(RC_CTSFLOW|RC_SEND_RDY); if (cflag & CCTS_OFLOW) { rc->rc_flags |= RC_CTSFLOW; val |= COR2_CtsAE; } else rc->rc_flags |= RC_SEND_RDY; if (tp->t_state & TS_TTSTOP) rc->rc_flags |= RC_OSUSP; else rc->rc_flags &= ~RC_OSUSP; if (cflag & CRTS_IFLOW) rc->rc_flags |= RC_RTSFLOW; else rc->rc_flags &= ~RC_RTSFLOW; if (inpflow) { if (ts->c_cc[VSTART] != _POSIX_VDISABLE) rcout(CD180_SCHR1, ts->c_cc[VSTART]); rcout(CD180_SCHR2, ts->c_cc[VSTOP]); val |= COR2_TxIBE; if (iflag & IXANY) val |= COR2_IXM; } rcout(CD180_COR2, rc->rc_cor2 = val); CCRCMD(rc->rc_rcb->rcb_unit, rc->rc_chan, CCR_CORCHG1 | CCR_CORCHG2 | CCR_CORCHG3); disc_optim(tp, ts, rc); /* modem ctl */ val = cflag & CLOCAL ? 0 : MCOR1_CDzd; if (cflag & CCTS_OFLOW) val |= MCOR1_CTSzd; rcout(CD180_MCOR1, val); val = cflag & CLOCAL ? 0 : MCOR2_CDod; if (cflag & CCTS_OFLOW) val |= MCOR2_CTSod; rcout(CD180_MCOR2, val); /* enable i/o and interrupts */ CCRCMD(rc->rc_rcb->rcb_unit, rc->rc_chan, CCR_XMTREN | ((cflag & CREAD) ? CCR_RCVREN : CCR_RCVRDIS)); WAITFORCCR(rc->rc_rcb->rcb_unit, rc->rc_chan); rc->rc_ier = cflag & CLOCAL ? 0 : IER_CD; if (cflag & CCTS_OFLOW) rc->rc_ier |= IER_CTS; if (cflag & CREAD) rc->rc_ier |= IER_RxData; if (tp->t_state & TS_BUSY) rc->rc_ier |= IER_TxRdy; if (ts->c_ospeed != 0) rc_modctl(rc, TIOCM_DTR, DMBIS); if ((cflag & CCTS_OFLOW) && (rc->rc_msvr & MSVR_CTS)) rc->rc_flags |= RC_SEND_RDY; rcout(CD180_IER, rc->rc_ier); (void) splx(s); return 0; } /* Re-initialize board after bogus interrupts */ static void rc_reinit(rcb) struct rc_softc *rcb; { register struct rc_chans *rc, *rce; register int nec; nec = rcb->rcb_addr; rc_hwreset(rcb->rcb_unit, nec, RC_FAKEID); rc = &rc_chans[rcb->rcb_unit * CD180_NCHAN]; rce = rc + CD180_NCHAN; for (; rc < rce; rc++) (void) rc_param(rc->rc_tp, &rc->rc_tp->t_termios); } static int rcioctl(dev, cmd, data, flag, p) dev_t dev; u_long cmd; int flag; caddr_t data; struct proc *p; { register struct rc_chans *rc = &rc_chans[GET_UNIT(dev)]; register int s, error; struct tty *tp = rc->rc_tp; error = (*linesw[tp->t_line].l_ioctl)(tp, cmd, data, flag, p); if (error != ENOIOCTL) return (error); error = ttioctl(tp, cmd, data, flag); disc_optim(tp, &tp->t_termios, rc); if (error != ENOIOCTL) return (error); s = spltty(); switch (cmd) { case TIOCSBRK: rc->rc_pendcmd = CD180_C_SBRK; break; case TIOCCBRK: rc->rc_pendcmd = CD180_C_EBRK; break; case TIOCSDTR: (void) rc_modctl(rc, TIOCM_DTR, DMBIS); break; case TIOCCDTR: (void) rc_modctl(rc, TIOCM_DTR, DMBIC); break; case TIOCMGET: *(int *) data = rc_modctl(rc, 0, DMGET); break; case TIOCMSET: (void) rc_modctl(rc, *(int *) data, DMSET); break; case TIOCMBIC: (void) rc_modctl(rc, *(int *) data, DMBIC); break; case TIOCMBIS: (void) rc_modctl(rc, *(int *) data, DMBIS); break; case TIOCMSDTRWAIT: error = suser(p->p_ucred, &p->p_acflag); if (error != 0) { splx(s); return (error); } rc->rc_dtrwait = *(int *)data * hz / 100; break; case TIOCMGDTRWAIT: *(int *)data = rc->rc_dtrwait * 100 / hz; break; default: (void) splx(s); return ENOTTY; } (void) splx(s); return 0; } /* Modem control routines */ static int rc_modctl(rc, bits, cmd) register struct rc_chans *rc; int bits, cmd; { register int nec = rc->rc_rcb->rcb_addr; u_char *dtr = &rc->rc_rcb->rcb_dtr, msvr; rcout(CD180_CAR, rc->rc_chan); switch (cmd) { case DMSET: rcout(RC_DTREG, (bits & TIOCM_DTR) ? ~(*dtr |= 1 << rc->rc_chan) : ~(*dtr &= ~(1 << rc->rc_chan))); msvr = rcin(CD180_MSVR); if (bits & TIOCM_RTS) msvr |= MSVR_RTS; else msvr &= ~MSVR_RTS; if (bits & TIOCM_DTR) msvr |= MSVR_DTR; else msvr &= ~MSVR_DTR; rcout(CD180_MSVR, msvr); break; case DMBIS: if (bits & TIOCM_DTR) rcout(RC_DTREG, ~(*dtr |= 1 << rc->rc_chan)); msvr = rcin(CD180_MSVR); if (bits & TIOCM_RTS) msvr |= MSVR_RTS; if (bits & TIOCM_DTR) msvr |= MSVR_DTR; rcout(CD180_MSVR, msvr); break; case DMGET: bits = TIOCM_LE; msvr = rc->rc_msvr = rcin(CD180_MSVR); if (msvr & MSVR_RTS) bits |= TIOCM_RTS; if (msvr & MSVR_CTS) bits |= TIOCM_CTS; if (msvr & MSVR_DSR) bits |= TIOCM_DSR; if (msvr & MSVR_DTR) bits |= TIOCM_DTR; if (msvr & MSVR_CD) bits |= TIOCM_CD; if (~rcin(RC_RIREG) & (1 << rc->rc_chan)) bits |= TIOCM_RI; return bits; case DMBIC: if (bits & TIOCM_DTR) rcout(RC_DTREG, ~(*dtr &= ~(1 << rc->rc_chan))); msvr = rcin(CD180_MSVR); if (bits & TIOCM_RTS) msvr &= ~MSVR_RTS; if (bits & TIOCM_DTR) msvr &= ~MSVR_DTR; rcout(CD180_MSVR, msvr); break; } rc->rc_msvr = rcin(CD180_MSVR); return 0; } /* Test the board. */ int rc_test(nec, unit) register int nec; int unit; { int chan = 0; int i = 0, rcnt, old_level; unsigned int iack, chipid; unsigned short divs; static u_char ctest[] = "\377\125\252\045\244\0\377"; #define CTLEN 8 #define ERR(s) { \ printf("rc%d: ", unit); printf s ; printf("\n"); \ (void) splx(old_level); return 1; } struct rtest { u_char txbuf[CD180_NFIFO]; /* TX buffer */ u_char rxbuf[CD180_NFIFO]; /* RX buffer */ int rxptr; /* RX pointer */ int txptr; /* TX pointer */ } tchans[CD180_NCHAN]; old_level = spltty(); chipid = RC_FAKEID; /* First, reset board to inital state */ rc_hwreset(unit, nec, chipid); divs = RC_BRD(19200); /* Initialize channels */ for (chan = 0; chan < CD180_NCHAN; chan++) { /* Select and reset channel */ rcout(CD180_CAR, chan); CCRCMD(unit, chan, CCR_ResetChan); WAITFORCCR(unit, chan); /* Set speed */ rcout(CD180_RBPRL, divs & 0xFF); rcout(CD180_RBPRH, divs >> 8); rcout(CD180_TBPRL, divs & 0xFF); rcout(CD180_TBPRH, divs >> 8); /* set timeout value */ rcout(CD180_RTPR, 0); /* Establish local loopback */ rcout(CD180_COR1, COR1_NOPAR | COR1_8BITS | COR1_1SB); rcout(CD180_COR2, COR2_LLM); rcout(CD180_COR3, CD180_NFIFO); CCRCMD(unit, chan, CCR_CORCHG1 | CCR_CORCHG2 | CCR_CORCHG3); CCRCMD(unit, chan, CCR_RCVREN | CCR_XMTREN); WAITFORCCR(unit, chan); rcout(CD180_MSVR, MSVR_RTS); /* Fill TXBUF with test data */ for (i = 0; i < CD180_NFIFO; i++) { tchans[chan].txbuf[i] = ctest[i]; tchans[chan].rxbuf[i] = 0; } tchans[chan].txptr = tchans[chan].rxptr = 0; /* Now, start transmit */ rcout(CD180_IER, IER_TxMpty|IER_RxData); } /* Pseudo-interrupt poll stuff */ for (rcnt = 10000; rcnt-- > 0; rcnt--) { i = ~(rcin(RC_BSR)); if (i & RC_BSR_TOUT) ERR(("BSR timeout bit set\n")) else if (i & RC_BSR_TXINT) { iack = rcin(RC_PILR_TX); if (iack != (GIVR_IT_TDI | chipid)) ERR(("Bad TX intr ack (%02x != %02x)\n", iack, GIVR_IT_TDI | chipid)); chan = (rcin(CD180_GICR) & GICR_CHAN) >> GICR_LSH; /* If no more data to transmit, disable TX intr */ if (tchans[chan].txptr >= CD180_NFIFO) { iack = rcin(CD180_IER); rcout(CD180_IER, iack & ~IER_TxMpty); } else { for (iack = tchans[chan].txptr; iack < CD180_NFIFO; iack++) rcout(CD180_TDR, tchans[chan].txbuf[iack]); tchans[chan].txptr = iack; } rcout(CD180_EOIR, 0); } else if (i & RC_BSR_RXINT) { u_char ucnt; iack = rcin(RC_PILR_RX); if (iack != (GIVR_IT_RGDI | chipid) && iack != (GIVR_IT_REI | chipid)) ERR(("Bad RX intr ack (%02x != %02x)\n", iack, GIVR_IT_RGDI | chipid)) chan = (rcin(CD180_GICR) & GICR_CHAN) >> GICR_LSH; ucnt = rcin(CD180_RDCR) & 0xF; while (ucnt-- > 0) { iack = rcin(CD180_RCSR); if (iack & RCSR_Timeout) break; if (iack & 0xF) ERR(("Bad char chan %d (RCSR = %02X)\n", chan, iack)) if (tchans[chan].rxptr > CD180_NFIFO) ERR(("Got extra chars chan %d\n", chan)) tchans[chan].rxbuf[tchans[chan].rxptr++] = rcin(CD180_RDR); } rcout(CD180_EOIR, 0); } rcout(RC_CTOUT, 0); for (iack = chan = 0; chan < CD180_NCHAN; chan++) if (tchans[chan].rxptr >= CD180_NFIFO) iack++; if (iack == CD180_NCHAN) break; } for (chan = 0; chan < CD180_NCHAN; chan++) { /* Select and reset channel */ rcout(CD180_CAR, chan); CCRCMD(unit, chan, CCR_ResetChan); } if (!rcnt) ERR(("looses characters during local loopback\n")) /* Now, check data */ for (chan = 0; chan < CD180_NCHAN; chan++) for (i = 0; i < CD180_NFIFO; i++) if (ctest[i] != tchans[chan].rxbuf[i]) ERR(("data mismatch chan %d ptr %d (%d != %d)\n", chan, i, ctest[i], tchans[chan].rxbuf[i])) (void) splx(old_level); return 0; } #ifdef RCDEBUG static void printrcflags(rc, comment) struct rc_chans *rc; char *comment; { u_short f = rc->rc_flags; register int nec = rc->rc_rcb->rcb_addr; printf("rc%d/%d: %s flags: %s%s%s%s%s%s%s%s%s%s%s%s\n", rc->rc_rcb->rcb_unit, rc->rc_chan, comment, (f & RC_DTR_OFF)?"DTR_OFF " :"", (f & RC_ACTOUT) ?"ACTOUT " :"", (f & RC_RTSFLOW)?"RTSFLOW " :"", (f & RC_CTSFLOW)?"CTSFLOW " :"", (f & RC_DORXFER)?"DORXFER " :"", (f & RC_DOXXFER)?"DOXXFER " :"", (f & RC_MODCHG) ?"MODCHG " :"", (f & RC_OSUSP) ?"OSUSP " :"", (f & RC_OSBUSY) ?"OSBUSY " :"", (f & RC_WAS_BUFOVFL) ?"BUFOVFL " :"", (f & RC_WAS_SILOVFL) ?"SILOVFL " :"", (f & RC_SEND_RDY) ?"SEND_RDY":""); rcout(CD180_CAR, rc->rc_chan); printf("rc%d/%d: msvr %02x ier %02x ccsr %02x\n", rc->rc_rcb->rcb_unit, rc->rc_chan, rcin(CD180_MSVR), rcin(CD180_IER), rcin(CD180_CCSR)); } #endif /* RCDEBUG */ static struct tty * rcdevtotty(dev) dev_t dev; { int unit; unit = GET_UNIT(dev); if (unit >= NRC * CD180_NCHAN) return NULL; return (&rc_tty[unit]); } static void rc_dtrwakeup(chan) void *chan; { struct rc_chans *rc; rc = (struct rc_chans *)chan; rc->rc_flags &= ~RC_DTR_OFF; wakeup(&rc->rc_dtrwait); } static void rc_discard_output(rc) struct rc_chans *rc; { disable_intr(); if (rc->rc_flags & RC_DOXXFER) { rc_scheduled_event -= LOTS_OF_EVENTS; rc->rc_flags &= ~RC_DOXXFER; } rc->rc_optr = rc->rc_obufend; rc->rc_tp->t_state &= ~TS_BUSY; enable_intr(); ttwwakeup(rc->rc_tp); } static void rc_wakeup(chan) void *chan; { timeout(rc_wakeup, (caddr_t)NULL, 1); if (rc_scheduled_event != 0) { int s; s = splsofttty(); rcpoll(); splx(s); } } static void disc_optim(tp, t, rc) struct tty *tp; struct termios *t; struct rc_chans *rc; { if (!(t->c_iflag & (ICRNL | IGNCR | IMAXBEL | INLCR | ISTRIP | IXON)) && (!(t->c_iflag & BRKINT) || (t->c_iflag & IGNBRK)) && (!(t->c_iflag & PARMRK) || (t->c_iflag & (IGNPAR | IGNBRK)) == (IGNPAR | IGNBRK)) && !(t->c_lflag & (ECHO | ICANON | IEXTEN | ISIG | PENDIN)) && linesw[tp->t_line].l_rint == ttyinput) tp->t_state |= TS_CAN_BYPASS_L_RINT; else tp->t_state &= ~TS_CAN_BYPASS_L_RINT; rc->rc_hotchar = linesw[tp->t_line].l_hotchar; } static void rc_wait0(nec, unit, chan, line) int nec, unit, chan, line; { int rcnt; for (rcnt = 50; rcnt && rcin(CD180_CCR); rcnt--) DELAY(30); if (rcnt == 0) printf("rc%d/%d: channel command timeout, rc.c line: %d\n", unit, chan, line); } static rc_devsw_installed = 0; static void rc_drvinit(void *unused) { dev_t dev; if( ! rc_devsw_installed ) { dev = makedev(CDEV_MAJOR, 0); cdevsw_add(&dev,&rc_cdevsw, NULL); rc_devsw_installed = 1; } } SYSINIT(rcdev,SI_SUB_DRIVERS,SI_ORDER_MIDDLE+CDEV_MAJOR,rc_drvinit,NULL) #endif /* NRC */ Index: head/sys/i386/isa/sio.c =================================================================== --- head/sys/i386/isa/sio.c (revision 38245) +++ head/sys/i386/isa/sio.c (revision 38246) @@ -1,3044 +1,3039 @@ /*- * Copyright (c) 1991 The Regents of the University of California. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * from: @(#)com.c 7.5 (Berkeley) 5/16/91 - * $Id: sio.c,v 1.208 1998/06/24 09:23:32 phk Exp $ + * $Id: sio.c,v 1.209 1998/07/15 12:18:14 bde Exp $ */ #include "opt_comconsole.h" #include "opt_compat.h" #include "opt_ddb.h" #include "opt_devfs.h" #include "opt_sio.h" #include "sio.h" #include "pnp.h" #ifndef EXTRA_SIO #if NPNP > 0 #define EXTRA_SIO 2 #else #define EXTRA_SIO 0 #endif #endif #define NSIOTOT (NSIO + EXTRA_SIO) /* * Serial driver, based on 386BSD-0.1 com driver. * Mostly rewritten to use pseudo-DMA. * Works for National Semiconductor NS8250-NS16550AF UARTs. * COM driver, based on HP dca driver. * * Changes for PC-Card integration: * - Added PC-Card driver table and handlers */ #include #include #include #include #include #include #include #include #include +#include #include #include #include #ifdef DEVFS #include #endif #include +#include #include #include #include #include #ifdef COM_ESP #include #endif #include #include "card.h" #if NCARD > 0 #include #include #endif #if NPNP > 0 #include #endif #ifdef SMP #define disable_intr() COM_DISABLE_INTR() #define enable_intr() COM_ENABLE_INTR() #endif /* SMP */ #define LOTS_OF_EVENTS 64 /* helps separate urgent events from input */ #define RB_I_HIGH_WATER (TTYHOG - 2 * RS_IBUFSIZE) #define RS_IBUFSIZE 256 #define CALLOUT_MASK 0x80 #define CONTROL_MASK 0x60 #define CONTROL_INIT_STATE 0x20 #define CONTROL_LOCK_STATE 0x40 #define DEV_TO_UNIT(dev) (MINOR_TO_UNIT(minor(dev))) #define MINOR_MAGIC_MASK (CALLOUT_MASK | CONTROL_MASK) #define MINOR_TO_UNIT(mynor) ((mynor) & ~MINOR_MAGIC_MASK) #ifdef COM_MULTIPORT /* checks in flags for multiport and which is multiport "master chip" * for a given card */ #define COM_ISMULTIPORT(dev) ((dev)->id_flags & 0x01) #define COM_MPMASTER(dev) (((dev)->id_flags >> 8) & 0x0ff) #define COM_NOTAST4(dev) ((dev)->id_flags & 0x04) #endif /* COM_MULTIPORT */ #define COM_CONSOLE(dev) ((dev)->id_flags & 0x10) #define COM_FORCECONSOLE(dev) ((dev)->id_flags & 0x20) #define COM_LLCONSOLE(dev) ((dev)->id_flags & 0x40) #define COM_LOSESOUTINTS(dev) ((dev)->id_flags & 0x08) #define COM_NOFIFO(dev) ((dev)->id_flags & 0x02) #define COM_ST16650A(dev) ((dev)->id_flags & 0x20000) #define COM_C_NOPROBE (0x40000) #define COM_NOPROBE(dev) ((dev)->id_flags & COM_C_NOPROBE) #define COM_C_IIR_TXRDYBUG (0x80000) #define COM_IIR_TXRDYBUG(dev) ((dev)->id_flags & COM_C_IIR_TXRDYBUG) #define COM_FIFOSIZE(dev) (((dev)->id_flags & 0xff000000) >> 24) #define com_scr 7 /* scratch register for 16450-16550 (R/W) */ /* * Input buffer watermarks. * The external device is asked to stop sending when the buffer exactly reaches * high water, or when the high level requests it. * The high level is notified immediately (rather than at a later clock tick) * when this watermark is reached. * The buffer size is chosen so the watermark should almost never be reached. * The low watermark is invisibly 0 since the buffer is always emptied all at * once. */ #define RS_IHIGHWATER (3 * RS_IBUFSIZE / 4) /* * com state bits. * (CS_BUSY | CS_TTGO) and (CS_BUSY | CS_TTGO | CS_ODEVREADY) must be higher * than the other bits so that they can be tested as a group without masking * off the low bits. * * The following com and tty flags correspond closely: * CS_BUSY = TS_BUSY (maintained by comstart(), siopoll() and * siostop()) * CS_TTGO = ~TS_TTSTOP (maintained by comparam() and comstart()) * CS_CTS_OFLOW = CCTS_OFLOW (maintained by comparam()) * CS_RTS_IFLOW = CRTS_IFLOW (maintained by comparam()) * TS_FLUSH is not used. * XXX I think TIOCSETA doesn't clear TS_TTSTOP when it clears IXON. * XXX CS_*FLOW should be CF_*FLOW in com->flags (control flags not state). */ #define CS_BUSY 0x80 /* output in progress */ #define CS_TTGO 0x40 /* output not stopped by XOFF */ #define CS_ODEVREADY 0x20 /* external device h/w ready (CTS) */ #define CS_CHECKMSR 1 /* check of MSR scheduled */ #define CS_CTS_OFLOW 2 /* use CTS output flow control */ #define CS_DTR_OFF 0x10 /* DTR held off */ #define CS_ODONE 4 /* output completed */ #define CS_RTS_IFLOW 8 /* use RTS input flow control */ #define CSE_BUSYCHECK 1 /* siobusycheck() scheduled */ static char const * const error_desc[] = { #define CE_OVERRUN 0 "silo overflow", #define CE_INTERRUPT_BUF_OVERFLOW 1 "interrupt-level buffer overflow", #define CE_TTY_BUF_OVERFLOW 2 "tty-level buffer overflow", }; #define CE_NTYPES 3 #define CE_RECORD(com, errnum) (++(com)->delta_error_counts[errnum]) /* types. XXX - should be elsewhere */ typedef u_int Port_t; /* hardware port */ typedef u_char bool_t; /* boolean */ /* queue of linear buffers */ struct lbq { u_char *l_head; /* next char to process */ u_char *l_tail; /* one past the last char to process */ struct lbq *l_next; /* next in queue */ bool_t l_queued; /* nonzero if queued */ }; /* com device structure */ struct com_s { u_int id_flags; /* Copy isa device falgas */ u_char state; /* miscellaneous flag bits */ bool_t active_out; /* nonzero if the callout device is open */ u_char cfcr_image; /* copy of value written to CFCR */ #ifdef COM_ESP bool_t esp; /* is this unit a hayes esp board? */ #endif u_char extra_state; /* more flag bits, separate for order trick */ u_char fifo_image; /* copy of value written to FIFO */ bool_t hasfifo; /* nonzero for 16550 UARTs */ bool_t st16650a; /* Is a Startech 16650A or RTS/CTS compat */ bool_t loses_outints; /* nonzero if device loses output interrupts */ u_char mcr_image; /* copy of value written to MCR */ #ifdef COM_MULTIPORT bool_t multiport; /* is this unit part of a multiport device? */ #endif /* COM_MULTIPORT */ bool_t no_irq; /* nonzero if irq is not attached */ bool_t gone; /* hardware disappeared */ bool_t poll; /* nonzero if polling is required */ bool_t poll_output; /* nonzero if polling for output is required */ int unit; /* unit number */ int dtr_wait; /* time to hold DTR down on close (* 1/hz) */ u_int tx_fifo_size; u_int wopeners; /* # processes waiting for DCD in open() */ /* * The high level of the driver never reads status registers directly * because there would be too many side effects to handle conveniently. * Instead, it reads copies of the registers stored here by the * interrupt handler. */ u_char last_modem_status; /* last MSR read by intr handler */ u_char prev_modem_status; /* last MSR handled by high level */ u_char hotchar; /* ldisc-specific char to be handled ASAP */ u_char *ibuf; /* start of input buffer */ u_char *ibufend; /* end of input buffer */ u_char *ihighwater; /* threshold in input buffer */ u_char *iptr; /* next free spot in input buffer */ struct lbq obufq; /* head of queue of output buffers */ struct lbq obufs[2]; /* output buffers */ Port_t data_port; /* i/o ports */ #ifdef COM_ESP Port_t esp_port; #endif Port_t int_id_port; Port_t iobase; Port_t modem_ctl_port; Port_t line_status_port; Port_t modem_status_port; Port_t intr_ctl_port; /* Ports of IIR register */ struct tty *tp; /* cross reference */ /* Initial state. */ struct termios it_in; /* should be in struct tty */ struct termios it_out; /* Lock state. */ struct termios lt_in; /* should be in struct tty */ struct termios lt_out; bool_t do_timestamp; bool_t do_dcd_timestamp; struct timeval timestamp; struct timeval dcd_timestamp; u_long bytes_in; /* statistics */ u_long bytes_out; u_int delta_error_counts[CE_NTYPES]; u_long error_counts[CE_NTYPES]; /* * Ping-pong input buffers. The extra factor of 2 in the sizes is * to allow for an error byte for each input byte. */ #define CE_INPUT_OFFSET RS_IBUFSIZE u_char ibuf1[2 * RS_IBUFSIZE]; u_char ibuf2[2 * RS_IBUFSIZE]; /* * Data area for output buffers. Someday we should build the output * buffer queue without copying data. */ u_char obuf1[256]; u_char obuf2[256]; #ifdef DEVFS void *devfs_token_ttyd; void *devfs_token_ttyl; void *devfs_token_ttyi; void *devfs_token_cuaa; void *devfs_token_cual; void *devfs_token_cuai; #endif }; -/* - * XXX public functions in drivers should be declared in headers produced - * by `config', not here. - */ - -/* Interrupt handling entry point. */ -void siopoll __P((void)); - -/* Device switch entry points. */ -#define sioreset noreset -#define siommap nommap -#define siostrategy nostrategy - #ifdef COM_ESP static int espattach __P((struct isa_device *isdp, struct com_s *com, Port_t esp_port)); #endif static int sioattach __P((struct isa_device *dev)); static timeout_t siobusycheck; static timeout_t siodtrwakeup; static void comhardclose __P((struct com_s *com)); static void siointr1 __P((struct com_s *com)); static int commctl __P((struct com_s *com, int bits, int how)); static int comparam __P((struct tty *tp, struct termios *t)); +static swihand_t siopoll; static int sioprobe __P((struct isa_device *dev)); static void siosettimeout __P((void)); static void comstart __P((struct tty *tp)); static timeout_t comwakeup; static void disc_optim __P((struct tty *tp, struct termios *t, struct com_s *com)); #ifdef DSI_SOFT_MODEM static int LoadSoftModem __P((int unit,int base_io, u_long size, u_char *ptr)); #endif /* DSI_SOFT_MODEM */ static char driver_name[] = "sio"; /* table and macro for fast conversion from a unit number to its com struct */ static struct com_s *p_com_addr[NSIOTOT]; #define com_addr(unit) (p_com_addr[unit]) struct isa_driver siodriver = { sioprobe, sioattach, driver_name }; static d_open_t sioopen; static d_close_t sioclose; static d_read_t sioread; static d_write_t siowrite; static d_ioctl_t sioioctl; static d_stop_t siostop; static d_devtotty_t siodevtotty; #define CDEV_MAJOR 28 static struct cdevsw sio_cdevsw = { sioopen, sioclose, sioread, siowrite, sioioctl, siostop, noreset, siodevtotty, ttpoll, nommap, NULL, driver_name, NULL, -1, }; static int comconsole = -1; static volatile speed_t comdefaultrate = CONSPEED; static u_int com_events; /* input chars + weighted output completions */ static Port_t siocniobase; +static bool_t siopoll_registered; static int sio_timeout; static int sio_timeouts_until_log; static struct callout_handle sio_timeout_handle = CALLOUT_HANDLE_INITIALIZER(&sio_timeout_handle); #if 0 /* XXX */ static struct tty *sio_tty[NSIOTOT]; #else static struct tty sio_tty[NSIOTOT]; #endif static const int nsio_tty = NSIOTOT; static struct speedtab comspeedtab[] = { { 0, 0 }, { 50, COMBRD(50) }, { 75, COMBRD(75) }, { 110, COMBRD(110) }, { 134, COMBRD(134) }, { 150, COMBRD(150) }, { 200, COMBRD(200) }, { 300, COMBRD(300) }, { 600, COMBRD(600) }, { 1200, COMBRD(1200) }, { 1800, COMBRD(1800) }, { 2400, COMBRD(2400) }, { 4800, COMBRD(4800) }, { 9600, COMBRD(9600) }, { 19200, COMBRD(19200) }, { 38400, COMBRD(38400) }, { 57600, COMBRD(57600) }, { 115200, COMBRD(115200) }, { -1, -1 } }; #ifdef COM_ESP /* XXX configure this properly. */ static Port_t likely_com_ports[] = { 0x3f8, 0x2f8, 0x3e8, 0x2e8, }; static Port_t likely_esp_ports[] = { 0x140, 0x180, 0x280, 0 }; #endif /* * handle sysctl read/write requests for console speed * * In addition to setting comdefaultrate for I/O through /dev/console, * also set the initial and lock values for the /dev/ttyXX device * if there is one associated with the console. Finally, if the /dev/tty * device has already been open, change the speed on the open running port * itself. */ static int sysctl_machdep_comdefaultrate SYSCTL_HANDLER_ARGS { int error, s; speed_t newspeed; struct com_s *com; struct tty *tp; newspeed = comdefaultrate; error = sysctl_handle_opaque(oidp, &newspeed, sizeof newspeed, req); if (error || !req->newptr) return (error); comdefaultrate = newspeed; if (comconsole < 0) /* serial console not selected? */ return (0); com = com_addr(comconsole); if (!com) return (ENXIO); /* * set the initial and lock rates for /dev/ttydXX and /dev/cuaXX * (note, the lock rates really are boolean -- if non-zero, disallow * speed changes) */ com->it_in.c_ispeed = com->it_in.c_ospeed = com->lt_in.c_ispeed = com->lt_in.c_ospeed = com->it_out.c_ispeed = com->it_out.c_ospeed = com->lt_out.c_ispeed = com->lt_out.c_ospeed = comdefaultrate; /* * if we're open, change the running rate too */ tp = com->tp; if (tp && (tp->t_state & TS_ISOPEN)) { tp->t_termios.c_ispeed = tp->t_termios.c_ospeed = comdefaultrate; s = spltty(); error = comparam(tp, &tp->t_termios); splx(s); } return error; } SYSCTL_PROC(_machdep, OID_AUTO, conspeed, CTLTYPE_INT | CTLFLAG_RW, 0, 0, sysctl_machdep_comdefaultrate, "I", ""); #if NCARD > 0 /* * PC-Card (PCMCIA) specific code. */ static int sioinit __P((struct pccard_devinfo *)); static void siounload __P((struct pccard_devinfo *)); static int card_intr __P((struct pccard_devinfo *)); static struct pccard_device sio_info = { driver_name, sioinit, siounload, card_intr, 0, /* Attributes - presently unused */ &tty_imask /* Interrupt mask for device */ /* XXX - Should this also include net_imask? */ }; DATA_SET(pccarddrv_set, sio_info); /* * Initialize the device - called from Slot manager. */ int sioinit(struct pccard_devinfo *devi) { /* validate unit number. */ if (devi->isahd.id_unit >= (NSIOTOT)) return(ENODEV); /* Make sure it isn't already probed. */ if (com_addr(devi->isahd.id_unit)) return(EBUSY); /* It's already probed as serial by Upper */ devi->isahd.id_flags |= COM_C_NOPROBE; /* * Probe the device. If a value is returned, the * device was found at the location. */ if (sioprobe(&devi->isahd) == 0) return(ENXIO); if (sioattach(&devi->isahd) == 0) return(ENXIO); return(0); } /* * siounload - unload the driver and clear the table. * XXX TODO: * This is usually called when the card is ejected, but * can be caused by a modunload of a controller driver. * The idea is to reset the driver's view of the device * and ensure that any driver entry points such as * read and write do not hang. */ static void siounload(struct pccard_devinfo *devi) { struct com_s *com; if (!devi) { printf("NULL devi in siounload\n"); return; } com = com_addr(devi->isahd.id_unit); if (!com) { printf("NULL com in siounload\n"); return; } if (!com->iobase) { printf("sio%d already unloaded!\n",devi->isahd.id_unit); return; } if (com->tp && (com->tp->t_state & TS_ISOPEN)) { com->gone = 1; printf("sio%d: unload\n", devi->isahd.id_unit); com->tp->t_gen++; ttyclose(com->tp); ttwakeup(com->tp); ttwwakeup(com->tp); } else { com_addr(com->unit) = NULL; bzero(com, sizeof *com); free(com,M_TTYS); printf("sio%d: unload,gone\n", devi->isahd.id_unit); } } /* * card_intr - Shared interrupt called from * front end of PC-Card handler. */ static int card_intr(struct pccard_devinfo *devi) { struct com_s *com; COM_LOCK(); com = com_addr(devi->isahd.id_unit); if (com && !com->gone) siointr1(com_addr(devi->isahd.id_unit)); COM_UNLOCK(); return(1); } #endif /* NCARD > 0 */ static int sioprobe(dev) struct isa_device *dev; { static bool_t already_init; bool_t failures[10]; int fn; struct isa_device *idev; Port_t iobase; intrmask_t irqmap[4]; intrmask_t irqs; u_char mcr_image; int result; struct isa_device *xdev; if (!already_init) { /* * Turn off MCR_IENABLE for all likely serial ports. An unused * port with its MCR_IENABLE gate open will inhibit interrupts * from any used port that shares the interrupt vector. * XXX the gate enable is elsewhere for some multiports. */ for (xdev = isa_devtab_tty; xdev->id_driver != NULL; xdev++) if (xdev->id_driver == &siodriver && xdev->id_enabled) outb(xdev->id_iobase + com_mcr, 0); already_init = TRUE; } if (COM_LLCONSOLE(dev)) { printf("sio%d: reserved for low-level i/o\n", dev->id_unit); return (0); } /* * If the device is on a multiport card and has an AST/4 * compatible interrupt control register, initialize this * register and prepare to leave MCR_IENABLE clear in the mcr. * Otherwise, prepare to set MCR_IENABLE in the mcr. * Point idev to the device struct giving the correct id_irq. * This is the struct for the master device if there is one. */ idev = dev; mcr_image = MCR_IENABLE; #ifdef COM_MULTIPORT if (COM_ISMULTIPORT(dev)) { idev = find_isadev(isa_devtab_tty, &siodriver, COM_MPMASTER(dev)); if (idev == NULL) { printf("sio%d: master device %d not configured\n", dev->id_unit, COM_MPMASTER(dev)); dev->id_irq = 0; idev = dev; } if (!COM_NOTAST4(dev)) { outb(idev->id_iobase + com_scr, idev->id_irq ? 0x80 : 0); mcr_image = 0; } } #endif /* COM_MULTIPORT */ if (idev->id_irq == 0) mcr_image = 0; bzero(failures, sizeof failures); iobase = dev->id_iobase; /* * We don't want to get actual interrupts, just masked ones. * Interrupts from this line should already be masked in the ICU, * but mask them in the processor as well in case there are some * (misconfigured) shared interrupts. */ disable_intr(); /* EXTRA DELAY? */ /* * Initialize the speed and the word size and wait long enough to * drain the maximum of 16 bytes of junk in device output queues. * The speed is undefined after a master reset and must be set * before relying on anything related to output. There may be * junk after a (very fast) soft reboot and (apparently) after * master reset. * XXX what about the UART bug avoided by waiting in comparam()? * We don't want to to wait long enough to drain at 2 bps. */ if (iobase == siocniobase) DELAY((16 + 1) * 1000000 / (comdefaultrate / 10)); else { outb(iobase + com_cfcr, CFCR_DLAB | CFCR_8BITS); outb(iobase + com_dlbl, COMBRD(SIO_TEST_SPEED) & 0xff); outb(iobase + com_dlbh, (u_int) COMBRD(SIO_TEST_SPEED) >> 8); outb(iobase + com_cfcr, CFCR_8BITS); DELAY((16 + 1) * 1000000 / (SIO_TEST_SPEED / 10)); } /* * Enable the interrupt gate and disable device interupts. This * should leave the device driving the interrupt line low and * guarantee an edge trigger if an interrupt can be generated. */ /* EXTRA DELAY? */ outb(iobase + com_mcr, mcr_image); outb(iobase + com_ier, 0); DELAY(1000); /* XXX */ irqmap[0] = isa_irq_pending(); /* * Attempt to set loopback mode so that we can send a null byte * without annoying any external device. */ /* EXTRA DELAY? */ outb(iobase + com_mcr, mcr_image | MCR_LOOPBACK); /* * Attempt to generate an output interrupt. On 8250's, setting * IER_ETXRDY generates an interrupt independent of the current * setting and independent of whether the THR is empty. On 16450's, * setting IER_ETXRDY generates an interrupt independent of the * current setting. On 16550A's, setting IER_ETXRDY only * generates an interrupt when IER_ETXRDY is not already set. */ outb(iobase + com_ier, IER_ETXRDY); /* * On some 16x50 incompatibles, setting IER_ETXRDY doesn't generate * an interrupt. They'd better generate one for actually doing * output. Loopback may be broken on the same incompatibles but * it's unlikely to do more than allow the null byte out. */ outb(iobase + com_data, 0); DELAY((1 + 2) * 1000000 / (SIO_TEST_SPEED / 10)); /* * Turn off loopback mode so that the interrupt gate works again * (MCR_IENABLE was hidden). This should leave the device driving * an interrupt line high. It doesn't matter if the interrupt * line oscillates while we are not looking at it, since interrupts * are disabled. */ /* EXTRA DELAY? */ outb(iobase + com_mcr, mcr_image); /* * It's a definitly Serial PCMCIA(16550A), but still be required * for IIR_TXRDY implementation ( Palido 321s, DC-1S... ) */ if ( COM_NOPROBE(dev) ) { /* Reading IIR register twice */ for ( fn = 0; fn < 2; fn ++ ) { DELAY(10000); failures[6] = inb(iobase + com_iir); } /* Check IIR_TXRDY clear ? */ result = IO_COMSIZE; if ( failures[6] & IIR_TXRDY ) { /* Nop, Double check with clearing IER */ outb(iobase + com_ier, 0); if ( inb(iobase + com_iir) & IIR_NOPEND ) { /* Ok. we're familia this gang */ dev->id_flags |= COM_C_IIR_TXRDYBUG; /* Set IIR_TXRDYBUG */ } else { /* Unknow, Just omit this chip.. XXX*/ result = 0; } } else { /* OK. this is well-known guys */ dev->id_flags &= ~COM_C_IIR_TXRDYBUG; /*Clear IIR_TXRDYBUG*/ } outb(iobase + com_cfcr, CFCR_8BITS); enable_intr(); return (iobase == siocniobase ? IO_COMSIZE : result); } /* * Check that * o the CFCR, IER and MCR in UART hold the values written to them * (the values happen to be all distinct - this is good for * avoiding false positive tests from bus echoes). * o an output interrupt is generated and its vector is correct. * o the interrupt goes away when the IIR in the UART is read. */ /* EXTRA DELAY? */ failures[0] = inb(iobase + com_cfcr) - CFCR_8BITS; failures[1] = inb(iobase + com_ier) - IER_ETXRDY; failures[2] = inb(iobase + com_mcr) - mcr_image; DELAY(10000); /* Some internal modems need this time */ irqmap[1] = isa_irq_pending(); failures[4] = (inb(iobase + com_iir) & IIR_IMASK) - IIR_TXRDY; DELAY(1000); /* XXX */ irqmap[2] = isa_irq_pending(); failures[6] = (inb(iobase + com_iir) & IIR_IMASK) - IIR_NOPEND; /* * Turn off all device interrupts and check that they go off properly. * Leave MCR_IENABLE alone. For ports without a master port, it gates * the OUT2 output of the UART to * the ICU input. Closing the gate would give a floating ICU input * (unless there is another device driving at) and spurious interrupts. * (On the system that this was first tested on, the input floats high * and gives a (masked) interrupt as soon as the gate is closed.) */ outb(iobase + com_ier, 0); outb(iobase + com_cfcr, CFCR_8BITS); /* dummy to avoid bus echo */ failures[7] = inb(iobase + com_ier); DELAY(1000); /* XXX */ irqmap[3] = isa_irq_pending(); failures[9] = (inb(iobase + com_iir) & IIR_IMASK) - IIR_NOPEND; enable_intr(); irqs = irqmap[1] & ~irqmap[0]; if (idev->id_irq != 0 && (idev->id_irq & irqs) == 0) printf( "sio%d: configured irq %d not in bitmap of probed irqs %#x\n", dev->id_unit, ffs(idev->id_irq) - 1, irqs); if (bootverbose) printf("sio%d: irq maps: %#x %#x %#x %#x\n", dev->id_unit, irqmap[0], irqmap[1], irqmap[2], irqmap[3]); result = IO_COMSIZE; for (fn = 0; fn < sizeof failures; ++fn) if (failures[fn]) { outb(iobase + com_mcr, 0); result = 0; if (bootverbose) { printf("sio%d: probe failed test(s):", dev->id_unit); for (fn = 0; fn < sizeof failures; ++fn) if (failures[fn]) printf(" %d", fn); printf("\n"); } break; } return (iobase == siocniobase ? IO_COMSIZE : result); } #ifdef COM_ESP static int espattach(isdp, com, esp_port) struct isa_device *isdp; struct com_s *com; Port_t esp_port; { u_char dips; u_char val; /* * Check the ESP-specific I/O port to see if we're an ESP * card. If not, return failure immediately. */ if ((inb(esp_port) & 0xf3) == 0) { printf(" port 0x%x is not an ESP board?\n", esp_port); return (0); } /* * We've got something that claims to be a Hayes ESP card. * Let's hope so. */ /* Get the dip-switch configuration */ outb(esp_port + ESP_CMD1, ESP_GETDIPS); dips = inb(esp_port + ESP_STATUS1); /* * Bits 0,1 of dips say which COM port we are. */ if (com->iobase == likely_com_ports[dips & 0x03]) printf(" : ESP"); else { printf(" esp_port has com %d\n", dips & 0x03); return (0); } /* * Check for ESP version 2.0 or later: bits 4,5,6 = 010. */ outb(esp_port + ESP_CMD1, ESP_GETTEST); val = inb(esp_port + ESP_STATUS1); /* clear reg 1 */ val = inb(esp_port + ESP_STATUS2); if ((val & 0x70) < 0x20) { printf("-old (%o)", val & 0x70); return (0); } /* * Check for ability to emulate 16550: bit 7 == 1 */ if ((dips & 0x80) == 0) { printf(" slave"); return (0); } /* * Okay, we seem to be a Hayes ESP card. Whee. */ com->esp = TRUE; com->esp_port = esp_port; return (1); } #endif /* COM_ESP */ static int sioattach(isdp) struct isa_device *isdp; { struct com_s *com; dev_t dev; #ifdef COM_ESP Port_t *espp; #endif Port_t iobase; int s; int unit; isdp->id_ri_flags |= RI_FAST; iobase = isdp->id_iobase; unit = isdp->id_unit; com = malloc(sizeof *com, M_TTYS, M_NOWAIT); if (com == NULL) return (0); /* * sioprobe() has initialized the device registers as follows: * o cfcr = CFCR_8BITS. * It is most important that CFCR_DLAB is off, so that the * data port is not hidden when we enable interrupts. * o ier = 0. * Interrupts are only enabled when the line is open. * o mcr = MCR_IENABLE, or 0 if the port has AST/4 compatible * interrupt control register or the config specifies no irq. * Keeping MCR_DTR and MCR_RTS off might stop the external * device from sending before we are ready. */ bzero(com, sizeof *com); com->unit = unit; com->cfcr_image = CFCR_8BITS; com->dtr_wait = 3 * hz; com->loses_outints = COM_LOSESOUTINTS(isdp) != 0; com->no_irq = isdp->id_irq == 0; com->tx_fifo_size = 1; com->iptr = com->ibuf = com->ibuf1; com->ibufend = com->ibuf1 + RS_IBUFSIZE; com->ihighwater = com->ibuf1 + RS_IHIGHWATER; com->obufs[0].l_head = com->obuf1; com->obufs[1].l_head = com->obuf2; com->iobase = iobase; com->data_port = iobase + com_data; com->int_id_port = iobase + com_iir; com->modem_ctl_port = iobase + com_mcr; com->mcr_image = inb(com->modem_ctl_port); com->line_status_port = iobase + com_lsr; com->modem_status_port = iobase + com_msr; com->intr_ctl_port = iobase + com_ier; /* * We don't use all the flags from since they * are only relevant for logins. It's important to have echo off * initially so that the line doesn't start blathering before the * echo flag can be turned off. */ com->it_in.c_iflag = 0; com->it_in.c_oflag = 0; com->it_in.c_cflag = TTYDEF_CFLAG; com->it_in.c_lflag = 0; if (unit == comconsole) { com->it_in.c_iflag = TTYDEF_IFLAG; com->it_in.c_oflag = TTYDEF_OFLAG; com->it_in.c_cflag = TTYDEF_CFLAG | CLOCAL; com->it_in.c_lflag = TTYDEF_LFLAG; com->lt_out.c_cflag = com->lt_in.c_cflag = CLOCAL; com->lt_out.c_ispeed = com->lt_out.c_ospeed = com->lt_in.c_ispeed = com->lt_in.c_ospeed = com->it_in.c_ispeed = com->it_in.c_ospeed = comdefaultrate; } else com->it_in.c_ispeed = com->it_in.c_ospeed = TTYDEF_SPEED; termioschars(&com->it_in); com->it_out = com->it_in; /* attempt to determine UART type */ printf("sio%d: type", unit); #ifdef DSI_SOFT_MODEM if((inb(iobase+7) ^ inb(iobase+7)) & 0x80) { printf(" Digicom Systems, Inc. SoftModem"); goto determined_type; } #endif /* DSI_SOFT_MODEM */ #ifdef COM_MULTIPORT if (!COM_ISMULTIPORT(isdp) && !COM_IIR_TXRDYBUG(isdp)) #else if (!COM_IIR_TXRDYBUG(isdp)) #endif { u_char scr; u_char scr1; u_char scr2; scr = inb(iobase + com_scr); outb(iobase + com_scr, 0xa5); scr1 = inb(iobase + com_scr); outb(iobase + com_scr, 0x5a); scr2 = inb(iobase + com_scr); outb(iobase + com_scr, scr); if (scr1 != 0xa5 || scr2 != 0x5a) { printf(" 8250"); goto determined_type; } } outb(iobase + com_fifo, FIFO_ENABLE | FIFO_RX_HIGH); DELAY(100); com->st16650a = 0; switch (inb(com->int_id_port) & IIR_FIFO_MASK) { case FIFO_RX_LOW: printf(" 16450"); break; case FIFO_RX_MEDL: printf(" 16450?"); break; case FIFO_RX_MEDH: printf(" 16550?"); break; case FIFO_RX_HIGH: if (COM_NOFIFO(isdp)) { printf(" 16550A fifo disabled"); } else { com->hasfifo = TRUE; if (COM_ST16650A(isdp)) { com->st16650a = 1; com->tx_fifo_size = 32; printf(" ST16650A"); } else { com->tx_fifo_size = COM_FIFOSIZE(isdp); printf(" 16550A"); } } #ifdef COM_ESP for (espp = likely_esp_ports; *espp != 0; espp++) if (espattach(isdp, com, *espp)) { com->tx_fifo_size = 1024; break; } #endif if (!com->st16650a) { if (!com->tx_fifo_size) com->tx_fifo_size = 16; else printf(" lookalike with %d bytes FIFO", com->tx_fifo_size); } break; } #ifdef COM_ESP if (com->esp) { /* * Set 16550 compatibility mode. * We don't use the ESP_MODE_SCALE bit to increase the * fifo trigger levels because we can't handle large * bursts of input. * XXX flow control should be set in comparam(), not here. */ outb(com->esp_port + ESP_CMD1, ESP_SETMODE); outb(com->esp_port + ESP_CMD2, ESP_MODE_RTS | ESP_MODE_FIFO); /* Set RTS/CTS flow control. */ outb(com->esp_port + ESP_CMD1, ESP_SETFLOWTYPE); outb(com->esp_port + ESP_CMD2, ESP_FLOW_RTS); outb(com->esp_port + ESP_CMD2, ESP_FLOW_CTS); /* Set flow-control levels. */ outb(com->esp_port + ESP_CMD1, ESP_SETRXFLOW); outb(com->esp_port + ESP_CMD2, HIBYTE(768)); outb(com->esp_port + ESP_CMD2, LOBYTE(768)); outb(com->esp_port + ESP_CMD2, HIBYTE(512)); outb(com->esp_port + ESP_CMD2, LOBYTE(512)); } #endif /* COM_ESP */ outb(iobase + com_fifo, 0); determined_type: ; #ifdef COM_MULTIPORT if (COM_ISMULTIPORT(isdp)) { com->multiport = TRUE; printf(" (multiport"); if (unit == COM_MPMASTER(isdp)) printf(" master"); printf(")"); com->no_irq = find_isadev(isa_devtab_tty, &siodriver, COM_MPMASTER(isdp))->id_irq == 0; } #endif /* COM_MULTIPORT */ if (unit == comconsole) printf(", console"); if ( COM_IIR_TXRDYBUG(isdp) ) printf(" with a bogus IIR_TXRDY register"); printf("\n"); s = spltty(); com_addr(unit) = com; splx(s); dev = makedev(CDEV_MAJOR, 0); cdevsw_add(&dev, &sio_cdevsw, NULL); #ifdef DEVFS com->devfs_token_ttyd = devfs_add_devswf(&sio_cdevsw, unit, DV_CHR, UID_ROOT, GID_WHEEL, 0600, "ttyd%r", unit); com->devfs_token_ttyi = devfs_add_devswf(&sio_cdevsw, unit | CONTROL_INIT_STATE, DV_CHR, UID_ROOT, GID_WHEEL, 0600, "ttyid%r", unit); com->devfs_token_ttyl = devfs_add_devswf(&sio_cdevsw, unit | CONTROL_LOCK_STATE, DV_CHR, UID_ROOT, GID_WHEEL, 0600, "ttyld%r", unit); com->devfs_token_cuaa = devfs_add_devswf(&sio_cdevsw, unit | CALLOUT_MASK, DV_CHR, UID_UUCP, GID_DIALER, 0660, "cuaa%r", unit); com->devfs_token_cuai = devfs_add_devswf(&sio_cdevsw, unit | CALLOUT_MASK | CONTROL_INIT_STATE, DV_CHR, UID_UUCP, GID_DIALER, 0660, "cuaia%r", unit); com->devfs_token_cual = devfs_add_devswf(&sio_cdevsw, unit | CALLOUT_MASK | CONTROL_LOCK_STATE, DV_CHR, UID_UUCP, GID_DIALER, 0660, "cuala%r", unit); #endif + if (!siopoll_registered) { + register_swi(SWI_TTY, siopoll); + siopoll_registered = TRUE; + } com->id_flags = isdp->id_flags; /* Heritate id_flags for later */ return (1); } static int sioopen(dev, flag, mode, p) dev_t dev; int flag; int mode; struct proc *p; { struct com_s *com; int error; Port_t iobase; int mynor; int s; struct tty *tp; int unit; mynor = minor(dev); unit = MINOR_TO_UNIT(mynor); if ((u_int) unit >= NSIOTOT || (com = com_addr(unit)) == NULL) return (ENXIO); if (com->gone) return (ENXIO); if (mynor & CONTROL_MASK) return (0); #if 0 /* XXX */ tp = com->tp = sio_tty[unit] = ttymalloc(sio_tty[unit]); #else tp = com->tp = &sio_tty[unit]; #endif s = spltty(); /* * We jump to this label after all non-interrupted sleeps to pick * up any changes of the device state. */ open_top: while (com->state & CS_DTR_OFF) { error = tsleep(&com->dtr_wait, TTIPRI | PCATCH, "siodtr", 0); if (com_addr(unit) == NULL) return (ENXIO); if (error != 0 || com->gone) goto out; } if (tp->t_state & TS_ISOPEN) { /* * The device is open, so everything has been initialized. * Handle conflicts. */ if (mynor & CALLOUT_MASK) { if (!com->active_out) { error = EBUSY; goto out; } } else { if (com->active_out) { if (flag & O_NONBLOCK) { error = EBUSY; goto out; } error = tsleep(&com->active_out, TTIPRI | PCATCH, "siobi", 0); if (com_addr(unit) == NULL) return (ENXIO); if (error != 0 || com->gone) goto out; goto open_top; } } if (tp->t_state & TS_XCLUDE && p->p_ucred->cr_uid != 0) { error = EBUSY; goto out; } } else { /* * The device isn't open, so there are no conflicts. * Initialize it. Initialization is done twice in many * cases: to preempt sleeping callin opens if we are * callout, and to complete a callin open after DCD rises. */ tp->t_oproc = comstart; tp->t_param = comparam; tp->t_dev = dev; tp->t_termios = mynor & CALLOUT_MASK ? com->it_out : com->it_in; (void)commctl(com, TIOCM_DTR | TIOCM_RTS, DMSET); com->poll = com->no_irq; com->poll_output = com->loses_outints; ++com->wopeners; error = comparam(tp, &tp->t_termios); --com->wopeners; if (error != 0) goto out; /* * XXX we should goto open_top if comparam() slept. */ ttsetwater(tp); iobase = com->iobase; if (com->hasfifo) { /* * (Re)enable and drain fifos. * * Certain SMC chips cause problems if the fifos * are enabled while input is ready. Turn off the * fifo if necessary to clear the input. We test * the input ready bit after enabling the fifos * since we've already enabled them in comparam() * and to handle races between enabling and fresh * input. */ while (TRUE) { outb(iobase + com_fifo, FIFO_RCV_RST | FIFO_XMT_RST | com->fifo_image); /* * XXX the delays are for superstitious * historical reasons. It must be less than * the character time at the maximum * supported speed (87 usec at 115200 bps * 8N1). Otherwise we might loop endlessly * if data is streaming in. We used to use * delays of 100. That usually worked * because DELAY(100) used to usually delay * for about 85 usec instead of 100. */ DELAY(50); if (!(inb(com->line_status_port) & LSR_RXRDY)) break; outb(iobase + com_fifo, 0); DELAY(50); (void) inb(com->data_port); } } disable_intr(); (void) inb(com->line_status_port); (void) inb(com->data_port); com->prev_modem_status = com->last_modem_status = inb(com->modem_status_port); if (COM_IIR_TXRDYBUG(com)) { outb(com->intr_ctl_port, IER_ERXRDY | IER_ERLS | IER_EMSC); } else { outb(com->intr_ctl_port, IER_ERXRDY | IER_ETXRDY | IER_ERLS | IER_EMSC); } enable_intr(); /* * Handle initial DCD. Callout devices get a fake initial * DCD (trapdoor DCD). If we are callout, then any sleeping * callin opens get woken up and resume sleeping on "siobi" * instead of "siodcd". */ /* * XXX `mynor & CALLOUT_MASK' should be * `tp->t_cflag & (SOFT_CARRIER | TRAPDOOR_CARRIER) where * TRAPDOOR_CARRIER is the default initial state for callout * devices and SOFT_CARRIER is like CLOCAL except it hides * the true carrier. */ if (com->prev_modem_status & MSR_DCD || mynor & CALLOUT_MASK) (*linesw[tp->t_line].l_modem)(tp, 1); } /* * Wait for DCD if necessary. */ if (!(tp->t_state & TS_CARR_ON) && !(mynor & CALLOUT_MASK) && !(tp->t_cflag & CLOCAL) && !(flag & O_NONBLOCK)) { ++com->wopeners; error = tsleep(TSA_CARR_ON(tp), TTIPRI | PCATCH, "siodcd", 0); if (com_addr(unit) == NULL) return (ENXIO); --com->wopeners; if (error != 0 || com->gone) goto out; goto open_top; } error = (*linesw[tp->t_line].l_open)(dev, tp); disc_optim(tp, &tp->t_termios, com); if (tp->t_state & TS_ISOPEN && mynor & CALLOUT_MASK) com->active_out = TRUE; siosettimeout(); out: splx(s); if (!(tp->t_state & TS_ISOPEN) && com->wopeners == 0) comhardclose(com); return (error); } static int sioclose(dev, flag, mode, p) dev_t dev; int flag; int mode; struct proc *p; { struct com_s *com; int mynor; int s; struct tty *tp; mynor = minor(dev); if (mynor & CONTROL_MASK) return (0); com = com_addr(MINOR_TO_UNIT(mynor)); tp = com->tp; s = spltty(); (*linesw[tp->t_line].l_close)(tp, flag); disc_optim(tp, &tp->t_termios, com); siostop(tp, FREAD | FWRITE); comhardclose(com); ttyclose(tp); siosettimeout(); splx(s); if (com->gone) { printf("sio%d: gone\n", com->unit); s = spltty(); com_addr(com->unit) = 0; bzero(tp,sizeof *tp); bzero(com,sizeof *com); free(com,M_TTYS); splx(s); } return (0); } static void comhardclose(com) struct com_s *com; { Port_t iobase; int s; struct tty *tp; int unit; unit = com->unit; iobase = com->iobase; s = spltty(); com->poll = FALSE; com->poll_output = FALSE; com->do_timestamp = FALSE; com->do_dcd_timestamp = FALSE; outb(iobase + com_cfcr, com->cfcr_image &= ~CFCR_SBREAK); { outb(iobase + com_ier, 0); tp = com->tp; if (tp->t_cflag & HUPCL /* * XXX we will miss any carrier drop between here and the * next open. Perhaps we should watch DCD even when the * port is closed; it is not sufficient to check it at * the next open because it might go up and down while * we're not watching. */ || !com->active_out && !(com->prev_modem_status & MSR_DCD) && !(com->it_in.c_cflag & CLOCAL) || !(tp->t_state & TS_ISOPEN)) { (void)commctl(com, TIOCM_DTR, DMBIC); if (com->dtr_wait != 0 && !(com->state & CS_DTR_OFF)) { timeout(siodtrwakeup, com, com->dtr_wait); com->state |= CS_DTR_OFF; } } } if (com->hasfifo) { /* * Disable fifos so that they are off after controlled * reboots. Some BIOSes fail to detect 16550s when the * fifos are enabled. */ outb(iobase + com_fifo, 0); } com->active_out = FALSE; wakeup(&com->active_out); wakeup(TSA_CARR_ON(tp)); /* restart any wopeners */ splx(s); } static int sioread(dev, uio, flag) dev_t dev; struct uio *uio; int flag; { int mynor; int unit; struct tty *tp; mynor = minor(dev); if (mynor & CONTROL_MASK) return (ENODEV); unit = MINOR_TO_UNIT(mynor); if (com_addr(unit)->gone) return (ENODEV); tp = com_addr(unit)->tp; return ((*linesw[tp->t_line].l_read)(tp, uio, flag)); } static int siowrite(dev, uio, flag) dev_t dev; struct uio *uio; int flag; { int mynor; struct tty *tp; int unit; mynor = minor(dev); if (mynor & CONTROL_MASK) return (ENODEV); unit = MINOR_TO_UNIT(mynor); if (com_addr(unit)->gone) return (ENODEV); tp = com_addr(unit)->tp; /* * (XXX) We disallow virtual consoles if the physical console is * a serial port. This is in case there is a display attached that * is not the console. In that situation we don't need/want the X * server taking over the console. */ if (constty != NULL && unit == comconsole) constty = NULL; return ((*linesw[tp->t_line].l_write)(tp, uio, flag)); } static void siobusycheck(chan) void *chan; { struct com_s *com; int s; com = (struct com_s *)chan; /* * Clear TS_BUSY if low-level output is complete. * spl locking is sufficient because siointr1() does not set CS_BUSY. * If siointr1() clears CS_BUSY after we look at it, then we'll get * called again. Reading the line status port outside of siointr1() * is safe because CS_BUSY is clear so there are no output interrupts * to lose. */ s = spltty(); if (com->state & CS_BUSY) com->extra_state &= ~CSE_BUSYCHECK; /* False alarm. */ else if ((inb(com->line_status_port) & (LSR_TSRE | LSR_TXRDY)) == (LSR_TSRE | LSR_TXRDY)) { com->tp->t_state &= ~TS_BUSY; ttwwakeup(com->tp); com->extra_state &= ~CSE_BUSYCHECK; } else timeout(siobusycheck, com, hz / 100); splx(s); } static void siodtrwakeup(chan) void *chan; { struct com_s *com; com = (struct com_s *)chan; com->state &= ~CS_DTR_OFF; wakeup(&com->dtr_wait); } void siointr(unit) int unit; { #ifndef COM_MULTIPORT COM_LOCK(); siointr1(com_addr(unit)); COM_UNLOCK(); #else /* COM_MULTIPORT */ struct com_s *com; bool_t possibly_more_intrs; /* * Loop until there is no activity on any port. This is necessary * to get an interrupt edge more than to avoid another interrupt. * If the IRQ signal is just an OR of the IRQ signals from several * devices, then the edge from one may be lost because another is * on. */ COM_LOCK(); do { possibly_more_intrs = FALSE; for (unit = 0; unit < NSIOTOT; ++unit) { com = com_addr(unit); /* * XXX COM_LOCK(); * would it work here, or be counter-productive? */ if (com != NULL && !com->gone && (inb(com->int_id_port) & IIR_IMASK) != IIR_NOPEND) { siointr1(com); possibly_more_intrs = TRUE; } /* XXX COM_UNLOCK(); */ } } while (possibly_more_intrs); COM_UNLOCK(); #endif /* COM_MULTIPORT */ } static void siointr1(com) struct com_s *com; { u_char line_status; u_char modem_status; u_char *ioptr; u_char recv_data; u_char int_ident; u_char int_ctl; u_char int_ctl_new; int_ctl = inb(com->intr_ctl_port); int_ctl_new = int_ctl; while (!com->gone) { line_status = inb(com->line_status_port); /* input event? (check first to help avoid overruns) */ while (line_status & LSR_RCV_MASK) { /* break/unnattached error bits or real input? */ if (!(line_status & LSR_RXRDY)) recv_data = 0; else recv_data = inb(com->data_port); if (line_status & (LSR_BI | LSR_FE | LSR_PE)) { /* * Don't store BI if IGNBRK or FE/PE if IGNPAR. * Otherwise, push the work to a higher level * (to handle PARMRK) if we're bypassing. * Otherwise, convert BI/FE and PE+INPCK to 0. * * This makes bypassing work right in the * usual "raw" case (IGNBRK set, and IGNPAR * and INPCK clear). * * Note: BI together with FE/PE means just BI. */ if (line_status & LSR_BI) { #if defined(DDB) && defined(BREAK_TO_DEBUGGER) if (com->unit == comconsole) { breakpoint(); goto cont; } #endif if (com->tp == NULL || com->tp->t_iflag & IGNBRK) goto cont; } else { if (com->tp == NULL || com->tp->t_iflag & IGNPAR) goto cont; } if (com->tp->t_state & TS_CAN_BYPASS_L_RINT && (line_status & (LSR_BI | LSR_FE) || com->tp->t_iflag & INPCK)) recv_data = 0; } ++com->bytes_in; if (com->hotchar != 0 && recv_data == com->hotchar) setsofttty(); ioptr = com->iptr; if (ioptr >= com->ibufend) CE_RECORD(com, CE_INTERRUPT_BUF_OVERFLOW); else { if (com->do_timestamp) microtime(&com->timestamp); ++com_events; schedsofttty(); #if 0 /* for testing input latency vs efficiency */ if (com->iptr - com->ibuf == 8) setsofttty(); #endif ioptr[0] = recv_data; ioptr[CE_INPUT_OFFSET] = line_status; com->iptr = ++ioptr; if (ioptr == com->ihighwater && com->state & CS_RTS_IFLOW) outb(com->modem_ctl_port, com->mcr_image &= ~MCR_RTS); if (line_status & LSR_OE) CE_RECORD(com, CE_OVERRUN); } cont: /* * "& 0x7F" is to avoid the gcc-1.40 generating a slow * jump from the top of the loop to here */ line_status = inb(com->line_status_port) & 0x7F; } /* modem status change? (always check before doing output) */ modem_status = inb(com->modem_status_port); if (modem_status != com->last_modem_status) { if (com->do_dcd_timestamp && !(com->last_modem_status & MSR_DCD) && modem_status & MSR_DCD) microtime(&com->dcd_timestamp); /* * Schedule high level to handle DCD changes. Note * that we don't use the delta bits anywhere. Some * UARTs mess them up, and it's easy to remember the * previous bits and calculate the delta. */ com->last_modem_status = modem_status; if (!(com->state & CS_CHECKMSR)) { com_events += LOTS_OF_EVENTS; com->state |= CS_CHECKMSR; setsofttty(); } /* handle CTS change immediately for crisp flow ctl */ if (com->state & CS_CTS_OFLOW) { if (modem_status & MSR_CTS) com->state |= CS_ODEVREADY; else com->state &= ~CS_ODEVREADY; } } /* output queued and everything ready? */ if (line_status & LSR_TXRDY && com->state >= (CS_BUSY | CS_TTGO | CS_ODEVREADY)) { ioptr = com->obufq.l_head; if (com->tx_fifo_size > 1) { u_int ocount; ocount = com->obufq.l_tail - ioptr; if (ocount > com->tx_fifo_size) ocount = com->tx_fifo_size; com->bytes_out += ocount; do outb(com->data_port, *ioptr++); while (--ocount != 0); } else { outb(com->data_port, *ioptr++); ++com->bytes_out; } com->obufq.l_head = ioptr; if (COM_IIR_TXRDYBUG(com)) { int_ctl_new = int_ctl | IER_ETXRDY; } if (ioptr >= com->obufq.l_tail) { struct lbq *qp; qp = com->obufq.l_next; qp->l_queued = FALSE; qp = qp->l_next; if (qp != NULL) { com->obufq.l_head = qp->l_head; com->obufq.l_tail = qp->l_tail; com->obufq.l_next = qp; } else { /* output just completed */ if ( COM_IIR_TXRDYBUG(com) ) { int_ctl_new = int_ctl & ~IER_ETXRDY; } com->state &= ~CS_BUSY; } if (!(com->state & CS_ODONE)) { com_events += LOTS_OF_EVENTS; com->state |= CS_ODONE; setsofttty(); /* handle at high level ASAP */ } } if ( COM_IIR_TXRDYBUG(com) && (int_ctl != int_ctl_new)) { outb(com->intr_ctl_port, int_ctl_new); } } /* finished? */ #ifndef COM_MULTIPORT if ((inb(com->int_id_port) & IIR_IMASK) == IIR_NOPEND) #endif /* COM_MULTIPORT */ return; } } static int sioioctl(dev, cmd, data, flag, p) dev_t dev; u_long cmd; caddr_t data; int flag; struct proc *p; { struct com_s *com; int error; Port_t iobase; int mynor; int s; struct tty *tp; #if defined(COMPAT_43) || defined(COMPAT_SUNOS) int oldcmd; struct termios term; #endif mynor = minor(dev); com = com_addr(MINOR_TO_UNIT(mynor)); if (com->gone) return (ENODEV); iobase = com->iobase; if (mynor & CONTROL_MASK) { struct termios *ct; switch (mynor & CONTROL_MASK) { case CONTROL_INIT_STATE: ct = mynor & CALLOUT_MASK ? &com->it_out : &com->it_in; break; case CONTROL_LOCK_STATE: ct = mynor & CALLOUT_MASK ? &com->lt_out : &com->lt_in; break; default: return (ENODEV); /* /dev/nodev */ } switch (cmd) { case TIOCSETA: error = suser(p->p_ucred, &p->p_acflag); if (error != 0) return (error); *ct = *(struct termios *)data; return (0); case TIOCGETA: *(struct termios *)data = *ct; return (0); case TIOCGETD: *(int *)data = TTYDISC; return (0); case TIOCGWINSZ: bzero(data, sizeof(struct winsize)); return (0); #ifdef DSI_SOFT_MODEM /* * Download micro-code to Digicom modem. */ case TIOCDSIMICROCODE: { u_long l; u_char *p,*pi; pi = (u_char*)(*(caddr_t*)data); error = copyin(pi,&l,sizeof l); if(error) {return error;}; pi += sizeof l; p = malloc(l,M_TEMP,M_NOWAIT); if(!p) {return ENOBUFS;} error = copyin(pi,p,l); if(error) {free(p,M_TEMP); return error;}; if(error = LoadSoftModem( MINOR_TO_UNIT(mynor),iobase,l,p)) {free(p,M_TEMP); return error;} free(p,M_TEMP); return(0); } #endif /* DSI_SOFT_MODEM */ default: return (ENOTTY); } } tp = com->tp; #if defined(COMPAT_43) || defined(COMPAT_SUNOS) term = tp->t_termios; oldcmd = cmd; error = ttsetcompat(tp, &cmd, data, &term); if (error != 0) return (error); if (cmd != oldcmd) data = (caddr_t)&term; #endif if (cmd == TIOCSETA || cmd == TIOCSETAW || cmd == TIOCSETAF) { int cc; struct termios *dt = (struct termios *)data; struct termios *lt = mynor & CALLOUT_MASK ? &com->lt_out : &com->lt_in; dt->c_iflag = (tp->t_iflag & lt->c_iflag) | (dt->c_iflag & ~lt->c_iflag); dt->c_oflag = (tp->t_oflag & lt->c_oflag) | (dt->c_oflag & ~lt->c_oflag); dt->c_cflag = (tp->t_cflag & lt->c_cflag) | (dt->c_cflag & ~lt->c_cflag); dt->c_lflag = (tp->t_lflag & lt->c_lflag) | (dt->c_lflag & ~lt->c_lflag); for (cc = 0; cc < NCCS; ++cc) if (lt->c_cc[cc] != 0) dt->c_cc[cc] = tp->t_cc[cc]; if (lt->c_ispeed != 0) dt->c_ispeed = tp->t_ispeed; if (lt->c_ospeed != 0) dt->c_ospeed = tp->t_ospeed; } error = (*linesw[tp->t_line].l_ioctl)(tp, cmd, data, flag, p); if (error != ENOIOCTL) return (error); s = spltty(); error = ttioctl(tp, cmd, data, flag); disc_optim(tp, &tp->t_termios, com); if (error != ENOIOCTL) { splx(s); return (error); } switch (cmd) { case TIOCSBRK: outb(iobase + com_cfcr, com->cfcr_image |= CFCR_SBREAK); break; case TIOCCBRK: outb(iobase + com_cfcr, com->cfcr_image &= ~CFCR_SBREAK); break; case TIOCSDTR: (void)commctl(com, TIOCM_DTR, DMBIS); break; case TIOCCDTR: (void)commctl(com, TIOCM_DTR, DMBIC); break; /* * XXX should disallow changing MCR_RTS if CS_RTS_IFLOW is set. The * changes get undone on the next call to comparam(). */ case TIOCMSET: (void)commctl(com, *(int *)data, DMSET); break; case TIOCMBIS: (void)commctl(com, *(int *)data, DMBIS); break; case TIOCMBIC: (void)commctl(com, *(int *)data, DMBIC); break; case TIOCMGET: *(int *)data = commctl(com, 0, DMGET); break; case TIOCMSDTRWAIT: /* must be root since the wait applies to following logins */ error = suser(p->p_ucred, &p->p_acflag); if (error != 0) { splx(s); return (error); } com->dtr_wait = *(int *)data * hz / 100; break; case TIOCMGDTRWAIT: *(int *)data = com->dtr_wait * 100 / hz; break; case TIOCTIMESTAMP: com->do_timestamp = TRUE; *(struct timeval *)data = com->timestamp; break; case TIOCDCDTIMESTAMP: com->do_dcd_timestamp = TRUE; *(struct timeval *)data = com->dcd_timestamp; break; default: splx(s); return (ENOTTY); } splx(s); return (0); } -void +static void siopoll() { int unit; if (com_events == 0) return; repeat: for (unit = 0; unit < NSIOTOT; ++unit) { u_char *buf; struct com_s *com; u_char *ibuf; int incc; struct tty *tp; com = com_addr(unit); if (com == NULL) continue; tp = com->tp; if (tp == NULL || com->gone) { /* * Discard any events related to never-opened or * going-away devices. */ disable_intr(); incc = com->iptr - com->ibuf; com->iptr = com->ibuf; if (com->state & CS_CHECKMSR) { incc += LOTS_OF_EVENTS; com->state &= ~CS_CHECKMSR; } com_events -= incc; enable_intr(); continue; } /* switch the role of the low-level input buffers */ if (com->iptr == (ibuf = com->ibuf)) { buf = NULL; /* not used, but compiler can't tell */ incc = 0; } else { buf = ibuf; disable_intr(); incc = com->iptr - buf; com_events -= incc; if (ibuf == com->ibuf1) ibuf = com->ibuf2; else ibuf = com->ibuf1; com->ibufend = ibuf + RS_IBUFSIZE; com->ihighwater = ibuf + RS_IHIGHWATER; com->iptr = ibuf; /* * There is now room for another low-level buffer full * of input, so enable RTS if it is now disabled and * there is room in the high-level buffer. */ if ((com->state & CS_RTS_IFLOW) && !(com->mcr_image & MCR_RTS) && !(tp->t_state & TS_TBLOCK)) outb(com->modem_ctl_port, com->mcr_image |= MCR_RTS); enable_intr(); com->ibuf = ibuf; } if (com->state & CS_CHECKMSR) { u_char delta_modem_status; disable_intr(); delta_modem_status = com->last_modem_status ^ com->prev_modem_status; com->prev_modem_status = com->last_modem_status; com_events -= LOTS_OF_EVENTS; com->state &= ~CS_CHECKMSR; enable_intr(); if (delta_modem_status & MSR_DCD) (*linesw[tp->t_line].l_modem) (tp, com->prev_modem_status & MSR_DCD); } if (com->state & CS_ODONE) { disable_intr(); com_events -= LOTS_OF_EVENTS; com->state &= ~CS_ODONE; enable_intr(); if (!(com->state & CS_BUSY) && !(com->extra_state & CSE_BUSYCHECK)) { timeout(siobusycheck, com, hz / 100); com->extra_state |= CSE_BUSYCHECK; } (*linesw[tp->t_line].l_start)(tp); } if (incc <= 0 || !(tp->t_state & TS_ISOPEN) || !(tp->t_cflag & CREAD)) continue; /* * Avoid the grotesquely inefficient lineswitch routine * (ttyinput) in "raw" mode. It usually takes about 450 * instructions (that's without canonical processing or echo!). * slinput is reasonably fast (usually 40 instructions plus * call overhead). */ if (tp->t_state & TS_CAN_BYPASS_L_RINT) { if (tp->t_rawq.c_cc + incc >= RB_I_HIGH_WATER && (com->state & CS_RTS_IFLOW || tp->t_iflag & IXOFF) && !(tp->t_state & TS_TBLOCK)) ttyblock(tp); tk_nin += incc; tk_rawcc += incc; tp->t_rawcc += incc; com->delta_error_counts[CE_TTY_BUF_OVERFLOW] += b_to_q((char *)buf, incc, &tp->t_rawq); ttwakeup(tp); if (tp->t_state & TS_TTSTOP && (tp->t_iflag & IXANY || tp->t_cc[VSTART] == tp->t_cc[VSTOP])) { tp->t_state &= ~TS_TTSTOP; tp->t_lflag &= ~FLUSHO; comstart(tp); } } else { do { u_char line_status; int recv_data; line_status = (u_char) buf[CE_INPUT_OFFSET]; recv_data = (u_char) *buf++; if (line_status & (LSR_BI | LSR_FE | LSR_OE | LSR_PE)) { if (line_status & LSR_BI) recv_data |= TTY_BI; if (line_status & LSR_FE) recv_data |= TTY_FE; if (line_status & LSR_OE) recv_data |= TTY_OE; if (line_status & LSR_PE) recv_data |= TTY_PE; } (*linesw[tp->t_line].l_rint)(recv_data, tp); } while (--incc > 0); } if (com_events == 0) break; } if (com_events >= LOTS_OF_EVENTS) goto repeat; } static int comparam(tp, t) struct tty *tp; struct termios *t; { u_int cfcr; int cflag; struct com_s *com; int divisor; u_char dlbh; u_char dlbl; int error; Port_t iobase; int s; int unit; int txtimeout; /* do historical conversions */ if (t->c_ispeed == 0) t->c_ispeed = t->c_ospeed; /* check requested parameters */ divisor = ttspeedtab(t->c_ospeed, comspeedtab); if (divisor < 0 || divisor > 0 && t->c_ispeed != t->c_ospeed) return (EINVAL); /* parameters are OK, convert them to the com struct and the device */ unit = DEV_TO_UNIT(tp->t_dev); com = com_addr(unit); iobase = com->iobase; s = spltty(); if (divisor == 0) (void)commctl(com, TIOCM_DTR, DMBIC); /* hang up line */ else (void)commctl(com, TIOCM_DTR, DMBIS); cflag = t->c_cflag; switch (cflag & CSIZE) { case CS5: cfcr = CFCR_5BITS; break; case CS6: cfcr = CFCR_6BITS; break; case CS7: cfcr = CFCR_7BITS; break; default: cfcr = CFCR_8BITS; break; } if (cflag & PARENB) { cfcr |= CFCR_PENAB; if (!(cflag & PARODD)) cfcr |= CFCR_PEVEN; } if (cflag & CSTOPB) cfcr |= CFCR_STOPB; if (com->hasfifo && divisor != 0) { /* * Use a fifo trigger level low enough so that the input * latency from the fifo is less than about 16 msec and * the total latency is less than about 30 msec. These * latencies are reasonable for humans. Serial comms * protocols shouldn't expect anything better since modem * latencies are larger. */ com->fifo_image = t->c_ospeed <= 4800 ? FIFO_ENABLE : FIFO_ENABLE | FIFO_RX_HIGH; #ifdef COM_ESP /* * The Hayes ESP card needs the fifo DMA mode bit set * in compatibility mode. If not, it will interrupt * for each character received. */ if (com->esp) com->fifo_image |= FIFO_DMA_MODE; #endif outb(iobase + com_fifo, com->fifo_image); } /* * Some UARTs lock up if the divisor latch registers are selected * while the UART is doing output (they refuse to transmit anything * more until given a hard reset). Fix this by stopping filling * the device buffers and waiting for them to drain. Reading the * line status port outside of siointr1() might lose some receiver * error bits, but that is acceptable here. */ disable_intr(); retry: com->state &= ~CS_TTGO; txtimeout = tp->t_timeout; enable_intr(); while ((inb(com->line_status_port) & (LSR_TSRE | LSR_TXRDY)) != (LSR_TSRE | LSR_TXRDY)) { tp->t_state |= TS_SO_OCOMPLETE; error = ttysleep(tp, TSA_OCOMPLETE(tp), TTIPRI | PCATCH, "siotx", hz / 100); if ( txtimeout != 0 && (!error || error == EAGAIN) && (txtimeout -= hz / 100) <= 0 ) error = EIO; if (com->gone) error = ENODEV; if (error != 0 && error != EAGAIN) { if (!(tp->t_state & TS_TTSTOP)) { disable_intr(); com->state |= CS_TTGO; enable_intr(); } splx(s); return (error); } } disable_intr(); /* very important while com_data is hidden */ /* * XXX - clearing CS_TTGO is not sufficient to stop further output, * because siopoll() calls comstart() which usually sets it again * because TS_TTSTOP is clear. Setting TS_TTSTOP would not be * sufficient, for similar reasons. */ if ((inb(com->line_status_port) & (LSR_TSRE | LSR_TXRDY)) != (LSR_TSRE | LSR_TXRDY)) goto retry; if (divisor != 0) { outb(iobase + com_cfcr, cfcr | CFCR_DLAB); /* * Only set the divisor registers if they would change, * since on some 16550 incompatibles (UMC8669F), setting * them while input is arriving them loses sync until * data stops arriving. */ dlbl = divisor & 0xFF; if (inb(iobase + com_dlbl) != dlbl) outb(iobase + com_dlbl, dlbl); dlbh = (u_int) divisor >> 8; if (inb(iobase + com_dlbh) != dlbh) outb(iobase + com_dlbh, dlbh); } outb(iobase + com_cfcr, com->cfcr_image = cfcr); if (!(tp->t_state & TS_TTSTOP)) com->state |= CS_TTGO; if (cflag & CRTS_IFLOW) { if (com->st16650a) { outb(iobase + com_cfcr, 0xbf); outb(iobase + com_fifo, inb(iobase + com_fifo) | 0x40); } com->state |= CS_RTS_IFLOW; /* * If CS_RTS_IFLOW just changed from off to on, the change * needs to be propagated to MCR_RTS. This isn't urgent, * so do it later by calling comstart() instead of repeating * a lot of code from comstart() here. */ } else if (com->state & CS_RTS_IFLOW) { com->state &= ~CS_RTS_IFLOW; /* * CS_RTS_IFLOW just changed from on to off. Force MCR_RTS * on here, since comstart() won't do it later. */ outb(com->modem_ctl_port, com->mcr_image |= MCR_RTS); if (com->st16650a) { outb(iobase + com_cfcr, 0xbf); outb(iobase + com_fifo, inb(iobase + com_fifo) & ~0x40); } } /* * Set up state to handle output flow control. * XXX - worth handling MDMBUF (DCD) flow control at the lowest level? * Now has 10+ msec latency, while CTS flow has 50- usec latency. */ com->state |= CS_ODEVREADY; com->state &= ~CS_CTS_OFLOW; if (cflag & CCTS_OFLOW) { com->state |= CS_CTS_OFLOW; if (!(com->last_modem_status & MSR_CTS)) com->state &= ~CS_ODEVREADY; if (com->st16650a) { outb(iobase + com_cfcr, 0xbf); outb(iobase + com_fifo, inb(iobase + com_fifo) | 0x80); } } else { if (com->st16650a) { outb(iobase + com_cfcr, 0xbf); outb(iobase + com_fifo, inb(iobase + com_fifo) & ~0x80); } } outb(iobase + com_cfcr, com->cfcr_image); /* XXX shouldn't call functions while intrs are disabled. */ disc_optim(tp, t, com); /* * Recover from fiddling with CS_TTGO. We used to call siointr1() * unconditionally, but that defeated the careful discarding of * stale input in sioopen(). */ if (com->state >= (CS_BUSY | CS_TTGO)) siointr1(com); enable_intr(); splx(s); comstart(tp); return (0); } static void comstart(tp) struct tty *tp; { struct com_s *com; int s; int unit; unit = DEV_TO_UNIT(tp->t_dev); com = com_addr(unit); s = spltty(); disable_intr(); if (tp->t_state & TS_TTSTOP) com->state &= ~CS_TTGO; else com->state |= CS_TTGO; if (tp->t_state & TS_TBLOCK) { if (com->mcr_image & MCR_RTS && com->state & CS_RTS_IFLOW) outb(com->modem_ctl_port, com->mcr_image &= ~MCR_RTS); } else { if (!(com->mcr_image & MCR_RTS) && com->iptr < com->ihighwater && com->state & CS_RTS_IFLOW) outb(com->modem_ctl_port, com->mcr_image |= MCR_RTS); } enable_intr(); if (tp->t_state & (TS_TIMEOUT | TS_TTSTOP)) { ttwwakeup(tp); splx(s); return; } if (tp->t_outq.c_cc != 0) { struct lbq *qp; struct lbq *next; if (!com->obufs[0].l_queued) { com->obufs[0].l_tail = com->obuf1 + q_to_b(&tp->t_outq, com->obuf1, sizeof com->obuf1); com->obufs[0].l_next = NULL; com->obufs[0].l_queued = TRUE; disable_intr(); if (com->state & CS_BUSY) { qp = com->obufq.l_next; while ((next = qp->l_next) != NULL) qp = next; qp->l_next = &com->obufs[0]; } else { com->obufq.l_head = com->obufs[0].l_head; com->obufq.l_tail = com->obufs[0].l_tail; com->obufq.l_next = &com->obufs[0]; com->state |= CS_BUSY; } enable_intr(); } if (tp->t_outq.c_cc != 0 && !com->obufs[1].l_queued) { com->obufs[1].l_tail = com->obuf2 + q_to_b(&tp->t_outq, com->obuf2, sizeof com->obuf2); com->obufs[1].l_next = NULL; com->obufs[1].l_queued = TRUE; disable_intr(); if (com->state & CS_BUSY) { qp = com->obufq.l_next; while ((next = qp->l_next) != NULL) qp = next; qp->l_next = &com->obufs[1]; } else { com->obufq.l_head = com->obufs[1].l_head; com->obufq.l_tail = com->obufs[1].l_tail; com->obufq.l_next = &com->obufs[1]; com->state |= CS_BUSY; } enable_intr(); } tp->t_state |= TS_BUSY; } disable_intr(); if (com->state >= (CS_BUSY | CS_TTGO)) siointr1(com); /* fake interrupt to start output */ enable_intr(); ttwwakeup(tp); splx(s); } static void siostop(tp, rw) struct tty *tp; int rw; { struct com_s *com; com = com_addr(DEV_TO_UNIT(tp->t_dev)); if (com->gone) return; disable_intr(); if (rw & FWRITE) { if (com->hasfifo) #ifdef COM_ESP /* XXX avoid h/w bug. */ if (!com->esp) #endif /* XXX does this flush everything? */ outb(com->iobase + com_fifo, FIFO_XMT_RST | com->fifo_image); com->obufs[0].l_queued = FALSE; com->obufs[1].l_queued = FALSE; if (com->state & CS_ODONE) com_events -= LOTS_OF_EVENTS; com->state &= ~(CS_ODONE | CS_BUSY); com->tp->t_state &= ~TS_BUSY; } if (rw & FREAD) { if (com->hasfifo) #ifdef COM_ESP /* XXX avoid h/w bug. */ if (!com->esp) #endif /* XXX does this flush everything? */ outb(com->iobase + com_fifo, FIFO_RCV_RST | com->fifo_image); com_events -= (com->iptr - com->ibuf); com->iptr = com->ibuf; } enable_intr(); comstart(tp); } static struct tty * siodevtotty(dev) dev_t dev; { int mynor; int unit; mynor = minor(dev); if (mynor & CONTROL_MASK) return (NULL); unit = MINOR_TO_UNIT(mynor); if ((u_int) unit >= NSIOTOT) return (NULL); return (&sio_tty[unit]); } static int commctl(com, bits, how) struct com_s *com; int bits; int how; { int mcr; int msr; if (how == DMGET) { bits = TIOCM_LE; /* XXX - always enabled while open */ mcr = com->mcr_image; if (mcr & MCR_DTR) bits |= TIOCM_DTR; if (mcr & MCR_RTS) bits |= TIOCM_RTS; msr = com->prev_modem_status; if (msr & MSR_CTS) bits |= TIOCM_CTS; if (msr & MSR_DCD) bits |= TIOCM_CD; if (msr & MSR_DSR) bits |= TIOCM_DSR; /* * XXX - MSR_RI is naturally volatile, and we make MSR_TERI * more volatile by reading the modem status a lot. Perhaps * we should latch both bits until the status is read here. */ if (msr & (MSR_RI | MSR_TERI)) bits |= TIOCM_RI; return (bits); } mcr = 0; if (bits & TIOCM_DTR) mcr |= MCR_DTR; if (bits & TIOCM_RTS) mcr |= MCR_RTS; if (com->gone) return(0); disable_intr(); switch (how) { case DMSET: outb(com->modem_ctl_port, com->mcr_image = mcr | (com->mcr_image & MCR_IENABLE)); break; case DMBIS: outb(com->modem_ctl_port, com->mcr_image |= mcr); break; case DMBIC: outb(com->modem_ctl_port, com->mcr_image &= ~mcr); break; } enable_intr(); return (0); } static void siosettimeout() { struct com_s *com; bool_t someopen; int unit; /* * Set our timeout period to 1 second if no polled devices are open. * Otherwise set it to max(1/200, 1/hz). * Enable timeouts iff some device is open. */ untimeout(comwakeup, (void *)NULL, sio_timeout_handle); sio_timeout = hz; someopen = FALSE; for (unit = 0; unit < NSIOTOT; ++unit) { com = com_addr(unit); if (com != NULL && com->tp != NULL && com->tp->t_state & TS_ISOPEN && !com->gone) { someopen = TRUE; if (com->poll || com->poll_output) { sio_timeout = hz > 200 ? hz / 200 : 1; break; } } } if (someopen) { sio_timeouts_until_log = hz / sio_timeout; sio_timeout_handle = timeout(comwakeup, (void *)NULL, sio_timeout); } else { /* Flush error messages, if any. */ sio_timeouts_until_log = 1; comwakeup((void *)NULL); untimeout(comwakeup, (void *)NULL, sio_timeout_handle); } } static void comwakeup(chan) void *chan; { struct com_s *com; int unit; sio_timeout_handle = timeout(comwakeup, (void *)NULL, sio_timeout); /* * Recover from lost output interrupts. * Poll any lines that don't use interrupts. */ for (unit = 0; unit < NSIOTOT; ++unit) { com = com_addr(unit); if (com != NULL && !com->gone && (com->state >= (CS_BUSY | CS_TTGO) || com->poll)) { disable_intr(); siointr1(com); enable_intr(); } } /* * Check for and log errors, but not too often. */ if (--sio_timeouts_until_log > 0) return; sio_timeouts_until_log = hz / sio_timeout; for (unit = 0; unit < NSIOTOT; ++unit) { int errnum; com = com_addr(unit); if (com == NULL) continue; if (com->gone) continue; for (errnum = 0; errnum < CE_NTYPES; ++errnum) { u_int delta; u_long total; disable_intr(); delta = com->delta_error_counts[errnum]; com->delta_error_counts[errnum] = 0; enable_intr(); if (delta == 0) continue; total = com->error_counts[errnum] += delta; log(LOG_ERR, "sio%d: %u more %s%s (total %lu)\n", unit, delta, error_desc[errnum], delta == 1 ? "" : "s", total); } } } static void disc_optim(tp, t, com) struct tty *tp; struct termios *t; struct com_s *com; { if (!(t->c_iflag & (ICRNL | IGNCR | IMAXBEL | INLCR | ISTRIP | IXON)) && (!(t->c_iflag & BRKINT) || (t->c_iflag & IGNBRK)) && (!(t->c_iflag & PARMRK) || (t->c_iflag & (IGNPAR | IGNBRK)) == (IGNPAR | IGNBRK)) && !(t->c_lflag & (ECHO | ICANON | IEXTEN | ISIG | PENDIN)) && linesw[tp->t_line].l_rint == ttyinput) tp->t_state |= TS_CAN_BYPASS_L_RINT; else tp->t_state &= ~TS_CAN_BYPASS_L_RINT; com->hotchar = linesw[tp->t_line].l_hotchar; } /* * Following are all routines needed for SIO to act as console */ #include struct siocnstate { u_char dlbl; u_char dlbh; u_char ier; u_char cfcr; u_char mcr; }; static speed_t siocngetspeed __P((Port_t, struct speedtab *)); static void siocnclose __P((struct siocnstate *sp)); static void siocnopen __P((struct siocnstate *sp)); static void siocntxwait __P((void)); static void siocntxwait() { int timo; /* * Wait for any pending transmission to finish. Required to avoid * the UART lockup bug when the speed is changed, and for normal * transmits. */ timo = 100000; while ((inb(siocniobase + com_lsr) & (LSR_TSRE | LSR_TXRDY)) != (LSR_TSRE | LSR_TXRDY) && --timo != 0) ; } /* * Read the serial port specified and try to figure out what speed * it's currently running at. We're assuming the serial port has * been initialized and is basicly idle. This routine is only intended * to be run at system startup. * * If the value read from the serial port doesn't make sense, return 0. */ static speed_t siocngetspeed(iobase, table) Port_t iobase; struct speedtab *table; { int code; u_char dlbh; u_char dlbl; u_char cfcr; cfcr = inb(iobase + com_cfcr); outb(iobase + com_cfcr, CFCR_DLAB | cfcr); dlbl = inb(iobase + com_dlbl); dlbh = inb(iobase + com_dlbh); outb(iobase + com_cfcr, cfcr); code = dlbh << 8 | dlbl; for ( ; table->sp_speed != -1; table++) if (table->sp_code == code) return (table->sp_speed); return 0; /* didn't match anything sane */ } static void siocnopen(sp) struct siocnstate *sp; { int divisor; u_char dlbh; u_char dlbl; Port_t iobase; /* * Save all the device control registers except the fifo register * and set our default ones (cs8 -parenb speed=comdefaultrate). * We can't save the fifo register since it is read-only. */ iobase = siocniobase; sp->ier = inb(iobase + com_ier); outb(iobase + com_ier, 0); /* spltty() doesn't stop siointr() */ siocntxwait(); sp->cfcr = inb(iobase + com_cfcr); outb(iobase + com_cfcr, CFCR_DLAB | CFCR_8BITS); sp->dlbl = inb(iobase + com_dlbl); sp->dlbh = inb(iobase + com_dlbh); /* * Only set the divisor registers if they would change, since on * some 16550 incompatibles (Startech), setting them clears the * data input register. This also reduces the effects of the * UMC8669F bug. */ divisor = ttspeedtab(comdefaultrate, comspeedtab); dlbl = divisor & 0xFF; if (sp->dlbl != dlbl) outb(iobase + com_dlbl, dlbl); dlbh = (u_int) divisor >> 8; if (sp->dlbh != dlbh) outb(iobase + com_dlbh, dlbh); outb(iobase + com_cfcr, CFCR_8BITS); sp->mcr = inb(iobase + com_mcr); /* * We don't want interrupts, but must be careful not to "disable" * them by clearing the MCR_IENABLE bit, since that might cause * an interrupt by floating the IRQ line. */ outb(iobase + com_mcr, (sp->mcr & MCR_IENABLE) | MCR_DTR | MCR_RTS); } static void siocnclose(sp) struct siocnstate *sp; { Port_t iobase; /* * Restore the device control registers. */ siocntxwait(); iobase = siocniobase; outb(iobase + com_cfcr, CFCR_DLAB | CFCR_8BITS); if (sp->dlbl != inb(iobase + com_dlbl)) outb(iobase + com_dlbl, sp->dlbl); if (sp->dlbh != inb(iobase + com_dlbh)) outb(iobase + com_dlbh, sp->dlbh); outb(iobase + com_cfcr, sp->cfcr); /* * XXX damp oscillations of MCR_DTR and MCR_RTS by not restoring them. */ outb(iobase + com_mcr, sp->mcr | MCR_DTR | MCR_RTS); outb(iobase + com_ier, sp->ier); } void siocnprobe(cp) struct consdev *cp; { speed_t boot_speed; u_char cfcr; struct isa_device *dvp; int s; struct siocnstate sp; /* * Find our first enabled console, if any. If it is a high-level * console device, then initialize it and return successfully. * If it is a low-level console device, then initialize it and * return unsuccessfully. It must be initialized in both cases * for early use by console drivers and debuggers. Initializing * the hardware is not necessary in all cases, since the i/o * routines initialize it on the fly, but it is necessary if * input might arrive while the hardware is switched back to an * uninitialized state. We can't handle multiple console devices * yet because our low-level routines don't take a device arg. * We trust the user to set the console flags properly so that we * don't need to probe. */ cp->cn_pri = CN_DEAD; for (dvp = isa_devtab_tty; dvp->id_driver != NULL; dvp++) if (dvp->id_driver == &siodriver && dvp->id_enabled && COM_CONSOLE(dvp)) { siocniobase = dvp->id_iobase; s = spltty(); if (boothowto & RB_SERIAL) { boot_speed = siocngetspeed(siocniobase, comspeedtab); if (boot_speed) comdefaultrate = boot_speed; } /* * Initialize the divisor latch. We can't rely on * siocnopen() to do this the first time, since it * avoids writing to the latch if the latch appears * to have the correct value. Also, if we didn't * just read the speed from the hardware, then we * need to set the speed in hardware so that * switching it later is null. */ cfcr = inb(siocniobase + com_cfcr); outb(siocniobase + com_cfcr, CFCR_DLAB | cfcr); outb(siocniobase + com_dlbl, COMBRD(comdefaultrate) & 0xff); outb(siocniobase + com_dlbh, (u_int) COMBRD(comdefaultrate) >> 8); outb(siocniobase + com_cfcr, cfcr); siocnopen(&sp); splx(s); if (!COM_LLCONSOLE(dvp)) { cp->cn_dev = makedev(CDEV_MAJOR, dvp->id_unit); cp->cn_pri = COM_FORCECONSOLE(dvp) || boothowto & RB_SERIAL ? CN_REMOTE : CN_NORMAL; } break; } } void siocninit(cp) struct consdev *cp; { comconsole = DEV_TO_UNIT(cp->cn_dev); } int siocncheckc(dev) dev_t dev; { int c; Port_t iobase; int s; struct siocnstate sp; iobase = siocniobase; s = spltty(); siocnopen(&sp); if (inb(iobase + com_lsr) & LSR_RXRDY) c = inb(iobase + com_data); else c = -1; siocnclose(&sp); splx(s); return (c); } int siocngetc(dev) dev_t dev; { int c; Port_t iobase; int s; struct siocnstate sp; iobase = siocniobase; s = spltty(); siocnopen(&sp); while (!(inb(iobase + com_lsr) & LSR_RXRDY)) ; c = inb(iobase + com_data); siocnclose(&sp); splx(s); return (c); } void siocnputc(dev, c) dev_t dev; int c; { int s; struct siocnstate sp; s = spltty(); siocnopen(&sp); siocntxwait(); outb(siocniobase + com_data, c); siocnclose(&sp); splx(s); } #ifdef DSI_SOFT_MODEM /* * The magic code to download microcode to a "Connection 14.4+Fax" * modem from Digicom Systems Inc. Very magic. */ #define DSI_ERROR(str) { ptr = str; goto error; } static int LoadSoftModem(int unit, int base_io, u_long size, u_char *ptr) { int int_c,int_k; int data_0188, data_0187; /* * First see if it is a DSI SoftModem */ if(!((inb(base_io+7) ^ inb(base_io+7)) & 0x80)) return ENODEV; data_0188 = inb(base_io+4); data_0187 = inb(base_io+3); outb(base_io+3,0x80); outb(base_io+4,0x0C); outb(base_io+0,0x31); outb(base_io+1,0x8C); outb(base_io+7,0x10); outb(base_io+7,0x19); if(0x18 != (inb(base_io+7) & 0x1A)) DSI_ERROR("dsp bus not granted"); if(0x01 != (inb(base_io+7) & 0x01)) { outb(base_io+7,0x18); outb(base_io+7,0x19); if(0x01 != (inb(base_io+7) & 0x01)) DSI_ERROR("program mem not granted"); } int_c = 0; while(1) { if(int_c >= 7 || size <= 0x1800) break; for(int_k = 0 ; int_k < 0x800; int_k++) { outb(base_io+0,*ptr++); outb(base_io+1,*ptr++); outb(base_io+2,*ptr++); } size -= 0x1800; int_c++; } if(size > 0x1800) { outb(base_io+7,0x18); outb(base_io+7,0x19); if(0x00 != (inb(base_io+7) & 0x01)) DSI_ERROR("program data not granted"); for(int_k = 0 ; int_k < 0x800; int_k++) { outb(base_io+1,*ptr++); outb(base_io+2,0); outb(base_io+1,*ptr++); outb(base_io+2,*ptr++); } size -= 0x1800; while(size > 0x1800) { for(int_k = 0 ; int_k < 0xC00; int_k++) { outb(base_io+1,*ptr++); outb(base_io+2,*ptr++); } size -= 0x1800; } if(size < 0x1800) { for(int_k=0;int_k 0) { if(int_c == 7) { outb(base_io+7,0x18); outb(base_io+7,0x19); if(0x00 != (inb(base_io+7) & 0x01)) DSI_ERROR("program data not granted"); for(int_k = 0 ; int_k < size/3; int_k++) { outb(base_io+1,*ptr++); outb(base_io+2,0); outb(base_io+1,*ptr++); outb(base_io+2,*ptr++); } } else { for(int_k = 0 ; int_k < size/3; int_k++) { outb(base_io+0,*ptr++); outb(base_io+1,*ptr++); outb(base_io+2,*ptr++); } } } outb(base_io+7,0x11); outb(base_io+7,3); outb(base_io+4,data_0188 & 0xfb); outb(base_io+3,data_0187); return 0; error: printf("sio%d: DSI SoftModem microcode load failed: <%s>\n",unit,ptr); outb(base_io+7,0x00); \ outb(base_io+3,data_0187); \ outb(base_io+4,data_0188); \ return EIO; } #endif /* DSI_SOFT_MODEM */ /* * support PnP cards if we are using 'em */ #if NPNP > 0 static struct siopnp_ids { u_long vend_id; char *id_str; } siopnp_ids[] = { { 0x5015f435, "MOT1550"}, { 0x8113b04e, "Supra1381"}, { 0x9012b04e, "Supra1290"}, { 0x11007256, "USR0011"}, { 0x30207256, "USR2030"}, { 0 } }; static char *siopnp_probe(u_long csn, u_long vend_id); static void siopnp_attach(u_long csn, u_long vend_id, char *name, struct isa_device *dev); static u_long nsiopnp = NSIO; static struct pnp_device siopnp = { "siopnp", siopnp_probe, siopnp_attach, &nsiopnp, &tty_imask }; DATA_SET (pnpdevice_set, siopnp); static char * siopnp_probe(u_long csn, u_long vend_id) { struct siopnp_ids *ids; char *s = NULL; for(ids = siopnp_ids; ids->vend_id != 0; ids++) { if (vend_id == ids->vend_id) { s = ids->id_str; break; } } if (s) { struct pnp_cinfo d; read_pnp_parms(&d, 0); if (d.enable == 0 || d.flags & 1) { printf("CSN %d is disabled.\n", csn); return (NULL); } } return (s); } static void siopnp_attach(u_long csn, u_long vend_id, char *name, struct isa_device *dev) { struct pnp_cinfo d; struct isa_device *dvp; if (dev->id_unit >= NSIOTOT) return; if (read_pnp_parms(&d, 0) == 0) { printf("failed to read pnp parms\n"); return; } write_pnp_parms(&d, 0); enable_pnp_card(); dev->id_iobase = d.port[0]; dev->id_irq = (1 << d.irq[0]); dev->id_intr = siointr; dev->id_ri_flags = RI_FAST; dev->id_drq = -1; if (dev->id_driver == NULL) { dev->id_driver = &siodriver; dvp = find_isadev(isa_devtab_tty, &siodriver, 0); if (dvp != NULL) dev->id_id = dvp->id_id; } if ((dev->id_alive = sioprobe(dev)) != 0) sioattach(dev); else printf("sio%d: probe failed\n", dev->id_unit); } #endif Index: head/sys/isa/sio.c =================================================================== --- head/sys/isa/sio.c (revision 38245) +++ head/sys/isa/sio.c (revision 38246) @@ -1,3044 +1,3039 @@ /*- * Copyright (c) 1991 The Regents of the University of California. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * from: @(#)com.c 7.5 (Berkeley) 5/16/91 - * $Id: sio.c,v 1.208 1998/06/24 09:23:32 phk Exp $ + * $Id: sio.c,v 1.209 1998/07/15 12:18:14 bde Exp $ */ #include "opt_comconsole.h" #include "opt_compat.h" #include "opt_ddb.h" #include "opt_devfs.h" #include "opt_sio.h" #include "sio.h" #include "pnp.h" #ifndef EXTRA_SIO #if NPNP > 0 #define EXTRA_SIO 2 #else #define EXTRA_SIO 0 #endif #endif #define NSIOTOT (NSIO + EXTRA_SIO) /* * Serial driver, based on 386BSD-0.1 com driver. * Mostly rewritten to use pseudo-DMA. * Works for National Semiconductor NS8250-NS16550AF UARTs. * COM driver, based on HP dca driver. * * Changes for PC-Card integration: * - Added PC-Card driver table and handlers */ #include #include #include #include #include #include #include #include #include +#include #include #include #include #ifdef DEVFS #include #endif #include +#include #include #include #include #include #ifdef COM_ESP #include #endif #include #include "card.h" #if NCARD > 0 #include #include #endif #if NPNP > 0 #include #endif #ifdef SMP #define disable_intr() COM_DISABLE_INTR() #define enable_intr() COM_ENABLE_INTR() #endif /* SMP */ #define LOTS_OF_EVENTS 64 /* helps separate urgent events from input */ #define RB_I_HIGH_WATER (TTYHOG - 2 * RS_IBUFSIZE) #define RS_IBUFSIZE 256 #define CALLOUT_MASK 0x80 #define CONTROL_MASK 0x60 #define CONTROL_INIT_STATE 0x20 #define CONTROL_LOCK_STATE 0x40 #define DEV_TO_UNIT(dev) (MINOR_TO_UNIT(minor(dev))) #define MINOR_MAGIC_MASK (CALLOUT_MASK | CONTROL_MASK) #define MINOR_TO_UNIT(mynor) ((mynor) & ~MINOR_MAGIC_MASK) #ifdef COM_MULTIPORT /* checks in flags for multiport and which is multiport "master chip" * for a given card */ #define COM_ISMULTIPORT(dev) ((dev)->id_flags & 0x01) #define COM_MPMASTER(dev) (((dev)->id_flags >> 8) & 0x0ff) #define COM_NOTAST4(dev) ((dev)->id_flags & 0x04) #endif /* COM_MULTIPORT */ #define COM_CONSOLE(dev) ((dev)->id_flags & 0x10) #define COM_FORCECONSOLE(dev) ((dev)->id_flags & 0x20) #define COM_LLCONSOLE(dev) ((dev)->id_flags & 0x40) #define COM_LOSESOUTINTS(dev) ((dev)->id_flags & 0x08) #define COM_NOFIFO(dev) ((dev)->id_flags & 0x02) #define COM_ST16650A(dev) ((dev)->id_flags & 0x20000) #define COM_C_NOPROBE (0x40000) #define COM_NOPROBE(dev) ((dev)->id_flags & COM_C_NOPROBE) #define COM_C_IIR_TXRDYBUG (0x80000) #define COM_IIR_TXRDYBUG(dev) ((dev)->id_flags & COM_C_IIR_TXRDYBUG) #define COM_FIFOSIZE(dev) (((dev)->id_flags & 0xff000000) >> 24) #define com_scr 7 /* scratch register for 16450-16550 (R/W) */ /* * Input buffer watermarks. * The external device is asked to stop sending when the buffer exactly reaches * high water, or when the high level requests it. * The high level is notified immediately (rather than at a later clock tick) * when this watermark is reached. * The buffer size is chosen so the watermark should almost never be reached. * The low watermark is invisibly 0 since the buffer is always emptied all at * once. */ #define RS_IHIGHWATER (3 * RS_IBUFSIZE / 4) /* * com state bits. * (CS_BUSY | CS_TTGO) and (CS_BUSY | CS_TTGO | CS_ODEVREADY) must be higher * than the other bits so that they can be tested as a group without masking * off the low bits. * * The following com and tty flags correspond closely: * CS_BUSY = TS_BUSY (maintained by comstart(), siopoll() and * siostop()) * CS_TTGO = ~TS_TTSTOP (maintained by comparam() and comstart()) * CS_CTS_OFLOW = CCTS_OFLOW (maintained by comparam()) * CS_RTS_IFLOW = CRTS_IFLOW (maintained by comparam()) * TS_FLUSH is not used. * XXX I think TIOCSETA doesn't clear TS_TTSTOP when it clears IXON. * XXX CS_*FLOW should be CF_*FLOW in com->flags (control flags not state). */ #define CS_BUSY 0x80 /* output in progress */ #define CS_TTGO 0x40 /* output not stopped by XOFF */ #define CS_ODEVREADY 0x20 /* external device h/w ready (CTS) */ #define CS_CHECKMSR 1 /* check of MSR scheduled */ #define CS_CTS_OFLOW 2 /* use CTS output flow control */ #define CS_DTR_OFF 0x10 /* DTR held off */ #define CS_ODONE 4 /* output completed */ #define CS_RTS_IFLOW 8 /* use RTS input flow control */ #define CSE_BUSYCHECK 1 /* siobusycheck() scheduled */ static char const * const error_desc[] = { #define CE_OVERRUN 0 "silo overflow", #define CE_INTERRUPT_BUF_OVERFLOW 1 "interrupt-level buffer overflow", #define CE_TTY_BUF_OVERFLOW 2 "tty-level buffer overflow", }; #define CE_NTYPES 3 #define CE_RECORD(com, errnum) (++(com)->delta_error_counts[errnum]) /* types. XXX - should be elsewhere */ typedef u_int Port_t; /* hardware port */ typedef u_char bool_t; /* boolean */ /* queue of linear buffers */ struct lbq { u_char *l_head; /* next char to process */ u_char *l_tail; /* one past the last char to process */ struct lbq *l_next; /* next in queue */ bool_t l_queued; /* nonzero if queued */ }; /* com device structure */ struct com_s { u_int id_flags; /* Copy isa device falgas */ u_char state; /* miscellaneous flag bits */ bool_t active_out; /* nonzero if the callout device is open */ u_char cfcr_image; /* copy of value written to CFCR */ #ifdef COM_ESP bool_t esp; /* is this unit a hayes esp board? */ #endif u_char extra_state; /* more flag bits, separate for order trick */ u_char fifo_image; /* copy of value written to FIFO */ bool_t hasfifo; /* nonzero for 16550 UARTs */ bool_t st16650a; /* Is a Startech 16650A or RTS/CTS compat */ bool_t loses_outints; /* nonzero if device loses output interrupts */ u_char mcr_image; /* copy of value written to MCR */ #ifdef COM_MULTIPORT bool_t multiport; /* is this unit part of a multiport device? */ #endif /* COM_MULTIPORT */ bool_t no_irq; /* nonzero if irq is not attached */ bool_t gone; /* hardware disappeared */ bool_t poll; /* nonzero if polling is required */ bool_t poll_output; /* nonzero if polling for output is required */ int unit; /* unit number */ int dtr_wait; /* time to hold DTR down on close (* 1/hz) */ u_int tx_fifo_size; u_int wopeners; /* # processes waiting for DCD in open() */ /* * The high level of the driver never reads status registers directly * because there would be too many side effects to handle conveniently. * Instead, it reads copies of the registers stored here by the * interrupt handler. */ u_char last_modem_status; /* last MSR read by intr handler */ u_char prev_modem_status; /* last MSR handled by high level */ u_char hotchar; /* ldisc-specific char to be handled ASAP */ u_char *ibuf; /* start of input buffer */ u_char *ibufend; /* end of input buffer */ u_char *ihighwater; /* threshold in input buffer */ u_char *iptr; /* next free spot in input buffer */ struct lbq obufq; /* head of queue of output buffers */ struct lbq obufs[2]; /* output buffers */ Port_t data_port; /* i/o ports */ #ifdef COM_ESP Port_t esp_port; #endif Port_t int_id_port; Port_t iobase; Port_t modem_ctl_port; Port_t line_status_port; Port_t modem_status_port; Port_t intr_ctl_port; /* Ports of IIR register */ struct tty *tp; /* cross reference */ /* Initial state. */ struct termios it_in; /* should be in struct tty */ struct termios it_out; /* Lock state. */ struct termios lt_in; /* should be in struct tty */ struct termios lt_out; bool_t do_timestamp; bool_t do_dcd_timestamp; struct timeval timestamp; struct timeval dcd_timestamp; u_long bytes_in; /* statistics */ u_long bytes_out; u_int delta_error_counts[CE_NTYPES]; u_long error_counts[CE_NTYPES]; /* * Ping-pong input buffers. The extra factor of 2 in the sizes is * to allow for an error byte for each input byte. */ #define CE_INPUT_OFFSET RS_IBUFSIZE u_char ibuf1[2 * RS_IBUFSIZE]; u_char ibuf2[2 * RS_IBUFSIZE]; /* * Data area for output buffers. Someday we should build the output * buffer queue without copying data. */ u_char obuf1[256]; u_char obuf2[256]; #ifdef DEVFS void *devfs_token_ttyd; void *devfs_token_ttyl; void *devfs_token_ttyi; void *devfs_token_cuaa; void *devfs_token_cual; void *devfs_token_cuai; #endif }; -/* - * XXX public functions in drivers should be declared in headers produced - * by `config', not here. - */ - -/* Interrupt handling entry point. */ -void siopoll __P((void)); - -/* Device switch entry points. */ -#define sioreset noreset -#define siommap nommap -#define siostrategy nostrategy - #ifdef COM_ESP static int espattach __P((struct isa_device *isdp, struct com_s *com, Port_t esp_port)); #endif static int sioattach __P((struct isa_device *dev)); static timeout_t siobusycheck; static timeout_t siodtrwakeup; static void comhardclose __P((struct com_s *com)); static void siointr1 __P((struct com_s *com)); static int commctl __P((struct com_s *com, int bits, int how)); static int comparam __P((struct tty *tp, struct termios *t)); +static swihand_t siopoll; static int sioprobe __P((struct isa_device *dev)); static void siosettimeout __P((void)); static void comstart __P((struct tty *tp)); static timeout_t comwakeup; static void disc_optim __P((struct tty *tp, struct termios *t, struct com_s *com)); #ifdef DSI_SOFT_MODEM static int LoadSoftModem __P((int unit,int base_io, u_long size, u_char *ptr)); #endif /* DSI_SOFT_MODEM */ static char driver_name[] = "sio"; /* table and macro for fast conversion from a unit number to its com struct */ static struct com_s *p_com_addr[NSIOTOT]; #define com_addr(unit) (p_com_addr[unit]) struct isa_driver siodriver = { sioprobe, sioattach, driver_name }; static d_open_t sioopen; static d_close_t sioclose; static d_read_t sioread; static d_write_t siowrite; static d_ioctl_t sioioctl; static d_stop_t siostop; static d_devtotty_t siodevtotty; #define CDEV_MAJOR 28 static struct cdevsw sio_cdevsw = { sioopen, sioclose, sioread, siowrite, sioioctl, siostop, noreset, siodevtotty, ttpoll, nommap, NULL, driver_name, NULL, -1, }; static int comconsole = -1; static volatile speed_t comdefaultrate = CONSPEED; static u_int com_events; /* input chars + weighted output completions */ static Port_t siocniobase; +static bool_t siopoll_registered; static int sio_timeout; static int sio_timeouts_until_log; static struct callout_handle sio_timeout_handle = CALLOUT_HANDLE_INITIALIZER(&sio_timeout_handle); #if 0 /* XXX */ static struct tty *sio_tty[NSIOTOT]; #else static struct tty sio_tty[NSIOTOT]; #endif static const int nsio_tty = NSIOTOT; static struct speedtab comspeedtab[] = { { 0, 0 }, { 50, COMBRD(50) }, { 75, COMBRD(75) }, { 110, COMBRD(110) }, { 134, COMBRD(134) }, { 150, COMBRD(150) }, { 200, COMBRD(200) }, { 300, COMBRD(300) }, { 600, COMBRD(600) }, { 1200, COMBRD(1200) }, { 1800, COMBRD(1800) }, { 2400, COMBRD(2400) }, { 4800, COMBRD(4800) }, { 9600, COMBRD(9600) }, { 19200, COMBRD(19200) }, { 38400, COMBRD(38400) }, { 57600, COMBRD(57600) }, { 115200, COMBRD(115200) }, { -1, -1 } }; #ifdef COM_ESP /* XXX configure this properly. */ static Port_t likely_com_ports[] = { 0x3f8, 0x2f8, 0x3e8, 0x2e8, }; static Port_t likely_esp_ports[] = { 0x140, 0x180, 0x280, 0 }; #endif /* * handle sysctl read/write requests for console speed * * In addition to setting comdefaultrate for I/O through /dev/console, * also set the initial and lock values for the /dev/ttyXX device * if there is one associated with the console. Finally, if the /dev/tty * device has already been open, change the speed on the open running port * itself. */ static int sysctl_machdep_comdefaultrate SYSCTL_HANDLER_ARGS { int error, s; speed_t newspeed; struct com_s *com; struct tty *tp; newspeed = comdefaultrate; error = sysctl_handle_opaque(oidp, &newspeed, sizeof newspeed, req); if (error || !req->newptr) return (error); comdefaultrate = newspeed; if (comconsole < 0) /* serial console not selected? */ return (0); com = com_addr(comconsole); if (!com) return (ENXIO); /* * set the initial and lock rates for /dev/ttydXX and /dev/cuaXX * (note, the lock rates really are boolean -- if non-zero, disallow * speed changes) */ com->it_in.c_ispeed = com->it_in.c_ospeed = com->lt_in.c_ispeed = com->lt_in.c_ospeed = com->it_out.c_ispeed = com->it_out.c_ospeed = com->lt_out.c_ispeed = com->lt_out.c_ospeed = comdefaultrate; /* * if we're open, change the running rate too */ tp = com->tp; if (tp && (tp->t_state & TS_ISOPEN)) { tp->t_termios.c_ispeed = tp->t_termios.c_ospeed = comdefaultrate; s = spltty(); error = comparam(tp, &tp->t_termios); splx(s); } return error; } SYSCTL_PROC(_machdep, OID_AUTO, conspeed, CTLTYPE_INT | CTLFLAG_RW, 0, 0, sysctl_machdep_comdefaultrate, "I", ""); #if NCARD > 0 /* * PC-Card (PCMCIA) specific code. */ static int sioinit __P((struct pccard_devinfo *)); static void siounload __P((struct pccard_devinfo *)); static int card_intr __P((struct pccard_devinfo *)); static struct pccard_device sio_info = { driver_name, sioinit, siounload, card_intr, 0, /* Attributes - presently unused */ &tty_imask /* Interrupt mask for device */ /* XXX - Should this also include net_imask? */ }; DATA_SET(pccarddrv_set, sio_info); /* * Initialize the device - called from Slot manager. */ int sioinit(struct pccard_devinfo *devi) { /* validate unit number. */ if (devi->isahd.id_unit >= (NSIOTOT)) return(ENODEV); /* Make sure it isn't already probed. */ if (com_addr(devi->isahd.id_unit)) return(EBUSY); /* It's already probed as serial by Upper */ devi->isahd.id_flags |= COM_C_NOPROBE; /* * Probe the device. If a value is returned, the * device was found at the location. */ if (sioprobe(&devi->isahd) == 0) return(ENXIO); if (sioattach(&devi->isahd) == 0) return(ENXIO); return(0); } /* * siounload - unload the driver and clear the table. * XXX TODO: * This is usually called when the card is ejected, but * can be caused by a modunload of a controller driver. * The idea is to reset the driver's view of the device * and ensure that any driver entry points such as * read and write do not hang. */ static void siounload(struct pccard_devinfo *devi) { struct com_s *com; if (!devi) { printf("NULL devi in siounload\n"); return; } com = com_addr(devi->isahd.id_unit); if (!com) { printf("NULL com in siounload\n"); return; } if (!com->iobase) { printf("sio%d already unloaded!\n",devi->isahd.id_unit); return; } if (com->tp && (com->tp->t_state & TS_ISOPEN)) { com->gone = 1; printf("sio%d: unload\n", devi->isahd.id_unit); com->tp->t_gen++; ttyclose(com->tp); ttwakeup(com->tp); ttwwakeup(com->tp); } else { com_addr(com->unit) = NULL; bzero(com, sizeof *com); free(com,M_TTYS); printf("sio%d: unload,gone\n", devi->isahd.id_unit); } } /* * card_intr - Shared interrupt called from * front end of PC-Card handler. */ static int card_intr(struct pccard_devinfo *devi) { struct com_s *com; COM_LOCK(); com = com_addr(devi->isahd.id_unit); if (com && !com->gone) siointr1(com_addr(devi->isahd.id_unit)); COM_UNLOCK(); return(1); } #endif /* NCARD > 0 */ static int sioprobe(dev) struct isa_device *dev; { static bool_t already_init; bool_t failures[10]; int fn; struct isa_device *idev; Port_t iobase; intrmask_t irqmap[4]; intrmask_t irqs; u_char mcr_image; int result; struct isa_device *xdev; if (!already_init) { /* * Turn off MCR_IENABLE for all likely serial ports. An unused * port with its MCR_IENABLE gate open will inhibit interrupts * from any used port that shares the interrupt vector. * XXX the gate enable is elsewhere for some multiports. */ for (xdev = isa_devtab_tty; xdev->id_driver != NULL; xdev++) if (xdev->id_driver == &siodriver && xdev->id_enabled) outb(xdev->id_iobase + com_mcr, 0); already_init = TRUE; } if (COM_LLCONSOLE(dev)) { printf("sio%d: reserved for low-level i/o\n", dev->id_unit); return (0); } /* * If the device is on a multiport card and has an AST/4 * compatible interrupt control register, initialize this * register and prepare to leave MCR_IENABLE clear in the mcr. * Otherwise, prepare to set MCR_IENABLE in the mcr. * Point idev to the device struct giving the correct id_irq. * This is the struct for the master device if there is one. */ idev = dev; mcr_image = MCR_IENABLE; #ifdef COM_MULTIPORT if (COM_ISMULTIPORT(dev)) { idev = find_isadev(isa_devtab_tty, &siodriver, COM_MPMASTER(dev)); if (idev == NULL) { printf("sio%d: master device %d not configured\n", dev->id_unit, COM_MPMASTER(dev)); dev->id_irq = 0; idev = dev; } if (!COM_NOTAST4(dev)) { outb(idev->id_iobase + com_scr, idev->id_irq ? 0x80 : 0); mcr_image = 0; } } #endif /* COM_MULTIPORT */ if (idev->id_irq == 0) mcr_image = 0; bzero(failures, sizeof failures); iobase = dev->id_iobase; /* * We don't want to get actual interrupts, just masked ones. * Interrupts from this line should already be masked in the ICU, * but mask them in the processor as well in case there are some * (misconfigured) shared interrupts. */ disable_intr(); /* EXTRA DELAY? */ /* * Initialize the speed and the word size and wait long enough to * drain the maximum of 16 bytes of junk in device output queues. * The speed is undefined after a master reset and must be set * before relying on anything related to output. There may be * junk after a (very fast) soft reboot and (apparently) after * master reset. * XXX what about the UART bug avoided by waiting in comparam()? * We don't want to to wait long enough to drain at 2 bps. */ if (iobase == siocniobase) DELAY((16 + 1) * 1000000 / (comdefaultrate / 10)); else { outb(iobase + com_cfcr, CFCR_DLAB | CFCR_8BITS); outb(iobase + com_dlbl, COMBRD(SIO_TEST_SPEED) & 0xff); outb(iobase + com_dlbh, (u_int) COMBRD(SIO_TEST_SPEED) >> 8); outb(iobase + com_cfcr, CFCR_8BITS); DELAY((16 + 1) * 1000000 / (SIO_TEST_SPEED / 10)); } /* * Enable the interrupt gate and disable device interupts. This * should leave the device driving the interrupt line low and * guarantee an edge trigger if an interrupt can be generated. */ /* EXTRA DELAY? */ outb(iobase + com_mcr, mcr_image); outb(iobase + com_ier, 0); DELAY(1000); /* XXX */ irqmap[0] = isa_irq_pending(); /* * Attempt to set loopback mode so that we can send a null byte * without annoying any external device. */ /* EXTRA DELAY? */ outb(iobase + com_mcr, mcr_image | MCR_LOOPBACK); /* * Attempt to generate an output interrupt. On 8250's, setting * IER_ETXRDY generates an interrupt independent of the current * setting and independent of whether the THR is empty. On 16450's, * setting IER_ETXRDY generates an interrupt independent of the * current setting. On 16550A's, setting IER_ETXRDY only * generates an interrupt when IER_ETXRDY is not already set. */ outb(iobase + com_ier, IER_ETXRDY); /* * On some 16x50 incompatibles, setting IER_ETXRDY doesn't generate * an interrupt. They'd better generate one for actually doing * output. Loopback may be broken on the same incompatibles but * it's unlikely to do more than allow the null byte out. */ outb(iobase + com_data, 0); DELAY((1 + 2) * 1000000 / (SIO_TEST_SPEED / 10)); /* * Turn off loopback mode so that the interrupt gate works again * (MCR_IENABLE was hidden). This should leave the device driving * an interrupt line high. It doesn't matter if the interrupt * line oscillates while we are not looking at it, since interrupts * are disabled. */ /* EXTRA DELAY? */ outb(iobase + com_mcr, mcr_image); /* * It's a definitly Serial PCMCIA(16550A), but still be required * for IIR_TXRDY implementation ( Palido 321s, DC-1S... ) */ if ( COM_NOPROBE(dev) ) { /* Reading IIR register twice */ for ( fn = 0; fn < 2; fn ++ ) { DELAY(10000); failures[6] = inb(iobase + com_iir); } /* Check IIR_TXRDY clear ? */ result = IO_COMSIZE; if ( failures[6] & IIR_TXRDY ) { /* Nop, Double check with clearing IER */ outb(iobase + com_ier, 0); if ( inb(iobase + com_iir) & IIR_NOPEND ) { /* Ok. we're familia this gang */ dev->id_flags |= COM_C_IIR_TXRDYBUG; /* Set IIR_TXRDYBUG */ } else { /* Unknow, Just omit this chip.. XXX*/ result = 0; } } else { /* OK. this is well-known guys */ dev->id_flags &= ~COM_C_IIR_TXRDYBUG; /*Clear IIR_TXRDYBUG*/ } outb(iobase + com_cfcr, CFCR_8BITS); enable_intr(); return (iobase == siocniobase ? IO_COMSIZE : result); } /* * Check that * o the CFCR, IER and MCR in UART hold the values written to them * (the values happen to be all distinct - this is good for * avoiding false positive tests from bus echoes). * o an output interrupt is generated and its vector is correct. * o the interrupt goes away when the IIR in the UART is read. */ /* EXTRA DELAY? */ failures[0] = inb(iobase + com_cfcr) - CFCR_8BITS; failures[1] = inb(iobase + com_ier) - IER_ETXRDY; failures[2] = inb(iobase + com_mcr) - mcr_image; DELAY(10000); /* Some internal modems need this time */ irqmap[1] = isa_irq_pending(); failures[4] = (inb(iobase + com_iir) & IIR_IMASK) - IIR_TXRDY; DELAY(1000); /* XXX */ irqmap[2] = isa_irq_pending(); failures[6] = (inb(iobase + com_iir) & IIR_IMASK) - IIR_NOPEND; /* * Turn off all device interrupts and check that they go off properly. * Leave MCR_IENABLE alone. For ports without a master port, it gates * the OUT2 output of the UART to * the ICU input. Closing the gate would give a floating ICU input * (unless there is another device driving at) and spurious interrupts. * (On the system that this was first tested on, the input floats high * and gives a (masked) interrupt as soon as the gate is closed.) */ outb(iobase + com_ier, 0); outb(iobase + com_cfcr, CFCR_8BITS); /* dummy to avoid bus echo */ failures[7] = inb(iobase + com_ier); DELAY(1000); /* XXX */ irqmap[3] = isa_irq_pending(); failures[9] = (inb(iobase + com_iir) & IIR_IMASK) - IIR_NOPEND; enable_intr(); irqs = irqmap[1] & ~irqmap[0]; if (idev->id_irq != 0 && (idev->id_irq & irqs) == 0) printf( "sio%d: configured irq %d not in bitmap of probed irqs %#x\n", dev->id_unit, ffs(idev->id_irq) - 1, irqs); if (bootverbose) printf("sio%d: irq maps: %#x %#x %#x %#x\n", dev->id_unit, irqmap[0], irqmap[1], irqmap[2], irqmap[3]); result = IO_COMSIZE; for (fn = 0; fn < sizeof failures; ++fn) if (failures[fn]) { outb(iobase + com_mcr, 0); result = 0; if (bootverbose) { printf("sio%d: probe failed test(s):", dev->id_unit); for (fn = 0; fn < sizeof failures; ++fn) if (failures[fn]) printf(" %d", fn); printf("\n"); } break; } return (iobase == siocniobase ? IO_COMSIZE : result); } #ifdef COM_ESP static int espattach(isdp, com, esp_port) struct isa_device *isdp; struct com_s *com; Port_t esp_port; { u_char dips; u_char val; /* * Check the ESP-specific I/O port to see if we're an ESP * card. If not, return failure immediately. */ if ((inb(esp_port) & 0xf3) == 0) { printf(" port 0x%x is not an ESP board?\n", esp_port); return (0); } /* * We've got something that claims to be a Hayes ESP card. * Let's hope so. */ /* Get the dip-switch configuration */ outb(esp_port + ESP_CMD1, ESP_GETDIPS); dips = inb(esp_port + ESP_STATUS1); /* * Bits 0,1 of dips say which COM port we are. */ if (com->iobase == likely_com_ports[dips & 0x03]) printf(" : ESP"); else { printf(" esp_port has com %d\n", dips & 0x03); return (0); } /* * Check for ESP version 2.0 or later: bits 4,5,6 = 010. */ outb(esp_port + ESP_CMD1, ESP_GETTEST); val = inb(esp_port + ESP_STATUS1); /* clear reg 1 */ val = inb(esp_port + ESP_STATUS2); if ((val & 0x70) < 0x20) { printf("-old (%o)", val & 0x70); return (0); } /* * Check for ability to emulate 16550: bit 7 == 1 */ if ((dips & 0x80) == 0) { printf(" slave"); return (0); } /* * Okay, we seem to be a Hayes ESP card. Whee. */ com->esp = TRUE; com->esp_port = esp_port; return (1); } #endif /* COM_ESP */ static int sioattach(isdp) struct isa_device *isdp; { struct com_s *com; dev_t dev; #ifdef COM_ESP Port_t *espp; #endif Port_t iobase; int s; int unit; isdp->id_ri_flags |= RI_FAST; iobase = isdp->id_iobase; unit = isdp->id_unit; com = malloc(sizeof *com, M_TTYS, M_NOWAIT); if (com == NULL) return (0); /* * sioprobe() has initialized the device registers as follows: * o cfcr = CFCR_8BITS. * It is most important that CFCR_DLAB is off, so that the * data port is not hidden when we enable interrupts. * o ier = 0. * Interrupts are only enabled when the line is open. * o mcr = MCR_IENABLE, or 0 if the port has AST/4 compatible * interrupt control register or the config specifies no irq. * Keeping MCR_DTR and MCR_RTS off might stop the external * device from sending before we are ready. */ bzero(com, sizeof *com); com->unit = unit; com->cfcr_image = CFCR_8BITS; com->dtr_wait = 3 * hz; com->loses_outints = COM_LOSESOUTINTS(isdp) != 0; com->no_irq = isdp->id_irq == 0; com->tx_fifo_size = 1; com->iptr = com->ibuf = com->ibuf1; com->ibufend = com->ibuf1 + RS_IBUFSIZE; com->ihighwater = com->ibuf1 + RS_IHIGHWATER; com->obufs[0].l_head = com->obuf1; com->obufs[1].l_head = com->obuf2; com->iobase = iobase; com->data_port = iobase + com_data; com->int_id_port = iobase + com_iir; com->modem_ctl_port = iobase + com_mcr; com->mcr_image = inb(com->modem_ctl_port); com->line_status_port = iobase + com_lsr; com->modem_status_port = iobase + com_msr; com->intr_ctl_port = iobase + com_ier; /* * We don't use all the flags from since they * are only relevant for logins. It's important to have echo off * initially so that the line doesn't start blathering before the * echo flag can be turned off. */ com->it_in.c_iflag = 0; com->it_in.c_oflag = 0; com->it_in.c_cflag = TTYDEF_CFLAG; com->it_in.c_lflag = 0; if (unit == comconsole) { com->it_in.c_iflag = TTYDEF_IFLAG; com->it_in.c_oflag = TTYDEF_OFLAG; com->it_in.c_cflag = TTYDEF_CFLAG | CLOCAL; com->it_in.c_lflag = TTYDEF_LFLAG; com->lt_out.c_cflag = com->lt_in.c_cflag = CLOCAL; com->lt_out.c_ispeed = com->lt_out.c_ospeed = com->lt_in.c_ispeed = com->lt_in.c_ospeed = com->it_in.c_ispeed = com->it_in.c_ospeed = comdefaultrate; } else com->it_in.c_ispeed = com->it_in.c_ospeed = TTYDEF_SPEED; termioschars(&com->it_in); com->it_out = com->it_in; /* attempt to determine UART type */ printf("sio%d: type", unit); #ifdef DSI_SOFT_MODEM if((inb(iobase+7) ^ inb(iobase+7)) & 0x80) { printf(" Digicom Systems, Inc. SoftModem"); goto determined_type; } #endif /* DSI_SOFT_MODEM */ #ifdef COM_MULTIPORT if (!COM_ISMULTIPORT(isdp) && !COM_IIR_TXRDYBUG(isdp)) #else if (!COM_IIR_TXRDYBUG(isdp)) #endif { u_char scr; u_char scr1; u_char scr2; scr = inb(iobase + com_scr); outb(iobase + com_scr, 0xa5); scr1 = inb(iobase + com_scr); outb(iobase + com_scr, 0x5a); scr2 = inb(iobase + com_scr); outb(iobase + com_scr, scr); if (scr1 != 0xa5 || scr2 != 0x5a) { printf(" 8250"); goto determined_type; } } outb(iobase + com_fifo, FIFO_ENABLE | FIFO_RX_HIGH); DELAY(100); com->st16650a = 0; switch (inb(com->int_id_port) & IIR_FIFO_MASK) { case FIFO_RX_LOW: printf(" 16450"); break; case FIFO_RX_MEDL: printf(" 16450?"); break; case FIFO_RX_MEDH: printf(" 16550?"); break; case FIFO_RX_HIGH: if (COM_NOFIFO(isdp)) { printf(" 16550A fifo disabled"); } else { com->hasfifo = TRUE; if (COM_ST16650A(isdp)) { com->st16650a = 1; com->tx_fifo_size = 32; printf(" ST16650A"); } else { com->tx_fifo_size = COM_FIFOSIZE(isdp); printf(" 16550A"); } } #ifdef COM_ESP for (espp = likely_esp_ports; *espp != 0; espp++) if (espattach(isdp, com, *espp)) { com->tx_fifo_size = 1024; break; } #endif if (!com->st16650a) { if (!com->tx_fifo_size) com->tx_fifo_size = 16; else printf(" lookalike with %d bytes FIFO", com->tx_fifo_size); } break; } #ifdef COM_ESP if (com->esp) { /* * Set 16550 compatibility mode. * We don't use the ESP_MODE_SCALE bit to increase the * fifo trigger levels because we can't handle large * bursts of input. * XXX flow control should be set in comparam(), not here. */ outb(com->esp_port + ESP_CMD1, ESP_SETMODE); outb(com->esp_port + ESP_CMD2, ESP_MODE_RTS | ESP_MODE_FIFO); /* Set RTS/CTS flow control. */ outb(com->esp_port + ESP_CMD1, ESP_SETFLOWTYPE); outb(com->esp_port + ESP_CMD2, ESP_FLOW_RTS); outb(com->esp_port + ESP_CMD2, ESP_FLOW_CTS); /* Set flow-control levels. */ outb(com->esp_port + ESP_CMD1, ESP_SETRXFLOW); outb(com->esp_port + ESP_CMD2, HIBYTE(768)); outb(com->esp_port + ESP_CMD2, LOBYTE(768)); outb(com->esp_port + ESP_CMD2, HIBYTE(512)); outb(com->esp_port + ESP_CMD2, LOBYTE(512)); } #endif /* COM_ESP */ outb(iobase + com_fifo, 0); determined_type: ; #ifdef COM_MULTIPORT if (COM_ISMULTIPORT(isdp)) { com->multiport = TRUE; printf(" (multiport"); if (unit == COM_MPMASTER(isdp)) printf(" master"); printf(")"); com->no_irq = find_isadev(isa_devtab_tty, &siodriver, COM_MPMASTER(isdp))->id_irq == 0; } #endif /* COM_MULTIPORT */ if (unit == comconsole) printf(", console"); if ( COM_IIR_TXRDYBUG(isdp) ) printf(" with a bogus IIR_TXRDY register"); printf("\n"); s = spltty(); com_addr(unit) = com; splx(s); dev = makedev(CDEV_MAJOR, 0); cdevsw_add(&dev, &sio_cdevsw, NULL); #ifdef DEVFS com->devfs_token_ttyd = devfs_add_devswf(&sio_cdevsw, unit, DV_CHR, UID_ROOT, GID_WHEEL, 0600, "ttyd%r", unit); com->devfs_token_ttyi = devfs_add_devswf(&sio_cdevsw, unit | CONTROL_INIT_STATE, DV_CHR, UID_ROOT, GID_WHEEL, 0600, "ttyid%r", unit); com->devfs_token_ttyl = devfs_add_devswf(&sio_cdevsw, unit | CONTROL_LOCK_STATE, DV_CHR, UID_ROOT, GID_WHEEL, 0600, "ttyld%r", unit); com->devfs_token_cuaa = devfs_add_devswf(&sio_cdevsw, unit | CALLOUT_MASK, DV_CHR, UID_UUCP, GID_DIALER, 0660, "cuaa%r", unit); com->devfs_token_cuai = devfs_add_devswf(&sio_cdevsw, unit | CALLOUT_MASK | CONTROL_INIT_STATE, DV_CHR, UID_UUCP, GID_DIALER, 0660, "cuaia%r", unit); com->devfs_token_cual = devfs_add_devswf(&sio_cdevsw, unit | CALLOUT_MASK | CONTROL_LOCK_STATE, DV_CHR, UID_UUCP, GID_DIALER, 0660, "cuala%r", unit); #endif + if (!siopoll_registered) { + register_swi(SWI_TTY, siopoll); + siopoll_registered = TRUE; + } com->id_flags = isdp->id_flags; /* Heritate id_flags for later */ return (1); } static int sioopen(dev, flag, mode, p) dev_t dev; int flag; int mode; struct proc *p; { struct com_s *com; int error; Port_t iobase; int mynor; int s; struct tty *tp; int unit; mynor = minor(dev); unit = MINOR_TO_UNIT(mynor); if ((u_int) unit >= NSIOTOT || (com = com_addr(unit)) == NULL) return (ENXIO); if (com->gone) return (ENXIO); if (mynor & CONTROL_MASK) return (0); #if 0 /* XXX */ tp = com->tp = sio_tty[unit] = ttymalloc(sio_tty[unit]); #else tp = com->tp = &sio_tty[unit]; #endif s = spltty(); /* * We jump to this label after all non-interrupted sleeps to pick * up any changes of the device state. */ open_top: while (com->state & CS_DTR_OFF) { error = tsleep(&com->dtr_wait, TTIPRI | PCATCH, "siodtr", 0); if (com_addr(unit) == NULL) return (ENXIO); if (error != 0 || com->gone) goto out; } if (tp->t_state & TS_ISOPEN) { /* * The device is open, so everything has been initialized. * Handle conflicts. */ if (mynor & CALLOUT_MASK) { if (!com->active_out) { error = EBUSY; goto out; } } else { if (com->active_out) { if (flag & O_NONBLOCK) { error = EBUSY; goto out; } error = tsleep(&com->active_out, TTIPRI | PCATCH, "siobi", 0); if (com_addr(unit) == NULL) return (ENXIO); if (error != 0 || com->gone) goto out; goto open_top; } } if (tp->t_state & TS_XCLUDE && p->p_ucred->cr_uid != 0) { error = EBUSY; goto out; } } else { /* * The device isn't open, so there are no conflicts. * Initialize it. Initialization is done twice in many * cases: to preempt sleeping callin opens if we are * callout, and to complete a callin open after DCD rises. */ tp->t_oproc = comstart; tp->t_param = comparam; tp->t_dev = dev; tp->t_termios = mynor & CALLOUT_MASK ? com->it_out : com->it_in; (void)commctl(com, TIOCM_DTR | TIOCM_RTS, DMSET); com->poll = com->no_irq; com->poll_output = com->loses_outints; ++com->wopeners; error = comparam(tp, &tp->t_termios); --com->wopeners; if (error != 0) goto out; /* * XXX we should goto open_top if comparam() slept. */ ttsetwater(tp); iobase = com->iobase; if (com->hasfifo) { /* * (Re)enable and drain fifos. * * Certain SMC chips cause problems if the fifos * are enabled while input is ready. Turn off the * fifo if necessary to clear the input. We test * the input ready bit after enabling the fifos * since we've already enabled them in comparam() * and to handle races between enabling and fresh * input. */ while (TRUE) { outb(iobase + com_fifo, FIFO_RCV_RST | FIFO_XMT_RST | com->fifo_image); /* * XXX the delays are for superstitious * historical reasons. It must be less than * the character time at the maximum * supported speed (87 usec at 115200 bps * 8N1). Otherwise we might loop endlessly * if data is streaming in. We used to use * delays of 100. That usually worked * because DELAY(100) used to usually delay * for about 85 usec instead of 100. */ DELAY(50); if (!(inb(com->line_status_port) & LSR_RXRDY)) break; outb(iobase + com_fifo, 0); DELAY(50); (void) inb(com->data_port); } } disable_intr(); (void) inb(com->line_status_port); (void) inb(com->data_port); com->prev_modem_status = com->last_modem_status = inb(com->modem_status_port); if (COM_IIR_TXRDYBUG(com)) { outb(com->intr_ctl_port, IER_ERXRDY | IER_ERLS | IER_EMSC); } else { outb(com->intr_ctl_port, IER_ERXRDY | IER_ETXRDY | IER_ERLS | IER_EMSC); } enable_intr(); /* * Handle initial DCD. Callout devices get a fake initial * DCD (trapdoor DCD). If we are callout, then any sleeping * callin opens get woken up and resume sleeping on "siobi" * instead of "siodcd". */ /* * XXX `mynor & CALLOUT_MASK' should be * `tp->t_cflag & (SOFT_CARRIER | TRAPDOOR_CARRIER) where * TRAPDOOR_CARRIER is the default initial state for callout * devices and SOFT_CARRIER is like CLOCAL except it hides * the true carrier. */ if (com->prev_modem_status & MSR_DCD || mynor & CALLOUT_MASK) (*linesw[tp->t_line].l_modem)(tp, 1); } /* * Wait for DCD if necessary. */ if (!(tp->t_state & TS_CARR_ON) && !(mynor & CALLOUT_MASK) && !(tp->t_cflag & CLOCAL) && !(flag & O_NONBLOCK)) { ++com->wopeners; error = tsleep(TSA_CARR_ON(tp), TTIPRI | PCATCH, "siodcd", 0); if (com_addr(unit) == NULL) return (ENXIO); --com->wopeners; if (error != 0 || com->gone) goto out; goto open_top; } error = (*linesw[tp->t_line].l_open)(dev, tp); disc_optim(tp, &tp->t_termios, com); if (tp->t_state & TS_ISOPEN && mynor & CALLOUT_MASK) com->active_out = TRUE; siosettimeout(); out: splx(s); if (!(tp->t_state & TS_ISOPEN) && com->wopeners == 0) comhardclose(com); return (error); } static int sioclose(dev, flag, mode, p) dev_t dev; int flag; int mode; struct proc *p; { struct com_s *com; int mynor; int s; struct tty *tp; mynor = minor(dev); if (mynor & CONTROL_MASK) return (0); com = com_addr(MINOR_TO_UNIT(mynor)); tp = com->tp; s = spltty(); (*linesw[tp->t_line].l_close)(tp, flag); disc_optim(tp, &tp->t_termios, com); siostop(tp, FREAD | FWRITE); comhardclose(com); ttyclose(tp); siosettimeout(); splx(s); if (com->gone) { printf("sio%d: gone\n", com->unit); s = spltty(); com_addr(com->unit) = 0; bzero(tp,sizeof *tp); bzero(com,sizeof *com); free(com,M_TTYS); splx(s); } return (0); } static void comhardclose(com) struct com_s *com; { Port_t iobase; int s; struct tty *tp; int unit; unit = com->unit; iobase = com->iobase; s = spltty(); com->poll = FALSE; com->poll_output = FALSE; com->do_timestamp = FALSE; com->do_dcd_timestamp = FALSE; outb(iobase + com_cfcr, com->cfcr_image &= ~CFCR_SBREAK); { outb(iobase + com_ier, 0); tp = com->tp; if (tp->t_cflag & HUPCL /* * XXX we will miss any carrier drop between here and the * next open. Perhaps we should watch DCD even when the * port is closed; it is not sufficient to check it at * the next open because it might go up and down while * we're not watching. */ || !com->active_out && !(com->prev_modem_status & MSR_DCD) && !(com->it_in.c_cflag & CLOCAL) || !(tp->t_state & TS_ISOPEN)) { (void)commctl(com, TIOCM_DTR, DMBIC); if (com->dtr_wait != 0 && !(com->state & CS_DTR_OFF)) { timeout(siodtrwakeup, com, com->dtr_wait); com->state |= CS_DTR_OFF; } } } if (com->hasfifo) { /* * Disable fifos so that they are off after controlled * reboots. Some BIOSes fail to detect 16550s when the * fifos are enabled. */ outb(iobase + com_fifo, 0); } com->active_out = FALSE; wakeup(&com->active_out); wakeup(TSA_CARR_ON(tp)); /* restart any wopeners */ splx(s); } static int sioread(dev, uio, flag) dev_t dev; struct uio *uio; int flag; { int mynor; int unit; struct tty *tp; mynor = minor(dev); if (mynor & CONTROL_MASK) return (ENODEV); unit = MINOR_TO_UNIT(mynor); if (com_addr(unit)->gone) return (ENODEV); tp = com_addr(unit)->tp; return ((*linesw[tp->t_line].l_read)(tp, uio, flag)); } static int siowrite(dev, uio, flag) dev_t dev; struct uio *uio; int flag; { int mynor; struct tty *tp; int unit; mynor = minor(dev); if (mynor & CONTROL_MASK) return (ENODEV); unit = MINOR_TO_UNIT(mynor); if (com_addr(unit)->gone) return (ENODEV); tp = com_addr(unit)->tp; /* * (XXX) We disallow virtual consoles if the physical console is * a serial port. This is in case there is a display attached that * is not the console. In that situation we don't need/want the X * server taking over the console. */ if (constty != NULL && unit == comconsole) constty = NULL; return ((*linesw[tp->t_line].l_write)(tp, uio, flag)); } static void siobusycheck(chan) void *chan; { struct com_s *com; int s; com = (struct com_s *)chan; /* * Clear TS_BUSY if low-level output is complete. * spl locking is sufficient because siointr1() does not set CS_BUSY. * If siointr1() clears CS_BUSY after we look at it, then we'll get * called again. Reading the line status port outside of siointr1() * is safe because CS_BUSY is clear so there are no output interrupts * to lose. */ s = spltty(); if (com->state & CS_BUSY) com->extra_state &= ~CSE_BUSYCHECK; /* False alarm. */ else if ((inb(com->line_status_port) & (LSR_TSRE | LSR_TXRDY)) == (LSR_TSRE | LSR_TXRDY)) { com->tp->t_state &= ~TS_BUSY; ttwwakeup(com->tp); com->extra_state &= ~CSE_BUSYCHECK; } else timeout(siobusycheck, com, hz / 100); splx(s); } static void siodtrwakeup(chan) void *chan; { struct com_s *com; com = (struct com_s *)chan; com->state &= ~CS_DTR_OFF; wakeup(&com->dtr_wait); } void siointr(unit) int unit; { #ifndef COM_MULTIPORT COM_LOCK(); siointr1(com_addr(unit)); COM_UNLOCK(); #else /* COM_MULTIPORT */ struct com_s *com; bool_t possibly_more_intrs; /* * Loop until there is no activity on any port. This is necessary * to get an interrupt edge more than to avoid another interrupt. * If the IRQ signal is just an OR of the IRQ signals from several * devices, then the edge from one may be lost because another is * on. */ COM_LOCK(); do { possibly_more_intrs = FALSE; for (unit = 0; unit < NSIOTOT; ++unit) { com = com_addr(unit); /* * XXX COM_LOCK(); * would it work here, or be counter-productive? */ if (com != NULL && !com->gone && (inb(com->int_id_port) & IIR_IMASK) != IIR_NOPEND) { siointr1(com); possibly_more_intrs = TRUE; } /* XXX COM_UNLOCK(); */ } } while (possibly_more_intrs); COM_UNLOCK(); #endif /* COM_MULTIPORT */ } static void siointr1(com) struct com_s *com; { u_char line_status; u_char modem_status; u_char *ioptr; u_char recv_data; u_char int_ident; u_char int_ctl; u_char int_ctl_new; int_ctl = inb(com->intr_ctl_port); int_ctl_new = int_ctl; while (!com->gone) { line_status = inb(com->line_status_port); /* input event? (check first to help avoid overruns) */ while (line_status & LSR_RCV_MASK) { /* break/unnattached error bits or real input? */ if (!(line_status & LSR_RXRDY)) recv_data = 0; else recv_data = inb(com->data_port); if (line_status & (LSR_BI | LSR_FE | LSR_PE)) { /* * Don't store BI if IGNBRK or FE/PE if IGNPAR. * Otherwise, push the work to a higher level * (to handle PARMRK) if we're bypassing. * Otherwise, convert BI/FE and PE+INPCK to 0. * * This makes bypassing work right in the * usual "raw" case (IGNBRK set, and IGNPAR * and INPCK clear). * * Note: BI together with FE/PE means just BI. */ if (line_status & LSR_BI) { #if defined(DDB) && defined(BREAK_TO_DEBUGGER) if (com->unit == comconsole) { breakpoint(); goto cont; } #endif if (com->tp == NULL || com->tp->t_iflag & IGNBRK) goto cont; } else { if (com->tp == NULL || com->tp->t_iflag & IGNPAR) goto cont; } if (com->tp->t_state & TS_CAN_BYPASS_L_RINT && (line_status & (LSR_BI | LSR_FE) || com->tp->t_iflag & INPCK)) recv_data = 0; } ++com->bytes_in; if (com->hotchar != 0 && recv_data == com->hotchar) setsofttty(); ioptr = com->iptr; if (ioptr >= com->ibufend) CE_RECORD(com, CE_INTERRUPT_BUF_OVERFLOW); else { if (com->do_timestamp) microtime(&com->timestamp); ++com_events; schedsofttty(); #if 0 /* for testing input latency vs efficiency */ if (com->iptr - com->ibuf == 8) setsofttty(); #endif ioptr[0] = recv_data; ioptr[CE_INPUT_OFFSET] = line_status; com->iptr = ++ioptr; if (ioptr == com->ihighwater && com->state & CS_RTS_IFLOW) outb(com->modem_ctl_port, com->mcr_image &= ~MCR_RTS); if (line_status & LSR_OE) CE_RECORD(com, CE_OVERRUN); } cont: /* * "& 0x7F" is to avoid the gcc-1.40 generating a slow * jump from the top of the loop to here */ line_status = inb(com->line_status_port) & 0x7F; } /* modem status change? (always check before doing output) */ modem_status = inb(com->modem_status_port); if (modem_status != com->last_modem_status) { if (com->do_dcd_timestamp && !(com->last_modem_status & MSR_DCD) && modem_status & MSR_DCD) microtime(&com->dcd_timestamp); /* * Schedule high level to handle DCD changes. Note * that we don't use the delta bits anywhere. Some * UARTs mess them up, and it's easy to remember the * previous bits and calculate the delta. */ com->last_modem_status = modem_status; if (!(com->state & CS_CHECKMSR)) { com_events += LOTS_OF_EVENTS; com->state |= CS_CHECKMSR; setsofttty(); } /* handle CTS change immediately for crisp flow ctl */ if (com->state & CS_CTS_OFLOW) { if (modem_status & MSR_CTS) com->state |= CS_ODEVREADY; else com->state &= ~CS_ODEVREADY; } } /* output queued and everything ready? */ if (line_status & LSR_TXRDY && com->state >= (CS_BUSY | CS_TTGO | CS_ODEVREADY)) { ioptr = com->obufq.l_head; if (com->tx_fifo_size > 1) { u_int ocount; ocount = com->obufq.l_tail - ioptr; if (ocount > com->tx_fifo_size) ocount = com->tx_fifo_size; com->bytes_out += ocount; do outb(com->data_port, *ioptr++); while (--ocount != 0); } else { outb(com->data_port, *ioptr++); ++com->bytes_out; } com->obufq.l_head = ioptr; if (COM_IIR_TXRDYBUG(com)) { int_ctl_new = int_ctl | IER_ETXRDY; } if (ioptr >= com->obufq.l_tail) { struct lbq *qp; qp = com->obufq.l_next; qp->l_queued = FALSE; qp = qp->l_next; if (qp != NULL) { com->obufq.l_head = qp->l_head; com->obufq.l_tail = qp->l_tail; com->obufq.l_next = qp; } else { /* output just completed */ if ( COM_IIR_TXRDYBUG(com) ) { int_ctl_new = int_ctl & ~IER_ETXRDY; } com->state &= ~CS_BUSY; } if (!(com->state & CS_ODONE)) { com_events += LOTS_OF_EVENTS; com->state |= CS_ODONE; setsofttty(); /* handle at high level ASAP */ } } if ( COM_IIR_TXRDYBUG(com) && (int_ctl != int_ctl_new)) { outb(com->intr_ctl_port, int_ctl_new); } } /* finished? */ #ifndef COM_MULTIPORT if ((inb(com->int_id_port) & IIR_IMASK) == IIR_NOPEND) #endif /* COM_MULTIPORT */ return; } } static int sioioctl(dev, cmd, data, flag, p) dev_t dev; u_long cmd; caddr_t data; int flag; struct proc *p; { struct com_s *com; int error; Port_t iobase; int mynor; int s; struct tty *tp; #if defined(COMPAT_43) || defined(COMPAT_SUNOS) int oldcmd; struct termios term; #endif mynor = minor(dev); com = com_addr(MINOR_TO_UNIT(mynor)); if (com->gone) return (ENODEV); iobase = com->iobase; if (mynor & CONTROL_MASK) { struct termios *ct; switch (mynor & CONTROL_MASK) { case CONTROL_INIT_STATE: ct = mynor & CALLOUT_MASK ? &com->it_out : &com->it_in; break; case CONTROL_LOCK_STATE: ct = mynor & CALLOUT_MASK ? &com->lt_out : &com->lt_in; break; default: return (ENODEV); /* /dev/nodev */ } switch (cmd) { case TIOCSETA: error = suser(p->p_ucred, &p->p_acflag); if (error != 0) return (error); *ct = *(struct termios *)data; return (0); case TIOCGETA: *(struct termios *)data = *ct; return (0); case TIOCGETD: *(int *)data = TTYDISC; return (0); case TIOCGWINSZ: bzero(data, sizeof(struct winsize)); return (0); #ifdef DSI_SOFT_MODEM /* * Download micro-code to Digicom modem. */ case TIOCDSIMICROCODE: { u_long l; u_char *p,*pi; pi = (u_char*)(*(caddr_t*)data); error = copyin(pi,&l,sizeof l); if(error) {return error;}; pi += sizeof l; p = malloc(l,M_TEMP,M_NOWAIT); if(!p) {return ENOBUFS;} error = copyin(pi,p,l); if(error) {free(p,M_TEMP); return error;}; if(error = LoadSoftModem( MINOR_TO_UNIT(mynor),iobase,l,p)) {free(p,M_TEMP); return error;} free(p,M_TEMP); return(0); } #endif /* DSI_SOFT_MODEM */ default: return (ENOTTY); } } tp = com->tp; #if defined(COMPAT_43) || defined(COMPAT_SUNOS) term = tp->t_termios; oldcmd = cmd; error = ttsetcompat(tp, &cmd, data, &term); if (error != 0) return (error); if (cmd != oldcmd) data = (caddr_t)&term; #endif if (cmd == TIOCSETA || cmd == TIOCSETAW || cmd == TIOCSETAF) { int cc; struct termios *dt = (struct termios *)data; struct termios *lt = mynor & CALLOUT_MASK ? &com->lt_out : &com->lt_in; dt->c_iflag = (tp->t_iflag & lt->c_iflag) | (dt->c_iflag & ~lt->c_iflag); dt->c_oflag = (tp->t_oflag & lt->c_oflag) | (dt->c_oflag & ~lt->c_oflag); dt->c_cflag = (tp->t_cflag & lt->c_cflag) | (dt->c_cflag & ~lt->c_cflag); dt->c_lflag = (tp->t_lflag & lt->c_lflag) | (dt->c_lflag & ~lt->c_lflag); for (cc = 0; cc < NCCS; ++cc) if (lt->c_cc[cc] != 0) dt->c_cc[cc] = tp->t_cc[cc]; if (lt->c_ispeed != 0) dt->c_ispeed = tp->t_ispeed; if (lt->c_ospeed != 0) dt->c_ospeed = tp->t_ospeed; } error = (*linesw[tp->t_line].l_ioctl)(tp, cmd, data, flag, p); if (error != ENOIOCTL) return (error); s = spltty(); error = ttioctl(tp, cmd, data, flag); disc_optim(tp, &tp->t_termios, com); if (error != ENOIOCTL) { splx(s); return (error); } switch (cmd) { case TIOCSBRK: outb(iobase + com_cfcr, com->cfcr_image |= CFCR_SBREAK); break; case TIOCCBRK: outb(iobase + com_cfcr, com->cfcr_image &= ~CFCR_SBREAK); break; case TIOCSDTR: (void)commctl(com, TIOCM_DTR, DMBIS); break; case TIOCCDTR: (void)commctl(com, TIOCM_DTR, DMBIC); break; /* * XXX should disallow changing MCR_RTS if CS_RTS_IFLOW is set. The * changes get undone on the next call to comparam(). */ case TIOCMSET: (void)commctl(com, *(int *)data, DMSET); break; case TIOCMBIS: (void)commctl(com, *(int *)data, DMBIS); break; case TIOCMBIC: (void)commctl(com, *(int *)data, DMBIC); break; case TIOCMGET: *(int *)data = commctl(com, 0, DMGET); break; case TIOCMSDTRWAIT: /* must be root since the wait applies to following logins */ error = suser(p->p_ucred, &p->p_acflag); if (error != 0) { splx(s); return (error); } com->dtr_wait = *(int *)data * hz / 100; break; case TIOCMGDTRWAIT: *(int *)data = com->dtr_wait * 100 / hz; break; case TIOCTIMESTAMP: com->do_timestamp = TRUE; *(struct timeval *)data = com->timestamp; break; case TIOCDCDTIMESTAMP: com->do_dcd_timestamp = TRUE; *(struct timeval *)data = com->dcd_timestamp; break; default: splx(s); return (ENOTTY); } splx(s); return (0); } -void +static void siopoll() { int unit; if (com_events == 0) return; repeat: for (unit = 0; unit < NSIOTOT; ++unit) { u_char *buf; struct com_s *com; u_char *ibuf; int incc; struct tty *tp; com = com_addr(unit); if (com == NULL) continue; tp = com->tp; if (tp == NULL || com->gone) { /* * Discard any events related to never-opened or * going-away devices. */ disable_intr(); incc = com->iptr - com->ibuf; com->iptr = com->ibuf; if (com->state & CS_CHECKMSR) { incc += LOTS_OF_EVENTS; com->state &= ~CS_CHECKMSR; } com_events -= incc; enable_intr(); continue; } /* switch the role of the low-level input buffers */ if (com->iptr == (ibuf = com->ibuf)) { buf = NULL; /* not used, but compiler can't tell */ incc = 0; } else { buf = ibuf; disable_intr(); incc = com->iptr - buf; com_events -= incc; if (ibuf == com->ibuf1) ibuf = com->ibuf2; else ibuf = com->ibuf1; com->ibufend = ibuf + RS_IBUFSIZE; com->ihighwater = ibuf + RS_IHIGHWATER; com->iptr = ibuf; /* * There is now room for another low-level buffer full * of input, so enable RTS if it is now disabled and * there is room in the high-level buffer. */ if ((com->state & CS_RTS_IFLOW) && !(com->mcr_image & MCR_RTS) && !(tp->t_state & TS_TBLOCK)) outb(com->modem_ctl_port, com->mcr_image |= MCR_RTS); enable_intr(); com->ibuf = ibuf; } if (com->state & CS_CHECKMSR) { u_char delta_modem_status; disable_intr(); delta_modem_status = com->last_modem_status ^ com->prev_modem_status; com->prev_modem_status = com->last_modem_status; com_events -= LOTS_OF_EVENTS; com->state &= ~CS_CHECKMSR; enable_intr(); if (delta_modem_status & MSR_DCD) (*linesw[tp->t_line].l_modem) (tp, com->prev_modem_status & MSR_DCD); } if (com->state & CS_ODONE) { disable_intr(); com_events -= LOTS_OF_EVENTS; com->state &= ~CS_ODONE; enable_intr(); if (!(com->state & CS_BUSY) && !(com->extra_state & CSE_BUSYCHECK)) { timeout(siobusycheck, com, hz / 100); com->extra_state |= CSE_BUSYCHECK; } (*linesw[tp->t_line].l_start)(tp); } if (incc <= 0 || !(tp->t_state & TS_ISOPEN) || !(tp->t_cflag & CREAD)) continue; /* * Avoid the grotesquely inefficient lineswitch routine * (ttyinput) in "raw" mode. It usually takes about 450 * instructions (that's without canonical processing or echo!). * slinput is reasonably fast (usually 40 instructions plus * call overhead). */ if (tp->t_state & TS_CAN_BYPASS_L_RINT) { if (tp->t_rawq.c_cc + incc >= RB_I_HIGH_WATER && (com->state & CS_RTS_IFLOW || tp->t_iflag & IXOFF) && !(tp->t_state & TS_TBLOCK)) ttyblock(tp); tk_nin += incc; tk_rawcc += incc; tp->t_rawcc += incc; com->delta_error_counts[CE_TTY_BUF_OVERFLOW] += b_to_q((char *)buf, incc, &tp->t_rawq); ttwakeup(tp); if (tp->t_state & TS_TTSTOP && (tp->t_iflag & IXANY || tp->t_cc[VSTART] == tp->t_cc[VSTOP])) { tp->t_state &= ~TS_TTSTOP; tp->t_lflag &= ~FLUSHO; comstart(tp); } } else { do { u_char line_status; int recv_data; line_status = (u_char) buf[CE_INPUT_OFFSET]; recv_data = (u_char) *buf++; if (line_status & (LSR_BI | LSR_FE | LSR_OE | LSR_PE)) { if (line_status & LSR_BI) recv_data |= TTY_BI; if (line_status & LSR_FE) recv_data |= TTY_FE; if (line_status & LSR_OE) recv_data |= TTY_OE; if (line_status & LSR_PE) recv_data |= TTY_PE; } (*linesw[tp->t_line].l_rint)(recv_data, tp); } while (--incc > 0); } if (com_events == 0) break; } if (com_events >= LOTS_OF_EVENTS) goto repeat; } static int comparam(tp, t) struct tty *tp; struct termios *t; { u_int cfcr; int cflag; struct com_s *com; int divisor; u_char dlbh; u_char dlbl; int error; Port_t iobase; int s; int unit; int txtimeout; /* do historical conversions */ if (t->c_ispeed == 0) t->c_ispeed = t->c_ospeed; /* check requested parameters */ divisor = ttspeedtab(t->c_ospeed, comspeedtab); if (divisor < 0 || divisor > 0 && t->c_ispeed != t->c_ospeed) return (EINVAL); /* parameters are OK, convert them to the com struct and the device */ unit = DEV_TO_UNIT(tp->t_dev); com = com_addr(unit); iobase = com->iobase; s = spltty(); if (divisor == 0) (void)commctl(com, TIOCM_DTR, DMBIC); /* hang up line */ else (void)commctl(com, TIOCM_DTR, DMBIS); cflag = t->c_cflag; switch (cflag & CSIZE) { case CS5: cfcr = CFCR_5BITS; break; case CS6: cfcr = CFCR_6BITS; break; case CS7: cfcr = CFCR_7BITS; break; default: cfcr = CFCR_8BITS; break; } if (cflag & PARENB) { cfcr |= CFCR_PENAB; if (!(cflag & PARODD)) cfcr |= CFCR_PEVEN; } if (cflag & CSTOPB) cfcr |= CFCR_STOPB; if (com->hasfifo && divisor != 0) { /* * Use a fifo trigger level low enough so that the input * latency from the fifo is less than about 16 msec and * the total latency is less than about 30 msec. These * latencies are reasonable for humans. Serial comms * protocols shouldn't expect anything better since modem * latencies are larger. */ com->fifo_image = t->c_ospeed <= 4800 ? FIFO_ENABLE : FIFO_ENABLE | FIFO_RX_HIGH; #ifdef COM_ESP /* * The Hayes ESP card needs the fifo DMA mode bit set * in compatibility mode. If not, it will interrupt * for each character received. */ if (com->esp) com->fifo_image |= FIFO_DMA_MODE; #endif outb(iobase + com_fifo, com->fifo_image); } /* * Some UARTs lock up if the divisor latch registers are selected * while the UART is doing output (they refuse to transmit anything * more until given a hard reset). Fix this by stopping filling * the device buffers and waiting for them to drain. Reading the * line status port outside of siointr1() might lose some receiver * error bits, but that is acceptable here. */ disable_intr(); retry: com->state &= ~CS_TTGO; txtimeout = tp->t_timeout; enable_intr(); while ((inb(com->line_status_port) & (LSR_TSRE | LSR_TXRDY)) != (LSR_TSRE | LSR_TXRDY)) { tp->t_state |= TS_SO_OCOMPLETE; error = ttysleep(tp, TSA_OCOMPLETE(tp), TTIPRI | PCATCH, "siotx", hz / 100); if ( txtimeout != 0 && (!error || error == EAGAIN) && (txtimeout -= hz / 100) <= 0 ) error = EIO; if (com->gone) error = ENODEV; if (error != 0 && error != EAGAIN) { if (!(tp->t_state & TS_TTSTOP)) { disable_intr(); com->state |= CS_TTGO; enable_intr(); } splx(s); return (error); } } disable_intr(); /* very important while com_data is hidden */ /* * XXX - clearing CS_TTGO is not sufficient to stop further output, * because siopoll() calls comstart() which usually sets it again * because TS_TTSTOP is clear. Setting TS_TTSTOP would not be * sufficient, for similar reasons. */ if ((inb(com->line_status_port) & (LSR_TSRE | LSR_TXRDY)) != (LSR_TSRE | LSR_TXRDY)) goto retry; if (divisor != 0) { outb(iobase + com_cfcr, cfcr | CFCR_DLAB); /* * Only set the divisor registers if they would change, * since on some 16550 incompatibles (UMC8669F), setting * them while input is arriving them loses sync until * data stops arriving. */ dlbl = divisor & 0xFF; if (inb(iobase + com_dlbl) != dlbl) outb(iobase + com_dlbl, dlbl); dlbh = (u_int) divisor >> 8; if (inb(iobase + com_dlbh) != dlbh) outb(iobase + com_dlbh, dlbh); } outb(iobase + com_cfcr, com->cfcr_image = cfcr); if (!(tp->t_state & TS_TTSTOP)) com->state |= CS_TTGO; if (cflag & CRTS_IFLOW) { if (com->st16650a) { outb(iobase + com_cfcr, 0xbf); outb(iobase + com_fifo, inb(iobase + com_fifo) | 0x40); } com->state |= CS_RTS_IFLOW; /* * If CS_RTS_IFLOW just changed from off to on, the change * needs to be propagated to MCR_RTS. This isn't urgent, * so do it later by calling comstart() instead of repeating * a lot of code from comstart() here. */ } else if (com->state & CS_RTS_IFLOW) { com->state &= ~CS_RTS_IFLOW; /* * CS_RTS_IFLOW just changed from on to off. Force MCR_RTS * on here, since comstart() won't do it later. */ outb(com->modem_ctl_port, com->mcr_image |= MCR_RTS); if (com->st16650a) { outb(iobase + com_cfcr, 0xbf); outb(iobase + com_fifo, inb(iobase + com_fifo) & ~0x40); } } /* * Set up state to handle output flow control. * XXX - worth handling MDMBUF (DCD) flow control at the lowest level? * Now has 10+ msec latency, while CTS flow has 50- usec latency. */ com->state |= CS_ODEVREADY; com->state &= ~CS_CTS_OFLOW; if (cflag & CCTS_OFLOW) { com->state |= CS_CTS_OFLOW; if (!(com->last_modem_status & MSR_CTS)) com->state &= ~CS_ODEVREADY; if (com->st16650a) { outb(iobase + com_cfcr, 0xbf); outb(iobase + com_fifo, inb(iobase + com_fifo) | 0x80); } } else { if (com->st16650a) { outb(iobase + com_cfcr, 0xbf); outb(iobase + com_fifo, inb(iobase + com_fifo) & ~0x80); } } outb(iobase + com_cfcr, com->cfcr_image); /* XXX shouldn't call functions while intrs are disabled. */ disc_optim(tp, t, com); /* * Recover from fiddling with CS_TTGO. We used to call siointr1() * unconditionally, but that defeated the careful discarding of * stale input in sioopen(). */ if (com->state >= (CS_BUSY | CS_TTGO)) siointr1(com); enable_intr(); splx(s); comstart(tp); return (0); } static void comstart(tp) struct tty *tp; { struct com_s *com; int s; int unit; unit = DEV_TO_UNIT(tp->t_dev); com = com_addr(unit); s = spltty(); disable_intr(); if (tp->t_state & TS_TTSTOP) com->state &= ~CS_TTGO; else com->state |= CS_TTGO; if (tp->t_state & TS_TBLOCK) { if (com->mcr_image & MCR_RTS && com->state & CS_RTS_IFLOW) outb(com->modem_ctl_port, com->mcr_image &= ~MCR_RTS); } else { if (!(com->mcr_image & MCR_RTS) && com->iptr < com->ihighwater && com->state & CS_RTS_IFLOW) outb(com->modem_ctl_port, com->mcr_image |= MCR_RTS); } enable_intr(); if (tp->t_state & (TS_TIMEOUT | TS_TTSTOP)) { ttwwakeup(tp); splx(s); return; } if (tp->t_outq.c_cc != 0) { struct lbq *qp; struct lbq *next; if (!com->obufs[0].l_queued) { com->obufs[0].l_tail = com->obuf1 + q_to_b(&tp->t_outq, com->obuf1, sizeof com->obuf1); com->obufs[0].l_next = NULL; com->obufs[0].l_queued = TRUE; disable_intr(); if (com->state & CS_BUSY) { qp = com->obufq.l_next; while ((next = qp->l_next) != NULL) qp = next; qp->l_next = &com->obufs[0]; } else { com->obufq.l_head = com->obufs[0].l_head; com->obufq.l_tail = com->obufs[0].l_tail; com->obufq.l_next = &com->obufs[0]; com->state |= CS_BUSY; } enable_intr(); } if (tp->t_outq.c_cc != 0 && !com->obufs[1].l_queued) { com->obufs[1].l_tail = com->obuf2 + q_to_b(&tp->t_outq, com->obuf2, sizeof com->obuf2); com->obufs[1].l_next = NULL; com->obufs[1].l_queued = TRUE; disable_intr(); if (com->state & CS_BUSY) { qp = com->obufq.l_next; while ((next = qp->l_next) != NULL) qp = next; qp->l_next = &com->obufs[1]; } else { com->obufq.l_head = com->obufs[1].l_head; com->obufq.l_tail = com->obufs[1].l_tail; com->obufq.l_next = &com->obufs[1]; com->state |= CS_BUSY; } enable_intr(); } tp->t_state |= TS_BUSY; } disable_intr(); if (com->state >= (CS_BUSY | CS_TTGO)) siointr1(com); /* fake interrupt to start output */ enable_intr(); ttwwakeup(tp); splx(s); } static void siostop(tp, rw) struct tty *tp; int rw; { struct com_s *com; com = com_addr(DEV_TO_UNIT(tp->t_dev)); if (com->gone) return; disable_intr(); if (rw & FWRITE) { if (com->hasfifo) #ifdef COM_ESP /* XXX avoid h/w bug. */ if (!com->esp) #endif /* XXX does this flush everything? */ outb(com->iobase + com_fifo, FIFO_XMT_RST | com->fifo_image); com->obufs[0].l_queued = FALSE; com->obufs[1].l_queued = FALSE; if (com->state & CS_ODONE) com_events -= LOTS_OF_EVENTS; com->state &= ~(CS_ODONE | CS_BUSY); com->tp->t_state &= ~TS_BUSY; } if (rw & FREAD) { if (com->hasfifo) #ifdef COM_ESP /* XXX avoid h/w bug. */ if (!com->esp) #endif /* XXX does this flush everything? */ outb(com->iobase + com_fifo, FIFO_RCV_RST | com->fifo_image); com_events -= (com->iptr - com->ibuf); com->iptr = com->ibuf; } enable_intr(); comstart(tp); } static struct tty * siodevtotty(dev) dev_t dev; { int mynor; int unit; mynor = minor(dev); if (mynor & CONTROL_MASK) return (NULL); unit = MINOR_TO_UNIT(mynor); if ((u_int) unit >= NSIOTOT) return (NULL); return (&sio_tty[unit]); } static int commctl(com, bits, how) struct com_s *com; int bits; int how; { int mcr; int msr; if (how == DMGET) { bits = TIOCM_LE; /* XXX - always enabled while open */ mcr = com->mcr_image; if (mcr & MCR_DTR) bits |= TIOCM_DTR; if (mcr & MCR_RTS) bits |= TIOCM_RTS; msr = com->prev_modem_status; if (msr & MSR_CTS) bits |= TIOCM_CTS; if (msr & MSR_DCD) bits |= TIOCM_CD; if (msr & MSR_DSR) bits |= TIOCM_DSR; /* * XXX - MSR_RI is naturally volatile, and we make MSR_TERI * more volatile by reading the modem status a lot. Perhaps * we should latch both bits until the status is read here. */ if (msr & (MSR_RI | MSR_TERI)) bits |= TIOCM_RI; return (bits); } mcr = 0; if (bits & TIOCM_DTR) mcr |= MCR_DTR; if (bits & TIOCM_RTS) mcr |= MCR_RTS; if (com->gone) return(0); disable_intr(); switch (how) { case DMSET: outb(com->modem_ctl_port, com->mcr_image = mcr | (com->mcr_image & MCR_IENABLE)); break; case DMBIS: outb(com->modem_ctl_port, com->mcr_image |= mcr); break; case DMBIC: outb(com->modem_ctl_port, com->mcr_image &= ~mcr); break; } enable_intr(); return (0); } static void siosettimeout() { struct com_s *com; bool_t someopen; int unit; /* * Set our timeout period to 1 second if no polled devices are open. * Otherwise set it to max(1/200, 1/hz). * Enable timeouts iff some device is open. */ untimeout(comwakeup, (void *)NULL, sio_timeout_handle); sio_timeout = hz; someopen = FALSE; for (unit = 0; unit < NSIOTOT; ++unit) { com = com_addr(unit); if (com != NULL && com->tp != NULL && com->tp->t_state & TS_ISOPEN && !com->gone) { someopen = TRUE; if (com->poll || com->poll_output) { sio_timeout = hz > 200 ? hz / 200 : 1; break; } } } if (someopen) { sio_timeouts_until_log = hz / sio_timeout; sio_timeout_handle = timeout(comwakeup, (void *)NULL, sio_timeout); } else { /* Flush error messages, if any. */ sio_timeouts_until_log = 1; comwakeup((void *)NULL); untimeout(comwakeup, (void *)NULL, sio_timeout_handle); } } static void comwakeup(chan) void *chan; { struct com_s *com; int unit; sio_timeout_handle = timeout(comwakeup, (void *)NULL, sio_timeout); /* * Recover from lost output interrupts. * Poll any lines that don't use interrupts. */ for (unit = 0; unit < NSIOTOT; ++unit) { com = com_addr(unit); if (com != NULL && !com->gone && (com->state >= (CS_BUSY | CS_TTGO) || com->poll)) { disable_intr(); siointr1(com); enable_intr(); } } /* * Check for and log errors, but not too often. */ if (--sio_timeouts_until_log > 0) return; sio_timeouts_until_log = hz / sio_timeout; for (unit = 0; unit < NSIOTOT; ++unit) { int errnum; com = com_addr(unit); if (com == NULL) continue; if (com->gone) continue; for (errnum = 0; errnum < CE_NTYPES; ++errnum) { u_int delta; u_long total; disable_intr(); delta = com->delta_error_counts[errnum]; com->delta_error_counts[errnum] = 0; enable_intr(); if (delta == 0) continue; total = com->error_counts[errnum] += delta; log(LOG_ERR, "sio%d: %u more %s%s (total %lu)\n", unit, delta, error_desc[errnum], delta == 1 ? "" : "s", total); } } } static void disc_optim(tp, t, com) struct tty *tp; struct termios *t; struct com_s *com; { if (!(t->c_iflag & (ICRNL | IGNCR | IMAXBEL | INLCR | ISTRIP | IXON)) && (!(t->c_iflag & BRKINT) || (t->c_iflag & IGNBRK)) && (!(t->c_iflag & PARMRK) || (t->c_iflag & (IGNPAR | IGNBRK)) == (IGNPAR | IGNBRK)) && !(t->c_lflag & (ECHO | ICANON | IEXTEN | ISIG | PENDIN)) && linesw[tp->t_line].l_rint == ttyinput) tp->t_state |= TS_CAN_BYPASS_L_RINT; else tp->t_state &= ~TS_CAN_BYPASS_L_RINT; com->hotchar = linesw[tp->t_line].l_hotchar; } /* * Following are all routines needed for SIO to act as console */ #include struct siocnstate { u_char dlbl; u_char dlbh; u_char ier; u_char cfcr; u_char mcr; }; static speed_t siocngetspeed __P((Port_t, struct speedtab *)); static void siocnclose __P((struct siocnstate *sp)); static void siocnopen __P((struct siocnstate *sp)); static void siocntxwait __P((void)); static void siocntxwait() { int timo; /* * Wait for any pending transmission to finish. Required to avoid * the UART lockup bug when the speed is changed, and for normal * transmits. */ timo = 100000; while ((inb(siocniobase + com_lsr) & (LSR_TSRE | LSR_TXRDY)) != (LSR_TSRE | LSR_TXRDY) && --timo != 0) ; } /* * Read the serial port specified and try to figure out what speed * it's currently running at. We're assuming the serial port has * been initialized and is basicly idle. This routine is only intended * to be run at system startup. * * If the value read from the serial port doesn't make sense, return 0. */ static speed_t siocngetspeed(iobase, table) Port_t iobase; struct speedtab *table; { int code; u_char dlbh; u_char dlbl; u_char cfcr; cfcr = inb(iobase + com_cfcr); outb(iobase + com_cfcr, CFCR_DLAB | cfcr); dlbl = inb(iobase + com_dlbl); dlbh = inb(iobase + com_dlbh); outb(iobase + com_cfcr, cfcr); code = dlbh << 8 | dlbl; for ( ; table->sp_speed != -1; table++) if (table->sp_code == code) return (table->sp_speed); return 0; /* didn't match anything sane */ } static void siocnopen(sp) struct siocnstate *sp; { int divisor; u_char dlbh; u_char dlbl; Port_t iobase; /* * Save all the device control registers except the fifo register * and set our default ones (cs8 -parenb speed=comdefaultrate). * We can't save the fifo register since it is read-only. */ iobase = siocniobase; sp->ier = inb(iobase + com_ier); outb(iobase + com_ier, 0); /* spltty() doesn't stop siointr() */ siocntxwait(); sp->cfcr = inb(iobase + com_cfcr); outb(iobase + com_cfcr, CFCR_DLAB | CFCR_8BITS); sp->dlbl = inb(iobase + com_dlbl); sp->dlbh = inb(iobase + com_dlbh); /* * Only set the divisor registers if they would change, since on * some 16550 incompatibles (Startech), setting them clears the * data input register. This also reduces the effects of the * UMC8669F bug. */ divisor = ttspeedtab(comdefaultrate, comspeedtab); dlbl = divisor & 0xFF; if (sp->dlbl != dlbl) outb(iobase + com_dlbl, dlbl); dlbh = (u_int) divisor >> 8; if (sp->dlbh != dlbh) outb(iobase + com_dlbh, dlbh); outb(iobase + com_cfcr, CFCR_8BITS); sp->mcr = inb(iobase + com_mcr); /* * We don't want interrupts, but must be careful not to "disable" * them by clearing the MCR_IENABLE bit, since that might cause * an interrupt by floating the IRQ line. */ outb(iobase + com_mcr, (sp->mcr & MCR_IENABLE) | MCR_DTR | MCR_RTS); } static void siocnclose(sp) struct siocnstate *sp; { Port_t iobase; /* * Restore the device control registers. */ siocntxwait(); iobase = siocniobase; outb(iobase + com_cfcr, CFCR_DLAB | CFCR_8BITS); if (sp->dlbl != inb(iobase + com_dlbl)) outb(iobase + com_dlbl, sp->dlbl); if (sp->dlbh != inb(iobase + com_dlbh)) outb(iobase + com_dlbh, sp->dlbh); outb(iobase + com_cfcr, sp->cfcr); /* * XXX damp oscillations of MCR_DTR and MCR_RTS by not restoring them. */ outb(iobase + com_mcr, sp->mcr | MCR_DTR | MCR_RTS); outb(iobase + com_ier, sp->ier); } void siocnprobe(cp) struct consdev *cp; { speed_t boot_speed; u_char cfcr; struct isa_device *dvp; int s; struct siocnstate sp; /* * Find our first enabled console, if any. If it is a high-level * console device, then initialize it and return successfully. * If it is a low-level console device, then initialize it and * return unsuccessfully. It must be initialized in both cases * for early use by console drivers and debuggers. Initializing * the hardware is not necessary in all cases, since the i/o * routines initialize it on the fly, but it is necessary if * input might arrive while the hardware is switched back to an * uninitialized state. We can't handle multiple console devices * yet because our low-level routines don't take a device arg. * We trust the user to set the console flags properly so that we * don't need to probe. */ cp->cn_pri = CN_DEAD; for (dvp = isa_devtab_tty; dvp->id_driver != NULL; dvp++) if (dvp->id_driver == &siodriver && dvp->id_enabled && COM_CONSOLE(dvp)) { siocniobase = dvp->id_iobase; s = spltty(); if (boothowto & RB_SERIAL) { boot_speed = siocngetspeed(siocniobase, comspeedtab); if (boot_speed) comdefaultrate = boot_speed; } /* * Initialize the divisor latch. We can't rely on * siocnopen() to do this the first time, since it * avoids writing to the latch if the latch appears * to have the correct value. Also, if we didn't * just read the speed from the hardware, then we * need to set the speed in hardware so that * switching it later is null. */ cfcr = inb(siocniobase + com_cfcr); outb(siocniobase + com_cfcr, CFCR_DLAB | cfcr); outb(siocniobase + com_dlbl, COMBRD(comdefaultrate) & 0xff); outb(siocniobase + com_dlbh, (u_int) COMBRD(comdefaultrate) >> 8); outb(siocniobase + com_cfcr, cfcr); siocnopen(&sp); splx(s); if (!COM_LLCONSOLE(dvp)) { cp->cn_dev = makedev(CDEV_MAJOR, dvp->id_unit); cp->cn_pri = COM_FORCECONSOLE(dvp) || boothowto & RB_SERIAL ? CN_REMOTE : CN_NORMAL; } break; } } void siocninit(cp) struct consdev *cp; { comconsole = DEV_TO_UNIT(cp->cn_dev); } int siocncheckc(dev) dev_t dev; { int c; Port_t iobase; int s; struct siocnstate sp; iobase = siocniobase; s = spltty(); siocnopen(&sp); if (inb(iobase + com_lsr) & LSR_RXRDY) c = inb(iobase + com_data); else c = -1; siocnclose(&sp); splx(s); return (c); } int siocngetc(dev) dev_t dev; { int c; Port_t iobase; int s; struct siocnstate sp; iobase = siocniobase; s = spltty(); siocnopen(&sp); while (!(inb(iobase + com_lsr) & LSR_RXRDY)) ; c = inb(iobase + com_data); siocnclose(&sp); splx(s); return (c); } void siocnputc(dev, c) dev_t dev; int c; { int s; struct siocnstate sp; s = spltty(); siocnopen(&sp); siocntxwait(); outb(siocniobase + com_data, c); siocnclose(&sp); splx(s); } #ifdef DSI_SOFT_MODEM /* * The magic code to download microcode to a "Connection 14.4+Fax" * modem from Digicom Systems Inc. Very magic. */ #define DSI_ERROR(str) { ptr = str; goto error; } static int LoadSoftModem(int unit, int base_io, u_long size, u_char *ptr) { int int_c,int_k; int data_0188, data_0187; /* * First see if it is a DSI SoftModem */ if(!((inb(base_io+7) ^ inb(base_io+7)) & 0x80)) return ENODEV; data_0188 = inb(base_io+4); data_0187 = inb(base_io+3); outb(base_io+3,0x80); outb(base_io+4,0x0C); outb(base_io+0,0x31); outb(base_io+1,0x8C); outb(base_io+7,0x10); outb(base_io+7,0x19); if(0x18 != (inb(base_io+7) & 0x1A)) DSI_ERROR("dsp bus not granted"); if(0x01 != (inb(base_io+7) & 0x01)) { outb(base_io+7,0x18); outb(base_io+7,0x19); if(0x01 != (inb(base_io+7) & 0x01)) DSI_ERROR("program mem not granted"); } int_c = 0; while(1) { if(int_c >= 7 || size <= 0x1800) break; for(int_k = 0 ; int_k < 0x800; int_k++) { outb(base_io+0,*ptr++); outb(base_io+1,*ptr++); outb(base_io+2,*ptr++); } size -= 0x1800; int_c++; } if(size > 0x1800) { outb(base_io+7,0x18); outb(base_io+7,0x19); if(0x00 != (inb(base_io+7) & 0x01)) DSI_ERROR("program data not granted"); for(int_k = 0 ; int_k < 0x800; int_k++) { outb(base_io+1,*ptr++); outb(base_io+2,0); outb(base_io+1,*ptr++); outb(base_io+2,*ptr++); } size -= 0x1800; while(size > 0x1800) { for(int_k = 0 ; int_k < 0xC00; int_k++) { outb(base_io+1,*ptr++); outb(base_io+2,*ptr++); } size -= 0x1800; } if(size < 0x1800) { for(int_k=0;int_k 0) { if(int_c == 7) { outb(base_io+7,0x18); outb(base_io+7,0x19); if(0x00 != (inb(base_io+7) & 0x01)) DSI_ERROR("program data not granted"); for(int_k = 0 ; int_k < size/3; int_k++) { outb(base_io+1,*ptr++); outb(base_io+2,0); outb(base_io+1,*ptr++); outb(base_io+2,*ptr++); } } else { for(int_k = 0 ; int_k < size/3; int_k++) { outb(base_io+0,*ptr++); outb(base_io+1,*ptr++); outb(base_io+2,*ptr++); } } } outb(base_io+7,0x11); outb(base_io+7,3); outb(base_io+4,data_0188 & 0xfb); outb(base_io+3,data_0187); return 0; error: printf("sio%d: DSI SoftModem microcode load failed: <%s>\n",unit,ptr); outb(base_io+7,0x00); \ outb(base_io+3,data_0187); \ outb(base_io+4,data_0188); \ return EIO; } #endif /* DSI_SOFT_MODEM */ /* * support PnP cards if we are using 'em */ #if NPNP > 0 static struct siopnp_ids { u_long vend_id; char *id_str; } siopnp_ids[] = { { 0x5015f435, "MOT1550"}, { 0x8113b04e, "Supra1381"}, { 0x9012b04e, "Supra1290"}, { 0x11007256, "USR0011"}, { 0x30207256, "USR2030"}, { 0 } }; static char *siopnp_probe(u_long csn, u_long vend_id); static void siopnp_attach(u_long csn, u_long vend_id, char *name, struct isa_device *dev); static u_long nsiopnp = NSIO; static struct pnp_device siopnp = { "siopnp", siopnp_probe, siopnp_attach, &nsiopnp, &tty_imask }; DATA_SET (pnpdevice_set, siopnp); static char * siopnp_probe(u_long csn, u_long vend_id) { struct siopnp_ids *ids; char *s = NULL; for(ids = siopnp_ids; ids->vend_id != 0; ids++) { if (vend_id == ids->vend_id) { s = ids->id_str; break; } } if (s) { struct pnp_cinfo d; read_pnp_parms(&d, 0); if (d.enable == 0 || d.flags & 1) { printf("CSN %d is disabled.\n", csn); return (NULL); } } return (s); } static void siopnp_attach(u_long csn, u_long vend_id, char *name, struct isa_device *dev) { struct pnp_cinfo d; struct isa_device *dvp; if (dev->id_unit >= NSIOTOT) return; if (read_pnp_parms(&d, 0) == 0) { printf("failed to read pnp parms\n"); return; } write_pnp_parms(&d, 0); enable_pnp_card(); dev->id_iobase = d.port[0]; dev->id_irq = (1 << d.irq[0]); dev->id_intr = siointr; dev->id_ri_flags = RI_FAST; dev->id_drq = -1; if (dev->id_driver == NULL) { dev->id_driver = &siodriver; dvp = find_isadev(isa_devtab_tty, &siodriver, 0); if (dvp != NULL) dev->id_id = dvp->id_id; } if ((dev->id_alive = sioprobe(dev)) != 0) sioattach(dev); else printf("sio%d: probe failed\n", dev->id_unit); } #endif