diff options
author | Sergey Babkin <babkin@FreeBSD.org> | 2000-10-24 03:44:31 +0000 |
---|---|---|
committer | Sergey Babkin <babkin@FreeBSD.org> | 2000-10-24 03:44:31 +0000 |
commit | 356329f0ddab666477df3f449cd96bc31a3e3ae9 (patch) | |
tree | 929f5b1862ad768703c07d565bf0a7d73a183b94 | |
parent | 821c54a1eb5a1029ec693ff41b87b842db4af0a5 (diff) | |
download | src-356329f0ddab666477df3f449cd96bc31a3e3ae9.tar.gz src-356329f0ddab666477df3f449cd96bc31a3e3ae9.zip |
Added the CAM-ified wds driver for the ancient WD7000 SCSI card.
Last time it was present in FreeBSD 3.x, before CAM.
Reviewed by: gibbs
Approved by: gibbs
Notes
Notes:
svn path=/head/; revision=67482
-rw-r--r-- | sys/dev/wds/wd7000.c | 1443 |
1 files changed, 1443 insertions, 0 deletions
diff --git a/sys/dev/wds/wd7000.c b/sys/dev/wds/wd7000.c new file mode 100644 index 000000000000..abba71d3bf33 --- /dev/null +++ b/sys/dev/wds/wd7000.c @@ -0,0 +1,1443 @@ +/* + * Copyright (c) 1994 Ludd, University of Lule}, Sweden. + * Copyright (c) 2000 Sergey A. Babkin + * All rights reserved. + * + * Written by Olof Johansson (offe@ludd.luth.se) 1995. + * Based on code written by Theo de Raadt (deraadt@fsa.ca). + * Resurrected, ported to CAM and generally cleaned up by Sergey Babkin + * <babkin@bellatlantic.net> or <babkin@users.sourceforge.net>. + * + * 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 at Ludd, University of Lule} + * and by the FreeBSD project. + * 4. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 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. + * + * $FreeBSD$ + */ + +/* All bugs are subject to removal without further notice */ + +/* + * offe 01/07/95 + * + * This version of the driver _still_ doesn't implement scatter/gather for the + * WD7000-FASST2. This is due to the fact that my controller doesn't seem to + * support it. That, and the lack of documentation makes it impossible for me + * to implement it. What I've done instead is allocated a local buffer, + * contiguous buffer big enough to handle the requests. I haven't seen any + * read/write bigger than 64k, so I allocate a buffer of 64+16k. The data + * that needs to be DMA'd to/from the controller is copied to/from that + * buffer before/after the command is sent to the card. + * + * SB 03/30/00 + * + * An intermediate buffer is needed anyway to make sure that the buffer is + * located under 16MB, otherwise it's out of reach of ISA cards. I've added + * optimizations to allocate space in buffer in fragments. + */ + +/* + * Jumpers: (see The Ref(TM) for more info) + * W1/W2 - interrupt selection: + * W1 (1-2) IRQ3, (3-4) IRQ4, (5-6) IRQ5, (7-8) IRQ7, (9-10) IRQ9 + * W2 (21-22) IRQ10, (19-20) IRQ11, (17-18) IRQ12, (15-16) IRQ14, (13-14) IRQ15 + * + * W2 - DRQ/DACK selection, DRQ and DACK must be the same: + * (5-6) DRQ5 (11-12) DACK5 + * (3-4) DRQ6 (9-10) DACK6 + * (1-2) DRQ7 (7-8) DACK7 + * + * W3 - I/O address selection: open pair of pins (OFF) means 1, jumpered (ON) means 0 + * pair (1-2) is bit 3, ..., pair (9-10) is bit 7. All the other bits are equal + * to the value 0x300. In bitwise representation that would be: + * 0 0 1 1 (9-10) (7-8) (5-6) (3-4) (1-2) 0 0 0 + * For example, address 0x3C0, bitwise 1111000000 will be represented as: + * (9-10) OFF, (7-8) OFF, (5-6) ON, (3-4) ON, (1-2) ON + * + * W4 - BIOS address: open pair of pins (OFF) means 1, jumpered (ON) means 0 + * pair (1-2) is bit 13, ..., pair (7-8) is bit 16. All the other bits are + * equal to the value 0xC0000. In bitwise representation that would be: + * 1 1 0 (7-8) (5-6) (3-4) (1-2) 0 0000 0000 0000 + * For example, address 0xD8000 will be represented as: + * (7-8) OFF, (5-6) OFF, (3-4) ON, (1-2) ON + * + * W98 (on newer cards) - BIOS enabled; on older cards just remove the BIOS + * chip to disable it + * W99 (on newer cards) - ROM size (1-2) OFF, (3-4) ON + * + * W5 - terminator power + * ON - host supplies term. power + * OFF - target supplies term. power + * + * W6, W9 - floppy support (a bit cryptic): + * W6 ON, W9 ON - disabled + * W6 OFF, W9 ON - enabled with HardCard only + * W6 OFF, W9 OFF - enabled with no hardCard or Combo + * + * Default: I/O 0x350, IRQ15, DMA6 + */ + +/* + * debugging levels: + * 0 - disabled + * 1 - print debugging messages + * 2 - collect debugging messages in an internal log buffer which can be + * printed later by calling wds_printlog from DDB + * + * Both kind of logs are heavy and interact significantly with the timing + * of commands, so the observed problems may become invisible if debug + * logging is enabled. + * + * The light-weight logging facility may be enabled by defining + * WDS_ENABLE_SMALLOG as 1. It has very little overhead and allows observing + * the traces of various race conditions without affectiong them but the log is + * quite terse. The small log can be printer from DDB by calling + * wds_printsmallog. + */ +#ifndef WDS_DEBUG +#define WDS_DEBUG 0 +#endif + +#ifndef WDS_ENABLE_SMALLOG +#define WDS_ENABLE_SMALLOG 0 +#endif + +#include <sys/types.h> +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/errno.h> +#include <sys/kernel.h> +#include <sys/assym.h> + +#include <sys/bio.h> +#include <sys/buf.h> +#include <sys/proc.h> +#include <sys/disklabel.h> + +#include <cam/cam.h> +#include <cam/cam_ccb.h> +#include <cam/cam_sim.h> +#include <cam/cam_xpt_sim.h> +#include <cam/cam_debug.h> +#include <cam/scsi/scsi_all.h> +#include <cam/scsi/scsi_message.h> + +#include <machine/clock.h> + +#include <vm/vm.h> +#include <vm/vm_param.h> +#include <vm/pmap.h> + +#include <sys/module.h> +#include <sys/bus.h> +#include <machine/bus.h> +#include <machine/resource.h> +#include <sys/rman.h> + +#include <isa/isavar.h> +#include <isa/pnpvar.h> + +/* somehow offsetof() was lost in FreeBSD 5.0, so declare it */ +#undef offsetof +#define offsetof(type, field) ( (int)( &((type *)0)->field ) ) + +#define WDSTOPHYS(wp, a) ( ((u_long)a) - ((u_long)wp->dx) + ((u_long)wp->dx_p) ) +#define WDSTOVIRT(wp, a) ( ((char *)a) - ((char*)wp->dx_p) + ((char *)wp->dx) ) + +/* 0x10000 (64k) should be enough. But just to be sure... */ +#define BUFSIZ 0x12000 +/* buffer fragment size, no more than 32 frags per buffer */ +#define FRAGSIZ 0x1000 + + +/* WD7000 registers */ +#define WDS_STAT 0 /* read */ +#define WDS_IRQSTAT 1 /* read */ + +#define WDS_CMD 0 /* write */ +#define WDS_IRQACK 1 /* write */ +#define WDS_HCR 2 /* write */ + +#define WDS_NPORTS 4 /* number of ports used */ + +/* WDS_STAT (read) defs */ +#define WDS_IRQ 0x80 +#define WDS_RDY 0x40 +#define WDS_REJ 0x20 +#define WDS_INIT 0x10 + +/* WDS_IRQSTAT (read) defs */ +#define WDSI_MASK 0xc0 +#define WDSI_ERR 0x00 +#define WDSI_MFREE 0x80 +#define WDSI_MSVC 0xc0 + +/* WDS_CMD (write) defs */ +#define WDSC_NOOP 0x00 +#define WDSC_INIT 0x01 +#define WDSC_DISUNSOL 0x02 /* disable unsolicited ints */ +#define WDSC_ENAUNSOL 0x03 /* enable unsolicited ints */ +#define WDSC_IRQMFREE 0x04 /* interrupt on free RQM */ +#define WDSC_SCSIRESETSOFT 0x05 /* soft reset */ +#define WDSC_SCSIRESETHARD 0x06 /* hard reset ack */ +#define WDSC_MSTART(m) (0x80 + (m)) /* start mailbox */ +#define WDSC_MMSTART(m) (0xc0 + (m)) /* start all mailboxes */ + +/* WDS_HCR (write) defs */ +#define WDSH_IRQEN 0x08 +#define WDSH_DRQEN 0x04 +#define WDSH_SCSIRESET 0x02 +#define WDSH_ASCRESET 0x01 + +struct wds_cmd { + u_int8_t cmd; + u_int8_t targ; + u_int8_t scb[12]; + u_int8_t stat; + u_int8_t venderr; + u_int8_t len[3]; + u_int8_t data[3]; + u_int8_t next[3]; + u_int8_t write; + u_int8_t xx[6]; +}; + +struct wds_req { + struct wds_cmd cmd; + union ccb *ccb; + enum { + WR_DONE = 0x01, + WR_SENSE = 0x02 + } flags; + u_int8_t *buf; /* address of linear data buffer */ + u_int32_t mask; /* mask of allocated fragments */ + u_int8_t ombn; + u_int8_t id; /* number of request */ +}; + +#define WDSX_SCSICMD 0x00 +#define WDSX_OPEN_RCVBUF 0x80 +#define WDSX_RCV_CMD 0x81 +#define WDSX_RCV_DATA 0x82 +#define WDSX_RCV_DATASTAT 0x83 +#define WDSX_SND_DATA 0x84 +#define WDSX_SND_DATASTAT 0x85 +#define WDSX_SND_CMDSTAT 0x86 +#define WDSX_READINIT 0x88 +#define WDSX_READSCSIID 0x89 +#define WDSX_SETUNSOLIRQMASK 0x8a +#define WDSX_GETUNSOLIRQMASK 0x8b +#define WDSX_GETFIRMREV 0x8c +#define WDSX_EXECDIAG 0x8d +#define WDSX_SETEXECPARM 0x8e +#define WDSX_GETEXECPARM 0x8f + +struct wds_mb { + u_int8_t stat; + u_int8_t addr[3]; +}; +/* ICMB status value */ +#define ICMB_OK 0x01 +#define ICMB_OKERR 0x02 +#define ICMB_ETIME 0x04 +#define ICMB_ERESET 0x05 +#define ICMB_ETARCMD 0x06 +#define ICMB_ERESEL 0x80 +#define ICMB_ESEL 0x81 +#define ICMB_EABORT 0x82 +#define ICMB_ESRESET 0x83 +#define ICMB_EHRESET 0x84 + +struct wds_setup { + u_int8_t cmd; + u_int8_t scsi_id; + u_int8_t buson_t; + u_int8_t busoff_t; + u_int8_t xx; + u_int8_t mbaddr[3]; + u_int8_t nomb; + u_int8_t nimb; +}; + +/* the code depends on equality of these parameters */ +#define MAXSIMUL 8 +#define WDS_NOMB MAXSIMUL +#define WDS_NIMB MAXSIMUL + +static int fragsiz; +static int nfrags; + +/* structure for data exchange with controller */ + +struct wdsdx { + struct wds_req req[MAXSIMUL]; + struct wds_mb ombs[MAXSIMUL]; + struct wds_mb imbs[MAXSIMUL]; + u_int8_t data[BUFSIZ]; +}; + +/* structure softc */ + +struct wds { + device_t dev; + int unit; + int addr; + int drq; + struct cam_sim *sim; /* SIM descriptor for this card */ + struct cam_path *path; /* wildcard path for this card */ + char want_wdsr; /* resource shortage flag */ + u_int32_t data_free; + u_int32_t wdsr_free; + struct wdsdx *dx; + struct wdsdx *dx_p; /* physical address */ + struct resource *port_r; + int port_rid; + struct resource *drq_r; + int drq_rid; + struct resource *intr_r; + int intr_rid; + void *intr_cookie; + bus_dma_tag_t bustag; + bus_dmamap_t busmap; +}; + +#define ccb_wdsr spriv_ptr1 /* for wds request */ + +static int wds_probe(device_t dev); +static int wds_attach(device_t dev); +static void wds_intr(struct wds *wp); + +static void wds_action(struct cam_sim * sim, union ccb * ccb); +static void wds_poll(struct cam_sim * sim); + +static int wds_preinit(struct wds *wp); +static int wds_init(struct wds *wp); + +static void wds_alloc_callback(void *arg, bus_dma_segment_t *seg, + int nseg, int error); +static void wds_free_resources(struct wds *wp); + +static struct wds_req *wdsr_alloc(struct wds *wp); + +static void wds_scsi_io(struct cam_sim * sim, struct ccb_scsiio * csio); +static void wdsr_ccb_done(struct wds *wp, struct wds_req *r, + union ccb *ccb, u_int32_t status); + +static void wds_done(struct wds *wp, struct wds_req *r, u_int8_t stat); +static int wds_runsense(struct wds *wp, struct wds_req *r); +static int wds_getvers(struct wds *wp); + +static int wds_cmd(int base, u_int8_t * p, int l); +static void wds_wait(int reg, int mask, int val); + +static struct wds_req *cmdtovirt(struct wds *wp, u_int32_t phys); + +static u_int32_t frag_alloc(struct wds *wp, int size, u_int8_t **res, + u_int32_t *maskp); +static void frag_free(struct wds *wp, u_int32_t mask); + +void wds_print(void); + +#if WDS_ENABLE_SMALLOG==1 +static __inline void smallog(char c); +void wds_printsmallog(void); +#endif /* SMALLOG */ + +/* SCSI ID of the adapter itself */ +#ifndef WDS_HBA_ID +#define WDS_HBA_ID 7 +#endif + +#if WDS_DEBUG == 2 +#define LOGLINESIZ 81 +#define NLOGLINES 300 +#define DBX wds_nextlog(), LOGLINESIZ, +#define DBG snprintf + +static char wds_log[NLOGLINES][LOGLINESIZ]; +static int logwrite = 0, logread = 0; +static char *wds_nextlog(void); +void wds_printlog(void); + +#elif WDS_DEBUG != 0 +#define DBX +#define DBG printf +#else +#define DBX +#define DBG if(0) printf +#endif + +/* the table of supported bus methods */ +static device_method_t wds_isa_methods[] = { + DEVMETHOD(device_probe, wds_probe), + DEVMETHOD(device_attach, wds_attach), + { 0, 0 } +}; + +static driver_t wds_isa_driver = { + "wds", + wds_isa_methods, + sizeof(struct wds), +}; + +static devclass_t wds_devclass; + +DRIVER_MODULE(wds, isa, wds_isa_driver, wds_devclass, 0, 0); + +#if WDS_ENABLE_SMALLOG==1 +#define SMALLOGSIZ 512 +static char wds_smallog[SMALLOGSIZ]; +static char *wds_smallogp = wds_smallog; +static char wds_smallogover = 0; + +static __inline void +smallog(char c) +{ + *wds_smallogp = c; + if (++wds_smallogp == &wds_smallog[SMALLOGSIZ]) { + wds_smallogp = wds_smallog; + wds_smallogover = 1; + } +} + +#define smallog2(a, b) (smallog(a), smallog(b)) +#define smallog3(a, b, c) (smallog(a), smallog(b), smallog(c)) +#define smallog4(a, b, c, d) (smallog(a),smallog(b),smallog(c),smallog(d)) + +void +wds_printsmallog(void) +{ + int i; + char *p; + + printf("wds: "); + p = wds_smallogover ? wds_smallogp : wds_smallog; + i = 0; + do { + printf("%c", *p); + if (++p == &wds_smallog[SMALLOGSIZ]) + p = wds_smallog; + if (++i == 70) { + i = 0; + printf("\nwds: "); + } + } while (p != wds_smallogp); + printf("\n"); +} +#else +#define smallog(a) +#define smallog2(a, b) +#define smallog3(a, b, c) +#define smallog4(a, b, c, d) +#endif /* SMALLOG */ + +static int +wds_probe(device_t dev) +{ + struct wds *wp; + int error = 0; + int irq; + + /* No pnp support */ + if (isa_get_vendorid(dev)) + return (ENXIO); + + wp = (struct wds *) device_get_softc(dev); + wp->unit = device_get_unit(dev); + wp->dev = dev; + + wp->addr = bus_get_resource_start(dev, SYS_RES_IOPORT, 0 /*rid*/); + if (wp->addr == 0 || wp->addr <0x300 + || wp->addr > 0x3f8 || wp->addr & 0x7) { + device_printf(dev, "invalid port address 0x%x\n", wp->addr); + return (ENXIO); + } + + if (bus_set_resource(dev, SYS_RES_IOPORT, 0, wp->addr, WDS_NPORTS) < 0) + return (ENXIO); + + /* get the DRQ */ + wp->drq = bus_get_resource_start(dev, SYS_RES_DRQ, 0 /*rid*/); + if (wp->drq < 5 || wp->drq > 7) { + device_printf(dev, "invalid DRQ %d\n", wp->drq); + return (ENXIO); + } + + /* get the IRQ */ + irq = bus_get_resource_start(dev, SYS_RES_IRQ, 0 /*rid*/); + if (irq < 3) { + device_printf(dev, "invalid IRQ %d\n", irq); + return (ENXIO); + } + + wp->port_rid = 0; + wp->port_r = bus_alloc_resource(dev, SYS_RES_IOPORT, &wp->port_rid, + /*start*/ 0, /*end*/ ~0, + /*count*/ 0, RF_ACTIVE); + if (wp->port_r == NULL) + return (ENXIO); + + error = wds_preinit(wp); + + /* + * We cannot hold resources between probe and + * attach as we may never be attached. + */ + wds_free_resources(wp); + + return (error); +} + +static int +wds_attach(device_t dev) +{ + struct wds *wp; + struct cam_devq *devq; + struct cam_sim *sim; + struct cam_path *pathp; + int i; + int error = 0; + + wp = (struct wds *)device_get_softc(dev); + + wp->port_rid = 0; + wp->port_r = bus_alloc_resource(dev, SYS_RES_IOPORT, &wp->port_rid, + /*start*/ 0, /*end*/ ~0, + /*count*/ 0, RF_ACTIVE); + if (wp->port_r == NULL) + return (ENXIO); + + /* We must now release resources on error. */ + + wp->drq_rid = 0; + wp->drq_r = bus_alloc_resource(dev, SYS_RES_DRQ, &wp->drq_rid, + /*start*/ 0, /*end*/ ~0, + /*count*/ 0, RF_ACTIVE); + if (wp->drq_r == NULL) + goto bad; + + wp->intr_rid = 0; + wp->intr_r = bus_alloc_resource(dev, SYS_RES_IRQ, &wp->intr_rid, + /*start*/ 0, /*end*/ ~0, + /*count*/ 0, RF_ACTIVE); + if (wp->intr_r == NULL) + goto bad; + error = bus_setup_intr(dev, wp->intr_r, INTR_TYPE_CAM, + (driver_intr_t *)wds_intr, (void *)wp, + &wp->intr_cookie); + if (error) + goto bad; + + /* now create the memory buffer */ + error = bus_dma_tag_create(NULL, /*alignment*/4, + /*boundary*/0, + /*lowaddr*/BUS_SPACE_MAXADDR_24BIT, + /*highaddr*/ BUS_SPACE_MAXADDR, + /*filter*/ NULL, /*filterarg*/ NULL, + /*maxsize*/ sizeof(* wp->dx), + /*nsegments*/ 1, + /*maxsegsz*/ sizeof(* wp->dx), /*flags*/ 0, + &wp->bustag); + if (error) + goto bad; + + error = bus_dmamem_alloc(wp->bustag, (void **)&wp->dx, + /*flags*/ 0, &wp->busmap); + if (error) + goto bad; + + bus_dmamap_load(wp->bustag, wp->busmap, (void *)wp->dx, + sizeof(* wp->dx), wds_alloc_callback, + (void *)&wp->dx_p, /*flags*/0); + + /* initialize the wds_req structures on this unit */ + for(i=0; i<MAXSIMUL; i++) { + wp->dx->req[i].id = i; + wp->wdsr_free |= 1<<i; + } + + /* initialize the memory buffer allocation for this unit */ + if (BUFSIZ / FRAGSIZ > 32) { + fragsiz = (BUFSIZ / 32) & ~0x01; /* keep it word-aligned */ + device_printf(dev, "data buffer fragment size too small. " + "BUFSIZE / FRAGSIZE must be <= 32\n"); + } else + fragsiz = FRAGSIZ & ~0x01; /* keep it word-aligned */ + + wp->data_free = 0; + nfrags = 0; + for (i = fragsiz; i <= BUFSIZ; i += fragsiz) { + nfrags++; + wp->data_free = (wp->data_free << 1) | 1; + } + + /* complete the hardware initialization */ + if (wds_init(wp) != 0) + goto bad; + + if (wds_getvers(wp) == -1) + device_printf(dev, "getvers failed\n"); + device_printf(dev, "using %d bytes / %d frags for dma buffer\n", + BUFSIZ, nfrags); + + devq = cam_simq_alloc(MAXSIMUL); + if (devq == NULL) + goto bad; + + sim = cam_sim_alloc(wds_action, wds_poll, "wds", (void *) wp, + wp->unit, 1, 1, devq); + if (sim == NULL) { + cam_simq_free(devq); + goto bad; + } + wp->sim = sim; + + if (xpt_bus_register(sim, 0) != CAM_SUCCESS) { + cam_sim_free(sim, /* free_devq */ TRUE); + goto bad; + } + if (xpt_create_path(&pathp, /* periph */ NULL, + cam_sim_path(sim), CAM_TARGET_WILDCARD, + CAM_LUN_WILDCARD) != CAM_REQ_CMP) { + xpt_bus_deregister(cam_sim_path(sim)); + cam_sim_free(sim, /* free_devq */ TRUE); + goto bad; + } + wp->path = pathp; + + return (0); + +bad: + wds_free_resources(wp); + if (error) + return (error); + else /* exact error is unknown */ + return (ENXIO); +} + +/* callback to save the physical address */ +static void +wds_alloc_callback(void *arg, bus_dma_segment_t *seg, int nseg, int error) +{ + *(bus_addr_t *)arg = seg[0].ds_addr; +} + +static void +wds_free_resources(struct wds *wp) +{ + /* check every resource and free if not zero */ + + /* interrupt handler */ + if (wp->intr_r) { + bus_teardown_intr(wp->dev, wp->intr_r, wp->intr_cookie); + bus_release_resource(wp->dev, SYS_RES_IRQ, wp->intr_rid, + wp->intr_r); + wp->intr_r = 0; + } + + /* all kinds of memory maps we could have allocated */ + if (wp->dx_p) { + bus_dmamap_unload(wp->bustag, wp->busmap); + wp->dx_p = 0; + } + if (wp->dx) { /* wp->busmap may be legitimately equal to 0 */ + /* the map will also be freed */ + bus_dmamem_free(wp->bustag, wp->dx, wp->busmap); + wp->dx = 0; + } + if (wp->bustag) { + bus_dma_tag_destroy(wp->bustag); + wp->bustag = 0; + } + /* release all the bus resources */ + if (wp->drq_r) { + bus_release_resource(wp->dev, SYS_RES_DRQ, + wp->drq_rid, wp->drq_r); + wp->drq_r = 0; + } + if (wp->port_r) { + bus_release_resource(wp->dev, SYS_RES_IOPORT, + wp->port_rid, wp->port_r); + wp->port_r = 0; + } +} + +/* allocate contiguous fragments from the buffer */ +static u_int32_t +frag_alloc(struct wds *wp, int size, u_int8_t **res, u_int32_t *maskp) +{ + int i; + u_int32_t mask; + u_int32_t free; + + if (size > fragsiz * nfrags) + return (CAM_REQ_TOO_BIG); + + mask = 1; /* always allocate at least 1 fragment */ + for (i = fragsiz; i < size; i += fragsiz) + mask = (mask << 1) | 1; + + free = wp->data_free; + if(free != 0) { + i = ffs(free)-1; /* ffs counts bits from 1 */ + for (mask <<= i; i < nfrags; i++) { + if ((free & mask) == mask) { + wp->data_free &= ~mask; /* mark frags as busy */ + *maskp = mask; + *res = &wp->dx->data[fragsiz * i]; + DBG(DBX "wds%d: allocated buffer mask=0x%x\n", + wp->unit, mask); + return (CAM_REQ_CMP); + } + if (mask & 0x80000000) + break; + + mask <<= 1; + } + } + return (CAM_REQUEUE_REQ); /* no free memory now, try later */ +} + +static void +frag_free(struct wds *wp, u_int32_t mask) +{ + wp->data_free |= mask; /* mark frags as free */ + DBG(DBX "wds%d: freed buffer mask=0x%x\n", wp->unit, mask); +} + +static struct wds_req * +wdsr_alloc(struct wds *wp) +{ + struct wds_req *r; + int x; + int i; + + r = NULL; + x = splcam(); + + /* anyway most of the time only 1 or 2 commands will + * be active because SCSI disconnect is not supported + * by hardware, so the search should be fast enough + */ + i = ffs(wp->wdsr_free) - 1; + if(i < 0) { + splx(x); + return (NULL); + } + wp->wdsr_free &= ~ (1<<i); + r = &wp->dx->req[i]; + r->flags = 0; /* reset all flags */ + r->ombn = i; /* luckily we have one omb per wdsr */ + wp->dx->ombs[i].stat = 1; + + r->mask = 0; + splx(x); + smallog3('r', i + '0', r->ombn + '0'); + return (r); +} + +static void +wds_intr(struct wds *wp) +{ + struct wds_req *rp; + struct wds_mb *in; + u_int8_t stat; + u_int8_t c; + int addr = wp->addr; + + DBG(DBX "wds%d: interrupt [\n", wp->unit); + smallog('['); + + if (inb(addr + WDS_STAT) & WDS_IRQ) { + c = inb(addr + WDS_IRQSTAT); + if ((c & WDSI_MASK) == WDSI_MSVC) { + c = c & ~WDSI_MASK; + in = &wp->dx->imbs[c]; + + rp = cmdtovirt(wp, scsi_3btoul(in->addr)); + stat = in->stat; + + if (rp != NULL) + wds_done(wp, rp, stat); + else + device_printf(wp->dev, + "got weird command address %p" + "from controller\n", rp); + + in->stat = 0; + } else + device_printf(wp->dev, + "weird interrupt, irqstat=0x%x\n", c); + outb(addr + WDS_IRQACK, 0); + } else { + smallog('?'); + } + smallog(']'); + DBG(DBX "wds%d: ]\n", wp->unit); +} + +static void +wds_done(struct wds *wp, struct wds_req *r, u_int8_t stat) +{ + struct ccb_hdr *ccb_h; + struct ccb_scsiio *csio; + int status; + + smallog('d'); + + if (r->flags & WR_DONE) { + device_printf(wp->dev, + "request %d reported done twice\n", r->id); + smallog2('x', r->id + '0'); + return; + } + + smallog(r->id + '0'); + ccb_h = &r->ccb->ccb_h; + csio = &r->ccb->csio; + status = CAM_REQ_CMP_ERR; + + DBG(DBX "wds%d: %s stat=0x%x c->stat=0x%x c->venderr=0x%x\n", wp->unit, + r->flags & WR_SENSE ? "(sense)" : "", + stat, r->cmd.stat, r->cmd.venderr); + + if (r->flags & WR_SENSE) { + if (stat == ICMB_OK || (stat == ICMB_OKERR && r->cmd.stat == 0)) { + DBG(DBX "wds%d: sense 0x%x\n", wp->unit, r->buf[0]); + /* it has the same size now but for future */ + bcopy(r->buf, &csio->sense_data, + sizeof(struct scsi_sense_data) > csio->sense_len ? + csio->sense_len : sizeof(struct scsi_sense_data)); + if (sizeof(struct scsi_sense_data) >= csio->sense_len) + csio->sense_resid = 0; + else + csio->sense_resid = + csio->sense_len + - sizeof(struct scsi_sense_data); + status = CAM_AUTOSNS_VALID | CAM_SCSI_STATUS_ERROR; + } else { + status = CAM_AUTOSENSE_FAIL; + } + } else { + switch (stat) { + case ICMB_OK: + if (ccb_h) { + csio->resid = 0; + csio->scsi_status = r->cmd.stat; + status = CAM_REQ_CMP; + } + break; + case ICMB_OKERR: + if (ccb_h) { + csio->scsi_status = r->cmd.stat; + if (r->cmd.stat) { + if (ccb_h->flags & CAM_DIS_AUTOSENSE) + status = CAM_SCSI_STATUS_ERROR; + else { + if ( wds_runsense(wp, r) == CAM_REQ_CMP ) + return; + /* in case of error continue with freeing of CCB */ + } + } else { + csio->resid = 0; + status = CAM_REQ_CMP; + } + } + break; + case ICMB_ETIME: + if (ccb_h) + status = CAM_SEL_TIMEOUT; + break; + case ICMB_ERESET: + case ICMB_ETARCMD: + case ICMB_ERESEL: + case ICMB_ESEL: + case ICMB_EABORT: + case ICMB_ESRESET: + case ICMB_EHRESET: + if (ccb_h) + status = CAM_REQ_CMP_ERR; + break; + } + + if (ccb_h && (ccb_h->flags & CAM_DIR_MASK) == CAM_DIR_IN) { + /* we accept only virtual addresses in wds_action() */ + bcopy(r->buf, csio->data_ptr, csio->dxfer_len); + } + } + + r->flags |= WR_DONE; + wp->dx->ombs[r->ombn].stat = 0; + + if (ccb_h) { + wdsr_ccb_done(wp, r, r->ccb, status); + smallog3('-', ccb_h->target_id + '0', ccb_h->target_lun + '0'); + } else { + frag_free(wp, r->mask); + if (wp->want_wdsr) { + wp->want_wdsr = 0; + xpt_release_simq(wp->sim, /* run queue */ 1); + } + wp->wdsr_free |= (1 << r->id); + } + + DBG(DBX "wds%d: request 0x%x done\n", wp->unit, (u_int) r); +} + +/* command returned bad status, request sense */ + +static int +wds_runsense(struct wds *wp, struct wds_req *r) +{ + u_int8_t c; + struct ccb_hdr *ccb_h; + + ccb_h = &r->ccb->ccb_h; + + r->flags |= WR_SENSE; + scsi_ulto3b(WDSTOPHYS(wp, &r->cmd), + wp->dx->ombs[r->ombn].addr); + bzero(&r->cmd, sizeof r->cmd); + r->cmd.cmd = WDSX_SCSICMD; + r->cmd.targ = (ccb_h->target_id << 5) | + ccb_h->target_lun; + + scsi_ulto3b(0, r->cmd.next); + + r->cmd.scb[0] = REQUEST_SENSE; + r->cmd.scb[1] = ccb_h->target_lun << 5; + r->cmd.scb[4] = sizeof(struct scsi_sense_data); + r->cmd.scb[5] = 0; + scsi_ulto3b(WDSTOPHYS(wp, r->buf), r->cmd.data); + scsi_ulto3b(sizeof(struct scsi_sense_data), r->cmd.len); + r->cmd.write = 0x80; + + outb(wp->addr + WDS_HCR, WDSH_IRQEN | WDSH_DRQEN); + + wp->dx->ombs[r->ombn].stat = 1; + c = WDSC_MSTART(r->ombn); + + if (wds_cmd(wp->addr, &c, sizeof c) != 0) { + device_printf(wp->dev, "unable to start outgoing sense mbox\n"); + wp->dx->ombs[r->ombn].stat = 0; + wdsr_ccb_done(wp, r, r->ccb, CAM_AUTOSENSE_FAIL); + return CAM_AUTOSENSE_FAIL; + } else { + DBG(DBX "wds%d: enqueued status cmd 0x%x, r=0x%x\n", + wp->unit, r->cmd.scb[0] & 0xFF, (u_int) r); + /* don't free CCB yet */ + smallog3('*', ccb_h->target_id + '0', + ccb_h->target_lun + '0'); + return CAM_REQ_CMP; + } +} + +static int +wds_getvers(struct wds *wp) +{ + struct wds_req *r; + int base; + u_int8_t c; + int i; + + base = wp->addr; + + r = wdsr_alloc(wp); + if (!r) { + device_printf(wp->dev, "no request slot available!\n"); + return (-1); + } + r->flags &= ~WR_DONE; + + r->ccb = NULL; + + scsi_ulto3b(WDSTOPHYS(wp, &r->cmd), wp->dx->ombs[r->ombn].addr); + + bzero(&r->cmd, sizeof r->cmd); + r->cmd.cmd = WDSX_GETFIRMREV; + + outb(base + WDS_HCR, WDSH_DRQEN); + + c = WDSC_MSTART(r->ombn); + if (wds_cmd(base, (u_int8_t *) & c, sizeof c)) { + device_printf(wp->dev, "version request failed\n"); + wp->wdsr_free |= (1 << r->id); + wp->dx->ombs[r->ombn].stat = 0; + return (-1); + } + while (1) { + i = 0; + while ((inb(base + WDS_STAT) & WDS_IRQ) == 0) { + DELAY(9000); + if (++i == 100) { + device_printf(wp->dev, "getvers timeout\n"); + return (-1); + } + } + wds_intr(wp); + if (r->flags & WR_DONE) { + device_printf(wp->dev, "firmware version %d.%02d\n", + r->cmd.targ, r->cmd.scb[0]); + wp->wdsr_free |= (1 << r->id); + return (0); + } + } +} + +static void +wdsr_ccb_done(struct wds *wp, struct wds_req *r, + union ccb *ccb, u_int32_t status) +{ + ccb->ccb_h.ccb_wdsr = 0; + + if (r != NULL) { + /* To implement timeouts we would need to know how to abort the + * command on controller, and this is a great mystery. + * So for now we just pass the responsibility for timeouts + * to the controlles itself, it does that reasonably good. + */ + /* untimeout(_timeout, (caddr_t) hcb, ccb->ccb_h.timeout_ch); */ + /* we're about to free a hcb, so the shortage has ended */ + frag_free(wp, r->mask); + if (wp->want_wdsr && status != CAM_REQUEUE_REQ) { + wp->want_wdsr = 0; + status |= CAM_RELEASE_SIMQ; + smallog('R'); + } + wp->wdsr_free |= (1 << r->id); + } + ccb->ccb_h.status = + status | (ccb->ccb_h.status & ~(CAM_STATUS_MASK | CAM_SIM_QUEUED)); + xpt_done(ccb); +} + +static void +wds_scsi_io(struct cam_sim * sim, struct ccb_scsiio * csio) +{ + int unit = cam_sim_unit(sim); + struct wds *wp; + struct ccb_hdr *ccb_h; + struct wds_req *r; + int base; + u_int8_t c; + int error; + int n; + + wp = (struct wds *)cam_sim_softc(sim); + ccb_h = &csio->ccb_h; + + DBG(DBX "wds%d: cmd TARG=%d LUN=%d\n", unit, ccb_h->target_id, + ccb_h->target_lun); + + if (ccb_h->target_id > 7 || ccb_h->target_id == WDS_HBA_ID) { + ccb_h->status = CAM_TID_INVALID; + xpt_done((union ccb *) csio); + return; + } + if (ccb_h->target_lun > 7) { + ccb_h->status = CAM_LUN_INVALID; + xpt_done((union ccb *) csio); + return; + } + if (csio->dxfer_len > BUFSIZ) { + ccb_h->status = CAM_REQ_TOO_BIG; + xpt_done((union ccb *) csio); + return; + } + if (ccb_h->flags & (CAM_CDB_PHYS | CAM_SCATTER_VALID | CAM_DATA_PHYS)) { + /* don't support these */ + ccb_h->status = CAM_REQ_INVALID; + xpt_done((union ccb *) csio); + return; + } + base = wp->addr; + + /* + * this check is mostly for debugging purposes, + * "can't happen" normally. + */ + if(wp->want_wdsr) { + DBG(DBX "wds%d: someone already waits for buffer\n", unit); + smallog('b'); + n = xpt_freeze_simq(sim, /* count */ 1); + smallog('0'+n); + ccb_h->status = CAM_REQUEUE_REQ; + xpt_done((union ccb *) csio); + return; + } + + r = wdsr_alloc(wp); + if (r == NULL) { + device_printf(wp->dev, "no request slot available!\n"); + wp->want_wdsr = 1; + n = xpt_freeze_simq(sim, /* count */ 1); + smallog2('f', '0'+n); + ccb_h->status = CAM_REQUEUE_REQ; + xpt_done((union ccb *) csio); + return; + } + + ccb_h->ccb_wdsr = (void *) r; + r->ccb = (union ccb *) csio; + + switch (error = frag_alloc(wp, csio->dxfer_len, &r->buf, &r->mask)) { + case CAM_REQ_CMP: + break; + case CAM_REQUEUE_REQ: + DBG(DBX "wds%d: no data buffer available\n", unit); + wp->want_wdsr = 1; + n = xpt_freeze_simq(sim, /* count */ 1); + smallog2('f', '0'+n); + wdsr_ccb_done(wp, r, r->ccb, CAM_REQUEUE_REQ); + return; + default: + DBG(DBX "wds%d: request is too big\n", unit); + wdsr_ccb_done(wp, r, r->ccb, error); + break; + } + + ccb_h->status |= CAM_SIM_QUEUED; + r->flags &= ~WR_DONE; + + scsi_ulto3b(WDSTOPHYS(wp, &r->cmd), wp->dx->ombs[r->ombn].addr); + + bzero(&r->cmd, sizeof r->cmd); + r->cmd.cmd = WDSX_SCSICMD; + r->cmd.targ = (ccb_h->target_id << 5) | ccb_h->target_lun; + + if (ccb_h->flags & CAM_CDB_POINTER) + bcopy(csio->cdb_io.cdb_ptr, &r->cmd.scb, + csio->cdb_len < 12 ? csio->cdb_len : 12); + else + bcopy(csio->cdb_io.cdb_bytes, &r->cmd.scb, + csio->cdb_len < 12 ? csio->cdb_len : 12); + + scsi_ulto3b(csio->dxfer_len, r->cmd.len); + + if (csio->dxfer_len > 0 + && (ccb_h->flags & CAM_DIR_MASK) == CAM_DIR_OUT) { + /* we already rejected physical or scattered addresses */ + bcopy(csio->data_ptr, r->buf, csio->dxfer_len); + } + scsi_ulto3b(csio->dxfer_len ? WDSTOPHYS(wp, r->buf) : 0, r->cmd.data); + + if ((ccb_h->flags & CAM_DIR_MASK) == CAM_DIR_IN) + r->cmd.write = 0x80; + else + r->cmd.write = 0x00; + + scsi_ulto3b(0, r->cmd.next); + + outb(base + WDS_HCR, WDSH_IRQEN | WDSH_DRQEN); + + c = WDSC_MSTART(r->ombn); + + if (wds_cmd(base, &c, sizeof c) != 0) { + device_printf(wp->dev, "unable to start outgoing mbox\n"); + wp->dx->ombs[r->ombn].stat = 0; + wdsr_ccb_done(wp, r, r->ccb, CAM_RESRC_UNAVAIL); + return; + } + DBG(DBX "wds%d: enqueued cmd 0x%x, r=0x%x\n", unit, + r->cmd.scb[0] & 0xFF, (u_int) r); + + smallog3('+', ccb_h->target_id + '0', ccb_h->target_lun + '0'); +} + +static void +wds_action(struct cam_sim * sim, union ccb * ccb) +{ + int unit = cam_sim_unit(sim); + int s; + + DBG(DBX "wds%d: action 0x%x\n", unit, ccb->ccb_h.func_code); + switch (ccb->ccb_h.func_code) { + case XPT_SCSI_IO: + s = splcam(); + DBG(DBX "wds%d: SCSI IO entered\n", unit); + wds_scsi_io(sim, &ccb->csio); + DBG(DBX "wds%d: SCSI IO returned\n", unit); + splx(s); + break; + case XPT_RESET_BUS: + /* how to do it right ? */ + printf("wds%d: reset\n", unit); + ccb->ccb_h.status = CAM_REQ_CMP; + xpt_done(ccb); + break; + case XPT_ABORT: + ccb->ccb_h.status = CAM_UA_ABORT; + xpt_done(ccb); + break; + case XPT_CALC_GEOMETRY: + { + struct ccb_calc_geometry *ccg; + u_int32_t size_mb; + u_int32_t secs_per_cylinder; + + ccg = &ccb->ccg; + size_mb = ccg->volume_size + / ((1024L * 1024L) / ccg->block_size); + + ccg->heads = 64; + ccg->secs_per_track = 16; + secs_per_cylinder = ccg->heads * ccg->secs_per_track; + ccg->cylinders = ccg->volume_size / secs_per_cylinder; + ccb->ccb_h.status = CAM_REQ_CMP; + xpt_done(ccb); + break; + } + case XPT_PATH_INQ: /* Path routing inquiry */ + { + struct ccb_pathinq *cpi = &ccb->cpi; + + cpi->version_num = 1; /* XXX??? */ + cpi->hba_inquiry = 0; /* nothing fancy */ + cpi->target_sprt = 0; + cpi->hba_misc = 0; + cpi->hba_eng_cnt = 0; + cpi->max_target = 7; + cpi->max_lun = 7; + cpi->initiator_id = WDS_HBA_ID; + cpi->hba_misc = 0; + cpi->bus_id = cam_sim_bus(sim); + cpi->base_transfer_speed = 3300; + strncpy(cpi->sim_vid, "FreeBSD", SIM_IDLEN); + strncpy(cpi->hba_vid, "WD/FDC", HBA_IDLEN); + strncpy(cpi->dev_name, cam_sim_name(sim), DEV_IDLEN); + cpi->unit_number = cam_sim_unit(sim); + cpi->ccb_h.status = CAM_REQ_CMP; + xpt_done(ccb); + break; + } + default: + ccb->ccb_h.status = CAM_REQ_INVALID; + xpt_done(ccb); + break; + } +} + +static void +wds_poll(struct cam_sim * sim) +{ + wds_intr((struct wds *)cam_sim_softc(sim)); +} + +/* part of initialization done in probe() */ +/* returns 0 if OK, ENXIO if bad */ + +static int +wds_preinit(struct wds *wp) +{ + int base; + int i; + + base = wp->addr; + + /* + * Sending a command causes the CMDRDY bit to clear. + */ + outb(base + WDS_CMD, WDSC_NOOP); + if (inb(base + WDS_STAT) & WDS_RDY) + return (ENXIO); + + /* + * the controller exists. reset and init. + */ + outb(base + WDS_HCR, WDSH_ASCRESET | WDSH_SCSIRESET); + DELAY(30); + outb(base + WDS_HCR, 0); + + if ((inb(base + WDS_STAT) & (WDS_RDY)) != WDS_RDY) { + for (i = 0; i < 10; i++) { + if ((inb(base + WDS_STAT) & (WDS_RDY)) == WDS_RDY) + break; + DELAY(40000); + } + if ((inb(base + WDS_STAT) & (WDS_RDY)) != WDS_RDY) + /* probe timeout */ + return (ENXIO); + } + + return (0); +} + +/* part of initialization done in attach() */ +/* returns 0 if OK, 1 if bad */ + +static int +wds_init(struct wds *wp) +{ + struct wds_setup init; + int base; + int i; + struct wds_cmd wc; + + base = wp->addr; + + outb(base + WDS_HCR, WDSH_DRQEN); + + isa_dmacascade(wp->drq); + + if ((inb(base + WDS_STAT) & (WDS_RDY)) != WDS_RDY) { + for (i = 0; i < 10; i++) { + if ((inb(base + WDS_STAT) & (WDS_RDY)) == WDS_RDY) + break; + DELAY(40000); + } + if ((inb(base + WDS_STAT) & (WDS_RDY)) != WDS_RDY) + /* probe timeout */ + return (1); + } + bzero(&init, sizeof init); + init.cmd = WDSC_INIT; + init.scsi_id = WDS_HBA_ID; + init.buson_t = 24; + init.busoff_t = 48; + scsi_ulto3b(WDSTOPHYS(wp, &wp->dx->ombs), init.mbaddr); + init.xx = 0; + init.nomb = WDS_NOMB; + init.nimb = WDS_NIMB; + + wds_wait(base + WDS_STAT, WDS_RDY, WDS_RDY); + if (wds_cmd(base, (u_int8_t *) & init, sizeof init) != 0) { + device_printf(wp->dev, "wds_cmd init failed\n"); + return (1); + } + wds_wait(base + WDS_STAT, WDS_INIT, WDS_INIT); + + wds_wait(base + WDS_STAT, WDS_RDY, WDS_RDY); + + bzero(&wc, sizeof wc); + wc.cmd = WDSC_DISUNSOL; + if (wds_cmd(base, (char *) &wc, sizeof wc) != 0) { + device_printf(wp->dev, "wds_cmd init2 failed\n"); + return (1); + } + return (0); +} + +static int +wds_cmd(int base, u_int8_t * p, int l) +{ + int s = splcam(); + + while (l--) { + do { + outb(base + WDS_CMD, *p); + wds_wait(base + WDS_STAT, WDS_RDY, WDS_RDY); + } while (inb(base + WDS_STAT) & WDS_REJ); + p++; + } + + wds_wait(base + WDS_STAT, WDS_RDY, WDS_RDY); + + splx(s); + + return (0); +} + +static void +wds_wait(int reg, int mask, int val) +{ + while ((inb(reg) & mask) != val) + ; +} + +static struct wds_req * +cmdtovirt(struct wds *wp, u_int32_t phys) +{ + char *a; + + a = WDSTOVIRT(wp, phys); + if( a < (char *)&wp->dx->req[0] || a>= (char *)&wp->dx->req[MAXSIMUL]) { + device_printf(wp->dev, "weird phys address 0x%x\n", phys); + return (NULL); + } + a -= (int)offsetof(struct wds_req, cmd); /* convert cmd to request */ + return ((struct wds_req *)a); +} + +/* for debugging, print out all the data about the status of devices */ +void +wds_print(void) +{ + int unit; + int i; + struct wds_req *r; + struct wds *wp; + + for (unit = 0; unit < devclass_get_maxunit(wds_devclass); unit++) { + wp = (struct wds *) devclass_get_device(wds_devclass, unit); + if (wp == NULL) + continue; + printf("wds%d: want_wdsr=0x%x stat=0x%x irq=%s irqstat=0x%x\n", + unit, wp->want_wdsr, inb(wp->addr + WDS_STAT) & 0xff, + (inb(wp->addr + WDS_STAT) & WDS_IRQ) ? "ready" : "no", + inb(wp->addr + WDS_IRQSTAT) & 0xff); + for (i = 0; i < MAXSIMUL; i++) { + r = &wp->dx->req[i]; + if( wp->wdsr_free & (1 << r->id) ) { + printf("req=%d flg=0x%x ombn=%d ombstat=%d " + "mask=0x%x targ=%d lun=%d cmd=0x%x\n", + i, r->flags, r->ombn, + wp->dx->ombs[r->ombn].stat, + r->mask, r->cmd.targ >> 5, + r->cmd.targ & 7, r->cmd.scb[0]); + } + } + } +} + +#if WDS_DEBUG == 2 +/* create circular log buffer */ +static char * +wds_nextlog(void) +{ + int n = logwrite; + + if (++logwrite >= NLOGLINES) + logwrite = 0; + if (logread == logwrite) + if (++logread >= NLOGLINES) + logread = 0; + return (wds_log[n]); +} + +void +wds_printlog(void) +{ + /* print the circular buffer */ + int i; + + for (i = logread; i != logwrite;) { + printf("%s", wds_log[i]); + if (i == NLOGLINES) + i = 0; + else + i++; + } +} +#endif /* WDS_DEBUG */ |