Index: head/sys/amd64/amd64/vm_machdep.c =================================================================== --- head/sys/amd64/amd64/vm_machdep.c (revision 29279) +++ head/sys/amd64/amd64/vm_machdep.c (revision 29280) @@ -1,959 +1,987 @@ /*- * Copyright (c) 1982, 1986 The Regents of the University of California. * Copyright (c) 1989, 1990 William Jolitz * Copyright (c) 1994 John Dyson * All rights reserved. * * This code is derived from software contributed to Berkeley by * the Systems Programming Group of the University of Utah Computer * Science Department, and 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. * * from: @(#)vm_machdep.c 7.3 (Berkeley) 5/13/91 * Utah $Hdr: vm_machdep.c 1.16.1.1 89/06/23$ - * $Id: vm_machdep.c,v 1.86 1997/08/26 18:10:34 peter Exp $ + * $Id: vm_machdep.c,v 1.87 1997/09/02 20:05:31 bde Exp $ */ #include "npx.h" #include "opt_bounce.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef PC98 #include #else #include #endif #ifdef BOUNCE_BUFFERS static vm_offset_t vm_bounce_kva __P((int size, int waitok)); static void vm_bounce_kva_free __P((vm_offset_t addr, vm_offset_t size, int now)); static vm_offset_t vm_bounce_page_find __P((int count)); static void vm_bounce_page_free __P((vm_offset_t pa, int count)); static volatile int kvasfreecnt; caddr_t bouncememory; int bouncepages; static int bpwait; static vm_offset_t *bouncepa; static int bmwait, bmfreeing; #define BITS_IN_UNSIGNED (8*sizeof(unsigned)) static int bounceallocarraysize; static unsigned *bounceallocarray; static int bouncefree; #if defined(PC98) && defined (EPSON_BOUNCEDMA) #define SIXTEENMEG (3840*4096) /* 15MB boundary */ #else #define SIXTEENMEG (4096*4096) #endif #define MAXBKVA 1024 int maxbkva = MAXBKVA*PAGE_SIZE; /* special list that can be used at interrupt time for eventual kva free */ static struct kvasfree { vm_offset_t addr; vm_offset_t size; } kvaf[MAXBKVA]; /* * get bounce buffer pages (count physically contiguous) * (only 1 inplemented now) */ static vm_offset_t vm_bounce_page_find(count) int count; { int bit; int s,i; if (count != 1) panic("vm_bounce_page_find -- no support for > 1 page yet!!!"); s = splbio(); retry: for (i = 0; i < bounceallocarraysize; i++) { if (bounceallocarray[i] != 0xffffffff) { bit = ffs(~bounceallocarray[i]); if (bit) { bounceallocarray[i] |= 1 << (bit - 1) ; bouncefree -= count; splx(s); return bouncepa[(i * BITS_IN_UNSIGNED + (bit - 1))]; } } } bpwait = 1; tsleep((caddr_t) &bounceallocarray, PRIBIO, "bncwai", 0); goto retry; } static void vm_bounce_kva_free(addr, size, now) vm_offset_t addr; vm_offset_t size; int now; { int s = splbio(); kvaf[kvasfreecnt].addr = addr; kvaf[kvasfreecnt].size = size; ++kvasfreecnt; if( now) { /* * this will do wakeups */ vm_bounce_kva(0,0); } else { if (bmwait) { /* * if anyone is waiting on the bounce-map, then wakeup */ wakeup((caddr_t) io_map); bmwait = 0; } } splx(s); } /* * free count bounce buffer pages */ static void vm_bounce_page_free(pa, count) vm_offset_t pa; int count; { int allocindex; int index; int bit; if (count != 1) panic("vm_bounce_page_free -- no support for > 1 page yet!!!"); for(index=0;indexb_flags & B_BOUNCE) { printf("vm_bounce_alloc: called recursively???\n"); return; } if (bp->b_bufsize < bp->b_bcount) { printf( "vm_bounce_alloc: b_bufsize(0x%lx) < b_bcount(0x%lx) !!\n", bp->b_bufsize, bp->b_bcount); panic("vm_bounce_alloc"); } /* * This is not really necessary * if( bp->b_bufsize != bp->b_bcount) { * printf("size: %d, count: %d\n", bp->b_bufsize, bp->b_bcount); * } */ vastart = (vm_offset_t) bp->b_data; vaend = (vm_offset_t) bp->b_data + bp->b_bufsize; vapstart = trunc_page(vastart); vapend = round_page(vaend); countvmpg = (vapend - vapstart) / PAGE_SIZE; /* * if any page is above 16MB, then go into bounce-buffer mode */ va = vapstart; for (i = 0; i < countvmpg; i++) { pa = pmap_kextract(va); if (pa >= SIXTEENMEG) ++dobounceflag; if( pa == 0) panic("vm_bounce_alloc: Unmapped page"); va += PAGE_SIZE; } if (dobounceflag == 0) return; if (bouncepages < dobounceflag) panic("Not enough bounce buffers!!!"); /* * allocate a replacement kva for b_addr */ kva = vm_bounce_kva(countvmpg*PAGE_SIZE, 1); #if 0 printf("%s: vapstart: %x, vapend: %x, countvmpg: %d, kva: %x ", (bp->b_flags & B_READ) ? "read":"write", vapstart, vapend, countvmpg, kva); #endif va = vapstart; for (i = 0; i < countvmpg; i++) { pa = pmap_kextract(va); if (pa >= SIXTEENMEG) { /* * allocate a replacement page */ vm_offset_t bpa = vm_bounce_page_find(1); pmap_kenter(kva + (PAGE_SIZE * i), bpa); #if 0 printf("r(%d): (%x,%x,%x) ", i, va, pa, bpa); #endif /* * if we are writing, the copy the data into the page */ if ((bp->b_flags & B_READ) == 0) { bcopy((caddr_t) va, (caddr_t) kva + (PAGE_SIZE * i), PAGE_SIZE); } } else { /* * use original page */ pmap_kenter(kva + (PAGE_SIZE * i), pa); } va += PAGE_SIZE; } /* * flag the buffer as being bounced */ bp->b_flags |= B_BOUNCE; /* * save the original buffer kva */ bp->b_savekva = bp->b_data; /* * put our new kva into the buffer (offset by original offset) */ bp->b_data = (caddr_t) (((vm_offset_t) kva) | ((vm_offset_t) bp->b_savekva & PAGE_MASK)); #if 0 printf("b_savekva: %x, newva: %x\n", bp->b_savekva, bp->b_data); #endif return; } /* * hook into biodone to free bounce buffer */ void vm_bounce_free(bp) struct buf *bp; { int i; vm_offset_t origkva, bouncekva, bouncekvaend; /* * if this isn't a bounced buffer, then just return */ if ((bp->b_flags & B_BOUNCE) == 0) return; /* * This check is not necessary * if (bp->b_bufsize != bp->b_bcount) { * printf("vm_bounce_free: b_bufsize=%d, b_bcount=%d\n", * bp->b_bufsize, bp->b_bcount); * } */ origkva = (vm_offset_t) bp->b_savekva; bouncekva = (vm_offset_t) bp->b_data; /* printf("free: %d ", bp->b_bufsize); */ /* * check every page in the kva space for b_addr */ for (i = 0; i < bp->b_bufsize; ) { vm_offset_t mybouncepa; vm_offset_t copycount; copycount = round_page(bouncekva + 1) - bouncekva; mybouncepa = pmap_kextract(trunc_page(bouncekva)); /* * if this is a bounced pa, then process as one */ if ( mybouncepa != pmap_kextract( trunc_page( origkva))) { vm_offset_t tocopy = copycount; if (i + tocopy > bp->b_bufsize) tocopy = bp->b_bufsize - i; /* * if this is a read, then copy from bounce buffer into original buffer */ if (bp->b_flags & B_READ) bcopy((caddr_t) bouncekva, (caddr_t) origkva, tocopy); /* * free the bounce allocation */ /* printf("(kva: %x, pa: %x)", bouncekva, mybouncepa); */ vm_bounce_page_free(mybouncepa, 1); } origkva += copycount; bouncekva += copycount; i += copycount; } /* printf("\n"); */ /* * add the old kva into the "to free" list */ bouncekva= trunc_page((vm_offset_t) bp->b_data); bouncekvaend= round_page((vm_offset_t)bp->b_data + bp->b_bufsize); /* printf("freeva: %d\n", (bouncekvaend - bouncekva) / PAGE_SIZE); */ vm_bounce_kva_free( bouncekva, (bouncekvaend - bouncekva), 0); bp->b_data = bp->b_savekva; bp->b_savekva = 0; bp->b_flags &= ~B_BOUNCE; return; } /* * init the bounce buffer system */ void vm_bounce_init() { int i; kvasfreecnt = 0; if (bouncepages == 0) return; bounceallocarraysize = (bouncepages + BITS_IN_UNSIGNED - 1) / BITS_IN_UNSIGNED; bounceallocarray = malloc(bounceallocarraysize * sizeof(unsigned), M_TEMP, M_NOWAIT); if (!bounceallocarray) panic("Cannot allocate bounce resource array"); bouncepa = malloc(bouncepages * sizeof(vm_offset_t), M_TEMP, M_NOWAIT); if (!bouncepa) panic("Cannot allocate physical memory array"); for(i=0;i= SIXTEENMEG) panic("bounce memory out of range"); if( pa == 0) panic("bounce memory not resident"); bouncepa[i] = pa; bounceallocarray[i/(8*sizeof(int))] &= ~(1<<(i%(8*sizeof(int)))); } bouncefree = bouncepages; } #endif /* BOUNCE_BUFFERS */ /* * quick version of vm_fault */ void vm_fault_quick(v, prot) caddr_t v; int prot; { if (prot & VM_PROT_WRITE) subyte(v, fubyte(v)); else fubyte(v); } /* * Finish a fork operation, with process p2 nearly set up. * Copy and update the pcb, set up the stack so that the child * ready to run and return to user mode. */ void cpu_fork(p1, p2) register struct proc *p1, *p2; { struct pcb *pcb2 = &p2->p_addr->u_pcb; /* Ensure that p1's pcb is up to date. */ if (npxproc == p1) npxsave(&p1->p_addr->u_pcb.pcb_savefpu); /* Copy p1's pcb. */ p2->p_addr->u_pcb = p1->p_addr->u_pcb; /* * Create a new fresh stack for the new process. * Copy the trap frame for the return to user mode as if from a * syscall. This copies the user mode register values. */ p2->p_md.md_regs = (struct trapframe *) ((int)p2->p_addr + UPAGES * PAGE_SIZE) - 1; *p2->p_md.md_regs = *p1->p_md.md_regs; /* * Set registers for trampoline to user mode. Leave space for the * return address on stack. These are the kernel mode register values. */ pcb2->pcb_cr3 = vtophys(p2->p_vmspace->vm_pmap.pm_pdir); pcb2->pcb_edi = p2->p_md.md_regs->tf_edi; pcb2->pcb_esi = (int)fork_return; pcb2->pcb_ebp = p2->p_md.md_regs->tf_ebp; pcb2->pcb_esp = (int)p2->p_md.md_regs - sizeof(void *); pcb2->pcb_ebx = (int)p2; pcb2->pcb_eip = (int)fork_trampoline; /* * pcb2->pcb_ldt: duplicated below, if necessary. * pcb2->pcb_ldt_len: cloned above. * pcb2->pcb_savefpu: cloned above. * pcb2->pcb_flags: cloned above (always 0 here?). * pcb2->pcb_onfault: cloned above (always NULL here?). */ #ifdef VM86 /* * XXX don't copy the i/o pages. this should probably be fixed. */ pcb2->pcb_ext = 0; #endif #ifdef USER_LDT /* Copy the LDT, if necessary. */ if (pcb2->pcb_ldt != 0) { union descriptor *new_ldt; size_t len = pcb2->pcb_ldt_len * sizeof(union descriptor); new_ldt = (union descriptor *)kmem_alloc(kernel_map, len); bcopy(pcb2->pcb_ldt, new_ldt, len); pcb2->pcb_ldt = (caddr_t)new_ldt; } #endif /* * Now, cpu_switch() can schedule the new process. * pcb_esp is loaded pointing to the cpu_switch() stack frame * containing the return address when exiting cpu_switch. * This will normally be to proc_trampoline(), which will have * %ebx loaded with the new proc's pointer. proc_trampoline() * will set up a stack to call fork_return(p, frame); to complete * the return to user-mode. */ } /* * Intercept the return address from a freshly forked process that has NOT * been scheduled yet. * * This is needed to make kernel threads stay in kernel mode. */ void cpu_set_fork_handler(p, func, arg) struct proc *p; void (*func) __P((void *)); void *arg; { /* * Note that the trap frame follows the args, so the function * is really called like this: func(arg, frame); */ p->p_addr->u_pcb.pcb_esi = (int) func; /* function */ p->p_addr->u_pcb.pcb_ebx = (int) arg; /* first arg */ } void cpu_exit(p) register struct proc *p; { #if defined(USER_LDT) || defined(VM86) struct pcb *pcb = &p->p_addr->u_pcb; #endif #if NNPX > 0 npxexit(p); #endif /* NNPX */ #ifdef VM86 if (pcb->pcb_ext != 0) { /* * XXX do we need to move the TSS off the allocated pages * before freeing them? (not done here) */ kmem_free(kernel_map, (vm_offset_t)pcb->pcb_ext, ctob(IOPAGES + 1)); pcb->pcb_ext = 0; } #endif #ifdef USER_LDT if (pcb->pcb_ldt != 0) { if (pcb == curpcb) lldt(GSEL(GUSERLDT_SEL, SEL_KPL)); kmem_free(kernel_map, (vm_offset_t)pcb->pcb_ldt, pcb->pcb_ldt_len * sizeof(union descriptor)); pcb->pcb_ldt_len = (int)pcb->pcb_ldt = 0; } #endif cnt.v_swtch++; cpu_switch(p); panic("cpu_exit"); } void cpu_wait(p) struct proc *p; { /* drop per-process resources */ pmap_dispose_proc(p); vmspace_free(p->p_vmspace); } /* * Dump the machine specific header information at the start of a core dump. */ int cpu_coredump(p, vp, cred) struct proc *p; struct vnode *vp; struct ucred *cred; { return (vn_rdwr(UIO_WRITE, vp, (caddr_t) p->p_addr, ctob(UPAGES), (off_t)0, UIO_SYSSPACE, IO_NODELOCKED|IO_UNIT, cred, (int *)NULL, p)); } #ifdef notyet static void setredzone(pte, vaddr) u_short *pte; caddr_t vaddr; { /* eventually do this by setting up an expand-down stack segment for ss0: selector, allowing stack access down to top of u. this means though that protection violations need to be handled thru a double fault exception that must do an integral task switch to a known good context, within which a dump can be taken. a sensible scheme might be to save the initial context used by sched (that has physical memory mapped 1:1 at bottom) and take the dump while still in mapped mode */ } #endif /* * Convert kernel VA to physical address */ u_long kvtop(void *addr) { vm_offset_t va; va = pmap_kextract((vm_offset_t)addr); if (va == 0) panic("kvtop: zero page frame"); return((int)va); } /* * Map an IO request into kernel virtual address space. * * All requests are (re)mapped into kernel VA space. * Notice that we use b_bufsize for the size of the buffer * to be mapped. b_bcount might be modified by the driver. */ void vmapbuf(bp) register struct buf *bp; { register caddr_t addr, v, kva; vm_offset_t pa; if ((bp->b_flags & B_PHYS) == 0) panic("vmapbuf"); for (v = bp->b_saveaddr, addr = (caddr_t)trunc_page(bp->b_data); addr < bp->b_data + bp->b_bufsize; addr += PAGE_SIZE, v += PAGE_SIZE) { /* * Do the vm_fault if needed; do the copy-on-write thing * when reading stuff off device into memory. */ vm_fault_quick(addr, (bp->b_flags&B_READ)?(VM_PROT_READ|VM_PROT_WRITE):VM_PROT_READ); pa = trunc_page(pmap_kextract((vm_offset_t) addr)); if (pa == 0) panic("vmapbuf: page not present"); vm_page_hold(PHYS_TO_VM_PAGE(pa)); pmap_kenter((vm_offset_t) v, pa); } kva = bp->b_saveaddr; bp->b_saveaddr = bp->b_data; bp->b_data = kva + (((vm_offset_t) bp->b_data) & PAGE_MASK); } /* * Free the io map PTEs associated with this IO operation. * We also invalidate the TLB entries and restore the original b_addr. */ void vunmapbuf(bp) register struct buf *bp; { register caddr_t addr; vm_offset_t pa; if ((bp->b_flags & B_PHYS) == 0) panic("vunmapbuf"); for (addr = (caddr_t)trunc_page(bp->b_data); addr < bp->b_data + bp->b_bufsize; addr += PAGE_SIZE) { pa = trunc_page(pmap_kextract((vm_offset_t) addr)); pmap_kremove((vm_offset_t) addr); vm_page_unhold(PHYS_TO_VM_PAGE(pa)); } bp->b_data = bp->b_saveaddr; } /* * Force reset the processor by invalidating the entire address space! */ void cpu_reset() { #ifdef PC98 /* * Attempt to do a CPU reset via CPU reset port. */ asm("cli"); outb(0x37, 0x0f); /* SHUT0 = 0. */ outb(0x37, 0x0b); /* SHUT1 = 0. */ outb(0xf0, 0x00); /* Reset. */ #else /* * Attempt to do a CPU reset via the keyboard controller, * do not turn of the GateA20, as any machine that fails * to do the reset here would then end up in no man's land. */ #if !defined(BROKEN_KEYBOARD_RESET) outb(IO_KBD + 4, 0xFE); DELAY(500000); /* wait 0.5 sec to see if that did it */ printf("Keyboard reset did not work, attempting CPU shutdown\n"); DELAY(1000000); /* wait 1 sec for printf to complete */ #endif #endif /* PC98 */ /* force a shutdown by unmapping entire address space ! */ bzero((caddr_t) PTD, PAGE_SIZE); /* "good night, sweet prince .... " */ invltlb(); /* NOTREACHED */ while(1); } /* * Grow the user stack to allow for 'sp'. This version grows the stack in * chunks of SGROWSIZ. */ int grow(p, sp) struct proc *p; u_int sp; { unsigned int nss; caddr_t v; struct vmspace *vm = p->p_vmspace; if ((caddr_t)sp <= vm->vm_maxsaddr || (unsigned)sp >= (unsigned)USRSTACK) return (1); nss = roundup(USRSTACK - (unsigned)sp, PAGE_SIZE); if (nss > p->p_rlimit[RLIMIT_STACK].rlim_cur) return (0); if (vm->vm_ssize && roundup(vm->vm_ssize << PAGE_SHIFT, SGROWSIZ) < nss) { int grow_amount; /* * If necessary, grow the VM that the stack occupies * to allow for the rlimit. This allows us to not have * to allocate all of the VM up-front in execve (which * is expensive). * Grow the VM by the amount requested rounded up to * the nearest SGROWSIZ to provide for some hysteresis. */ grow_amount = roundup((nss - (vm->vm_ssize << PAGE_SHIFT)), SGROWSIZ); v = (char *)USRSTACK - roundup(vm->vm_ssize << PAGE_SHIFT, SGROWSIZ) - grow_amount; /* * If there isn't enough room to extend by SGROWSIZ, then * just extend to the maximum size */ if (v < vm->vm_maxsaddr) { v = vm->vm_maxsaddr; grow_amount = MAXSSIZ - (vm->vm_ssize << PAGE_SHIFT); } if ((grow_amount == 0) || (vm_map_find(&vm->vm_map, NULL, 0, (vm_offset_t *)&v, grow_amount, FALSE, VM_PROT_ALL, VM_PROT_ALL, 0) != KERN_SUCCESS)) { return (0); } vm->vm_ssize += grow_amount >> PAGE_SHIFT; } return (1); } /* * Implement the pre-zeroed page mechanism. * This routine is called from the idle loop. */ int vm_page_zero_idle() { static int free_rover; vm_page_t m; int s; #ifdef WRONG if (cnt.v_free_count <= cnt.v_interrupt_free_min) return (0); #endif /* * XXX * We stop zeroing pages when there are sufficent prezeroed pages. * This threshold isn't really needed, except we want to * bypass unneeded calls to vm_page_list_find, and the * associated cache flush and latency. The pre-zero will * still be called when there are significantly more * non-prezeroed pages than zeroed pages. The threshold * of half the number of reserved pages is arbitrary, but * approximately the right amount. Eventually, we should * perhaps interrupt the zero operation when a process * is found to be ready to run. */ if (cnt.v_free_count - vm_page_zero_count <= cnt.v_free_reserved / 2) return (0); #ifdef SMP get_mplock(); #endif s = splvm(); enable_intr(); m = vm_page_list_find(PQ_FREE, free_rover); if (m != NULL) { --(*vm_page_queues[m->queue].lcnt); TAILQ_REMOVE(vm_page_queues[m->queue].pl, m, pageq); splx(s); #ifdef SMP rel_mplock(); #endif pmap_zero_page(VM_PAGE_TO_PHYS(m)); #ifdef SMP get_mplock(); #endif (void)splvm(); m->queue = PQ_ZERO + m->pc; ++(*vm_page_queues[m->queue].lcnt); TAILQ_INSERT_HEAD(vm_page_queues[m->queue].pl, m, pageq); free_rover = (free_rover + PQ_PRIME3) & PQ_L2_MASK; ++vm_page_zero_count; } splx(s); disable_intr(); #ifdef SMP rel_mplock(); #endif return (1); +} + +/* + * Tell whether this address is in some adapter memory region. + * Currently used by the kernel coredump code in order to avoid + * dumping the ``ISA memory hole'' which could cause indefinite hangs, + * or other unpredictable behaviour. + */ + +#include "isa.h" + +int +is_adapter_memory(addr) + vm_offset_t addr; +{ + +#if NISA > 0 + /* The ISA ``memory hole''. */ + if (addr >= 0xa0000 && addr < 0x100000) + return 1; +#endif + + /* + * stuff other tests for known memory-mapped devices (PCI?) + * here + */ + + return 0; } Index: head/sys/amd64/include/md_var.h =================================================================== --- head/sys/amd64/include/md_var.h (revision 29279) +++ head/sys/amd64/include/md_var.h (revision 29280) @@ -1,83 +1,84 @@ /*- * Copyright (c) 1995 Bruce D. Evans. * 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. Neither the name of the author nor the names of contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. * - * $Id: md_var.h,v 1.15 1997/06/15 02:02:55 wollman Exp $ + * $Id: md_var.h,v 1.16 1997/08/09 00:03:14 dyson Exp $ */ #ifndef _MACHINE_MD_VAR_H_ #define _MACHINE_MD_VAR_H_ /* * Miscellaneous machine-dependent declarations. */ extern int Maxmem; extern u_int atdevbase; /* offset in virtual memory of ISA io mem */ extern u_long cpu_feature; extern u_long cpu_high; extern u_long cpu_id; extern u_long cyrix_did; extern char cpu_vendor[]; extern char etext[]; extern char kstack[]; extern void (*netisrs[32]) __P((void)); extern int nfs_diskless_valid; extern char sigcode[]; extern int szsigcode; typedef void alias_for_inthand_t __P((u_int cs, u_int ef, u_int esp, u_int ss)); struct proc; struct reg; void bcopyb __P((const void *from, void *to, size_t len)); void cpu_reset __P((void)); void cpu_halt __P((void)); void cpu_power_down __P((void)); void cpu_switch_load_fs __P((void)) __asm(__STRING(cpu_switch_load_fs)); void cpu_switch_load_gs __P((void)) __asm(__STRING(cpu_switch_load_gs)); void doreti_iret __P((void)) __asm(__STRING(doreti_iret)); void doreti_iret_fault __P((void)) __asm(__STRING(doreti_iret_fault)); void doreti_popl_ds __P((void)) __asm(__STRING(doreti_popl_ds)); void doreti_popl_ds_fault __P((void)) __asm(__STRING(doreti_popl_ds_fault)); void doreti_popl_es __P((void)) __asm(__STRING(doreti_popl_es)); void doreti_popl_es_fault __P((void)) __asm(__STRING(doreti_popl_es_fault)); int fill_regs __P((struct proc *p, struct reg *regs)); void fillw __P((int /*u_short*/ pat, void *base, size_t cnt)); +int is_adapter_memory __P((vm_offset_t addr)); u_long kvtop __P((void *addr)); void setidt __P((int idx, alias_for_inthand_t *func, int typ, int dpl, int selec)); void userconfig __P((void)); void vm_bounce_init __P((void)); int vm_page_zero_idle __P((void)); #ifdef PC98 extern int need_pre_dma_flush; extern int need_post_dma_flush; #endif #endif /* !_MACHINE_MD_VAR_H_ */ Index: head/sys/i386/i386/vm_machdep.c =================================================================== --- head/sys/i386/i386/vm_machdep.c (revision 29279) +++ head/sys/i386/i386/vm_machdep.c (revision 29280) @@ -1,959 +1,987 @@ /*- * Copyright (c) 1982, 1986 The Regents of the University of California. * Copyright (c) 1989, 1990 William Jolitz * Copyright (c) 1994 John Dyson * All rights reserved. * * This code is derived from software contributed to Berkeley by * the Systems Programming Group of the University of Utah Computer * Science Department, and 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. * * from: @(#)vm_machdep.c 7.3 (Berkeley) 5/13/91 * Utah $Hdr: vm_machdep.c 1.16.1.1 89/06/23$ - * $Id: vm_machdep.c,v 1.86 1997/08/26 18:10:34 peter Exp $ + * $Id: vm_machdep.c,v 1.87 1997/09/02 20:05:31 bde Exp $ */ #include "npx.h" #include "opt_bounce.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef PC98 #include #else #include #endif #ifdef BOUNCE_BUFFERS static vm_offset_t vm_bounce_kva __P((int size, int waitok)); static void vm_bounce_kva_free __P((vm_offset_t addr, vm_offset_t size, int now)); static vm_offset_t vm_bounce_page_find __P((int count)); static void vm_bounce_page_free __P((vm_offset_t pa, int count)); static volatile int kvasfreecnt; caddr_t bouncememory; int bouncepages; static int bpwait; static vm_offset_t *bouncepa; static int bmwait, bmfreeing; #define BITS_IN_UNSIGNED (8*sizeof(unsigned)) static int bounceallocarraysize; static unsigned *bounceallocarray; static int bouncefree; #if defined(PC98) && defined (EPSON_BOUNCEDMA) #define SIXTEENMEG (3840*4096) /* 15MB boundary */ #else #define SIXTEENMEG (4096*4096) #endif #define MAXBKVA 1024 int maxbkva = MAXBKVA*PAGE_SIZE; /* special list that can be used at interrupt time for eventual kva free */ static struct kvasfree { vm_offset_t addr; vm_offset_t size; } kvaf[MAXBKVA]; /* * get bounce buffer pages (count physically contiguous) * (only 1 inplemented now) */ static vm_offset_t vm_bounce_page_find(count) int count; { int bit; int s,i; if (count != 1) panic("vm_bounce_page_find -- no support for > 1 page yet!!!"); s = splbio(); retry: for (i = 0; i < bounceallocarraysize; i++) { if (bounceallocarray[i] != 0xffffffff) { bit = ffs(~bounceallocarray[i]); if (bit) { bounceallocarray[i] |= 1 << (bit - 1) ; bouncefree -= count; splx(s); return bouncepa[(i * BITS_IN_UNSIGNED + (bit - 1))]; } } } bpwait = 1; tsleep((caddr_t) &bounceallocarray, PRIBIO, "bncwai", 0); goto retry; } static void vm_bounce_kva_free(addr, size, now) vm_offset_t addr; vm_offset_t size; int now; { int s = splbio(); kvaf[kvasfreecnt].addr = addr; kvaf[kvasfreecnt].size = size; ++kvasfreecnt; if( now) { /* * this will do wakeups */ vm_bounce_kva(0,0); } else { if (bmwait) { /* * if anyone is waiting on the bounce-map, then wakeup */ wakeup((caddr_t) io_map); bmwait = 0; } } splx(s); } /* * free count bounce buffer pages */ static void vm_bounce_page_free(pa, count) vm_offset_t pa; int count; { int allocindex; int index; int bit; if (count != 1) panic("vm_bounce_page_free -- no support for > 1 page yet!!!"); for(index=0;indexb_flags & B_BOUNCE) { printf("vm_bounce_alloc: called recursively???\n"); return; } if (bp->b_bufsize < bp->b_bcount) { printf( "vm_bounce_alloc: b_bufsize(0x%lx) < b_bcount(0x%lx) !!\n", bp->b_bufsize, bp->b_bcount); panic("vm_bounce_alloc"); } /* * This is not really necessary * if( bp->b_bufsize != bp->b_bcount) { * printf("size: %d, count: %d\n", bp->b_bufsize, bp->b_bcount); * } */ vastart = (vm_offset_t) bp->b_data; vaend = (vm_offset_t) bp->b_data + bp->b_bufsize; vapstart = trunc_page(vastart); vapend = round_page(vaend); countvmpg = (vapend - vapstart) / PAGE_SIZE; /* * if any page is above 16MB, then go into bounce-buffer mode */ va = vapstart; for (i = 0; i < countvmpg; i++) { pa = pmap_kextract(va); if (pa >= SIXTEENMEG) ++dobounceflag; if( pa == 0) panic("vm_bounce_alloc: Unmapped page"); va += PAGE_SIZE; } if (dobounceflag == 0) return; if (bouncepages < dobounceflag) panic("Not enough bounce buffers!!!"); /* * allocate a replacement kva for b_addr */ kva = vm_bounce_kva(countvmpg*PAGE_SIZE, 1); #if 0 printf("%s: vapstart: %x, vapend: %x, countvmpg: %d, kva: %x ", (bp->b_flags & B_READ) ? "read":"write", vapstart, vapend, countvmpg, kva); #endif va = vapstart; for (i = 0; i < countvmpg; i++) { pa = pmap_kextract(va); if (pa >= SIXTEENMEG) { /* * allocate a replacement page */ vm_offset_t bpa = vm_bounce_page_find(1); pmap_kenter(kva + (PAGE_SIZE * i), bpa); #if 0 printf("r(%d): (%x,%x,%x) ", i, va, pa, bpa); #endif /* * if we are writing, the copy the data into the page */ if ((bp->b_flags & B_READ) == 0) { bcopy((caddr_t) va, (caddr_t) kva + (PAGE_SIZE * i), PAGE_SIZE); } } else { /* * use original page */ pmap_kenter(kva + (PAGE_SIZE * i), pa); } va += PAGE_SIZE; } /* * flag the buffer as being bounced */ bp->b_flags |= B_BOUNCE; /* * save the original buffer kva */ bp->b_savekva = bp->b_data; /* * put our new kva into the buffer (offset by original offset) */ bp->b_data = (caddr_t) (((vm_offset_t) kva) | ((vm_offset_t) bp->b_savekva & PAGE_MASK)); #if 0 printf("b_savekva: %x, newva: %x\n", bp->b_savekva, bp->b_data); #endif return; } /* * hook into biodone to free bounce buffer */ void vm_bounce_free(bp) struct buf *bp; { int i; vm_offset_t origkva, bouncekva, bouncekvaend; /* * if this isn't a bounced buffer, then just return */ if ((bp->b_flags & B_BOUNCE) == 0) return; /* * This check is not necessary * if (bp->b_bufsize != bp->b_bcount) { * printf("vm_bounce_free: b_bufsize=%d, b_bcount=%d\n", * bp->b_bufsize, bp->b_bcount); * } */ origkva = (vm_offset_t) bp->b_savekva; bouncekva = (vm_offset_t) bp->b_data; /* printf("free: %d ", bp->b_bufsize); */ /* * check every page in the kva space for b_addr */ for (i = 0; i < bp->b_bufsize; ) { vm_offset_t mybouncepa; vm_offset_t copycount; copycount = round_page(bouncekva + 1) - bouncekva; mybouncepa = pmap_kextract(trunc_page(bouncekva)); /* * if this is a bounced pa, then process as one */ if ( mybouncepa != pmap_kextract( trunc_page( origkva))) { vm_offset_t tocopy = copycount; if (i + tocopy > bp->b_bufsize) tocopy = bp->b_bufsize - i; /* * if this is a read, then copy from bounce buffer into original buffer */ if (bp->b_flags & B_READ) bcopy((caddr_t) bouncekva, (caddr_t) origkva, tocopy); /* * free the bounce allocation */ /* printf("(kva: %x, pa: %x)", bouncekva, mybouncepa); */ vm_bounce_page_free(mybouncepa, 1); } origkva += copycount; bouncekva += copycount; i += copycount; } /* printf("\n"); */ /* * add the old kva into the "to free" list */ bouncekva= trunc_page((vm_offset_t) bp->b_data); bouncekvaend= round_page((vm_offset_t)bp->b_data + bp->b_bufsize); /* printf("freeva: %d\n", (bouncekvaend - bouncekva) / PAGE_SIZE); */ vm_bounce_kva_free( bouncekva, (bouncekvaend - bouncekva), 0); bp->b_data = bp->b_savekva; bp->b_savekva = 0; bp->b_flags &= ~B_BOUNCE; return; } /* * init the bounce buffer system */ void vm_bounce_init() { int i; kvasfreecnt = 0; if (bouncepages == 0) return; bounceallocarraysize = (bouncepages + BITS_IN_UNSIGNED - 1) / BITS_IN_UNSIGNED; bounceallocarray = malloc(bounceallocarraysize * sizeof(unsigned), M_TEMP, M_NOWAIT); if (!bounceallocarray) panic("Cannot allocate bounce resource array"); bouncepa = malloc(bouncepages * sizeof(vm_offset_t), M_TEMP, M_NOWAIT); if (!bouncepa) panic("Cannot allocate physical memory array"); for(i=0;i= SIXTEENMEG) panic("bounce memory out of range"); if( pa == 0) panic("bounce memory not resident"); bouncepa[i] = pa; bounceallocarray[i/(8*sizeof(int))] &= ~(1<<(i%(8*sizeof(int)))); } bouncefree = bouncepages; } #endif /* BOUNCE_BUFFERS */ /* * quick version of vm_fault */ void vm_fault_quick(v, prot) caddr_t v; int prot; { if (prot & VM_PROT_WRITE) subyte(v, fubyte(v)); else fubyte(v); } /* * Finish a fork operation, with process p2 nearly set up. * Copy and update the pcb, set up the stack so that the child * ready to run and return to user mode. */ void cpu_fork(p1, p2) register struct proc *p1, *p2; { struct pcb *pcb2 = &p2->p_addr->u_pcb; /* Ensure that p1's pcb is up to date. */ if (npxproc == p1) npxsave(&p1->p_addr->u_pcb.pcb_savefpu); /* Copy p1's pcb. */ p2->p_addr->u_pcb = p1->p_addr->u_pcb; /* * Create a new fresh stack for the new process. * Copy the trap frame for the return to user mode as if from a * syscall. This copies the user mode register values. */ p2->p_md.md_regs = (struct trapframe *) ((int)p2->p_addr + UPAGES * PAGE_SIZE) - 1; *p2->p_md.md_regs = *p1->p_md.md_regs; /* * Set registers for trampoline to user mode. Leave space for the * return address on stack. These are the kernel mode register values. */ pcb2->pcb_cr3 = vtophys(p2->p_vmspace->vm_pmap.pm_pdir); pcb2->pcb_edi = p2->p_md.md_regs->tf_edi; pcb2->pcb_esi = (int)fork_return; pcb2->pcb_ebp = p2->p_md.md_regs->tf_ebp; pcb2->pcb_esp = (int)p2->p_md.md_regs - sizeof(void *); pcb2->pcb_ebx = (int)p2; pcb2->pcb_eip = (int)fork_trampoline; /* * pcb2->pcb_ldt: duplicated below, if necessary. * pcb2->pcb_ldt_len: cloned above. * pcb2->pcb_savefpu: cloned above. * pcb2->pcb_flags: cloned above (always 0 here?). * pcb2->pcb_onfault: cloned above (always NULL here?). */ #ifdef VM86 /* * XXX don't copy the i/o pages. this should probably be fixed. */ pcb2->pcb_ext = 0; #endif #ifdef USER_LDT /* Copy the LDT, if necessary. */ if (pcb2->pcb_ldt != 0) { union descriptor *new_ldt; size_t len = pcb2->pcb_ldt_len * sizeof(union descriptor); new_ldt = (union descriptor *)kmem_alloc(kernel_map, len); bcopy(pcb2->pcb_ldt, new_ldt, len); pcb2->pcb_ldt = (caddr_t)new_ldt; } #endif /* * Now, cpu_switch() can schedule the new process. * pcb_esp is loaded pointing to the cpu_switch() stack frame * containing the return address when exiting cpu_switch. * This will normally be to proc_trampoline(), which will have * %ebx loaded with the new proc's pointer. proc_trampoline() * will set up a stack to call fork_return(p, frame); to complete * the return to user-mode. */ } /* * Intercept the return address from a freshly forked process that has NOT * been scheduled yet. * * This is needed to make kernel threads stay in kernel mode. */ void cpu_set_fork_handler(p, func, arg) struct proc *p; void (*func) __P((void *)); void *arg; { /* * Note that the trap frame follows the args, so the function * is really called like this: func(arg, frame); */ p->p_addr->u_pcb.pcb_esi = (int) func; /* function */ p->p_addr->u_pcb.pcb_ebx = (int) arg; /* first arg */ } void cpu_exit(p) register struct proc *p; { #if defined(USER_LDT) || defined(VM86) struct pcb *pcb = &p->p_addr->u_pcb; #endif #if NNPX > 0 npxexit(p); #endif /* NNPX */ #ifdef VM86 if (pcb->pcb_ext != 0) { /* * XXX do we need to move the TSS off the allocated pages * before freeing them? (not done here) */ kmem_free(kernel_map, (vm_offset_t)pcb->pcb_ext, ctob(IOPAGES + 1)); pcb->pcb_ext = 0; } #endif #ifdef USER_LDT if (pcb->pcb_ldt != 0) { if (pcb == curpcb) lldt(GSEL(GUSERLDT_SEL, SEL_KPL)); kmem_free(kernel_map, (vm_offset_t)pcb->pcb_ldt, pcb->pcb_ldt_len * sizeof(union descriptor)); pcb->pcb_ldt_len = (int)pcb->pcb_ldt = 0; } #endif cnt.v_swtch++; cpu_switch(p); panic("cpu_exit"); } void cpu_wait(p) struct proc *p; { /* drop per-process resources */ pmap_dispose_proc(p); vmspace_free(p->p_vmspace); } /* * Dump the machine specific header information at the start of a core dump. */ int cpu_coredump(p, vp, cred) struct proc *p; struct vnode *vp; struct ucred *cred; { return (vn_rdwr(UIO_WRITE, vp, (caddr_t) p->p_addr, ctob(UPAGES), (off_t)0, UIO_SYSSPACE, IO_NODELOCKED|IO_UNIT, cred, (int *)NULL, p)); } #ifdef notyet static void setredzone(pte, vaddr) u_short *pte; caddr_t vaddr; { /* eventually do this by setting up an expand-down stack segment for ss0: selector, allowing stack access down to top of u. this means though that protection violations need to be handled thru a double fault exception that must do an integral task switch to a known good context, within which a dump can be taken. a sensible scheme might be to save the initial context used by sched (that has physical memory mapped 1:1 at bottom) and take the dump while still in mapped mode */ } #endif /* * Convert kernel VA to physical address */ u_long kvtop(void *addr) { vm_offset_t va; va = pmap_kextract((vm_offset_t)addr); if (va == 0) panic("kvtop: zero page frame"); return((int)va); } /* * Map an IO request into kernel virtual address space. * * All requests are (re)mapped into kernel VA space. * Notice that we use b_bufsize for the size of the buffer * to be mapped. b_bcount might be modified by the driver. */ void vmapbuf(bp) register struct buf *bp; { register caddr_t addr, v, kva; vm_offset_t pa; if ((bp->b_flags & B_PHYS) == 0) panic("vmapbuf"); for (v = bp->b_saveaddr, addr = (caddr_t)trunc_page(bp->b_data); addr < bp->b_data + bp->b_bufsize; addr += PAGE_SIZE, v += PAGE_SIZE) { /* * Do the vm_fault if needed; do the copy-on-write thing * when reading stuff off device into memory. */ vm_fault_quick(addr, (bp->b_flags&B_READ)?(VM_PROT_READ|VM_PROT_WRITE):VM_PROT_READ); pa = trunc_page(pmap_kextract((vm_offset_t) addr)); if (pa == 0) panic("vmapbuf: page not present"); vm_page_hold(PHYS_TO_VM_PAGE(pa)); pmap_kenter((vm_offset_t) v, pa); } kva = bp->b_saveaddr; bp->b_saveaddr = bp->b_data; bp->b_data = kva + (((vm_offset_t) bp->b_data) & PAGE_MASK); } /* * Free the io map PTEs associated with this IO operation. * We also invalidate the TLB entries and restore the original b_addr. */ void vunmapbuf(bp) register struct buf *bp; { register caddr_t addr; vm_offset_t pa; if ((bp->b_flags & B_PHYS) == 0) panic("vunmapbuf"); for (addr = (caddr_t)trunc_page(bp->b_data); addr < bp->b_data + bp->b_bufsize; addr += PAGE_SIZE) { pa = trunc_page(pmap_kextract((vm_offset_t) addr)); pmap_kremove((vm_offset_t) addr); vm_page_unhold(PHYS_TO_VM_PAGE(pa)); } bp->b_data = bp->b_saveaddr; } /* * Force reset the processor by invalidating the entire address space! */ void cpu_reset() { #ifdef PC98 /* * Attempt to do a CPU reset via CPU reset port. */ asm("cli"); outb(0x37, 0x0f); /* SHUT0 = 0. */ outb(0x37, 0x0b); /* SHUT1 = 0. */ outb(0xf0, 0x00); /* Reset. */ #else /* * Attempt to do a CPU reset via the keyboard controller, * do not turn of the GateA20, as any machine that fails * to do the reset here would then end up in no man's land. */ #if !defined(BROKEN_KEYBOARD_RESET) outb(IO_KBD + 4, 0xFE); DELAY(500000); /* wait 0.5 sec to see if that did it */ printf("Keyboard reset did not work, attempting CPU shutdown\n"); DELAY(1000000); /* wait 1 sec for printf to complete */ #endif #endif /* PC98 */ /* force a shutdown by unmapping entire address space ! */ bzero((caddr_t) PTD, PAGE_SIZE); /* "good night, sweet prince .... " */ invltlb(); /* NOTREACHED */ while(1); } /* * Grow the user stack to allow for 'sp'. This version grows the stack in * chunks of SGROWSIZ. */ int grow(p, sp) struct proc *p; u_int sp; { unsigned int nss; caddr_t v; struct vmspace *vm = p->p_vmspace; if ((caddr_t)sp <= vm->vm_maxsaddr || (unsigned)sp >= (unsigned)USRSTACK) return (1); nss = roundup(USRSTACK - (unsigned)sp, PAGE_SIZE); if (nss > p->p_rlimit[RLIMIT_STACK].rlim_cur) return (0); if (vm->vm_ssize && roundup(vm->vm_ssize << PAGE_SHIFT, SGROWSIZ) < nss) { int grow_amount; /* * If necessary, grow the VM that the stack occupies * to allow for the rlimit. This allows us to not have * to allocate all of the VM up-front in execve (which * is expensive). * Grow the VM by the amount requested rounded up to * the nearest SGROWSIZ to provide for some hysteresis. */ grow_amount = roundup((nss - (vm->vm_ssize << PAGE_SHIFT)), SGROWSIZ); v = (char *)USRSTACK - roundup(vm->vm_ssize << PAGE_SHIFT, SGROWSIZ) - grow_amount; /* * If there isn't enough room to extend by SGROWSIZ, then * just extend to the maximum size */ if (v < vm->vm_maxsaddr) { v = vm->vm_maxsaddr; grow_amount = MAXSSIZ - (vm->vm_ssize << PAGE_SHIFT); } if ((grow_amount == 0) || (vm_map_find(&vm->vm_map, NULL, 0, (vm_offset_t *)&v, grow_amount, FALSE, VM_PROT_ALL, VM_PROT_ALL, 0) != KERN_SUCCESS)) { return (0); } vm->vm_ssize += grow_amount >> PAGE_SHIFT; } return (1); } /* * Implement the pre-zeroed page mechanism. * This routine is called from the idle loop. */ int vm_page_zero_idle() { static int free_rover; vm_page_t m; int s; #ifdef WRONG if (cnt.v_free_count <= cnt.v_interrupt_free_min) return (0); #endif /* * XXX * We stop zeroing pages when there are sufficent prezeroed pages. * This threshold isn't really needed, except we want to * bypass unneeded calls to vm_page_list_find, and the * associated cache flush and latency. The pre-zero will * still be called when there are significantly more * non-prezeroed pages than zeroed pages. The threshold * of half the number of reserved pages is arbitrary, but * approximately the right amount. Eventually, we should * perhaps interrupt the zero operation when a process * is found to be ready to run. */ if (cnt.v_free_count - vm_page_zero_count <= cnt.v_free_reserved / 2) return (0); #ifdef SMP get_mplock(); #endif s = splvm(); enable_intr(); m = vm_page_list_find(PQ_FREE, free_rover); if (m != NULL) { --(*vm_page_queues[m->queue].lcnt); TAILQ_REMOVE(vm_page_queues[m->queue].pl, m, pageq); splx(s); #ifdef SMP rel_mplock(); #endif pmap_zero_page(VM_PAGE_TO_PHYS(m)); #ifdef SMP get_mplock(); #endif (void)splvm(); m->queue = PQ_ZERO + m->pc; ++(*vm_page_queues[m->queue].lcnt); TAILQ_INSERT_HEAD(vm_page_queues[m->queue].pl, m, pageq); free_rover = (free_rover + PQ_PRIME3) & PQ_L2_MASK; ++vm_page_zero_count; } splx(s); disable_intr(); #ifdef SMP rel_mplock(); #endif return (1); +} + +/* + * Tell whether this address is in some adapter memory region. + * Currently used by the kernel coredump code in order to avoid + * dumping the ``ISA memory hole'' which could cause indefinite hangs, + * or other unpredictable behaviour. + */ + +#include "isa.h" + +int +is_adapter_memory(addr) + vm_offset_t addr; +{ + +#if NISA > 0 + /* The ISA ``memory hole''. */ + if (addr >= 0xa0000 && addr < 0x100000) + return 1; +#endif + + /* + * stuff other tests for known memory-mapped devices (PCI?) + * here + */ + + return 0; } Index: head/sys/i386/include/md_var.h =================================================================== --- head/sys/i386/include/md_var.h (revision 29279) +++ head/sys/i386/include/md_var.h (revision 29280) @@ -1,83 +1,84 @@ /*- * Copyright (c) 1995 Bruce D. Evans. * 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. Neither the name of the author nor the names of contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. * - * $Id: md_var.h,v 1.15 1997/06/15 02:02:55 wollman Exp $ + * $Id: md_var.h,v 1.16 1997/08/09 00:03:14 dyson Exp $ */ #ifndef _MACHINE_MD_VAR_H_ #define _MACHINE_MD_VAR_H_ /* * Miscellaneous machine-dependent declarations. */ extern int Maxmem; extern u_int atdevbase; /* offset in virtual memory of ISA io mem */ extern u_long cpu_feature; extern u_long cpu_high; extern u_long cpu_id; extern u_long cyrix_did; extern char cpu_vendor[]; extern char etext[]; extern char kstack[]; extern void (*netisrs[32]) __P((void)); extern int nfs_diskless_valid; extern char sigcode[]; extern int szsigcode; typedef void alias_for_inthand_t __P((u_int cs, u_int ef, u_int esp, u_int ss)); struct proc; struct reg; void bcopyb __P((const void *from, void *to, size_t len)); void cpu_reset __P((void)); void cpu_halt __P((void)); void cpu_power_down __P((void)); void cpu_switch_load_fs __P((void)) __asm(__STRING(cpu_switch_load_fs)); void cpu_switch_load_gs __P((void)) __asm(__STRING(cpu_switch_load_gs)); void doreti_iret __P((void)) __asm(__STRING(doreti_iret)); void doreti_iret_fault __P((void)) __asm(__STRING(doreti_iret_fault)); void doreti_popl_ds __P((void)) __asm(__STRING(doreti_popl_ds)); void doreti_popl_ds_fault __P((void)) __asm(__STRING(doreti_popl_ds_fault)); void doreti_popl_es __P((void)) __asm(__STRING(doreti_popl_es)); void doreti_popl_es_fault __P((void)) __asm(__STRING(doreti_popl_es_fault)); int fill_regs __P((struct proc *p, struct reg *regs)); void fillw __P((int /*u_short*/ pat, void *base, size_t cnt)); +int is_adapter_memory __P((vm_offset_t addr)); u_long kvtop __P((void *addr)); void setidt __P((int idx, alias_for_inthand_t *func, int typ, int dpl, int selec)); void userconfig __P((void)); void vm_bounce_init __P((void)); int vm_page_zero_idle __P((void)); #ifdef PC98 extern int need_pre_dma_flush; extern int need_post_dma_flush; #endif #endif /* !_MACHINE_MD_VAR_H_ */ Index: head/sys/i386/isa/wd.c =================================================================== --- head/sys/i386/isa/wd.c (revision 29279) +++ head/sys/i386/isa/wd.c (revision 29280) @@ -1,2414 +1,2418 @@ /*- * 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. * * from: @(#)wd.c 7.2 (Berkeley) 5/9/91 - * $Id: wd.c,v 1.135 1997/08/09 01:44:25 julian Exp $ + * $Id: wd.c,v 1.136 1997/09/04 18:49:47 sos Exp $ */ /* TODO: * o Bump error count after timeout. * o Satisfy ATA timing in all cases. * o Finish merging berry/sos timeout code (bump error count...). * o Merge/fix TIH/NetBSD bad144 code. * o Don't use polling except for initialization. Need to * reorganize the state machine. Then "extra" interrupts * shouldn't happen (except maybe one for initialization). * o Fix disklabel, boot and driver inconsistencies with * bad144 in standard versions. * o Support extended DOS partitions. * o Support swapping to DOS partitions. * o Handle bad sectors, clustering, disklabelling, DOS * partitions and swapping driver-independently. Use * i386/dkbad.c for bad sectors. Swapping will need new * driver entries for polled reinit and polled write). */ #include "wd.h" #ifdef NWDC #undef NWDC #endif #include "wdc.h" #include "opt_wd.h" #include "opt_atapi.h" #if NWDC > 0 #include #include #include #include #include #include #include #include #include #ifdef DEVFS #include #endif /*DEVFS*/ #include #include #include #include #include #include #include #include #include #include #include #include #ifdef ATAPI #include #endif #ifdef CMD640 #include #endif /*CMD640*/ extern void wdstart(int ctrlr); #define TIMEOUT 10000 #define RETRIES 5 /* number of retries before giving up */ #define RECOVERYTIME 500000 /* usec for controller to recover after err */ #define MAXTRANSFER 255 /* max size of transfer in sectors */ /* correct max is 256 but some controllers */ /* can't handle that in all cases */ #define WDOPT_32BIT 0x8000 #define WDOPT_SLEEPHACK 0x4000 #define WDOPT_DMA 0x2000 #define WDOPT_FORCEHD(x) (((x)&0x0f00)>>8) #define WDOPT_MULTIMASK 0x00ff /* * This biotab field doubles as a field for the physical unit number on * the controller. */ #define id_physid id_scsiid /* * Drive states. Used to initialize drive. */ #define CLOSED 0 /* disk is closed. */ #define WANTOPEN 1 /* open requested, not started */ #define RECAL 2 /* doing restore */ #define OPEN 3 /* done with open */ #define PRIMARY 0 /* * Disk geometry. A small part of struct disklabel. * XXX disklabel.5 contains an old clone of disklabel.h. */ struct diskgeom { u_long d_secsize; /* # of bytes per sector */ u_long d_nsectors; /* # of data sectors per track */ u_long d_ntracks; /* # of tracks per cylinder */ u_long d_ncylinders; /* # of data cylinders per unit */ u_long d_secpercyl; /* # of data sectors per cylinder */ u_long d_secperunit; /* # of data sectors per unit */ u_long d_precompcyl; /* XXX always 0 */ }; /* * The structure of a disk drive. */ struct disk { long dk_bc; /* byte count left */ short dk_skip; /* blocks already transferred */ int dk_ctrlr; /* physical controller number */ #ifdef CMD640 int dk_ctrlr_cmd640;/* controller number for CMD640 quirk */ #endif int dk_unit; /* physical unit number */ int dk_lunit; /* logical unit number */ char dk_state; /* control state */ u_char dk_status; /* copy of status reg. */ u_char dk_error; /* copy of error reg. */ u_char dk_timeout; /* countdown to next timeout */ int dk_port; /* i/o port base */ #ifdef DEVFS void *dk_bdev; /* devfs token for whole disk */ void *dk_cdev; /* devfs token for raw whole disk */ #endif u_long cfg_flags; /* configured characteristics */ short dk_flags; /* drive characteristics found */ #define DKFL_SINGLE 0x00004 /* sector at a time mode */ #define DKFL_ERROR 0x00008 /* processing a disk error */ #define DKFL_LABELLING 0x00080 /* readdisklabel() in progress */ #define DKFL_32BIT 0x00100 /* use 32-bit i/o mode */ #define DKFL_MULTI 0x00200 /* use multi-i/o mode */ #define DKFL_BADSCAN 0x00400 /* report all errors */ #define DKFL_USEDMA 0x00800 /* use DMA for data transfers */ #define DKFL_DMA 0x01000 /* using DMA on this transfer-- DKFL_SINGLE * overrides this */ struct wdparams dk_params; /* ESDI/IDE drive/controller parameters */ int dk_dkunit; /* disk stats unit number */ int dk_multi; /* multi transfers */ int dk_currentiosize; /* current io size */ struct diskgeom dk_dd; /* device configuration data */ struct diskslices *dk_slices; /* virtual drives */ void *dk_dmacookie; /* handle for DMA services */ }; #define WD_COUNT_RETRIES static int wdtest = 0; static struct disk *wddrives[NWD]; /* table of units */ static struct buf_queue_head drive_queue[NWD]; /* head of queue per drive */ static struct { int b_active; } wdutab[NWD]; /* static struct buf wdtab[NWDC]; */ static struct { struct buf_queue_head controller_queue; int b_errcnt; int b_active; } wdtab[NWDC]; struct wddma wddma; #ifdef notyet static struct buf rwdbuf[NWD]; /* buffers for raw IO */ #endif static int wdprobe(struct isa_device *dvp); static int wdattach(struct isa_device *dvp); static void wdustart(struct disk *du); static int wdcontrol(struct buf *bp); static int wdcommand(struct disk *du, u_int cylinder, u_int head, u_int sector, u_int count, u_int command); static int wdsetctlr(struct disk *du); #if 0 static int wdwsetctlr(struct disk *du); #endif static int wdsetmode(int mode, void *wdinfo); static int wdgetctlr(struct disk *du); static void wderror(struct buf *bp, struct disk *du, char *mesg); static void wdflushirq(struct disk *du, int old_ipl); static int wdreset(struct disk *du); static void wdsleep(int ctrlr, char *wmesg); static void wdstrategy1(struct buf *bp); static timeout_t wdtimeout; static int wdunwedge(struct disk *du); static int wdwait(struct disk *du, u_char bits_wanted, int timeout); struct isa_driver wdcdriver = { wdprobe, wdattach, "wdc", }; static d_open_t wdopen; static d_close_t wdclose; static d_strategy_t wdstrategy; static d_ioctl_t wdioctl; static d_dump_t wddump; static d_psize_t wdsize; #define CDEV_MAJOR 3 #define BDEV_MAJOR 0 static struct cdevsw wd_cdevsw; static struct bdevsw wd_bdevsw = { wdopen, wdclose, wdstrategy, wdioctl, /*0*/ wddump, wdsize, D_DISK, "wd", &wd_cdevsw, -1 }; #ifdef CMD640 static int atapictrlr; static int eide_quirks; #endif /* * Here we use the pci-subsystem to find out, whether there is * a cmd640b-chip attached on this pci-bus. This public routine * will be called by wdc_p.c . */ #ifdef CMD640 void wdc_pci(int quirks) { eide_quirks = quirks; } #endif /* * Probe for controller. */ static int wdprobe(struct isa_device *dvp) { int unit = dvp->id_unit; struct disk *du; if (unit >= NWDC) return (0); du = malloc(sizeof *du, M_TEMP, M_NOWAIT); if (du == NULL) return (0); bzero(du, sizeof *du); du->dk_ctrlr = dvp->id_unit; du->dk_port = dvp->id_iobase; /* check if we have registers that work */ outb(du->dk_port + wd_sdh, WDSD_IBM); /* set unit 0 */ outb(du->dk_port + wd_cyl_lo, 0xa5); /* wd_cyl_lo is read/write */ if (inb(du->dk_port + wd_cyl_lo) == 0xff) { /* XXX too weak */ #ifdef ATAPI /* There is no master, try the ATAPI slave. */ outb(du->dk_port + wd_sdh, WDSD_IBM | 0x10); outb(du->dk_port + wd_cyl_lo, 0xa5); if (inb(du->dk_port + wd_cyl_lo) == 0xff) #endif goto nodevice; } if (wdreset(du) == 0) goto reset_ok; #ifdef ATAPI /* test for ATAPI signature */ outb(du->dk_port + wd_sdh, WDSD_IBM); /* master */ if (inb(du->dk_port + wd_cyl_lo) == 0x14 && inb(du->dk_port + wd_cyl_hi) == 0xeb) goto reset_ok; du->dk_unit = 1; outb(du->dk_port + wd_sdh, WDSD_IBM | 0x10); /* slave */ if (inb(du->dk_port + wd_cyl_lo) == 0x14 && inb(du->dk_port + wd_cyl_hi) == 0xeb) goto reset_ok; #endif DELAY(RECOVERYTIME); if (wdreset(du) != 0) goto nodevice; reset_ok: /* execute a controller only command */ if (wdcommand(du, 0, 0, 0, 0, WDCC_DIAGNOSE) != 0 || wdwait(du, 0, TIMEOUT) < 0) goto nodevice; /* * drive(s) did not time out during diagnostic : * Get error status and check that both drives are OK. * Table 9-2 of ATA specs suggests that we must check for * a value of 0x01 * * Strangely, some controllers will return a status of * 0x81 (drive 0 OK, drive 1 failure), and then when * the DRV bit is set, return status of 0x01 (OK) for * drive 2. (This seems to contradict the ATA spec.) */ du->dk_error = inb(du->dk_port + wd_error); /* printf("Error : %x\n", du->dk_error); */ if(du->dk_error != 0x01) { if(du->dk_error & 0x80) { /* drive 1 failure */ /* first set the DRV bit */ u_int sdh; sdh = inb(du->dk_port+ wd_sdh); sdh = sdh | 0x10; outb(du->dk_port+ wd_sdh, sdh); /* Wait, to make sure drv 1 has completed diags */ if ( wdwait(du, 0, TIMEOUT) < 0) goto nodevice; /* Get status for drive 1 */ du->dk_error = inb(du->dk_port + wd_error); /* printf("Error (drv 1) : %x\n", du->dk_error); */ /* * Sometimes (apparently mostly with ATAPI * drives involved) 0x81 really means 0x81 * (drive 0 OK, drive 1 failed). */ if(du->dk_error != 0x01 && du->dk_error != 0x81) goto nodevice; } else /* drive 0 fail */ goto nodevice; } free(du, M_TEMP); return (IO_WDCSIZE); nodevice: free(du, M_TEMP); return (0); } /* * Attach each drive if possible. */ static int wdattach(struct isa_device *dvp) { #ifdef DEVFS int mynor; #endif int unit, lunit; struct isa_device *wdup; struct disk *du; struct wdparams *wp; if (dvp->id_unit >= NWDC) return (0); #ifdef CMD640 if (eide_quirks & Q_CMD640B) { if (dvp->id_unit == PRIMARY) { printf("wdc0: CMD640B workaround enabled\n"); TAILQ_INIT( &wdtab[PRIMARY].controller_queue); } } else TAILQ_INIT( &wdtab[dvp->id_unit].controller_queue); #else TAILQ_INIT( &wdtab[dvp->id_unit].controller_queue); #endif for (wdup = isa_biotab_wdc; wdup->id_driver != 0; wdup++) { if (wdup->id_iobase != dvp->id_iobase) continue; lunit = wdup->id_unit; if (lunit >= NWD) continue; unit = wdup->id_physid; du = malloc(sizeof *du, M_TEMP, M_NOWAIT); if (du == NULL) continue; if (wddrives[lunit] != NULL) panic("drive attached twice"); wddrives[lunit] = du; TAILQ_INIT( &drive_queue[lunit]); bzero(du, sizeof *du); du->dk_ctrlr = dvp->id_unit; #ifdef CMD640 if (eide_quirks & Q_CMD640B) { du->dk_ctrlr_cmd640 = PRIMARY; } else { du->dk_ctrlr_cmd640 = du->dk_ctrlr; } #endif du->dk_unit = unit; du->dk_lunit = lunit; du->dk_port = dvp->id_iobase; /* * Use the individual device flags or the controller * flags. */ du->cfg_flags = wdup->id_flags | ((dvp->id_flags) >> (16 * unit)); if (wdgetctlr(du) == 0) { /* * Print out description of drive. * wdp_model may not be null terminated. */ printf("wdc%d: unit %d (wd%d): <%.*s>", dvp->id_unit, unit, lunit, sizeof du->dk_params.wdp_model, du->dk_params.wdp_model); if (du->dk_flags & DKFL_USEDMA) printf(", DMA"); if (du->dk_flags & DKFL_32BIT) printf(", 32-bit"); if (du->dk_multi > 1) printf(", multi-block-%d", du->dk_multi); if (du->cfg_flags & WDOPT_SLEEPHACK) printf(", sleep-hack"); printf("\n"); if (du->dk_params.wdp_heads == 0) printf("wd%d: size unknown, using %s values\n", lunit, du->dk_dd.d_secperunit > 17 ? "BIOS" : "fake"); printf( "wd%d: %luMB (%lu sectors), %lu cyls, %lu heads, %lu S/T, %lu B/S\n", lunit, du->dk_dd.d_secperunit / ((1024L * 1024L) / du->dk_dd.d_secsize), du->dk_dd.d_secperunit, du->dk_dd.d_ncylinders, du->dk_dd.d_ntracks, du->dk_dd.d_nsectors, du->dk_dd.d_secsize); if (bootverbose) { wp = &du->dk_params; printf( "wd%d: ATA INQUIRE valid = %04x, dmamword = %04x, apio = %04x, udma = %04x\n", du->dk_lunit, wp->wdp_atavalid, wp->wdp_dmamword, wp->wdp_eidepiomodes, wp->wdp_udmamode); } /* * Start timeout routine for this drive. * XXX timeout should be per controller. */ wdtimeout(du); #ifdef DEVFS mynor = dkmakeminor(unit, WHOLE_DISK_SLICE, RAW_PART); du->dk_bdev = devfs_add_devswf(&wd_bdevsw, mynor, DV_BLK, UID_ROOT, GID_OPERATOR, 0640, "wd%d", unit); du->dk_cdev = devfs_add_devswf(&wd_cdevsw, mynor, DV_CHR, UID_ROOT, GID_OPERATOR, 0640, "rwd%d", unit); #endif if (dk_ndrive < DK_NDRIVE) { sprintf(dk_names[dk_ndrive], "wd%d", lunit); /* * XXX we don't know the transfer rate of the * drive. Guess the maximum ISA rate of * 4MB/sec. `wpms' is words per _second_ * according to iostat. */ dk_wpms[dk_ndrive] = 4 * 1024 * 1024 / 2; du->dk_dkunit = dk_ndrive++; } else { du->dk_dkunit = -1; } } else { free(du, M_TEMP); wddrives[lunit] = NULL; } } #ifdef ATAPI /* * Probe all free IDE units, searching for ATAPI drives. */ for (unit=0; unit<2; ++unit) { for (lunit=0; lunitdk_ctrlr == dvp->id_unit && wddrives[lunit]->dk_unit == unit) goto next; #ifdef CMD640 if (atapi_attach (dvp->id_unit, unit, dvp->id_iobase)) atapictrlr = dvp->id_unit; #else atapi_attach (dvp->id_unit, unit, dvp->id_iobase); #endif next: ; } #endif /* * Discard any interrupts generated by wdgetctlr(). wdflushirq() * doesn't work now because the ambient ipl is too high. */ #ifdef CMD640 if (eide_quirks & Q_CMD640B) { wdtab[PRIMARY].b_active = 2; } else { wdtab[dvp->id_unit].b_active = 2; } #else wdtab[dvp->id_unit].b_active = 2; #endif return (1); } /* Read/write routine for a buffer. Finds the proper unit, range checks * arguments, and schedules the transfer. Does not wait for the transfer * to complete. Multi-page transfers are supported. All I/O requests must * be a multiple of a sector in length. */ void wdstrategy(register struct buf *bp) { struct disk *du; int lunit = dkunit(bp->b_dev); int s; /* valid unit, controller, and request? */ if (lunit >= NWD || bp->b_blkno < 0 || (du = wddrives[lunit]) == NULL || bp->b_bcount % DEV_BSIZE != 0) { bp->b_error = EINVAL; bp->b_flags |= B_ERROR; goto done; } /* * Do bounds checking, adjust transfer, and set b_pblkno. */ if (dscheck(bp, du->dk_slices) <= 0) goto done; /* * Check for *any* block on this transfer being on the bad block list * if it is, then flag the block as a transfer that requires * bad block handling. Also, used as a hint for low level disksort * clustering code to keep from coalescing a bad transfer into * a normal transfer. Single block transfers for a large number of * blocks associated with a cluster I/O are undesirable. * * XXX the old disksort() doesn't look at B_BAD. Coalescing _is_ * desirable. We should split the results at bad blocks just * like we should split them at MAXTRANSFER boundaries. */ if (dsgetbad(bp->b_dev, du->dk_slices) != NULL) { long *badsect = dsgetbad(bp->b_dev, du->dk_slices)->bi_bad; int i; int nsecs = howmany(bp->b_bcount, DEV_BSIZE); /* XXX pblkno is too physical. */ daddr_t nspblkno = bp->b_pblkno - du->dk_slices->dss_slices[dkslice(bp->b_dev)].ds_offset; int blkend = nspblkno + nsecs; for (i = 0; badsect[i] != -1 && badsect[i] < blkend; i++) { if (badsect[i] >= nspblkno) { bp->b_flags |= B_BAD; break; } } } /* queue transfer on drive, activate drive and controller if idle */ s = splbio(); tqdisksort(&drive_queue[lunit], bp); if (wdutab[lunit].b_active == 0) wdustart(du); /* start drive */ /* Pick up changes made by readdisklabel(). */ if (du->dk_flags & DKFL_LABELLING && du->dk_state > RECAL) { wdsleep(du->dk_ctrlr, "wdlab"); du->dk_state = WANTOPEN; } #ifdef CMD640 if (wdtab[du->dk_ctrlr_cmd640].b_active == 0) #else if (wdtab[du->dk_ctrlr].b_active == 0) #endif wdstart(du->dk_ctrlr); /* start controller */ if (du->dk_dkunit >= 0) { /* * XXX perhaps we should only count successful transfers. */ dk_xfer[du->dk_dkunit]++; /* * XXX we can't count seeks correctly but we can do better * than this. E.g., assume that the geometry is correct * and count 1 seek if the starting cylinder of this i/o * differs from the starting cylinder of the previous i/o, * or count 1 seek if the starting bn of this i/o doesn't * immediately follow the ending bn of the previos i/o. */ dk_seek[du->dk_dkunit]++; } splx(s); return; done: s = splbio(); /* toss transfer, we're done early */ biodone(bp); splx(s); } static void wdstrategy1(struct buf *bp) { /* * XXX - do something to make wdstrategy() but not this block while * we're doing dsinit() and dsioctl(). */ wdstrategy(bp); } /* * Routine to queue a command to the controller. The unit's * request is linked into the active list for the controller. * If the controller is idle, the transfer is started. */ static void wdustart(register struct disk *du) { register struct buf *bp; #ifdef CMD640 int ctrlr = du->dk_ctrlr_cmd640; #else int ctrlr = du->dk_ctrlr; #endif /* unit already active? */ if (wdutab[du->dk_lunit].b_active) return; bp = drive_queue[du->dk_lunit].tqh_first; if (bp == NULL) { /* yes, an assign */ return; } TAILQ_REMOVE( &drive_queue[du->dk_lunit], bp, b_act); /* link onto controller queue */ TAILQ_INSERT_TAIL( &wdtab[ctrlr].controller_queue, bp, b_act); /* mark the drive unit as busy */ wdutab[du->dk_lunit].b_active = 1; } /* * Controller startup routine. This does the calculation, and starts * a single-sector read or write operation. Called to start a transfer, * or from the interrupt routine to continue a multi-sector transfer. * RESTRICTIONS: * 1. The transfer length must be an exact multiple of the sector size. */ void wdstart(int ctrlr) { register struct disk *du; register struct buf *bp; struct diskgeom *lp; /* XXX sic */ long blknum; long secpertrk, secpercyl; int lunit; int count; #ifdef CMD640 int ctrlr_atapi; if (eide_quirks & Q_CMD640B) { ctrlr = PRIMARY; ctrlr_atapi = atapictrlr; } else { ctrlr_atapi = ctrlr; } #endif #ifdef ATAPI if (wdtab[ctrlr].b_active == 2) wdtab[ctrlr].b_active = 0; if (wdtab[ctrlr].b_active) return; #endif /* is there a drive for the controller to do a transfer with? */ bp = wdtab[ctrlr].controller_queue.tqh_first; if (bp == NULL) { #ifdef ATAPI #ifdef CMD640 if (atapi_start && atapi_start (ctrlr_atapi)) wdtab[ctrlr].b_active = 3; #else if (atapi_start && atapi_start (ctrlr)) /* mark controller active in ATAPI mode */ wdtab[ctrlr].b_active = 3; #endif #endif return; } /* obtain controller and drive information */ lunit = dkunit(bp->b_dev); du = wddrives[lunit]; /* if not really a transfer, do control operations specially */ if (du->dk_state < OPEN) { if (du->dk_state != WANTOPEN) printf("wd%d: wdstart: weird dk_state %d\n", du->dk_lunit, du->dk_state); if (wdcontrol(bp) != 0) printf("wd%d: wdstart: wdcontrol returned nonzero, state = %d\n", du->dk_lunit, du->dk_state); return; } /* calculate transfer details */ blknum = bp->b_pblkno + du->dk_skip; #ifdef WDDEBUG if (du->dk_skip == 0) printf("wd%d: wdstart: %s %d@%d; map ", lunit, (bp->b_flags & B_READ) ? "read" : "write", bp->b_bcount, blknum); else printf(" %d)%x", du->dk_skip, inb(du->dk_port + wd_altsts)); #endif lp = &du->dk_dd; secpertrk = lp->d_nsectors; secpercyl = lp->d_secpercyl; if (du->dk_skip == 0) { du->dk_bc = bp->b_bcount; if (bp->b_flags & B_BAD /* * XXX handle large transfers inefficiently instead * of crashing on them. */ || howmany(du->dk_bc, DEV_BSIZE) > MAXTRANSFER) du->dk_flags |= DKFL_SINGLE; } if (du->dk_flags & DKFL_SINGLE && dsgetbad(bp->b_dev, du->dk_slices) != NULL) { /* XXX */ u_long ds_offset = du->dk_slices->dss_slices[dkslice(bp->b_dev)].ds_offset; blknum = transbad144(dsgetbad(bp->b_dev, du->dk_slices), blknum - ds_offset) + ds_offset; } wdtab[ctrlr].b_active = 1; /* mark controller active */ /* if starting a multisector transfer, or doing single transfers */ if (du->dk_skip == 0 || (du->dk_flags & DKFL_SINGLE)) { u_int command; u_int count; long cylin, head, sector; cylin = blknum / secpercyl; head = (blknum % secpercyl) / secpertrk; sector = blknum % secpertrk; /* * XXX this looks like an attempt to skip bad sectors * on write. */ if (wdtab[ctrlr].b_errcnt && (bp->b_flags & B_READ) == 0) du->dk_bc += DEV_BSIZE; count = howmany( du->dk_bc, DEV_BSIZE); du->dk_flags &= ~DKFL_MULTI; #ifdef B_FORMAT if (bp->b_flags & B_FORMAT) { command = WDCC_FORMAT; count = lp->d_nsectors; sector = lp->d_gap3 - 1; /* + 1 later */ } else #endif { if (du->dk_flags & DKFL_SINGLE) { command = (bp->b_flags & B_READ) ? WDCC_READ : WDCC_WRITE; count = 1; du->dk_currentiosize = 1; } else { if((du->dk_flags & DKFL_USEDMA) && wddma.wdd_dmaverify(du->dk_dmacookie, (void *)((int)bp->b_un.b_addr + du->dk_skip * DEV_BSIZE), du->dk_bc, bp->b_flags & B_READ)) { du->dk_flags |= DKFL_DMA; if( bp->b_flags & B_READ) command = WDCC_READ_DMA; else command = WDCC_WRITE_DMA; du->dk_currentiosize = count; } else if( (count > 1) && (du->dk_multi > 1)) { du->dk_flags |= DKFL_MULTI; if( bp->b_flags & B_READ) { command = WDCC_READ_MULTI; } else { command = WDCC_WRITE_MULTI; } du->dk_currentiosize = du->dk_multi; if( du->dk_currentiosize > count) du->dk_currentiosize = count; } else { if( bp->b_flags & B_READ) { command = WDCC_READ; } else { command = WDCC_WRITE; } du->dk_currentiosize = 1; } } } /* * XXX this loop may never terminate. The code to handle * counting down of retries and eventually failing the i/o * is in wdintr() and we can't get there from here. */ if (wdtest != 0) { if (--wdtest == 0) { wdtest = 100; printf("dummy wdunwedge\n"); wdunwedge(du); } } if(du->dk_dkunit >= 0) { dk_busy |= 1 << du->dk_dkunit; } if ((du->dk_flags & (DKFL_DMA|DKFL_SINGLE)) == DKFL_DMA) { wddma.wdd_dmaprep(du->dk_dmacookie, (void *)((int)bp->b_un.b_addr + du->dk_skip * DEV_BSIZE), du->dk_bc, bp->b_flags & B_READ); } while (wdcommand(du, cylin, head, sector, count, command) != 0) { wderror(bp, du, "wdstart: timeout waiting to give command"); wdunwedge(du); } #ifdef WDDEBUG printf("cylin %ld head %ld sector %ld addr %x sts %x\n", cylin, head, sector, (int)bp->b_un.b_addr + du->dk_skip * DEV_BSIZE, inb(du->dk_port + wd_altsts)); #endif } /* * Schedule wdtimeout() to wake up after a few seconds. Retrying * unmarked bad blocks can take 3 seconds! Then it is not good that * we retry 5 times. * * On the first try, we give it 10 seconds, for drives that may need * to spin up. * * XXX wdtimeout() doesn't increment the error count so we may loop * forever. More seriously, the loop isn't forever but causes a * crash. * * TODO fix b_resid bug elsewhere (fd.c....). Fix short but positive * counts being discarded after there is an error (in physio I * think). Discarding them would be OK if the (special) file offset * was not advanced. */ if (wdtab[ctrlr].b_errcnt == 0) du->dk_timeout = 1 + 10; else du->dk_timeout = 1 + 3; /* if this is a DMA op, start DMA and go away until it's done. */ if ((du->dk_flags & (DKFL_DMA|DKFL_SINGLE)) == DKFL_DMA) { wddma.wdd_dmastart(du->dk_dmacookie); return; } /* If this is a read operation, just go away until it's done. */ if (bp->b_flags & B_READ) return; /* Ready to send data? */ if (wdwait(du, WDCS_READY | WDCS_SEEKCMPLT | WDCS_DRQ, TIMEOUT) < 0) { wderror(bp, du, "wdstart: timeout waiting for DRQ"); /* * XXX what do we do now? If we've just issued the command, * then we can treat this failure the same as a command * failure. But if we are continuing a multi-sector write, * the command was issued ages ago, so we can't simply * restart it. * * XXX we waste a lot of time unnecessarily translating block * numbers to cylin/head/sector for continued i/o's. */ } count = 1; if( du->dk_flags & DKFL_MULTI) { count = howmany(du->dk_bc, DEV_BSIZE); if( count > du->dk_multi) count = du->dk_multi; if( du->dk_currentiosize > count) du->dk_currentiosize = count; } if (du->dk_flags & DKFL_32BIT) outsl(du->dk_port + wd_data, (void *)((int)bp->b_un.b_addr + du->dk_skip * DEV_BSIZE), (count * DEV_BSIZE) / sizeof(long)); else outsw(du->dk_port + wd_data, (void *)((int)bp->b_un.b_addr + du->dk_skip * DEV_BSIZE), (count * DEV_BSIZE) / sizeof(short)); du->dk_bc -= DEV_BSIZE * count; if (du->dk_dkunit >= 0) { /* * `wd's are blocks of 32 16-bit `word's according to * iostat. dk_wds[] is the one disk i/o statistic that * we can record correctly. * XXX perhaps we shouldn't record words for failed * transfers. */ dk_wds[du->dk_dkunit] += (count * DEV_BSIZE) >> 6; } } /* Interrupt routine for the controller. Acknowledge the interrupt, check for * errors on the current operation, mark it done if necessary, and start * the next request. Also check for a partially done transfer, and * continue with the next chunk if so. */ void wdintr(int unit) { register struct disk *du; register struct buf *bp; int dmastat; #ifdef CMD640 int ctrlr_atapi; if (eide_quirks & Q_CMD640B) { unit = PRIMARY; ctrlr_atapi = atapictrlr; } else { ctrlr_atapi = unit; } #endif if (wdtab[unit].b_active == 2) return; /* intr in wdflushirq() */ if (!wdtab[unit].b_active) { #ifdef WDDEBUG /* * These happen mostly because the power-mgt part of the * bios shuts us down, and we just manage to see the * interrupt from the "SLEEP" command. */ printf("wdc%d: extra interrupt\n", unit); #endif return; } #ifdef ATAPI if (wdtab[unit].b_active == 3) { /* process an ATAPI interrupt */ #ifdef CMD640 if (atapi_intr && atapi_intr (ctrlr_atapi)) #else if (atapi_intr && atapi_intr (unit)) #endif /* ATAPI op continues */ return; /* controller is free, start new op */ wdtab[unit].b_active = 0; wdstart (unit); return; } #endif bp = wdtab[unit].controller_queue.tqh_first; du = wddrives[dkunit(bp->b_dev)]; /* finish off DMA */ if (du->dk_flags & (DKFL_DMA|DKFL_USEDMA)) { /* XXX SMP boxes sometimes generate an early intr. Why? */ if ((wddma.wdd_dmastatus(du->dk_dmacookie) & WDDS_INTERRUPT) == 0) return; dmastat = wddma.wdd_dmadone(du->dk_dmacookie); } du->dk_timeout = 0; /* check drive status/failure */ if (wdwait(du, 0, TIMEOUT) < 0) { wderror(bp, du, "wdintr: timeout waiting for status"); du->dk_status |= WDCS_ERR; /* XXX */ } /* is it not a transfer, but a control operation? */ if (du->dk_state < OPEN) { wdtab[unit].b_active = 0; switch (wdcontrol(bp)) { case 0: return; case 1: wdstart(unit); return; case 2: goto done; } } /* have we an error? */ if ((du->dk_status & (WDCS_ERR | WDCS_ECCCOR)) || (((du->dk_flags & (DKFL_DMA|DKFL_SINGLE)) == DKFL_DMA) && dmastat != WDDS_INTERRUPT)) { unsigned int errstat; oops: /* * XXX bogus inb() here */ errstat = inb(du->dk_port + wd_error); if(((du->dk_flags & (DKFL_DMA|DKFL_SINGLE)) == DKFL_DMA) && (errstat & WDERR_ABORT)) { wderror(bp, du, "reverting to PIO mode"); du->dk_flags &= ~DKFL_USEDMA; } else if((du->dk_flags & DKFL_MULTI) && (errstat & WDERR_ABORT)) { wderror(bp, du, "reverting to non-multi sector mode"); du->dk_multi = 1; } if (!(du->dk_status & (WDCS_ERR | WDCS_ECCCOR)) && (((du->dk_flags & (DKFL_DMA|DKFL_SINGLE)) == DKFL_DMA) && (dmastat != WDDS_INTERRUPT))) printf("wd%d: DMA failure, DMA status %b\n", du->dk_lunit, dmastat, WDDS_BITS); #ifdef WDDEBUG wderror(bp, du, "wdintr"); #endif if ((du->dk_flags & DKFL_SINGLE) == 0) { du->dk_flags |= DKFL_ERROR; goto outt; } #ifdef B_FORMAT if (bp->b_flags & B_FORMAT) { bp->b_error = EIO; bp->b_flags |= B_ERROR; goto done; } #endif if (du->dk_flags & DKFL_BADSCAN) { bp->b_error = EIO; bp->b_flags |= B_ERROR; } else if (du->dk_status & WDCS_ERR) { if (++wdtab[unit].b_errcnt < RETRIES) { wdtab[unit].b_active = 0; } else { wderror(bp, du, "hard error"); bp->b_error = EIO; bp->b_flags |= B_ERROR; /* flag the error */ } } else if (du->dk_status & WDCS_ECCCOR) wderror(bp, du, "soft ecc"); } /* * If this was a successful read operation, fetch the data. */ if (((bp->b_flags & (B_READ | B_ERROR)) == B_READ) && !((du->dk_flags & (DKFL_DMA|DKFL_SINGLE)) == DKFL_DMA) && wdtab[unit].b_active) { int chk, dummy, multisize; multisize = chk = du->dk_currentiosize * DEV_BSIZE; if( du->dk_bc < chk) { chk = du->dk_bc; if( ((chk + DEV_BSIZE - 1) / DEV_BSIZE) < du->dk_currentiosize) { du->dk_currentiosize = (chk + DEV_BSIZE - 1) / DEV_BSIZE; multisize = du->dk_currentiosize * DEV_BSIZE; } } /* ready to receive data? */ if ((du->dk_status & (WDCS_READY | WDCS_SEEKCMPLT | WDCS_DRQ)) != (WDCS_READY | WDCS_SEEKCMPLT | WDCS_DRQ)) wderror(bp, du, "wdintr: read intr arrived early"); if (wdwait(du, WDCS_READY | WDCS_SEEKCMPLT | WDCS_DRQ, TIMEOUT) != 0) { wderror(bp, du, "wdintr: read error detected late"); goto oops; } /* suck in data */ if( du->dk_flags & DKFL_32BIT) insl(du->dk_port + wd_data, (void *)((int)bp->b_un.b_addr + du->dk_skip * DEV_BSIZE), chk / sizeof(long)); else insw(du->dk_port + wd_data, (void *)((int)bp->b_un.b_addr + du->dk_skip * DEV_BSIZE), chk / sizeof(short)); du->dk_bc -= chk; /* XXX for obsolete fractional sector reads. */ while (chk < multisize) { insw(du->dk_port + wd_data, &dummy, 1); chk += sizeof(short); } if (du->dk_dkunit >= 0) dk_wds[du->dk_dkunit] += chk >> 6; } /* final cleanup on DMA */ if (((bp->b_flags & B_ERROR) == 0) && ((du->dk_flags & (DKFL_DMA|DKFL_SINGLE)) == DKFL_DMA) && wdtab[unit].b_active) { int iosize; iosize = du->dk_currentiosize * DEV_BSIZE; du->dk_bc -= iosize; if (du->dk_dkunit >= 0) dk_wds[du->dk_dkunit] += iosize >> 6; } outt: if (wdtab[unit].b_active) { if ((bp->b_flags & B_ERROR) == 0) { du->dk_skip += du->dk_currentiosize;/* add to successful sectors */ if (wdtab[unit].b_errcnt) wderror(bp, du, "soft error"); wdtab[unit].b_errcnt = 0; /* see if more to transfer */ if (du->dk_bc > 0 && (du->dk_flags & DKFL_ERROR) == 0) { if( (du->dk_flags & DKFL_SINGLE) || ((bp->b_flags & B_READ) == 0)) { wdtab[unit].b_active = 0; wdstart(unit); } else { du->dk_timeout = 1 + 3; } return; /* next chunk is started */ } else if ((du->dk_flags & (DKFL_SINGLE | DKFL_ERROR)) == DKFL_ERROR) { du->dk_skip = 0; du->dk_flags &= ~DKFL_ERROR; du->dk_flags |= DKFL_SINGLE; wdtab[unit].b_active = 0; wdstart(unit); return; /* redo xfer sector by sector */ } } done: ; /* done with this transfer, with or without error */ du->dk_flags &= ~(DKFL_SINGLE|DKFL_DMA); TAILQ_REMOVE(&wdtab[unit].controller_queue, bp, b_act); wdtab[unit].b_errcnt = 0; bp->b_resid = bp->b_bcount - du->dk_skip * DEV_BSIZE; wdutab[du->dk_lunit].b_active = 0; du->dk_skip = 0; biodone(bp); } if(du->dk_dkunit >= 0) { dk_busy &= ~(1 << du->dk_dkunit); } /* controller idle */ wdtab[unit].b_active = 0; /* anything more on drive queue? */ wdustart(du); /* anything more for controller to do? */ #ifndef ATAPI /* This is not valid in ATAPI mode. */ if (wdtab[unit].controller_queue.tqh_first) #endif wdstart(unit); } /* * Initialize a drive. */ int wdopen(dev_t dev, int flags, int fmt, struct proc *p) { register unsigned int lunit; register struct disk *du; int error; lunit = dkunit(dev); if (lunit >= NWD || dktype(dev) != 0) return (ENXIO); du = wddrives[lunit]; if (du == NULL) return (ENXIO); /* Finish flushing IRQs left over from wdattach(). */ #ifdef CMD640 if (wdtab[du->dk_ctrlr_cmd640].b_active == 2) wdtab[du->dk_ctrlr_cmd640].b_active = 0; #else if (wdtab[du->dk_ctrlr].b_active == 2) wdtab[du->dk_ctrlr].b_active = 0; #endif du->dk_flags &= ~DKFL_BADSCAN; /* spin waiting for anybody else reading the disk label */ while (du->dk_flags & DKFL_LABELLING) tsleep((caddr_t)&du->dk_flags, PZERO - 1, "wdopen", 1); #if 1 wdsleep(du->dk_ctrlr, "wdopn1"); du->dk_flags |= DKFL_LABELLING; du->dk_state = WANTOPEN; { struct disklabel label; bzero(&label, sizeof label); label.d_secsize = du->dk_dd.d_secsize; label.d_nsectors = du->dk_dd.d_nsectors; label.d_ntracks = du->dk_dd.d_ntracks; label.d_ncylinders = du->dk_dd.d_ncylinders; label.d_secpercyl = du->dk_dd.d_secpercyl; label.d_secperunit = du->dk_dd.d_secperunit; error = dsopen("wd", dev, fmt, &du->dk_slices, &label, wdstrategy1, (ds_setgeom_t *)NULL, &wd_bdevsw, &wd_cdevsw); } du->dk_flags &= ~DKFL_LABELLING; wdsleep(du->dk_ctrlr, "wdopn2"); return (error); #else if ((du->dk_flags & DKFL_BSDLABEL) == 0) { /* * wdtab[ctrlr].b_active != 0 implies XXX applicable now ?? * drive_queue[lunit].b_act == NULL (?) XXX applicable now ?? * so the following guards most things (until the next i/o). * It doesn't guard against a new i/o starting and being * affected by the label being changed. Sigh. */ wdsleep(du->dk_ctrlr, "wdopn1"); du->dk_flags |= DKFL_LABELLING; du->dk_state = WANTOPEN; error = dsinit(dkmodpart(dev, RAW_PART), wdstrategy, &du->dk_dd, &du->dk_slices); if (error != 0) { du->dk_flags &= ~DKFL_LABELLING; return (error); } /* XXX check value returned by wdwsetctlr(). */ wdwsetctlr(du); if (dkslice(dev) == WHOLE_DISK_SLICE) { dsopen(dev, fmt, du->dk_slices); return (0); } /* * Read label using RAW_PART partition. * * If the drive has an MBR, then the current geometry (from * wdgetctlr()) is used to read it; then the BIOS/DOS * geometry is inferred and used to read the label off the * 'c' partition. Otherwise the label is read using the * current geometry. The label gives the final geometry. * If bad sector handling is enabled, then this geometry * is used to read the bad sector table. The geometry * changes occur inside readdisklabel() and are propagated * to the driver by resetting the state machine. * * XXX can now handle changes directly since dsinit() doesn't * do too much. */ msg = correct_readdisklabel(dkmodpart(dev, RAW_PART), wdstrategy, &du->dk_dd); /* XXX check value returned by wdwsetctlr(). */ wdwsetctlr(du); if (msg == NULL && du->dk_dd.d_flags & D_BADSECT) msg = readbad144(dkmodpart(dev, RAW_PART), wdstrategy, &du->dk_dd, &du->dk_bad); du->dk_flags &= ~DKFL_LABELLING; if (msg != NULL) { log(LOG_WARNING, "wd%d: cannot find label (%s)\n", lunit, msg); if (part != RAW_PART) return (EINVAL); /* XXX needs translation */ /* * Soon return. This is how slices without labels * are allowed. They only work on the raw partition. */ } else { unsigned long newsize, offset, size; #if 0 /* * Force RAW_PART partition to be the whole disk. */ offset = du->dk_dd.d_partitions[RAW_PART].p_offset; if (offset != 0) { printf( "wd%d: changing offset of '%c' partition from %lu to 0\n", du->dk_lunit, 'a' + RAW_PART, offset); du->dk_dd.d_partitions[RAW_PART].p_offset = 0; } size = du->dk_dd.d_partitions[RAW_PART].p_size; newsize = du->dk_dd.d_secperunit; /* XXX */ if (size != newsize) { printf( "wd%d: changing size of '%c' partition from %lu to %lu\n", du->dk_lunit, 'a' + RAW_PART, size, newsize); du->dk_dd.d_partitions[RAW_PART].p_size = newsize; } #endif } /* Pick up changes made by readdisklabel(). */ wdsleep(du->dk_ctrlr, "wdopn2"); du->dk_state = WANTOPEN; } /* * Warn if a partion is opened that overlaps another partition which * is open unless one is the "raw" partition (whole disk). */ if ((du->dk_openpart & mask) == 0 && part != RAW_PART) { int start, end; pp = &du->dk_dd.d_partitions[part]; start = pp->p_offset; end = pp->p_offset + pp->p_size; for (pp = du->dk_dd.d_partitions; pp < &du->dk_dd.d_partitions[du->dk_dd.d_npartitions]; pp++) { if (pp->p_offset + pp->p_size <= start || pp->p_offset >= end) continue; if (pp - du->dk_dd.d_partitions == RAW_PART) continue; if (du->dk_openpart & (1 << (pp - du->dk_dd.d_partitions))) log(LOG_WARNING, "wd%d%c: overlaps open partition (%c)\n", lunit, part + 'a', pp - du->dk_dd.d_partitions + 'a'); } } if (part >= du->dk_dd.d_npartitions && part != RAW_PART) return (ENXIO); dsopen(dev, fmt, du->dk_slices); return (0); #endif } /* * Implement operations other than read/write. * Called from wdstart or wdintr during opens and formats. * Uses finite-state-machine to track progress of operation in progress. * Returns 0 if operation still in progress, 1 if completed, 2 if error. */ static int wdcontrol(register struct buf *bp) { register struct disk *du; int ctrlr; du = wddrives[dkunit(bp->b_dev)]; #ifdef CMD640 ctrlr = du->dk_ctrlr_cmd640; #else ctrlr = du->dk_ctrlr; #endif switch (du->dk_state) { case WANTOPEN: tryagainrecal: wdtab[ctrlr].b_active = 1; if (wdcommand(du, 0, 0, 0, 0, WDCC_RESTORE | WD_STEP) != 0) { wderror(bp, du, "wdcontrol: wdcommand failed"); goto maybe_retry; } du->dk_state = RECAL; return (0); case RECAL: if (du->dk_status & WDCS_ERR || wdsetctlr(du) != 0) { wderror(bp, du, "wdcontrol: recal failed"); maybe_retry: if (du->dk_status & WDCS_ERR) wdunwedge(du); du->dk_state = WANTOPEN; if (++wdtab[ctrlr].b_errcnt < RETRIES) goto tryagainrecal; bp->b_error = ENXIO; /* XXX needs translation */ bp->b_flags |= B_ERROR; return (2); } wdtab[ctrlr].b_errcnt = 0; du->dk_state = OPEN; /* * The rest of the initialization can be done by normal * means. */ return (1); } panic("wdcontrol"); return (2); } /* * Wait uninterruptibly until controller is not busy, then send it a command. * The wait usually terminates immediately because we waited for the previous * command to terminate. */ static int wdcommand(struct disk *du, u_int cylinder, u_int head, u_int sector, u_int count, u_int command) { u_int wdc; wdc = du->dk_port; if (du->cfg_flags & WDOPT_SLEEPHACK) { /* OK, so the APM bios has put the disk into SLEEP mode, * how can we tell ? Uhm, we can't. There is no * standardized way of finding out, and the only way to * wake it up is to reset it. Bummer. * * All the many and varied versions of the IDE/ATA standard * explicitly tells us not to look at these registers if * the disk is in SLEEP mode. Well, too bad really, we * have to find out if it's in sleep mode before we can * avoid reading the registers. * * I have reason to belive that most disks will return * either 0xff or 0x00 in all but the status register * when in SLEEP mode, but I have yet to see one return * 0x00, so we don't check for that yet. * * The check for WDCS_BUSY is for the case where the * bios spins up the disk for us, but doesn't initialize * it correctly /phk */ if(inb(wdc + wd_precomp) + inb(wdc + wd_cyl_lo) + inb(wdc + wd_cyl_hi) + inb(wdc + wd_sdh) + inb(wdc + wd_sector) + inb(wdc + wd_seccnt) == 6 * 0xff) { if (bootverbose) printf("wd(%d,%d): disk aSLEEP\n", du->dk_ctrlr, du->dk_unit); wdunwedge(du); } else if(inb(wdc + wd_status) == WDCS_BUSY) { if (bootverbose) printf("wd(%d,%d): disk is BUSY\n", du->dk_ctrlr, du->dk_unit); wdunwedge(du); } } if (wdwait(du, 0, TIMEOUT) < 0) return (1); if( command == WDCC_FEATURES) { outb(wdc + wd_features, count); if ( count == WDFEA_SETXFER ) outb(wdc + wd_seccnt, sector); } else { outb(wdc + wd_precomp, du->dk_dd.d_precompcyl / 4); outb(wdc + wd_cyl_lo, cylinder); outb(wdc + wd_cyl_hi, cylinder >> 8); outb(wdc + wd_sdh, WDSD_IBM | (du->dk_unit << 4) | head); outb(wdc + wd_sector, sector + 1); outb(wdc + wd_seccnt, count); } if (wdwait(du, (command == WDCC_DIAGNOSE || command == WDCC_IDC) ? 0 : WDCS_READY, TIMEOUT) < 0) return (1); outb(wdc + wd_command, command); return (0); } static void wdsetmulti(struct disk *du) { /* * The config option flags low 8 bits define the maximum multi-block * transfer size. If the user wants the maximum that the drive * is capable of, just set the low bits of the config option to * 0x00ff. */ if ((du->cfg_flags & WDOPT_MULTIMASK) != 0 && (du->dk_multi > 1)) { int configval = du->cfg_flags & WDOPT_MULTIMASK; du->dk_multi = min(du->dk_multi, configval); if (wdcommand(du, 0, 0, 0, du->dk_multi, WDCC_SET_MULTI)) { du->dk_multi = 1; } else { if (wdwait(du, WDCS_READY, TIMEOUT) < 0) { du->dk_multi = 1; } } } else { du->dk_multi = 1; } } /* * issue IDC to drive to tell it just what geometry it is to be. */ static int wdsetctlr(struct disk *du) { int error = 0; #ifdef WDDEBUG printf("wd(%d,%d): wdsetctlr: C %lu H %lu S %lu\n", du->dk_ctrlr, du->dk_unit, du->dk_dd.d_ncylinders, du->dk_dd.d_ntracks, du->dk_dd.d_nsectors); #endif if (du->dk_dd.d_ntracks == 0 || du->dk_dd.d_ntracks > 16) { struct wdparams *wp; printf("wd%d: can't handle %lu heads from partition table ", du->dk_lunit, du->dk_dd.d_ntracks); /* obtain parameters */ wp = &du->dk_params; if (wp->wdp_heads > 0 && wp->wdp_heads <= 16) { printf("(controller value %u restored)\n", wp->wdp_heads); du->dk_dd.d_ntracks = wp->wdp_heads; } else { printf("(truncating to 16)\n"); du->dk_dd.d_ntracks = 16; } } if (du->dk_dd.d_nsectors == 0 || du->dk_dd.d_nsectors > 255) { printf("wd%d: cannot handle %lu sectors (max 255)\n", du->dk_lunit, du->dk_dd.d_nsectors); error = 1; } if (error) { #ifdef CMD640 wdtab[du->dk_ctrlr_cmd640].b_errcnt += RETRIES; #else wdtab[du->dk_ctrlr].b_errcnt += RETRIES; #endif return (1); } if (wdcommand(du, du->dk_dd.d_ncylinders, du->dk_dd.d_ntracks - 1, 0, du->dk_dd.d_nsectors, WDCC_IDC) != 0 || wdwait(du, WDCS_READY, TIMEOUT) < 0) { wderror((struct buf *)NULL, du, "wdsetctlr failed"); return (1); } wdsetmulti(du); #ifdef NOTYET /* set read caching and write caching */ wdcommand(du, 0, 0, 0, WDFEA_RCACHE, WDCC_FEATURES); wdwait(du, WDCS_READY, TIMEOUT); wdcommand(du, 0, 0, 0, WDFEA_WCACHE, WDCC_FEATURES); wdwait(du, WDCS_READY, TIMEOUT); #endif return (0); } #if 0 /* * Wait until driver is inactive, then set up controller. */ static int wdwsetctlr(struct disk *du) { int stat; int x; wdsleep(du->dk_ctrlr, "wdwset"); x = splbio(); stat = wdsetctlr(du); wdflushirq(du, x); splx(x); return (stat); } #endif /* * gross little callback function for wdddma interface. returns 1 for * success, 0 for failure. */ static int wdsetmode(int mode, void *wdinfo) { int i; struct disk *du; du = wdinfo; if (bootverbose) printf("wd%d: wdsetmode() setting transfer mode to %02x\n", du->dk_lunit, mode); i = wdcommand(du, 0, 0, mode, WDFEA_SETXFER, WDCC_FEATURES) == 0 && wdwait(du, WDCS_READY, TIMEOUT) == 0; return i; } /* * issue READP to drive to ask it what it is. */ static int wdgetctlr(struct disk *du) { int i; char tb[DEV_BSIZE], tb2[DEV_BSIZE]; struct wdparams *wp = NULL; u_long flags = du->cfg_flags; again: if (wdcommand(du, 0, 0, 0, 0, WDCC_READP) != 0 || wdwait(du, WDCS_READY | WDCS_SEEKCMPLT | WDCS_DRQ, TIMEOUT) != 0) { /* * if we failed on the second try, assume non-32bit */ if( du->dk_flags & DKFL_32BIT) goto failed; /* XXX need to check error status after final transfer. */ /* * Old drives don't support WDCC_READP. Try a seek to 0. * Some IDE controllers return trash if there is no drive * attached, so first test that the drive can be selected. * This also avoids long waits for nonexistent drives. */ if (wdwait(du, 0, TIMEOUT) < 0) return (1); outb(du->dk_port + wd_sdh, WDSD_IBM | (du->dk_unit << 4)); DELAY(5000); /* usually unnecessary; drive select is fast */ /* * Do this twice: may get a false WDCS_READY the first time. */ inb(du->dk_port + wd_status); if ((inb(du->dk_port + wd_status) & (WDCS_BUSY | WDCS_READY)) != WDCS_READY || wdcommand(du, 0, 0, 0, 0, WDCC_RESTORE | WD_STEP) != 0 || wdwait(du, WDCS_READY | WDCS_SEEKCMPLT, TIMEOUT) != 0) return (1); if (du->dk_unit == bootinfo.bi_n_bios_used) { du->dk_dd.d_secsize = DEV_BSIZE; du->dk_dd.d_nsectors = bootinfo.bi_bios_geom[du->dk_unit] & 0xff; du->dk_dd.d_ntracks = ((bootinfo.bi_bios_geom[du->dk_unit] >> 8) & 0xff) + 1; /* XXX Why 2 ? */ du->dk_dd.d_ncylinders = (bootinfo.bi_bios_geom[du->dk_unit] >> 16) + 2; du->dk_dd.d_secpercyl = du->dk_dd.d_ntracks * du->dk_dd.d_nsectors; du->dk_dd.d_secperunit = du->dk_dd.d_secpercyl * du->dk_dd.d_ncylinders; #if 0 du->dk_dd.d_partitions[WDRAW].p_size = du->dk_dd.d_secperunit; du->dk_dd.d_type = DTYPE_ST506; du->dk_dd.d_subtype |= DSTYPE_GEOMETRY; strncpy(du->dk_dd.d_typename, "Bios geometry", sizeof du->dk_dd.d_typename); strncpy(du->dk_params.wdp_model, "ST506", sizeof du->dk_params.wdp_model); #endif bootinfo.bi_n_bios_used ++; return 0; } /* * Fake minimal drive geometry for reading the MBR. * readdisklabel() may enlarge it to read the label and the * bad sector table. */ du->dk_dd.d_secsize = DEV_BSIZE; du->dk_dd.d_nsectors = 17; du->dk_dd.d_ntracks = 1; du->dk_dd.d_ncylinders = 1; du->dk_dd.d_secpercyl = 17; du->dk_dd.d_secperunit = 17; #if 0 /* * Fake maximal drive size for writing the label. */ du->dk_dd.d_partitions[RAW_PART].p_size = 64 * 16 * 1024; /* * Fake some more of the label for printing by disklabel(1) * in case there is no real label. */ du->dk_dd.d_type = DTYPE_ST506; du->dk_dd.d_subtype |= DSTYPE_GEOMETRY; strncpy(du->dk_dd.d_typename, "Fake geometry", sizeof du->dk_dd.d_typename); #endif /* Fake the model name for printing by wdattach(). */ strncpy(du->dk_params.wdp_model, "unknown", sizeof du->dk_params.wdp_model); return (0); } /* obtain parameters */ wp = &du->dk_params; if (du->dk_flags & DKFL_32BIT) insl(du->dk_port + wd_data, tb, sizeof(tb) / sizeof(long)); else insw(du->dk_port + wd_data, tb, sizeof(tb) / sizeof(short)); /* try 32-bit data path (VLB IDE controller) */ if (flags & WDOPT_32BIT) { if (! (du->dk_flags & DKFL_32BIT)) { bcopy(tb, tb2, sizeof(struct wdparams)); du->dk_flags |= DKFL_32BIT; goto again; } /* check that we really have 32-bit controller */ if (bcmp (tb, tb2, sizeof(struct wdparams)) != 0) { failed: /* test failed, use 16-bit i/o mode */ bcopy(tb2, tb, sizeof(struct wdparams)); du->dk_flags &= ~DKFL_32BIT; } } bcopy(tb, wp, sizeof(struct wdparams)); /* shuffle string byte order */ for (i = 0; i < sizeof(wp->wdp_model); i += 2) { u_short *p; p = (u_short *) (wp->wdp_model + i); *p = ntohs(*p); } /* * Clean up the wdp_model by converting nulls to spaces, and * then removing the trailing spaces. */ for (i=0; i < sizeof(wp->wdp_model); i++) { if (wp->wdp_model[i] == '\0') { wp->wdp_model[i] = ' '; } } for (i=sizeof(wp->wdp_model)-1; i>=0 && wp->wdp_model[i]==' '; i--) { wp->wdp_model[i] = '\0'; } /* * find out the drives maximum multi-block transfer capability */ du->dk_multi = wp->wdp_nsecperint & 0xff; wdsetmulti(du); du->dk_dmacookie = NULL; /* * check drive's DMA capability */ /* does user want this? */ if ((du->cfg_flags & WDOPT_DMA) && /* have we got a DMA controller? */ (wddma.wdd_candma && (du->dk_dmacookie = wddma.wdd_candma(du->dk_port, du->dk_unit))) && /* can said drive do DMA? */ (wddma.wdd_dmainit(du->dk_dmacookie, wp, wdsetmode, du))) du->dk_flags |= DKFL_USEDMA; #ifdef WDDEBUG printf( "\nwd(%d,%d): wdgetctlr: gc %x cyl %d trk %d sec %d type %d sz %d model %s\n", du->dk_ctrlr, du->dk_unit, wp->wdp_config, wp->wdp_cylinders, wp->wdp_heads, wp->wdp_sectors, wp->wdp_buffertype, wp->wdp_buffersize, wp->wdp_model); #endif /* update disklabel given drive information */ du->dk_dd.d_secsize = DEV_BSIZE; du->dk_dd.d_ncylinders = wp->wdp_cylinders; /* +- 1 */ du->dk_dd.d_ntracks = wp->wdp_heads; du->dk_dd.d_nsectors = wp->wdp_sectors; du->dk_dd.d_secpercyl = du->dk_dd.d_ntracks * du->dk_dd.d_nsectors; du->dk_dd.d_secperunit = du->dk_dd.d_secpercyl * du->dk_dd.d_ncylinders; if (WDOPT_FORCEHD(du->cfg_flags)) { du->dk_dd.d_ntracks = WDOPT_FORCEHD(du->cfg_flags); du->dk_dd.d_secpercyl = du->dk_dd.d_ntracks * du->dk_dd.d_nsectors; du->dk_dd.d_ncylinders = du->dk_dd.d_secperunit / du->dk_dd.d_secpercyl; } #if 0 du->dk_dd.d_partitions[RAW_PART].p_size = du->dk_dd.d_secperunit; /* dubious ... */ bcopy("ESDI/IDE", du->dk_dd.d_typename, 9); bcopy(wp->wdp_model + 20, du->dk_dd.d_packname, 14 - 1); /* better ... */ du->dk_dd.d_type = DTYPE_ESDI; du->dk_dd.d_subtype |= DSTYPE_GEOMETRY; #endif return (0); } int wdclose(dev_t dev, int flags, int fmt, struct proc *p) { dsclose(dev, fmt, wddrives[dkunit(dev)]->dk_slices); return (0); } int wdioctl(dev_t dev, int cmd, caddr_t addr, int flags, struct proc *p) { int lunit = dkunit(dev); register struct disk *du; int error; #ifdef notyet struct uio auio; struct iovec aiov; struct format_op *fop; #endif du = wddrives[lunit]; wdsleep(du->dk_ctrlr, "wdioct"); error = dsioctl("wd", dev, cmd, addr, flags, &du->dk_slices, wdstrategy1, (ds_setgeom_t *)NULL); if (error != -1) return (error); switch (cmd) { case DIOCSBADSCAN: if (*(int *)addr) du->dk_flags |= DKFL_BADSCAN; else du->dk_flags &= ~DKFL_BADSCAN; return (0); #ifdef notyet case DIOCWFORMAT: if (!(flag & FWRITE)) return (EBADF); fop = (struct format_op *)addr; aiov.iov_base = fop->df_buf; aiov.iov_len = fop->df_count; auio.uio_iov = &aiov; auio.uio_iovcnt = 1; auio.uio_resid = fop->df_count; auio.uio_segflg = 0; auio.uio_offset = fop->df_startblk * du->dk_dd.d_secsize; #error /* XXX the 386BSD interface is different */ error = physio(wdformat, &rwdbuf[lunit], 0, dev, B_WRITE, minphys, &auio); fop->df_count -= auio.uio_resid; fop->df_reg[0] = du->dk_status; fop->df_reg[1] = du->dk_error; return (error); #endif default: return (ENOTTY); } } #ifdef B_FORMAT int wdformat(struct buf *bp) { bp->b_flags |= B_FORMAT; wdstrategy(bp); /* * phk put this here, better that return(wdstrategy(bp)); * XXX */ return -1; } #endif int wdsize(dev_t dev) { struct disk *du; int lunit; lunit = dkunit(dev); if (lunit >= NWD || dktype(dev) != 0) return (-1); du = wddrives[lunit]; if (du == NULL) return (-1); return (dssize(dev, &du->dk_slices, wdopen, wdclose)); } /* * Dump core after a system crash. */ int wddump(dev_t dev) { register struct disk *du; struct disklabel *lp; long num; /* number of sectors to write */ int lunit, part; long blkoff, blknum; long blkchk, blkcnt, blknext; long cylin, head, sector; long secpertrk, secpercyl, nblocks; u_long ds_offset; char *addr; static int wddoingadump = 0; /* Toss any characters present prior to dump. */ while (cncheckc() != -1) ; /* Check for acceptable device. */ /* XXX should reset to maybe allow du->dk_state < OPEN. */ lunit = dkunit(dev); /* eventually support floppies? */ part = dkpart(dev); if (lunit >= NWD || (du = wddrives[lunit]) == NULL || du->dk_state < OPEN || (lp = dsgetlabel(dev, du->dk_slices)) == NULL) return (ENXIO); /* Size of memory to dump, in disk sectors. */ num = (u_long)Maxmem * PAGE_SIZE / du->dk_dd.d_secsize; secpertrk = du->dk_dd.d_nsectors; secpercyl = du->dk_dd.d_secpercyl; nblocks = lp->d_partitions[part].p_size; blkoff = lp->d_partitions[part].p_offset; /* XXX */ ds_offset = du->dk_slices->dss_slices[dkslice(dev)].ds_offset; blkoff += ds_offset; #if 0 pg("part %x, nblocks %d, dumplo %d num %d\n", part, nblocks, dumplo, num); #endif /* Check transfer bounds against partition size. */ if (dumplo < 0 || dumplo + num > nblocks) return (EINVAL); /* Check if we are being called recursively. */ if (wddoingadump) return (EFAULT); #if 0 /* Mark controller active for if we panic during the dump. */ wdtab[du->dk_ctrlr].b_active = 1; #endif wddoingadump = 1; /* Recalibrate the drive. */ DELAY(5); /* ATA spec XXX NOT */ if (wdcommand(du, 0, 0, 0, 0, WDCC_RESTORE | WD_STEP) != 0 || wdwait(du, WDCS_READY | WDCS_SEEKCMPLT, TIMEOUT) != 0 || wdsetctlr(du) != 0) { wderror((struct buf *)NULL, du, "wddump: recalibrate failed"); return (EIO); } du->dk_flags |= DKFL_SINGLE; addr = (char *) 0; blknum = dumplo + blkoff; while (num > 0) { blkcnt = num; if (blkcnt > MAXTRANSFER) blkcnt = MAXTRANSFER; /* Keep transfer within current cylinder. */ if ((blknum + blkcnt - 1) / secpercyl != blknum / secpercyl) blkcnt = secpercyl - (blknum % secpercyl); blknext = blknum + blkcnt; /* * See if one of the sectors is in the bad sector list * (if we have one). If the first sector is bad, then * reduce the transfer to this one bad sector; if another * sector is bad, then reduce reduce the transfer to * avoid any bad sectors. */ if (du->dk_flags & DKFL_SINGLE && dsgetbad(dev, du->dk_slices) != NULL) { for (blkchk = blknum; blkchk < blknum + blkcnt; blkchk++) { daddr_t blknew; blknew = transbad144(dsgetbad(dev, du->dk_slices), blkchk - ds_offset) + ds_offset; if (blknew != blkchk) { /* Found bad block. */ blkcnt = blkchk - blknum; if (blkcnt > 0) { blknext = blknum + blkcnt; goto out; } blkcnt = 1; blknext = blknum + blkcnt; #if 1 || defined(WDDEBUG) printf("bad block %lu -> %lu\n", blknum, blknew); #endif break; } } } out: /* Compute disk address. */ cylin = blknum / secpercyl; head = (blknum % secpercyl) / secpertrk; sector = blknum % secpertrk; #if 0 /* Let's just talk about this first... */ pg("cylin l%d head %ld sector %ld addr 0x%x count %ld", cylin, head, sector, addr, blkcnt); #endif /* Do the write. */ if (wdcommand(du, cylin, head, sector, blkcnt, WDCC_WRITE) != 0) { wderror((struct buf *)NULL, du, "wddump: timeout waiting to to give command"); return (EIO); } while (blkcnt != 0) { - pmap_enter(kernel_pmap, (vm_offset_t)CADDR1, trunc_page(addr), - VM_PROT_READ, TRUE); + if (is_adapter_memory((vm_offset_t)addr)) + pmap_enter(kernel_pmap, (vm_offset_t)CADDR1, + trunc_page(0), VM_PROT_READ, TRUE); + else + pmap_enter(kernel_pmap, (vm_offset_t)CADDR1, + trunc_page(addr), VM_PROT_READ, TRUE); /* Ready to send data? */ DELAY(5); /* ATA spec */ if (wdwait(du, WDCS_READY | WDCS_SEEKCMPLT | WDCS_DRQ, TIMEOUT) < 0) { wderror((struct buf *)NULL, du, "wddump: timeout waiting for DRQ"); return (EIO); } if (du->dk_flags & DKFL_32BIT) outsl(du->dk_port + wd_data, CADDR1 + ((int)addr & PAGE_MASK), DEV_BSIZE / sizeof(long)); else outsw(du->dk_port + wd_data, CADDR1 + ((int)addr & PAGE_MASK), DEV_BSIZE / sizeof(short)); addr += DEV_BSIZE; /* * If we are dumping core, it may take a while. * So reassure the user and hold off any watchdogs. */ if ((unsigned)addr % (1024 * 1024) == 0) { #ifdef HW_WDOG if (wdog_tickler) (*wdog_tickler)(); #endif /* HW_WDOG */ printf("%ld ", num / (1024 * 1024 / DEV_BSIZE)); } num--; blkcnt--; } /* Wait for completion. */ DELAY(5); /* ATA spec XXX NOT */ if (wdwait(du, WDCS_READY | WDCS_SEEKCMPLT, TIMEOUT) < 0) { wderror((struct buf *)NULL, du, "wddump: timeout waiting for status"); return (EIO); } /* Check final status. */ if (du->dk_status & (WDCS_READY | WDCS_SEEKCMPLT | WDCS_DRQ | WDCS_ERR) != (WDCS_READY | WDCS_SEEKCMPLT)) { wderror((struct buf *)NULL, du, "wddump: extra DRQ, or error"); return (EIO); } /* Update block count. */ blknum = blknext; /* Operator aborting dump? */ if (cncheckc() != -1) return (EINTR); } return (0); } static void wderror(struct buf *bp, struct disk *du, char *mesg) { if (bp == NULL) printf("wd%d: %s:\n", du->dk_lunit, mesg); else diskerr(bp, "wd", mesg, LOG_PRINTF, du->dk_skip, dsgetlabel(bp->b_dev, du->dk_slices)); printf("wd%d: status %b error %b\n", du->dk_lunit, du->dk_status, WDCS_BITS, du->dk_error, WDERR_BITS); } /* * Discard any interrupts that were latched by the interrupt system while * we were doing polled i/o. */ static void wdflushirq(struct disk *du, int old_ipl) { #ifdef CMD640 wdtab[du->dk_ctrlr_cmd640].b_active = 2; splx(old_ipl); (void)splbio(); wdtab[du->dk_ctrlr_cmd640].b_active = 0; #else wdtab[du->dk_ctrlr].b_active = 2; splx(old_ipl); (void)splbio(); wdtab[du->dk_ctrlr].b_active = 0; #endif } /* * Reset the controller. */ static int wdreset(struct disk *du) { int wdc, err = 0; if ((du->dk_flags & (DKFL_DMA|DKFL_USEDMA)) && du->dk_dmacookie) wddma.wdd_dmadone(du->dk_dmacookie); wdc = du->dk_port; (void)wdwait(du, 0, TIMEOUT); outb(wdc + wd_ctlr, WDCTL_IDS | WDCTL_RST); DELAY(10 * 1000); outb(wdc + wd_ctlr, WDCTL_IDS); #ifdef ATAPI if (wdwait(du, WDCS_READY | WDCS_SEEKCMPLT, TIMEOUT) != 0) err = 1; /* no IDE drive found */ du->dk_error = inb(wdc + wd_error); if (du->dk_error != 0x01) err = 1; /* the drive is incompatible */ #else if (wdwait(du, WDCS_READY | WDCS_SEEKCMPLT, TIMEOUT) != 0 || (du->dk_error = inb(wdc + wd_error)) != 0x01) return (1); #endif outb(wdc + wd_ctlr, WDCTL_4BIT); return (err); } /* * Sleep until driver is inactive. * This is used only for avoiding rare race conditions, so it is unimportant * that the sleep may be far too short or too long. */ static void wdsleep(int ctrlr, char *wmesg) { int s = splbio(); #ifdef CMD640 if (eide_quirks & Q_CMD640B) ctrlr = PRIMARY; #endif while (wdtab[ctrlr].b_active) tsleep((caddr_t)&wdtab[ctrlr].b_active, PZERO - 1, wmesg, 1); splx(s); } static void wdtimeout(void *cdu) { struct disk *du; int x; static int timeouts; du = (struct disk *)cdu; x = splbio(); if (du->dk_timeout != 0 && --du->dk_timeout == 0) { if(timeouts++ <= 5) { char *msg; msg = (timeouts > 5) ? "Last time I say: interrupt timeout. Probably a portable PC." : "interrupt timeout"; wderror((struct buf *)NULL, du, msg); if (du->dk_dmacookie) printf("wd%d: wdtimeout() DMA status %b\n", du->dk_lunit, wddma.wdd_dmastatus(du->dk_dmacookie), WDDS_BITS); } wdunwedge(du); wdflushirq(du, x); du->dk_skip = 0; du->dk_flags |= DKFL_SINGLE; wdstart(du->dk_ctrlr); } timeout(wdtimeout, cdu, hz); splx(x); } /* * Reset the controller after it has become wedged. This is different from * wdreset() so that wdreset() can be used in the probe and so that this * can restore the geometry . */ static int wdunwedge(struct disk *du) { struct disk *du1; int lunit; /* Schedule other drives for recalibration. */ for (lunit = 0; lunit < NWD; lunit++) if ((du1 = wddrives[lunit]) != NULL && du1 != du && du1->dk_ctrlr == du->dk_ctrlr && du1->dk_state > WANTOPEN) du1->dk_state = WANTOPEN; DELAY(RECOVERYTIME); if (wdreset(du) == 0) { /* * XXX - recalibrate current drive now because some callers * aren't prepared to have its state change. */ if (wdcommand(du, 0, 0, 0, 0, WDCC_RESTORE | WD_STEP) == 0 && wdwait(du, WDCS_READY | WDCS_SEEKCMPLT, TIMEOUT) == 0 && wdsetctlr(du) == 0) return (0); } wderror((struct buf *)NULL, du, "wdunwedge failed"); return (1); } /* * Wait uninterruptibly until controller is not busy and either certain * status bits are set or an error has occurred. * The wait is usually short unless it is for the controller to process * an entire critical command. * Return 1 for (possibly stale) controller errors, -1 for timeout errors, * or 0 for no errors. * Return controller status in du->dk_status and, if there was a controller * error, return the error code in du->dk_error. */ #ifdef WD_COUNT_RETRIES static int min_retries[NWDC]; #endif static int wdwait(struct disk *du, u_char bits_wanted, int timeout) { int wdc; u_char status; #define POLLING 1000 wdc = du->dk_port; timeout += POLLING; /* * This delay is really too long, but does not impact the performance * as much when using the multi-sector option. Shorter delays have * caused I/O errors on some drives and system configs. This should * probably be fixed if we develop a better short term delay mechanism. */ DELAY(1); do { #ifdef WD_COUNT_RETRIES if (min_retries[du->dk_ctrlr] > timeout || min_retries[du->dk_ctrlr] == 0) min_retries[du->dk_ctrlr] = timeout; #endif du->dk_status = status = inb(wdc + wd_status); #ifdef ATAPI /* * Atapi drives have a very interesting feature, when attached * as a slave on the IDE bus, and there is no master. * They release the bus after getting the command. * We should reselect the drive here to get the status. */ if (status == 0xff) { outb(wdc + wd_sdh, WDSD_IBM | du->dk_unit << 4); du->dk_status = status = inb(wdc + wd_status); } #endif if (!(status & WDCS_BUSY)) { if (status & WDCS_ERR) { du->dk_error = inb(wdc + wd_error); /* * We once returned here. This is wrong * because the error bit is apparently only * valid after the controller has interrupted * (e.g., the error bit is stale when we wait * for DRQ for writes). So we can't depend * on the error bit at all when polling for * command completion. */ } if ((status & bits_wanted) == bits_wanted) return (status & WDCS_ERR); } if (timeout < TIMEOUT) /* * Switch to a polling rate of about 1 KHz so that * the timeout is almost machine-independent. The * controller is taking a long time to respond, so * an extra msec won't matter. */ DELAY(1000); else DELAY(1); } while (--timeout != 0); return (-1); } static wd_devsw_installed = 0; static void wd_drvinit(void *unused) { if( ! wd_devsw_installed ) { bdevsw_add_generic(BDEV_MAJOR,CDEV_MAJOR, &wd_bdevsw); wd_devsw_installed = 1; } } SYSINIT(wddev,SI_SUB_DRIVERS,SI_ORDER_MIDDLE+CDEV_MAJOR,wd_drvinit,NULL) #endif /* NWDC > 0 */ Index: head/sys/scsi/sd.c =================================================================== --- head/sys/scsi/sd.c (revision 29279) +++ head/sys/scsi/sd.c (revision 29280) @@ -1,1081 +1,1085 @@ /* * Written by Julian Elischer (julian@dialix.oz.au) * for TRW Financial Systems for use under the MACH(2.5) operating system. * * TRW Financial Systems, in accordance with their agreement with Carnegie * Mellon University, makes this software available to CMU to distribute * or use in any manner that they see fit as long as this message is kept with * the software. For this reason TFS also grants any other persons or * organisations permission to use or modify this software. * * TFS supplies this software to be publicly redistributed * on the understanding that TFS is not responsible for the correct * functioning of this software in any circumstances. * * Ported to run under 386BSD by Julian Elischer (julian@dialix.oz.au) Sept 1992 * - * $Id: sd.c,v 1.107 1997/08/09 01:44:22 julian Exp $ + * $Id: sd.c,v 1.108 1997/09/02 20:06:37 bde Exp $ */ #include "opt_bounce.h" #include "opt_scsi.h" #define SPLSD splbio #include #include #include #include #include #include #include #include #include #ifdef DEVFS #include #endif /*DEVFS*/ #include #include #include #include #include #include #include #include #include /* XXX *//* for aborting dump */ #ifdef PC98 #include #endif static u_int32_t sdstrats, sdqueues; #define SECSIZE 512 #ifdef PC98 #define SDOUTSTANDING 2 #else #define SDOUTSTANDING 4 #endif #define SD_RETRIES 4 #define MAXTRANSFER 8 /* 1 page at a time */ #define PARTITION(dev) dkpart(dev) #define SDUNIT(dev) dkunit(dev) /* XXX introduce a dkmodunit() macro for this. */ #define SDSETUNIT(DEV, U) \ makedev(major(DEV), dkmakeminor((U), dkslice(DEV), dkpart(DEV))) static errval sd_get_parms __P((int unit, int flags)); static errval sd_reassign_blocks __P((int unit, int block)); static u_int32_t sd_size __P((int unit, int flags)); static void sdstrategy1 __P((struct buf *)); static int sd_sense_handler __P((struct scsi_xfer *)); static void sdstart __P((u_int32_t, u_int32_t)); struct scsi_data { u_int32_t flags; #define SDINIT 0x04 /* device has been init'd */ struct disk_parms { u_char heads; /* Number of heads */ u_int16_t cyls; /* Number of cylinders */ u_char sectors; /*dubious *//* Number of sectors/track */ u_int16_t secsiz; /* Number of bytes/sector */ u_int32_t disksize; /* total number sectors */ } params; struct diskslices *dk_slices; /* virtual drives */ struct buf_queue_head buf_queue; int dkunit; /* disk stats unit number */ #ifdef DEVFS void *b_devfs_token; void *c_devfs_token; void *ctl_devfs_token; #endif }; static int sdunit(dev_t dev) { return SDUNIT(dev); } static dev_t sdsetunit(dev_t dev, int unit) { return SDSETUNIT(dev, unit); } static errval sd_open __P((dev_t dev, int mode, int fmt, struct proc *p, struct scsi_link *sc_link)); static errval sd_ioctl(dev_t dev, int cmd, caddr_t addr, int flag, struct proc *p, struct scsi_link *sc_link); static errval sd_close __P((dev_t dev, int fflag, int fmt, struct proc *p, struct scsi_link *sc_link)); static void sd_strategy(struct buf *bp, struct scsi_link *sc_link); static d_open_t sdopen; static d_close_t sdclose; static d_ioctl_t sdioctl; static d_dump_t sddump; static d_psize_t sdsize; static d_strategy_t sdstrategy; #define CDEV_MAJOR 13 #define BDEV_MAJOR 4 static struct cdevsw sd_cdevsw; static struct bdevsw sd_bdevsw = { sdopen, sdclose, sdstrategy, sdioctl, /*4*/ sddump, sdsize, D_DISK, "sd", &sd_cdevsw, -1 }; SCSI_DEVICE_ENTRIES(sd) static struct scsi_device sd_switch = { sd_sense_handler, sdstart, /* have a queue, served by this */ NULL, /* have no async handler */ NULL, /* Use default 'done' routine */ "sd", 0, {0, 0}, 0, /* Link flags */ sdattach, "Direct-Access", sdopen, sizeof(struct scsi_data), T_DIRECT, sdunit, sdsetunit, sd_open, sd_ioctl, sd_close, sd_strategy, }; static struct scsi_xfer sx; static inline void sd_registerdev(int unit) { if(dk_ndrive < DK_NDRIVE) { sprintf(dk_names[dk_ndrive], "sd%d", unit); dk_wpms[dk_ndrive] = (8*1024*1024/2); SCSI_DATA(&sd_switch, unit)->dkunit = dk_ndrive++; } else { SCSI_DATA(&sd_switch, unit)->dkunit = -1; } } /* * The routine called by the low level scsi routine when it discovers * a device suitable for this driver. */ static errval sdattach(struct scsi_link *sc_link) { u_int32_t unit; struct disk_parms *dp; #ifdef DEVFS int mynor; #endif struct scsi_data *sd = sc_link->sd; unit = sc_link->dev_unit; dp = &(sd->params); if (sc_link->opennings > SDOUTSTANDING) sc_link->opennings = SDOUTSTANDING; TAILQ_INIT(&sd->buf_queue); /* * Use the subdriver to request information regarding * the drive. We cannot use interrupts yet, so the * request must specify this. */ sd_get_parms(unit, SCSI_NOSLEEP | SCSI_NOMASK); /* * if we don't have actual parameters, assume 512 bytes/sec * (could happen on removable media - MOD) * -- this avoids the division below from falling over */ if(dp->secsiz == 0) dp->secsiz = SECSIZE; printf("%ldMB (%ld %d byte sectors)", dp->disksize / ((1024L * 1024L) / dp->secsiz), dp->disksize, dp->secsiz); #ifndef SCSI_REPORT_GEOMETRY if ( (sc_link->flags & SDEV_BOOTVERBOSE) ) #endif { sc_print_addr(sc_link); printf("with %d cyls, %d heads, and an average %d sectors/track", dp->cyls, dp->heads, dp->sectors); } sd->flags |= SDINIT; sd_registerdev(unit); #ifdef DEVFS mynor = dkmakeminor(unit, WHOLE_DISK_SLICE, RAW_PART); sd->b_devfs_token = devfs_add_devswf(&sd_bdevsw, mynor, DV_BLK, UID_ROOT, GID_OPERATOR, 0640, "sd%d", unit); sd->c_devfs_token = devfs_add_devswf(&sd_cdevsw, mynor, DV_CHR, UID_ROOT, GID_OPERATOR, 0640, "rsd%d", unit); mynor = dkmakeminor(unit, 0, 0); /* XXX */ sd->ctl_devfs_token = devfs_add_devswf(&sd_cdevsw, mynor | SCSI_CONTROL_MASK, DV_CHR, UID_ROOT, GID_WHEEL, 0600, "rsd%d.ctl", unit); #endif return 0; } /* * open the device. Make sure the partition info is a up-to-date as can be. */ static errval sd_open(dev, mode, fmt, p, sc_link) dev_t dev; int mode; int fmt; struct proc *p; struct scsi_link *sc_link; { errval errcode = 0; u_int32_t unit; struct disklabel label; struct scsi_data *sd; unit = SDUNIT(dev); sd = sc_link->sd; /* * Make sure the disk has been initialised * At some point in the future, get the scsi driver * to look for a new device if we are not initted */ if ((!sd) || (!(sd->flags & SDINIT))) { return (ENXIO); } SC_DEBUG(sc_link, SDEV_DB1, ("sd_open: dev=0x%lx (unit %ld, partition %d)\n", dev, unit, PARTITION(dev))); /* * "unit attention" errors should occur here if the * drive has been restarted or the pack changed. * just ingnore the result, it's a decoy instruction * The error handlers will act on the error though * and invalidate any media information we had. */ scsi_test_unit_ready(sc_link, 0); errcode = scsi_device_lock(sc_link); if (errcode) return errcode; /* * If it's been invalidated, then forget the label */ sc_link->flags |= SDEV_OPEN; /* unit attn becomes an err now */ if (!(sc_link->flags & SDEV_MEDIA_LOADED) && sd->dk_slices != NULL) { /* * If somebody still has it open, then forbid re-entry. */ if (dsisopen(sd->dk_slices)) { errcode = ENXIO; goto bad; } dsgone(&sd->dk_slices); } /* * In case it is a funny one, tell it to start * not needed for most hard drives (ignore failure) */ scsi_start_unit(sc_link, SCSI_ERR_OK | SCSI_SILENT); /* * Check that it is still responding and ok. */ if (scsi_test_unit_ready(sc_link, 0)) { SC_DEBUG(sc_link, SDEV_DB3, ("device not reponding\n")); errcode = ENXIO; goto bad; } SC_DEBUG(sc_link, SDEV_DB3, ("device ok\n")); /* * Load the physical device parameters */ if(errcode = sd_get_parms(unit, 0)) /* sets SDEV_MEDIA_LOADED */ goto bad; switch (sd->params.secsiz) { case 512: case 1024: case 2048: break; default: printf("sd%ld: Can't deal with %d bytes logical blocks\n", unit, sd->params.secsiz); Debugger("sd"); errcode = ENXIO; goto bad; } SC_DEBUG(sc_link, SDEV_DB3, ("Params loaded ")); /* Lock the pack in. */ scsi_prevent(sc_link, PR_PREVENT, SCSI_ERR_OK | SCSI_SILENT); /* Build label for whole disk. */ bzero(&label, sizeof label); label.d_secsize = sd->params.secsiz; label.d_nsectors = sd->params.sectors; label.d_ntracks = sd->params.heads; label.d_ncylinders = sd->params.cyls; label.d_secpercyl = sd->params.heads * sd->params.sectors; if (label.d_secpercyl == 0) label.d_secpercyl = 100; /* XXX as long as it's not 0 - readdisklabel divides by it (?) */ label.d_secperunit = sd->params.disksize; /* Initialize slice tables. */ errcode = dsopen("sd", dev, fmt, &sd->dk_slices, &label, sdstrategy1, (ds_setgeom_t *)NULL, &sd_bdevsw, &sd_cdevsw); if (errcode != 0) goto bad; SC_DEBUG(sc_link, SDEV_DB3, ("Slice tables initialized ")); SC_DEBUG(sc_link, SDEV_DB3, ("open %ld %ld\n", sdstrats, sdqueues)); scsi_device_unlock(sc_link); return 0; bad: if (!dsisopen(sd->dk_slices)) { scsi_prevent(sc_link, PR_ALLOW, SCSI_ERR_OK | SCSI_SILENT); sc_link->flags &= ~SDEV_OPEN; } scsi_device_unlock(sc_link); return errcode; } /* * close the device.. only called if we are the LAST occurence of an open * device. Convenient now but usually a pain. */ static errval sd_close(dev, fflag, fmt, p, sc_link) dev_t dev; int fflag; int fmt; struct proc *p; struct scsi_link *sc_link; { struct scsi_data *sd; errval errcode; sd = sc_link->sd; errcode = scsi_device_lock(sc_link); if (errcode) return errcode; dsclose(dev, fmt, sd->dk_slices); if (!dsisopen(sd->dk_slices)) { scsi_prevent(sc_link, PR_ALLOW, SCSI_SILENT | SCSI_ERR_OK); sc_link->flags &= ~SDEV_OPEN; } scsi_device_unlock(sc_link); return (0); } /* * Actually translate the requested transfer into one the physical driver * can understand. The transfer is described by a buf and will include * only one physical transfer. */ static void sd_strategy(struct buf *bp, struct scsi_link *sc_link) { u_int32_t opri; struct scsi_data *sd; u_int32_t unit, secsize; sdstrats++; unit = SDUNIT((bp->b_dev)); sd = sc_link->sd; /* * If the device has been made invalid, error out */ if (!(sc_link->flags & SDEV_MEDIA_LOADED)) { bp->b_error = EIO; goto bad; } /* * check it's not too big a transfer for our adapter */ scsi_minphys(bp,&sd_switch); /* * Odd number of bytes or negative offset */ if (bp->b_blkno < 0 ) { bp->b_error = EINVAL; printf("sd_strategy: Negative block number: 0x%x\n", bp->b_blkno); goto bad; } secsize = sd->params.secsiz; /* make sure the blkno is scalable */ if( (bp->b_blkno % (secsize/DEV_BSIZE)) != 0 ) { bp->b_error = EINVAL; printf("sd_strategy: Block number is not multiple of sector size (2): 0x%x\n", bp->b_blkno); goto bad; } /* make sure that the transfer size is a multiple of the sector size */ if( (bp->b_bcount % secsize) != 0 ) { bp->b_error = EINVAL; printf("sd_strategy: Invalid b_bcount %d at block number: 0x%x\n", bp->b_bcount, bp->b_blkno); goto bad; } /* * Do bounds checking, adjust transfer, set b_cylin and b_pbklno. */ { int status; int sec_blk_ratio = secsize/DEV_BSIZE; /* save original block number and size */ int b_blkno = bp->b_blkno; int b_bcount = bp->b_bcount; /* replace with scaled values */ bp->b_blkno /= sec_blk_ratio; bp->b_bcount /= sec_blk_ratio; /* enforce limits and map to physical block number */ status = dscheck(bp, sd->dk_slices); /* prevent bad side effects in block system */ bp->b_blkno = b_blkno; bp->b_bcount = b_bcount; /* scale resid */ bp->b_resid *= sec_blk_ratio; /* see if the mapping failed */ if (status <= 0) goto done; /* XXX check b_resid */ } opri = SPLSD(); /* * Use a bounce buffer if necessary */ #ifdef BOUNCE_BUFFERS if (sc_link->flags & SDEV_BOUNCE) vm_bounce_alloc(bp); #endif /* * Place it in the queue of disk activities for this disk */ #ifdef SDDISKSORT tqdisksort(&sd->buf_queue, bp); #else TAILQ_INSERT_TAIL(&sd->buf_queue, bp, b_act); #endif /* * Tell the device to get going on the transfer if it's * not doing anything, otherwise just wait for completion */ sdstart(unit, 0); splx(opri); return /*0*/; bad: bp->b_flags |= B_ERROR; done: /* * Correctly set the buf to indicate a completed xfer */ bp->b_resid = bp->b_bcount; biodone(bp); return /*0*/; } static void sdstrategy1(struct buf *bp) { /* * XXX - do something to make sdstrategy() but not this block while * we're doing dsinit() and dsioctl(). */ sdstrategy(bp); } /* * sdstart looks to see if there is a buf waiting for the device * and that the device is not already busy. If both are true, * It dequeues the buf and creates a scsi command to perform the * transfer in the buf. The transfer request will call scsi_done * on completion, which will in turn call this routine again * so that the next queued transfer is performed. * The bufs are queued by the strategy routine (sdstrategy) * * This routine is also called after other non-queued requests * have been made of the scsi driver, to ensure that the queue * continues to be drained. * * must be called at the correct (highish) spl level * sdstart() is called at SPLSD from sdstrategy and scsi_done */ static void sdstart(u_int32_t unit, u_int32_t flags) { register struct scsi_link *sc_link = SCSI_LINK(&sd_switch, unit); register struct scsi_data *sd = sc_link->sd; struct buf *bp = NULL; struct scsi_rw_big cmd; u_int32_t blkno, nblk, secsize; SC_DEBUG(sc_link, SDEV_DB2, ("sdstart ")); /* * Check if the device has room for another command */ while (sc_link->opennings) { /* * there is excess capacity, but a special waits * It'll need the adapter as soon as we clear out of the * way and let it run (user level wait). */ if (sc_link->flags & SDEV_WAITING) { return; } /* * See if there is a buf with work for us to do.. */ bp = sd->buf_queue.tqh_first; if (bp == NULL) { /* yes, an assign */ return; } TAILQ_REMOVE(&sd->buf_queue, bp, b_act); /* * If the device has become invalid, abort all the * reads and writes until all files have been closed and * re-openned */ if (!(sc_link->flags & SDEV_MEDIA_LOADED)) { goto bad; } /* * We have a buf, now we know we are going to go through * With this thing.. */ secsize = sd->params.secsiz; blkno = bp->b_pblkno; if (bp->b_bcount & (secsize - 1)) { goto bad; } nblk = bp->b_bcount / secsize; /* * Fill out the scsi command */ cmd.op_code = (bp->b_flags & B_READ) ? READ_BIG : WRITE_BIG; cmd.addr_3 = (blkno & 0xff000000UL) >> 24; cmd.addr_2 = (blkno & 0xff0000) >> 16; cmd.addr_1 = (blkno & 0xff00) >> 8; cmd.addr_0 = blkno & 0xff; cmd.length2 = (nblk & 0xff00) >> 8; cmd.length1 = (nblk & 0xff); cmd.byte2 = cmd.reserved = cmd.control = 0; /* * Call the routine that chats with the adapter. * Note: we cannot sleep as we may be an interrupt */ if (scsi_scsi_cmd(sc_link, (struct scsi_generic *) &cmd, sizeof(cmd), (u_char *) bp->b_un.b_addr, bp->b_bcount, SD_RETRIES, 10000, bp, flags | ((bp->b_flags & B_READ) ? SCSI_DATA_IN : SCSI_DATA_OUT)) == SUCCESSFULLY_QUEUED) { sdqueues++; if(sd->dkunit >= 0) { dk_xfer[sd->dkunit]++; dk_seek[sd->dkunit]++; /* don't know */ dk_wds[sd->dkunit] += bp->b_bcount >> 6; } } else { bad: printf("sd%ld: oops not queued\n", unit); bp->b_error = EIO; bp->b_flags |= B_ERROR; biodone(bp); } } } /* * Perform special action on behalf of the user * Knows about the internals of this device */ static errval sd_ioctl(dev_t dev, int cmd, caddr_t addr, int flag, struct proc *p, struct scsi_link *sc_link) { /* struct sd_cmd_buf *args; */ errval error; struct scsi_data *sd; /* * Find the device that the user is talking about */ sd = sc_link->sd; SC_DEBUG(sc_link, SDEV_DB1, ("sdioctl (0x%x)", cmd)); #if 0 /* Wait until we have exclusive access to the device. */ /* XXX this is how wd does it. How did we work without this? */ wdsleep(du->dk_ctrlr, "wdioct"); #endif /* * If the device is not valid.. abandon ship */ if (!(sc_link->flags & SDEV_MEDIA_LOADED)) return (EIO); if (cmd == DIOCSBAD) return (EINVAL); /* XXX */ error = scsi_device_lock(sc_link); if (error) return error; error = dsioctl("sd", dev, cmd, addr, flag, &sd->dk_slices, sdstrategy1, (ds_setgeom_t *)NULL); scsi_device_unlock(sc_link); if (error != -1) return (error); if (PARTITION(dev) != RAW_PART) return (ENOTTY); return (scsi_do_ioctl(dev, cmd, addr, flag, p, sc_link)); } /* * Find out from the device what it's capacity is */ static u_int32_t sd_size(unit, flags) int unit, flags; { struct scsi_read_cap_data rdcap; struct scsi_read_capacity scsi_cmd; u_int32_t size; struct scsi_link *sc_link = SCSI_LINK(&sd_switch, unit); /* * make up a scsi command and ask the scsi driver to do * it for you. */ bzero(&scsi_cmd, sizeof(scsi_cmd)); scsi_cmd.op_code = READ_CAPACITY; /* * If the command works, interpret the result as a 4 byte * number of blocks */ if (scsi_scsi_cmd(sc_link, (struct scsi_generic *) &scsi_cmd, sizeof(scsi_cmd), (u_char *) & rdcap, sizeof(rdcap), SD_RETRIES, 2000, NULL, flags | SCSI_DATA_IN) != 0) { printf("sd%d: could not get size\n", unit); return (0); } else { size = rdcap.addr_0 + 1; size += rdcap.addr_1 << 8; size += rdcap.addr_2 << 16; size += rdcap.addr_3 << 24; } return (size); } /* * Tell the device to map out a defective block */ static errval sd_reassign_blocks(unit, block) int unit, block; { struct scsi_reassign_blocks scsi_cmd; struct scsi_reassign_blocks_data rbdata; struct scsi_link *sc_link = SCSI_LINK(&sd_switch, unit); bzero(&scsi_cmd, sizeof(scsi_cmd)); bzero(&rbdata, sizeof(rbdata)); scsi_cmd.op_code = REASSIGN_BLOCKS; rbdata.length_msb = 0; rbdata.length_lsb = sizeof(rbdata.defect_descriptor[0]); rbdata.defect_descriptor[0].dlbaddr_3 = ((block >> 24) & 0xff); rbdata.defect_descriptor[0].dlbaddr_2 = ((block >> 16) & 0xff); rbdata.defect_descriptor[0].dlbaddr_1 = ((block >> 8) & 0xff); rbdata.defect_descriptor[0].dlbaddr_0 = ((block) & 0xff); return (scsi_scsi_cmd(sc_link, (struct scsi_generic *) &scsi_cmd, sizeof(scsi_cmd), (u_char *) & rbdata, sizeof(rbdata), SD_RETRIES, 5000, NULL, SCSI_DATA_OUT)); } #define b2tol(a) (((unsigned)(a##_1) << 8) + (unsigned)a##_0 ) /* * Get the scsi driver to send a full inquiry to the * device and use the results to fill out the disk * parameter structure. */ static errval sd_get_parms(unit, flags) int unit, flags; { struct scsi_link *sc_link = SCSI_LINK(&sd_switch, unit); struct scsi_data *sd = sc_link->sd; struct disk_parms *disk_parms = &sd->params; struct scsi_mode_sense scsi_cmd; struct scsi_mode_sense_data { struct scsi_mode_header header; struct blk_desc blk_desc; union disk_pages pages; } scsi_sense; u_int32_t sectors; /* * First check if we have it all loaded */ if (sc_link->flags & SDEV_MEDIA_LOADED) return 0; /* * do a "mode sense page 4" */ bzero(&scsi_cmd, sizeof(scsi_cmd)); scsi_cmd.op_code = MODE_SENSE; scsi_cmd.page = 4; scsi_cmd.length = 0x20; #ifdef PC98 if (sd_bios_parms(disk_parms, sc_link)) { } else #endif /* * If the command worked, use the results to fill out * the parameter structure */ if (scsi_scsi_cmd(sc_link, (struct scsi_generic *) &scsi_cmd, sizeof(scsi_cmd), (u_char *) & scsi_sense, sizeof(scsi_sense), SD_RETRIES, 4000, NULL, flags | SCSI_DATA_IN) != 0) { printf("sd%d could not mode sense (4).", unit); printf(" Using ficticious geometry\n"); /* * use adaptec standard ficticious geometry * this depends on which controller (e.g. 1542C is * different. but we have to put SOMETHING here..) */ sectors = sd_size(unit, flags); disk_parms->heads = 64; disk_parms->sectors = 32; disk_parms->cyls = sectors / (64 * 32); disk_parms->secsiz = SECSIZE; disk_parms->disksize = sectors; } else { SC_DEBUG(sc_link, SDEV_DB3, ("%ld cyls, %d heads, %d precomp, %d red_write, %d land_zone\n", scsi_3btou(&scsi_sense.pages.rigid_geometry.ncyl_2), scsi_sense.pages.rigid_geometry.nheads, b2tol(scsi_sense.pages.rigid_geometry.st_cyl_wp), b2tol(scsi_sense.pages.rigid_geometry.st_cyl_rwc), b2tol(scsi_sense.pages.rigid_geometry.land_zone))); /* * KLUDGE!!(for zone recorded disks) * give a number of sectors so that sec * trks * cyls * is <= disk_size * can lead to wasted space! THINK ABOUT THIS ! */ disk_parms->heads = scsi_sense.pages.rigid_geometry.nheads; disk_parms->cyls = scsi_3btou(&scsi_sense.pages.rigid_geometry.ncyl_2); disk_parms->secsiz = scsi_3btou(scsi_sense.blk_desc.blklen); sectors = sd_size(unit, flags); disk_parms->disksize = sectors; /* Check if none of these values are zero */ if(disk_parms->heads && disk_parms->cyls) { sectors /= (disk_parms->heads * disk_parms->cyls); } else { /* set it to something reasonable */ disk_parms->heads = 64; disk_parms->cyls = sectors / (64 * 32); sectors = 32; } /* keep secsiz sane too - we may divide by it later */ if(disk_parms->secsiz == 0) disk_parms->secsiz = SECSIZE; disk_parms->sectors = sectors; /* dubious on SCSI *//*XXX */ } sc_link->flags |= SDEV_MEDIA_LOADED; return 0; } static int sdsize(dev_t dev) { struct scsi_data *sd; sd = SCSI_DATA(&sd_switch, (u_int32_t) SDUNIT(dev)); if (sd == NULL) return (-1); return (dssize(dev, &sd->dk_slices, sdopen, sdclose)); } /* * sense handler: Called to determine what to do when the * device returns a CHECK CONDITION. * * This will issue a retry when the device returns a * non-media hardware failure. The CDC-WREN IV does this * when you access it during thermal calibrarion, so the drive * is pretty useless without this. * * In general, you probably almost always would like to issue a retry * for your disk I/O. It can't hurt too much (the caller only retries * so many times) and it may save your butt. */ static int sd_sense_handler(struct scsi_xfer *xs) { struct scsi_sense_data *sense; struct scsi_inquiry_data *inqbuf; sense = &(xs->sense); /* I don't know what the heck to do with a deferred error, * so I'll just kick it back to the caller. */ if ((sense->error_code & SSD_ERRCODE) == 0x71) return SCSIRET_CONTINUE; if (((sense->error_code & SSD_ERRCODE) == 0x70) && ((sense->ext.extended.flags & SSD_KEY) == 0x05)) /* No point in retrying Illegal Requests */ return SCSIRET_CONTINUE; inqbuf = &(xs->sc_link->inqbuf); /* It is dangerous to retry on removable drives without * looking carefully at the additional sense code * and sense code qualifier and ensuring the disk hasn't changed: */ if (inqbuf->dev_qual2 & SID_REMOVABLE) return SCSIRET_CONTINUE; /* Retry all disk errors. */ scsi_sense_print(xs); if (xs->retries) printf(", retries:%d\n", xs->retries); else printf(", FAILURE\n"); return SCSIRET_DO_RETRY; } /* * dump all of physical memory into the partition specified, starting * at offset 'dumplo' into the partition. */ static errval sddump(dev_t dev) { /* dump core after a system crash */ struct disklabel *lp; register struct scsi_data *sd; /* disk unit to do the IO */ struct scsi_link *sc_link; int32_t num; /* number of sectors to write */ u_int32_t unit, part; int32_t blkoff, blknum, blkcnt = MAXTRANSFER; int32_t nblocks; char *addr; struct scsi_rw_big cmd; static int sddoingadump = 0; struct scsi_xfer *xs = &sx; errval retval; addr = (char *) 0; /* starting address */ /* toss any characters present prior to dump */ while (cncheckc() != -1) ; /* size of memory to dump */ num = Maxmem; unit = SDUNIT(dev); /* eventually support floppies? */ part = PARTITION(dev); /* file system */ sc_link = SCSI_LINK(&sd_switch, unit); if (!sc_link) return ENXIO; sd = sc_link->sd; /* was it ever initialized etc. ? */ if (!(sd->flags & SDINIT)) return (ENXIO); if ((sc_link->flags & SDEV_MEDIA_LOADED) != SDEV_MEDIA_LOADED) return (ENXIO); if (sd->dk_slices == NULL) Debugger("sddump: no slices"); if ((lp = dsgetlabel(dev, sd->dk_slices)) == NULL) return (ENXIO); /* Convert to disk sectors */ num = (u_int32_t) num * PAGE_SIZE / sd->params.secsiz; /* XXX it must be 512 */ /* check if controller active */ if (sddoingadump) return (EFAULT); nblocks = lp->d_partitions[part].p_size; blkoff = lp->d_partitions[part].p_offset; /* XXX */ blkoff += sd->dk_slices->dss_slices[dkslice(dev)].ds_offset; /* check transfer bounds against partition size */ if ((dumplo < 0) || ((dumplo + num) > nblocks)) return (EINVAL); sddoingadump = 1; blknum = dumplo + blkoff; while (num > 0) { - pmap_enter(kernel_pmap, (vm_offset_t)CADDR1, trunc_page(addr), - VM_PROT_READ, TRUE); + if (is_adapter_memory((vm_offset_t)addr)) + pmap_enter(kernel_pmap, (vm_offset_t)CADDR1, + trunc_page(0), VM_PROT_READ, TRUE); + else + pmap_enter(kernel_pmap, (vm_offset_t)CADDR1, + trunc_page(addr), VM_PROT_READ, TRUE); /* * Fill out the scsi command */ bzero(&cmd, sizeof(cmd)); cmd.op_code = WRITE_BIG; cmd.addr_3 = (blknum & 0xff000000) >> 24; cmd.addr_2 = (blknum & 0xff0000) >> 16; cmd.addr_1 = (blknum & 0xff00) >> 8; cmd.addr_0 = blknum & 0xff; cmd.length2 = (blkcnt & 0xff00) >> 8; cmd.length1 = (blkcnt & 0xff); /* * Fill out the scsi_xfer structure * Note: we cannot sleep as we may be an interrupt * don't use scsi_scsi_cmd() as it may want * to wait for an xs. */ bzero(xs, sizeof(sx)); xs->flags |= SCSI_NOMASK | SCSI_NOSLEEP | INUSE | SCSI_DATA_OUT; xs->sc_link = sc_link; xs->retries = SD_RETRIES; xs->timeout = 10000; /* 10000 millisecs for a disk ! */ xs->cmd = (struct scsi_generic *) &cmd; xs->cmdlen = sizeof(cmd); xs->resid = 0; xs->error = XS_NOERROR; xs->bp = 0; xs->data = (u_char *) CADDR1; /* XXX use pmap_enter() */ xs->datalen = blkcnt * sd->params.secsiz; /* * Pass all this info to the scsi driver. */ retval = (*(sc_link->adapter->scsi_cmd)) (xs); switch (retval) { case SUCCESSFULLY_QUEUED: case HAD_ERROR: return (ENXIO); /* we said not to sleep! */ case COMPLETE: break; default: return (ENXIO); /* we said not to sleep! */ } /* * If we are dumping core, it may take a while. * So reassure the user and hold off any watchdogs. */ if ((unsigned)addr % (1024 * 1024) == 0) { #ifdef HW_WDOG if (wdog_tickler) (*wdog_tickler)(); #endif /* HW_WDOG */ printf("%ld ", num / 2048); } /* update block count */ num -= blkcnt; blknum += blkcnt; (int) addr += blkcnt * sd->params.secsiz; /* operator aborting dump? */ if (cncheckc() != -1) return (EINTR); } return (0); } static sd_devsw_installed = 0; static void sd_drvinit(void *unused) { if( ! sd_devsw_installed ) { bdevsw_add_generic(BDEV_MAJOR, CDEV_MAJOR, &sd_bdevsw); sd_devsw_installed = 1; } } SYSINIT(sddev,SI_SUB_DRIVERS,SI_ORDER_MIDDLE+CDEV_MAJOR,sd_drvinit,NULL)