1179 lines
27 KiB
C
1179 lines
27 KiB
C
/* $NetBSD: mesh.c,v 1.11 2001/06/08 00:32:02 matt Exp $ */
|
|
|
|
/*-
|
|
* Copyright (c) 2000 Tsubai Masanari.
|
|
* Copyright (c) 1999 Internet Research Institute, Inc.
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
* 3. All advertising materials mentioning features or use of this software
|
|
* must display the following acknowledgement:
|
|
* This product includes software developed by
|
|
* Internet Research Institute, Inc.
|
|
* 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.
|
|
*/
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/buf.h>
|
|
#include <sys/device.h>
|
|
#include <sys/errno.h>
|
|
#include <sys/kernel.h>
|
|
#include <sys/malloc.h>
|
|
#include <sys/queue.h>
|
|
#include <sys/systm.h>
|
|
|
|
#include <uvm/uvm_extern.h>
|
|
|
|
#include <dev/scsipi/scsi_all.h>
|
|
#include <dev/scsipi/scsipi_all.h>
|
|
#include <dev/scsipi/scsiconf.h>
|
|
#include <dev/scsipi/scsi_message.h>
|
|
|
|
#include <dev/ofw/openfirm.h>
|
|
|
|
#include <machine/autoconf.h>
|
|
#include <machine/cpu.h>
|
|
#include <machine/pio.h>
|
|
|
|
#include <macppc/dev/dbdma.h>
|
|
#include <macppc/dev/meshreg.h>
|
|
|
|
#ifdef MESH_DEBUG
|
|
# define DPRINTF printf
|
|
#else
|
|
# define DPRINTF while (0) printf
|
|
#endif
|
|
|
|
#define T_SYNCMODE 0x01 /* target uses sync mode */
|
|
#define T_SYNCNEGO 0x02 /* sync negotiation done */
|
|
|
|
struct mesh_tinfo {
|
|
int flags;
|
|
int period;
|
|
int offset;
|
|
};
|
|
|
|
/* scb flags */
|
|
#define MESH_POLL 0x01
|
|
#define MESH_CHECK 0x02
|
|
#define MESH_READ 0x80
|
|
|
|
struct mesh_scb {
|
|
TAILQ_ENTRY(mesh_scb) chain;
|
|
int flags;
|
|
struct scsipi_xfer *xs;
|
|
struct scsi_generic cmd;
|
|
int cmdlen;
|
|
int target; /* target SCSI ID */
|
|
int resid;
|
|
vaddr_t daddr;
|
|
vsize_t dlen;
|
|
int status;
|
|
};
|
|
|
|
/* sc_flags value */
|
|
#define MESH_DMA_ACTIVE 0x01
|
|
|
|
struct mesh_softc {
|
|
struct device sc_dev; /* us as a device */
|
|
struct scsipi_channel sc_channel;
|
|
struct scsipi_adapter sc_adapter;
|
|
|
|
u_char *sc_reg; /* MESH base address */
|
|
dbdma_regmap_t *sc_dmareg; /* DMA register address */
|
|
dbdma_command_t *sc_dmacmd; /* DMA command area */
|
|
|
|
int sc_flags;
|
|
int sc_cfflags; /* copy of config flags */
|
|
int sc_meshid; /* MESH version */
|
|
int sc_minsync; /* minimum sync period */
|
|
int sc_irq;
|
|
int sc_freq; /* SCSI bus frequency in MHz */
|
|
int sc_id; /* our SCSI ID */
|
|
struct mesh_tinfo sc_tinfo[8]; /* target information */
|
|
|
|
int sc_nextstate;
|
|
int sc_prevphase;
|
|
struct mesh_scb *sc_nexus; /* current command */
|
|
|
|
int sc_msgout;
|
|
int sc_imsglen;
|
|
u_char sc_imsg[16];
|
|
u_char sc_omsg[16];
|
|
|
|
TAILQ_HEAD(, mesh_scb) free_scb;
|
|
TAILQ_HEAD(, mesh_scb) ready_scb;
|
|
struct mesh_scb sc_scb[16];
|
|
};
|
|
|
|
/* mesh_msgout() values */
|
|
#define SEND_REJECT 1
|
|
#define SEND_IDENTIFY 2
|
|
#define SEND_SDTR 4
|
|
|
|
static __inline int mesh_read_reg __P((struct mesh_softc *, int));
|
|
static __inline void mesh_set_reg __P((struct mesh_softc *, int, int));
|
|
|
|
int mesh_match __P((struct device *, struct cfdata *, void *));
|
|
void mesh_attach __P((struct device *, struct device *, void *));
|
|
void mesh_shutdownhook __P((void *));
|
|
int mesh_intr __P((void *));
|
|
void mesh_error __P((struct mesh_softc *, struct mesh_scb *, int, int));
|
|
void mesh_select __P((struct mesh_softc *, struct mesh_scb *));
|
|
void mesh_identify __P((struct mesh_softc *, struct mesh_scb *));
|
|
void mesh_command __P((struct mesh_softc *, struct mesh_scb *));
|
|
void mesh_dma_setup __P((struct mesh_softc *, struct mesh_scb *));
|
|
void mesh_dataio __P((struct mesh_softc *, struct mesh_scb *));
|
|
void mesh_status __P((struct mesh_softc *, struct mesh_scb *));
|
|
void mesh_msgin __P((struct mesh_softc *, struct mesh_scb *));
|
|
void mesh_msgout __P((struct mesh_softc *, int));
|
|
void mesh_bus_reset __P((struct mesh_softc *));
|
|
void mesh_reset __P((struct mesh_softc *));
|
|
int mesh_stp __P((struct mesh_softc *, int));
|
|
void mesh_setsync __P((struct mesh_softc *, struct mesh_tinfo *));
|
|
struct mesh_scb *mesh_get_scb __P((struct mesh_softc *));
|
|
void mesh_free_scb __P((struct mesh_softc *, struct mesh_scb *));
|
|
void mesh_scsi_request __P((struct scsipi_channel *,
|
|
scsipi_adapter_req_t, void *));
|
|
void mesh_sched __P((struct mesh_softc *));
|
|
int mesh_poll __P((struct mesh_softc *, struct scsipi_xfer *));
|
|
void mesh_done __P((struct mesh_softc *, struct mesh_scb *));
|
|
void mesh_timeout __P((void *));
|
|
void mesh_minphys __P((struct buf *));
|
|
|
|
|
|
#define MESH_DATAOUT 0
|
|
#define MESH_DATAIN MESH_STATUS0_IO
|
|
#define MESH_COMMAND MESH_STATUS0_CD
|
|
#define MESH_STATUS (MESH_STATUS0_CD | MESH_STATUS0_IO)
|
|
#define MESH_MSGOUT (MESH_STATUS0_MSG | MESH_STATUS0_CD)
|
|
#define MESH_MSGIN (MESH_STATUS0_MSG | MESH_STATUS0_CD | MESH_STATUS0_IO)
|
|
|
|
#define MESH_SELECTING 8
|
|
#define MESH_IDENTIFY 9
|
|
#define MESH_COMPLETE 10
|
|
#define MESH_BUSFREE 11
|
|
#define MESH_UNKNOWN -1
|
|
|
|
#define MESH_PHASE_MASK (MESH_STATUS0_MSG | MESH_STATUS0_CD | MESH_STATUS0_IO)
|
|
|
|
struct cfattach mesh_ca = {
|
|
sizeof(struct mesh_softc), mesh_match, mesh_attach
|
|
};
|
|
|
|
int
|
|
mesh_match(parent, cf, aux)
|
|
struct device *parent;
|
|
struct cfdata *cf;
|
|
void *aux;
|
|
{
|
|
struct confargs *ca = aux;
|
|
char compat[32];
|
|
|
|
if (strcmp(ca->ca_name, "mesh") == 0)
|
|
return 1;
|
|
|
|
bzero(compat, sizeof(compat));
|
|
OF_getprop(ca->ca_node, "compatible", compat, sizeof(compat));
|
|
if (strcmp(compat, "chrp,mesh0") == 0)
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
mesh_attach(parent, self, aux)
|
|
struct device *parent, *self;
|
|
void *aux;
|
|
{
|
|
struct mesh_softc *sc = (void *)self;
|
|
struct confargs *ca = aux;
|
|
int i;
|
|
u_int *reg;
|
|
|
|
reg = ca->ca_reg;
|
|
reg[0] += ca->ca_baseaddr;
|
|
reg[2] += ca->ca_baseaddr;
|
|
sc->sc_reg = mapiodev(reg[0], reg[1]);
|
|
sc->sc_irq = ca->ca_intr[0];
|
|
sc->sc_dmareg = mapiodev(reg[2], reg[3]);
|
|
|
|
sc->sc_cfflags = self->dv_cfdata->cf_flags;
|
|
sc->sc_meshid = mesh_read_reg(sc, MESH_MESH_ID) & 0x1f;
|
|
#if 0
|
|
if (sc->sc_meshid != (MESH_SIGNATURE & 0x1f) {
|
|
printf(": unknown MESH ID (0x%x)\n", sc->sc_meshid);
|
|
return;
|
|
}
|
|
#endif
|
|
if (OF_getprop(ca->ca_node, "clock-frequency", &sc->sc_freq, 4) != 4) {
|
|
printf(": cannot get clock-frequency\n");
|
|
return;
|
|
}
|
|
sc->sc_freq /= 1000000; /* in MHz */
|
|
sc->sc_minsync = 25; /* maximum sync rate = 10MB/sec */
|
|
sc->sc_id = 7;
|
|
|
|
TAILQ_INIT(&sc->free_scb);
|
|
TAILQ_INIT(&sc->ready_scb);
|
|
for (i = 0; i < sizeof(sc->sc_scb)/sizeof(sc->sc_scb[0]); i++)
|
|
TAILQ_INSERT_TAIL(&sc->free_scb, &sc->sc_scb[i], chain);
|
|
|
|
sc->sc_dmacmd = dbdma_alloc(sizeof(dbdma_command_t) * 20);
|
|
|
|
mesh_reset(sc);
|
|
mesh_bus_reset(sc);
|
|
|
|
printf(" irq %d: %dMHz, SCSI ID %d\n",
|
|
sc->sc_irq, sc->sc_freq, sc->sc_id);
|
|
|
|
sc->sc_adapter.adapt_dev = &sc->sc_dev;
|
|
sc->sc_adapter.adapt_nchannels = 1;
|
|
sc->sc_adapter.adapt_openings = 7;
|
|
sc->sc_adapter.adapt_max_periph = 1;
|
|
sc->sc_adapter.adapt_ioctl = NULL;
|
|
sc->sc_adapter.adapt_minphys = mesh_minphys;
|
|
sc->sc_adapter.adapt_request = mesh_scsi_request;
|
|
|
|
sc->sc_channel.chan_adapter = &sc->sc_adapter;
|
|
sc->sc_channel.chan_bustype = &scsi_bustype;
|
|
sc->sc_channel.chan_channel = 0;
|
|
sc->sc_channel.chan_ntargets = 8;
|
|
sc->sc_channel.chan_nluns = 8;
|
|
sc->sc_channel.chan_id = sc->sc_id;
|
|
|
|
config_found(&sc->sc_dev, &sc->sc_channel, scsiprint);
|
|
|
|
intr_establish(sc->sc_irq, IST_LEVEL, IPL_BIO, mesh_intr, sc);
|
|
|
|
/* Reset SCSI bus when halt. */
|
|
shutdownhook_establish(mesh_shutdownhook, sc);
|
|
}
|
|
|
|
#define MESH_SET_XFER(sc, count) do { \
|
|
mesh_set_reg(sc, MESH_XFER_COUNT0, count); \
|
|
mesh_set_reg(sc, MESH_XFER_COUNT1, count >> 8); \
|
|
} while (0)
|
|
|
|
#define MESH_GET_XFER(sc) ((mesh_read_reg(sc, MESH_XFER_COUNT1) << 8) | \
|
|
mesh_read_reg(sc, MESH_XFER_COUNT0))
|
|
|
|
int
|
|
mesh_read_reg(sc, reg)
|
|
struct mesh_softc *sc;
|
|
int reg;
|
|
{
|
|
return in8(sc->sc_reg + reg);
|
|
}
|
|
|
|
void
|
|
mesh_set_reg(sc, reg, val)
|
|
struct mesh_softc *sc;
|
|
int reg, val;
|
|
{
|
|
out8(sc->sc_reg + reg, val);
|
|
}
|
|
|
|
void
|
|
mesh_shutdownhook(arg)
|
|
void *arg;
|
|
{
|
|
struct mesh_softc *sc = arg;
|
|
|
|
/* Set to async mode. */
|
|
mesh_set_reg(sc, MESH_SYNC_PARAM, 2);
|
|
}
|
|
|
|
#ifdef MESH_DEBUG
|
|
static char scsi_phase[][8] = {
|
|
"DATAOUT",
|
|
"DATAIN",
|
|
"COMMAND",
|
|
"STATUS",
|
|
"",
|
|
"",
|
|
"MSGOUT",
|
|
"MSGIN"
|
|
};
|
|
#endif
|
|
|
|
int
|
|
mesh_intr(arg)
|
|
void *arg;
|
|
{
|
|
struct mesh_softc *sc = arg;
|
|
struct mesh_scb *scb;
|
|
int fifocnt;
|
|
u_char intr, exception, error, status0, status1;
|
|
|
|
intr = mesh_read_reg(sc, MESH_INTERRUPT);
|
|
if (intr == 0) {
|
|
DPRINTF("%s: stray interrupt\n", sc->sc_dev.dv_xname);
|
|
return 0;
|
|
}
|
|
|
|
exception = mesh_read_reg(sc, MESH_EXCEPTION);
|
|
error = mesh_read_reg(sc, MESH_ERROR);
|
|
status0 = mesh_read_reg(sc, MESH_BUS_STATUS0);
|
|
status1 = mesh_read_reg(sc, MESH_BUS_STATUS1);
|
|
|
|
/* clear interrupt */
|
|
mesh_set_reg(sc, MESH_INTERRUPT, intr);
|
|
|
|
#ifdef MESH_DEBUG
|
|
{
|
|
char buf1[64], buf2[64];
|
|
|
|
bitmask_snprintf(status0, MESH_STATUS0_BITMASK, buf1, sizeof buf1);
|
|
bitmask_snprintf(exception, MESH_EXC_BITMASK, buf2, sizeof buf2);
|
|
printf("mesh_intr status0 = 0x%s (%s), exc = 0x%s\n",
|
|
buf1, scsi_phase[status0 & 7], buf2);
|
|
}
|
|
#endif
|
|
|
|
scb = sc->sc_nexus;
|
|
if (scb == NULL) {
|
|
DPRINTF("%s: NULL nexus\n", sc->sc_dev.dv_xname);
|
|
return 1;
|
|
}
|
|
|
|
if (sc->sc_flags & MESH_DMA_ACTIVE) {
|
|
dbdma_stop(sc->sc_dmareg);
|
|
|
|
sc->sc_flags &= ~MESH_DMA_ACTIVE;
|
|
scb->resid = MESH_GET_XFER(sc);
|
|
|
|
fifocnt = mesh_read_reg(sc, MESH_FIFO_COUNT);
|
|
if (fifocnt != 0 && (scb->flags & MESH_READ)) {
|
|
char *cp = (char *)scb->daddr + scb->dlen - fifocnt;
|
|
|
|
DPRINTF("fifocnt = %d, resid = %d\n", fifocnt,
|
|
scb->resid);
|
|
while (fifocnt > 0) {
|
|
*cp++ = mesh_read_reg(sc, MESH_FIFO);
|
|
fifocnt--;
|
|
}
|
|
} else
|
|
mesh_set_reg(sc, MESH_SEQUENCE, MESH_CMD_FLUSH_FIFO);
|
|
}
|
|
|
|
if (intr & MESH_INTR_ERROR) {
|
|
mesh_error(sc, scb, error, 0);
|
|
return 1;
|
|
}
|
|
|
|
if (intr & MESH_INTR_EXCEPTION) {
|
|
/* selection timeout */
|
|
if (exception & MESH_EXC_SELTO) {
|
|
mesh_error(sc, scb, 0, exception);
|
|
return 1;
|
|
}
|
|
|
|
/* phase mismatch */
|
|
if (exception & MESH_EXC_PHASEMM) {
|
|
DPRINTF("%s: PHASE MISMATCH; nextstate = %d -> ",
|
|
sc->sc_dev.dv_xname, sc->sc_nextstate);
|
|
sc->sc_nextstate = status0 & MESH_PHASE_MASK;
|
|
|
|
DPRINTF("%d, resid = %d\n",
|
|
sc->sc_nextstate, scb->resid);
|
|
}
|
|
}
|
|
|
|
if (sc->sc_nextstate == MESH_UNKNOWN)
|
|
sc->sc_nextstate = status0 & MESH_PHASE_MASK;
|
|
|
|
switch (sc->sc_nextstate) {
|
|
|
|
case MESH_IDENTIFY:
|
|
mesh_identify(sc, scb);
|
|
break;
|
|
case MESH_COMMAND:
|
|
mesh_command(sc, scb);
|
|
break;
|
|
case MESH_DATAIN:
|
|
case MESH_DATAOUT:
|
|
mesh_dataio(sc, scb);
|
|
break;
|
|
case MESH_STATUS:
|
|
mesh_status(sc, scb);
|
|
break;
|
|
case MESH_MSGIN:
|
|
mesh_msgin(sc, scb);
|
|
break;
|
|
case MESH_COMPLETE:
|
|
mesh_done(sc, scb);
|
|
break;
|
|
|
|
default:
|
|
printf("%s: unknown state (%d)\n", sc->sc_dev.dv_xname,
|
|
sc->sc_nextstate);
|
|
scb->xs->error = XS_DRIVER_STUFFUP;
|
|
mesh_done(sc, scb);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
void
|
|
mesh_error(sc, scb, error, exception)
|
|
struct mesh_softc *sc;
|
|
struct mesh_scb *scb;
|
|
int error, exception;
|
|
{
|
|
if (error & MESH_ERR_SCSI_RESET) {
|
|
printf("%s: SCSI RESET\n", sc->sc_dev.dv_xname);
|
|
|
|
/* Wait until the RST signal is deasserted. */
|
|
while (mesh_read_reg(sc, MESH_BUS_STATUS1) & MESH_STATUS1_RST);
|
|
mesh_reset(sc);
|
|
return;
|
|
}
|
|
|
|
if (error & MESH_ERR_PARITY_ERR0) {
|
|
printf("%s: parity error\n", sc->sc_dev.dv_xname);
|
|
scb->xs->error = XS_DRIVER_STUFFUP;
|
|
}
|
|
|
|
if (error & MESH_ERR_DISCONNECT) {
|
|
printf("%s: unexpected disconnect\n", sc->sc_dev.dv_xname);
|
|
if (sc->sc_nextstate != MESH_COMPLETE)
|
|
scb->xs->error = XS_DRIVER_STUFFUP;
|
|
}
|
|
|
|
if (exception & MESH_EXC_SELTO) {
|
|
/* XXX should reset bus here? */
|
|
scb->xs->error = XS_SELTIMEOUT;
|
|
}
|
|
|
|
mesh_done(sc, scb);
|
|
}
|
|
|
|
void
|
|
mesh_select(sc, scb)
|
|
struct mesh_softc *sc;
|
|
struct mesh_scb *scb;
|
|
{
|
|
struct mesh_tinfo *ti = &sc->sc_tinfo[scb->target];
|
|
int timeout;
|
|
|
|
DPRINTF("mesh_select\n");
|
|
|
|
mesh_setsync(sc, ti);
|
|
MESH_SET_XFER(sc, 0);
|
|
|
|
/* arbitration */
|
|
|
|
/*
|
|
* MESH mistakenly asserts TARGET ID bit along with its own ID bit
|
|
* in arbitration phase (like selection). So we should load
|
|
* initiator ID to DestID register temporarily.
|
|
*/
|
|
mesh_set_reg(sc, MESH_DEST_ID, sc->sc_id);
|
|
mesh_set_reg(sc, MESH_INTR_MASK, 0); /* disable intr. */
|
|
mesh_set_reg(sc, MESH_SEQUENCE, MESH_CMD_ARBITRATE);
|
|
|
|
while (mesh_read_reg(sc, MESH_INTERRUPT) == 0);
|
|
mesh_set_reg(sc, MESH_INTERRUPT, 1);
|
|
mesh_set_reg(sc, MESH_INTR_MASK, 7);
|
|
|
|
/* selection */
|
|
mesh_set_reg(sc, MESH_DEST_ID, scb->target);
|
|
mesh_set_reg(sc, MESH_SEQUENCE, MESH_CMD_SELECT | MESH_SEQ_ATN);
|
|
|
|
sc->sc_prevphase = MESH_SELECTING;
|
|
sc->sc_nextstate = MESH_IDENTIFY;
|
|
|
|
timeout = (u_int64_t)scb->xs->timeout * (u_int64_t)hz / 1000;
|
|
if (timeout == 0)
|
|
timeout = 1;
|
|
|
|
callout_reset(&scb->xs->xs_callout, timeout, mesh_timeout, scb);
|
|
}
|
|
|
|
void
|
|
mesh_identify(sc, scb)
|
|
struct mesh_softc *sc;
|
|
struct mesh_scb *scb;
|
|
{
|
|
struct mesh_tinfo *ti = &sc->sc_tinfo[scb->target];
|
|
|
|
DPRINTF("mesh_identify\n");
|
|
mesh_set_reg(sc, MESH_SEQUENCE, MESH_CMD_FLUSH_FIFO);
|
|
|
|
if ((ti->flags & T_SYNCNEGO) == 0) {
|
|
ti->period = sc->sc_minsync;
|
|
ti->offset = 15;
|
|
mesh_msgout(sc, SEND_IDENTIFY | SEND_SDTR);
|
|
sc->sc_nextstate = MESH_MSGIN;
|
|
} else {
|
|
mesh_msgout(sc, SEND_IDENTIFY);
|
|
sc->sc_nextstate = MESH_COMMAND;
|
|
}
|
|
}
|
|
|
|
void
|
|
mesh_command(sc, scb)
|
|
struct mesh_softc *sc;
|
|
struct mesh_scb *scb;
|
|
{
|
|
int i;
|
|
char *cmdp;
|
|
|
|
#ifdef MESH_DEBUG
|
|
printf("mesh_command cdb = %02x", scb->cmd.opcode);
|
|
for (i = 0; i < 5; i++)
|
|
printf(" %02x", scb->cmd.bytes[i]);
|
|
printf("\n");
|
|
#endif
|
|
|
|
mesh_set_reg(sc, MESH_SEQUENCE, MESH_CMD_FLUSH_FIFO);
|
|
|
|
MESH_SET_XFER(sc, scb->cmdlen);
|
|
mesh_set_reg(sc, MESH_SEQUENCE, MESH_CMD_COMMAND);
|
|
|
|
cmdp = (char *)&scb->cmd;
|
|
for (i = 0; i < scb->cmdlen; i++)
|
|
mesh_set_reg(sc, MESH_FIFO, *cmdp++);
|
|
|
|
if (scb->resid == 0)
|
|
sc->sc_nextstate = MESH_STATUS; /* no data xfer */
|
|
else
|
|
sc->sc_nextstate = MESH_DATAIN;
|
|
}
|
|
|
|
void
|
|
mesh_dma_setup(sc, scb)
|
|
struct mesh_softc *sc;
|
|
struct mesh_scb *scb;
|
|
{
|
|
int datain = scb->flags & MESH_READ;
|
|
dbdma_command_t *cmdp;
|
|
u_int cmd;
|
|
vaddr_t va;
|
|
int count, offset;
|
|
|
|
cmdp = sc->sc_dmacmd;
|
|
cmd = datain ? DBDMA_CMD_IN_MORE : DBDMA_CMD_OUT_MORE;
|
|
|
|
count = scb->dlen;
|
|
|
|
if (count / NBPG > 32)
|
|
panic("mesh: transfer size >= 128k");
|
|
|
|
va = scb->daddr;
|
|
offset = va & PGOFSET;
|
|
|
|
/* if va is not page-aligned, setup the first page */
|
|
if (offset != 0) {
|
|
int rest = NBPG - offset; /* the rest in the page */
|
|
|
|
if (count > rest) { /* if continues to next page */
|
|
DBDMA_BUILD(cmdp, cmd, 0, rest, vtophys(va),
|
|
DBDMA_INT_NEVER, DBDMA_WAIT_NEVER,
|
|
DBDMA_BRANCH_NEVER);
|
|
count -= rest;
|
|
va += rest;
|
|
cmdp++;
|
|
}
|
|
}
|
|
|
|
/* now va is page-aligned */
|
|
while (count > NBPG) {
|
|
DBDMA_BUILD(cmdp, cmd, 0, NBPG, vtophys(va),
|
|
DBDMA_INT_NEVER, DBDMA_WAIT_NEVER, DBDMA_BRANCH_NEVER);
|
|
count -= NBPG;
|
|
va += NBPG;
|
|
cmdp++;
|
|
}
|
|
|
|
/* the last page (count <= NBPG here) */
|
|
cmd = datain ? DBDMA_CMD_IN_LAST : DBDMA_CMD_OUT_LAST;
|
|
DBDMA_BUILD(cmdp, cmd , 0, count, vtophys(va),
|
|
DBDMA_INT_NEVER, DBDMA_WAIT_NEVER, DBDMA_BRANCH_NEVER);
|
|
cmdp++;
|
|
|
|
DBDMA_BUILD(cmdp, DBDMA_CMD_STOP, 0, 0, 0,
|
|
DBDMA_INT_NEVER, DBDMA_WAIT_NEVER, DBDMA_BRANCH_NEVER);
|
|
}
|
|
|
|
void
|
|
mesh_dataio(sc, scb)
|
|
struct mesh_softc *sc;
|
|
struct mesh_scb *scb;
|
|
{
|
|
DPRINTF("mesh_dataio len = %ld (%s)\n", scb->dlen,
|
|
scb->flags & MESH_READ ? "read" : "write");
|
|
|
|
mesh_dma_setup(sc, scb);
|
|
|
|
if (scb->dlen == 65536)
|
|
MESH_SET_XFER(sc, 0); /* TC = 0 means 64KB transfer */
|
|
else
|
|
MESH_SET_XFER(sc, scb->dlen);
|
|
|
|
if (scb->flags & MESH_READ)
|
|
mesh_set_reg(sc, MESH_SEQUENCE, MESH_CMD_DATAIN | MESH_SEQ_DMA);
|
|
else
|
|
mesh_set_reg(sc, MESH_SEQUENCE, MESH_CMD_DATAOUT | MESH_SEQ_DMA);
|
|
dbdma_start(sc->sc_dmareg, sc->sc_dmacmd);
|
|
sc->sc_flags |= MESH_DMA_ACTIVE;
|
|
sc->sc_nextstate = MESH_STATUS;
|
|
}
|
|
|
|
void
|
|
mesh_status(sc, scb)
|
|
struct mesh_softc *sc;
|
|
struct mesh_scb *scb;
|
|
{
|
|
if (mesh_read_reg(sc, MESH_FIFO_COUNT) == 0) { /* XXX cheat */
|
|
DPRINTF("mesh_status(0)\n");
|
|
MESH_SET_XFER(sc, 1);
|
|
mesh_set_reg(sc, MESH_SEQUENCE, MESH_CMD_STATUS);
|
|
sc->sc_nextstate = MESH_STATUS;
|
|
return;
|
|
}
|
|
|
|
scb->status = mesh_read_reg(sc, MESH_FIFO);
|
|
DPRINTF("mesh_status(1): status = 0x%x\n", scb->status);
|
|
if (mesh_read_reg(sc, MESH_FIFO_COUNT) != 0)
|
|
DPRINTF("FIFO_COUNT=%d\n", mesh_read_reg(sc, MESH_FIFO_COUNT));
|
|
|
|
mesh_set_reg(sc, MESH_SEQUENCE, MESH_CMD_FLUSH_FIFO);
|
|
MESH_SET_XFER(sc, 1);
|
|
mesh_set_reg(sc, MESH_SEQUENCE, MESH_CMD_MSGIN);
|
|
|
|
sc->sc_nextstate = MESH_MSGIN;
|
|
}
|
|
|
|
#define IS1BYTEMSG(m) (((m) != 1 && (m) < 0x20) || (m) & 0x80)
|
|
#define IS2BYTEMSG(m) (((m) & 0xf0) == 0x20)
|
|
#define ISEXTMSG(m) ((m) == 1)
|
|
|
|
void
|
|
mesh_msgin(sc, scb)
|
|
struct mesh_softc *sc;
|
|
struct mesh_scb *scb;
|
|
{
|
|
DPRINTF("mesh_msgin\n");
|
|
|
|
if (mesh_read_reg(sc, MESH_FIFO_COUNT) == 0) { /* XXX cheat */
|
|
MESH_SET_XFER(sc, 1);
|
|
mesh_set_reg(sc, MESH_SEQUENCE, MESH_CMD_MSGIN);
|
|
sc->sc_imsglen = 0;
|
|
sc->sc_nextstate = MESH_MSGIN;
|
|
return;
|
|
}
|
|
|
|
sc->sc_imsg[sc->sc_imsglen++] = mesh_read_reg(sc, MESH_FIFO);
|
|
|
|
if (sc->sc_imsglen == 1 && IS1BYTEMSG(sc->sc_imsg[0]))
|
|
goto gotit;
|
|
if (sc->sc_imsglen == 2 && IS2BYTEMSG(sc->sc_imsg[0]))
|
|
goto gotit;
|
|
if (sc->sc_imsglen >= 3 && ISEXTMSG(sc->sc_imsg[0]) &&
|
|
sc->sc_imsglen == sc->sc_imsg[1] + 2)
|
|
goto gotit;
|
|
|
|
sc->sc_nextstate = MESH_MSGIN;
|
|
MESH_SET_XFER(sc, 1);
|
|
mesh_set_reg(sc, MESH_SEQUENCE, MESH_CMD_MSGIN);
|
|
return;
|
|
|
|
gotit:
|
|
#ifdef MESH_DEBUG
|
|
printf("msgin:");
|
|
for (i = 0; i < sc->sc_imsglen; i++)
|
|
printf(" 0x%02x", sc->sc_imsg[i]);
|
|
printf("\n");
|
|
#endif
|
|
|
|
switch (sc->sc_imsg[0]) {
|
|
case MSG_CMDCOMPLETE:
|
|
mesh_set_reg(sc, MESH_SEQUENCE, MESH_CMD_BUSFREE);
|
|
sc->sc_nextstate = MESH_COMPLETE;
|
|
sc->sc_imsglen = 0;
|
|
return;
|
|
|
|
case MSG_MESSAGE_REJECT:
|
|
if (sc->sc_msgout & SEND_SDTR) {
|
|
printf("SDTR rejected\n");
|
|
printf("using async mode\n");
|
|
sc->sc_tinfo[scb->target].period = 0;
|
|
sc->sc_tinfo[scb->target].offset = 0;
|
|
mesh_setsync(sc, &sc->sc_tinfo[scb->target]);
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case MSG_NOOP:
|
|
break;
|
|
|
|
case MSG_EXTENDED:
|
|
goto extended_msg;
|
|
|
|
default:
|
|
scsipi_printaddr(scb->xs->xs_periph);
|
|
printf("unrecognized MESSAGE(0x%02x); sending REJECT\n",
|
|
sc->sc_imsg[0]);
|
|
|
|
reject:
|
|
mesh_msgout(sc, SEND_REJECT);
|
|
return;
|
|
}
|
|
goto done;
|
|
|
|
extended_msg:
|
|
/* process an extended message */
|
|
switch (sc->sc_imsg[2]) {
|
|
case MSG_EXT_SDTR:
|
|
{
|
|
struct mesh_tinfo *ti = &sc->sc_tinfo[scb->target];
|
|
int period = sc->sc_imsg[3];
|
|
int offset = sc->sc_imsg[4];
|
|
int r = 250 / period;
|
|
int s = (100*250) / period - 100 * r;
|
|
|
|
if (period < sc->sc_minsync) {
|
|
ti->period = sc->sc_minsync;
|
|
ti->offset = 15;
|
|
mesh_msgout(sc, SEND_SDTR);
|
|
return;
|
|
}
|
|
scsipi_printaddr(scb->xs->xs_periph);
|
|
/* XXX if (offset != 0) ... */
|
|
printf("max sync rate %d.%02dMb/s\n", r, s);
|
|
ti->period = period;
|
|
ti->offset = offset;
|
|
ti->flags |= T_SYNCNEGO;
|
|
ti->flags |= T_SYNCMODE;
|
|
mesh_setsync(sc, ti);
|
|
goto done;
|
|
}
|
|
default:
|
|
printf("%s target %d: rejecting extended message 0x%x\n",
|
|
sc->sc_dev.dv_xname, scb->target, sc->sc_imsg[0]);
|
|
goto reject;
|
|
}
|
|
|
|
done:
|
|
sc->sc_imsglen = 0;
|
|
sc->sc_nextstate = MESH_UNKNOWN;
|
|
|
|
mesh_set_reg(sc, MESH_SEQUENCE, MESH_CMD_BUSFREE); /* XXX really? */
|
|
}
|
|
|
|
void
|
|
mesh_msgout(sc, msg)
|
|
struct mesh_softc *sc;
|
|
int msg;
|
|
{
|
|
struct mesh_scb *scb = sc->sc_nexus;
|
|
struct mesh_tinfo *ti;
|
|
int lun, len, i;
|
|
|
|
DPRINTF("mesh_msgout: sending");
|
|
|
|
sc->sc_msgout = msg;
|
|
len = 0;
|
|
|
|
if (msg & SEND_REJECT) {
|
|
DPRINTF(" REJECT");
|
|
sc->sc_omsg[len++] = MSG_MESSAGE_REJECT;
|
|
}
|
|
if (msg & SEND_IDENTIFY) {
|
|
DPRINTF(" IDENTIFY");
|
|
lun = scb->xs->xs_periph->periph_lun;
|
|
sc->sc_omsg[len++] = MSG_IDENTIFY(lun, 0);
|
|
}
|
|
if (msg & SEND_SDTR) {
|
|
DPRINTF(" SDTR");
|
|
ti = &sc->sc_tinfo[scb->target];
|
|
sc->sc_omsg[len++] = MSG_EXTENDED;
|
|
sc->sc_omsg[len++] = 3;
|
|
sc->sc_omsg[len++] = MSG_EXT_SDTR;
|
|
sc->sc_omsg[len++] = ti->period;
|
|
sc->sc_omsg[len++] = ti->offset;
|
|
}
|
|
DPRINTF("\n");
|
|
|
|
MESH_SET_XFER(sc, len);
|
|
if (len == 1) {
|
|
mesh_set_reg(sc, MESH_SEQUENCE, MESH_CMD_MSGOUT);
|
|
mesh_set_reg(sc, MESH_FIFO, sc->sc_omsg[0]);
|
|
} else {
|
|
mesh_set_reg(sc, MESH_SEQUENCE, MESH_CMD_MSGOUT | MESH_SEQ_ATN);
|
|
|
|
for (i = 0; i < len - 1; i++)
|
|
mesh_set_reg(sc, MESH_FIFO, sc->sc_omsg[i]);
|
|
|
|
/* Wait for the FIFO empty... */
|
|
while (mesh_read_reg(sc, MESH_FIFO_COUNT) > 0);
|
|
|
|
/* ...then write the last byte. */
|
|
mesh_set_reg(sc, MESH_FIFO, sc->sc_omsg[i]);
|
|
}
|
|
sc->sc_nextstate = MESH_UNKNOWN;
|
|
}
|
|
|
|
void
|
|
mesh_bus_reset(sc)
|
|
struct mesh_softc *sc;
|
|
{
|
|
DPRINTF("mesh_bus_reset\n");
|
|
|
|
/* Disable interrupts. */
|
|
mesh_set_reg(sc, MESH_INTR_MASK, 0);
|
|
|
|
/* Assert RST line. */
|
|
mesh_set_reg(sc, MESH_BUS_STATUS1, MESH_STATUS1_RST);
|
|
delay(50);
|
|
mesh_set_reg(sc, MESH_BUS_STATUS1, 0);
|
|
|
|
mesh_reset(sc);
|
|
}
|
|
|
|
void
|
|
mesh_reset(sc)
|
|
struct mesh_softc *sc;
|
|
{
|
|
int i;
|
|
|
|
DPRINTF("mesh_reset\n");
|
|
|
|
/* Reset DMA first. */
|
|
dbdma_reset(sc->sc_dmareg);
|
|
|
|
/* Disable interrupts. */
|
|
mesh_set_reg(sc, MESH_INTR_MASK, 0);
|
|
|
|
mesh_set_reg(sc, MESH_SEQUENCE, MESH_CMD_RESET_MESH);
|
|
delay(1);
|
|
|
|
/* Wait for reset done. */
|
|
while (mesh_read_reg(sc, MESH_INTERRUPT) == 0);
|
|
|
|
/* Clear interrupts */
|
|
mesh_set_reg(sc, MESH_INTERRUPT, 0x7);
|
|
|
|
/* Set SCSI ID */
|
|
mesh_set_reg(sc, MESH_SOURCE_ID, sc->sc_id);
|
|
|
|
/* Set to async mode by default. */
|
|
mesh_set_reg(sc, MESH_SYNC_PARAM, 2);
|
|
|
|
/* Set selection timeout to 250ms. */
|
|
mesh_set_reg(sc, MESH_SEL_TIMEOUT, 250 * sc->sc_freq / 500);
|
|
|
|
/* Enable parity check. */
|
|
mesh_set_reg(sc, MESH_SEQUENCE, MESH_CMD_ENABLE_PARITY);
|
|
|
|
/* Enable all interrupts. */
|
|
mesh_set_reg(sc, MESH_INTR_MASK, 0x7);
|
|
|
|
for (i = 0; i < 7; i++) {
|
|
struct mesh_tinfo *ti = &sc->sc_tinfo[i];
|
|
|
|
ti->flags = 0;
|
|
ti->period = ti->offset = 0;
|
|
if (sc->sc_cfflags & (0x100 << i))
|
|
ti->flags |= T_SYNCNEGO;
|
|
}
|
|
sc->sc_nexus = NULL;
|
|
}
|
|
|
|
int
|
|
mesh_stp(sc, v)
|
|
struct mesh_softc *sc;
|
|
int v;
|
|
{
|
|
/*
|
|
* stp(v) = 5 * clock_period (v == 0)
|
|
* = (v + 2) * 2 clock_period (v > 0)
|
|
*/
|
|
|
|
if (v == 0)
|
|
return 5 * 250 / sc->sc_freq;
|
|
else
|
|
return (v + 2) * 2 * 250 / sc->sc_freq;
|
|
}
|
|
|
|
void
|
|
mesh_setsync(sc, ti)
|
|
struct mesh_softc *sc;
|
|
struct mesh_tinfo *ti;
|
|
{
|
|
int period = ti->period;
|
|
int offset = ti->offset;
|
|
int v;
|
|
|
|
if ((ti->flags & T_SYNCMODE) == 0)
|
|
offset = 0;
|
|
|
|
if (offset == 0) { /* async mode */
|
|
mesh_set_reg(sc, MESH_SYNC_PARAM, 2);
|
|
return;
|
|
}
|
|
|
|
v = period * sc->sc_freq / 250 / 2 - 2;
|
|
if (v < 0)
|
|
v = 0;
|
|
if (mesh_stp(sc, v) < period)
|
|
v++;
|
|
if (v > 15)
|
|
v = 15;
|
|
mesh_set_reg(sc, MESH_SYNC_PARAM, (offset << 4) | v);
|
|
}
|
|
|
|
struct mesh_scb *
|
|
mesh_get_scb(sc)
|
|
struct mesh_softc *sc;
|
|
{
|
|
struct mesh_scb *scb;
|
|
int s;
|
|
|
|
s = splbio();
|
|
if ((scb = sc->free_scb.tqh_first) != NULL)
|
|
TAILQ_REMOVE(&sc->free_scb, scb, chain);
|
|
splx(s);
|
|
|
|
return scb;
|
|
}
|
|
|
|
void
|
|
mesh_free_scb(sc, scb)
|
|
struct mesh_softc *sc;
|
|
struct mesh_scb *scb;
|
|
{
|
|
int s;
|
|
|
|
s = splbio();
|
|
TAILQ_INSERT_HEAD(&sc->free_scb, scb, chain);
|
|
splx(s);
|
|
}
|
|
|
|
void
|
|
mesh_scsi_request(chan, req, arg)
|
|
struct scsipi_channel *chan;
|
|
scsipi_adapter_req_t req;
|
|
void *arg;
|
|
{
|
|
struct scsipi_xfer *xs;
|
|
struct scsipi_periph *periph;
|
|
struct mesh_softc *sc = (void *)chan->chan_adapter->adapt_dev;
|
|
struct mesh_scb *scb;
|
|
u_int flags;
|
|
int s;
|
|
|
|
switch (req) {
|
|
case ADAPTER_REQ_RUN_XFER:
|
|
xs = arg;
|
|
periph = xs->xs_periph;
|
|
flags = xs->xs_control;
|
|
|
|
|
|
if ((scb = mesh_get_scb(sc)) == NULL) {
|
|
xs->error = XS_RESOURCE_SHORTAGE;
|
|
scsipi_done(xs);
|
|
return;
|
|
}
|
|
scb->xs = xs;
|
|
scb->flags = 0;
|
|
scb->status = 0;
|
|
scb->daddr = (vaddr_t)xs->data;
|
|
scb->dlen = xs->datalen;
|
|
scb->resid = xs->datalen;
|
|
bcopy(xs->cmd, &scb->cmd, xs->cmdlen);
|
|
scb->cmdlen = xs->cmdlen;
|
|
scb->target = periph->periph_target;
|
|
sc->sc_imsglen = 0; /* XXX ? */
|
|
|
|
#ifdef MESH_DEBUG
|
|
{
|
|
int i;
|
|
printf("mesh_scsi_cmd: target = %d, cdb = %02x",
|
|
scb->target, scb->cmd.opcode);
|
|
for (i = 0; i < 5; i++)
|
|
printf(" %02x", scb->cmd.bytes[i]);
|
|
printf("\n");
|
|
}
|
|
#endif
|
|
|
|
if (flags & XS_CTL_POLL)
|
|
scb->flags |= MESH_POLL;
|
|
#if 0
|
|
if (flags & XS_CTL_DATA_OUT)
|
|
scb->flags &= ~MESH_READ;
|
|
#endif
|
|
if (flags & XS_CTL_DATA_IN)
|
|
scb->flags |= MESH_READ;
|
|
|
|
s = splbio();
|
|
|
|
TAILQ_INSERT_TAIL(&sc->ready_scb, scb, chain);
|
|
|
|
if (sc->sc_nexus == NULL) /* IDLE */
|
|
mesh_sched(sc);
|
|
|
|
splx(s);
|
|
|
|
if ((flags & XS_CTL_POLL) == 0)
|
|
return;
|
|
|
|
if (mesh_poll(sc, xs)) {
|
|
printf("%s: timeout\n", sc->sc_dev.dv_xname);
|
|
if (mesh_poll(sc, xs))
|
|
printf("%s: timeout again\n", sc->sc_dev.dv_xname);
|
|
}
|
|
return;
|
|
|
|
case ADAPTER_REQ_GROW_RESOURCES:
|
|
/* XXX Not supported. */
|
|
return;
|
|
|
|
case ADAPTER_REQ_SET_XFER_MODE:
|
|
/* XXX Not supported. */
|
|
return;
|
|
}
|
|
|
|
}
|
|
|
|
void
|
|
mesh_sched(sc)
|
|
struct mesh_softc *sc;
|
|
{
|
|
struct scsipi_xfer *xs;
|
|
struct mesh_scb *scb;
|
|
|
|
scb = sc->ready_scb.tqh_first;
|
|
start:
|
|
if (scb == NULL)
|
|
return;
|
|
|
|
xs = scb->xs;
|
|
|
|
if (sc->sc_nexus == NULL) {
|
|
TAILQ_REMOVE(&sc->ready_scb, scb, chain);
|
|
sc->sc_nexus = scb;
|
|
mesh_select(sc, scb);
|
|
return;
|
|
}
|
|
|
|
scb = scb->chain.tqe_next;
|
|
goto start;
|
|
}
|
|
|
|
int
|
|
mesh_poll(sc, xs)
|
|
struct mesh_softc *sc;
|
|
struct scsipi_xfer *xs;
|
|
{
|
|
int count = xs->timeout;
|
|
|
|
while (count) {
|
|
if (mesh_read_reg(sc, MESH_INTERRUPT))
|
|
mesh_intr(sc);
|
|
|
|
if (xs->xs_status & XS_STS_DONE)
|
|
return 0;
|
|
delay(1000);
|
|
count--;
|
|
};
|
|
return 1;
|
|
}
|
|
|
|
void
|
|
mesh_done(sc, scb)
|
|
struct mesh_softc *sc;
|
|
struct mesh_scb *scb;
|
|
{
|
|
struct scsipi_xfer *xs = scb->xs;
|
|
|
|
DPRINTF("mesh_done\n");
|
|
|
|
sc->sc_nextstate = MESH_BUSFREE;
|
|
sc->sc_nexus = NULL;
|
|
|
|
callout_stop(&scb->xs->xs_callout);
|
|
|
|
if (scb->status == SCSI_BUSY) {
|
|
xs->error = XS_BUSY;
|
|
printf("Target busy\n");
|
|
}
|
|
|
|
xs->xs_status = scb->status;
|
|
xs->resid = scb->resid;
|
|
if (scb->status == SCSI_CHECK) {
|
|
xs->error = XS_BUSY;
|
|
}
|
|
|
|
mesh_set_reg(sc, MESH_SYNC_PARAM, 2);
|
|
|
|
if ((xs->xs_control & XS_CTL_POLL) == 0)
|
|
mesh_sched(sc);
|
|
|
|
scsipi_done(xs);
|
|
mesh_free_scb(sc, scb);
|
|
}
|
|
|
|
void
|
|
mesh_timeout(arg)
|
|
void *arg;
|
|
{
|
|
struct mesh_scb *scb = arg;
|
|
struct mesh_softc *sc =
|
|
(void *)scb->xs->xs_periph->periph_channel->chan_adapter->adapt_dev;
|
|
int s;
|
|
int status0, status1;
|
|
int intr, error, exception;
|
|
|
|
printf("%s: timeout state %d\n", sc->sc_dev.dv_xname, sc->sc_nextstate);
|
|
|
|
intr = mesh_read_reg(sc, MESH_INTERRUPT);
|
|
exception = mesh_read_reg(sc, MESH_EXCEPTION);
|
|
error = mesh_read_reg(sc, MESH_ERROR);
|
|
status0 = mesh_read_reg(sc, MESH_BUS_STATUS0);
|
|
status1 = mesh_read_reg(sc, MESH_BUS_STATUS1);
|
|
|
|
s = splbio();
|
|
if (sc->sc_flags & MESH_DMA_ACTIVE) {
|
|
printf("mesh: resetting dma\n");
|
|
dbdma_reset(sc->sc_dmareg);
|
|
}
|
|
scb->xs->error = XS_TIMEOUT;
|
|
|
|
mesh_set_reg(sc, MESH_SEQUENCE, MESH_CMD_BUSFREE);
|
|
sc->sc_nextstate = MESH_COMPLETE;
|
|
|
|
splx(s);
|
|
}
|
|
|
|
void
|
|
mesh_minphys(bp)
|
|
struct buf *bp;
|
|
{
|
|
if (bp->b_bcount > 64*1024)
|
|
bp->b_bcount = 64*1024;
|
|
|
|
minphys(bp);
|
|
}
|