/* $OpenBSD: gdt_common.c,v 1.2 2000/02/12 09:45:49 niklas Exp $ */ /* * Copyright (c) 1999, 2000 Niklas Hallqvist. 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 Niklas Hallqvist. * 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. */ /* * This driver would not have written if it was not for the hardware donations * from both ICP-Vortex and Öko.neT. I want to thank them for their support. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define MIN(x, y) (((x) < (y)) ? (x) : (y)) int gdt_async_event __P((struct gdt_softc *, int)); void gdt_chain __P((struct gdt_softc *)); void gdt_clear_events __P((struct gdt_softc *)); void gdt_copy_internal_data __P((struct scsi_xfer *, u_int8_t *, size_t)); struct scsi_xfer *gdt_dequeue __P((struct gdt_softc *)); void gdt_enqueue __P((struct gdt_softc *, struct scsi_xfer *, int)); void gdt_enqueue_ccb __P((struct gdt_softc *, struct gdt_ccb *)); void gdt_eval_mapping __P((u_int32_t, int *, int *, int *)); int gdt_exec_ccb __P((struct gdt_ccb *)); void gdt_free_ccb __P((struct gdt_softc *, struct gdt_ccb *)); struct gdt_ccb *gdt_get_ccb __P((struct gdt_softc *, int)); int gdt_internal_cache_cmd __P((struct scsi_xfer *)); int gdt_internal_cmd __P((struct gdt_softc *, u_int8_t, u_int16_t, u_int32_t, u_int32_t, u_int32_t)); int gdt_raw_scsi_cmd __P((struct scsi_xfer *)); int gdt_scsi_cmd __P((struct scsi_xfer *)); void gdt_start_ccbs __P((struct gdt_softc *)); int gdt_sync_event __P((struct gdt_softc *, int, u_int8_t, struct scsi_xfer *)); void gdt_timeout __P((void *)); int gdt_wait __P((struct gdt_softc *, struct gdt_ccb *, int)); void gdt_watchdog __P((void *)); struct cfdriver gdt_cd = { NULL, "gdt", DV_DULL }; struct scsi_adapter gdt_switch = { gdt_scsi_cmd, gdtminphys, 0, 0, }; struct scsi_adapter gdt_raw_switch = { gdt_raw_scsi_cmd, gdtminphys, 0, 0, }; struct scsi_device gdt_dev = { NULL, NULL, NULL, NULL }; u_int8_t gdt_polling; u_int8_t gdt_from_wait; struct gdt_softc *gdt_wait_gdt; int gdt_wait_index; #ifdef GDT_DEBUG int gdt_debug = GDT_DEBUG; #endif int gdt_attach(gdt) struct gdt_softc *gdt; { u_int16_t cdev_cnt; int i, id, drv_cyls, drv_hds, drv_secs; gdt_polling = 1; gdt_from_wait = 0; gdt_clear_events(gdt); TAILQ_INIT(&gdt->sc_free_ccb); TAILQ_INIT(&gdt->sc_ccbq); LIST_INIT(&gdt->sc_queue); /* Initialize the ccbs */ for (i = 0; i < GDT_MAXCMDS; i++) { gdt->sc_ccbs[i].gc_cmd_index = i + 2; (void)gdt_ccb_set_cmd(gdt->sc_ccbs + i, GDT_GCF_UNUSED); TAILQ_INSERT_TAIL(&gdt->sc_free_ccb, &gdt->sc_ccbs[i], gc_chain); } /* Fill in the prototype scsi_link. */ gdt->sc_link.adapter_softc = gdt; gdt->sc_link.adapter = &gdt_switch; gdt->sc_link.adapter_target = 7; gdt->sc_link.device = &gdt_dev; gdt->sc_link.openings = GDT_MAXCMDS; /* XXX what is optimal? */ gdt->sc_link.adapter_buswidth = (gdt->sc_class & GDT_FC) ? GDT_MAXID : GDT_MAX_HDRIVES; if (!gdt_internal_cmd(gdt, GDT_SCREENSERVICE, GDT_INIT, 0, 0, 0)) { printf("screen service initialization error %d\n", gdt->sc_status); return (1); } if (!gdt_internal_cmd(gdt, GDT_CACHESERVICE, GDT_INIT, GDT_LINUX_OS, 0, 0)) { printf("cache service initialization error %d\n", gdt->sc_status); return (1); } if (!gdt_internal_cmd(gdt, GDT_CACHESERVICE, GDT_MOUNT, 0xffff, 1, 0)) { printf("cache service mount error %d\n", gdt->sc_status); return (1); } if (!gdt_internal_cmd(gdt, GDT_CACHESERVICE, GDT_INIT, GDT_LINUX_OS, 0, 0)) { printf("cache service post-mount initialization error %d\n", gdt->sc_status); return (1); } cdev_cnt = (u_int16_t)gdt->sc_info; /* Detect number of busses */ gdt_enc32(gdt->sc_scratch + GDT_IOC_VERSION, GDT_IOC_NEWEST); gdt->sc_scratch[GDT_IOC_LIST_ENTRIES] = GDT_MAXBUS; gdt->sc_scratch[GDT_IOC_FIRST_CHAN] = 0; gdt->sc_scratch[GDT_IOC_LAST_CHAN] = GDT_MAXBUS - 1; gdt_enc32(gdt->sc_scratch + GDT_IOC_LIST_OFFSET, GDT_IOC_HDR_SZ); if (gdt_internal_cmd(gdt, GDT_CACHESERVICE, GDT_IOCTL, GDT_IOCHAN_RAW_DESC, GDT_INVALID_CHANNEL, GDT_IOC_HDR_SZ + GDT_RAWIOC_SZ)) { gdt->sc_bus_cnt = gdt->sc_scratch[GDT_IOC_CHAN_COUNT]; for (i = 0; i < gdt->sc_bus_cnt; i++) { id = gdt->sc_scratch[GDT_IOC_HDR_SZ + i * GDT_RAWIOC_SZ + GDT_RAWIOC_PROC_ID]; gdt->sc_bus_id[id] = id < GDT_MAXID ? id : 0xff; } } else { /* New method failed, use fallback. */ gdt_enc32(gdt->sc_scratch + GDT_GETCH_CHANNEL_NO, i); for (i = 0; i < GDT_MAXBUS; i++) { if (!gdt_internal_cmd(gdt, GDT_CACHESERVICE, GDT_IOCTL, GDT_SCSI_CHAN_CNT | GDT_L_CTRL_PATTERN, GDT_IO_CHANNEL | GDT_INVALID_CHANNEL, GDT_GETCH_SZ)) { if (i == 0) { printf("cannot get channel count, " "error %d\n", gdt->sc_status); return (1); } break; } gdt->sc_bus_id[i] = (gdt->sc_scratch[GDT_GETCH_SIOP_ID] < GDT_MAXID) ? gdt->sc_scratch[GDT_GETCH_SIOP_ID] : 0xff; } gdt->sc_bus_cnt = i; } /* Read cache confgiuration */ if (!gdt_internal_cmd(gdt, GDT_CACHESERVICE, GDT_IOCTL, GDT_CACHE_INFO, GDT_INVALID_CHANNEL, GDT_CINFO_SZ)) { printf("cannot get cache info, error %d\n", gdt->sc_status); return (1); } gdt->sc_cpar.cp_version = gdt_dec32(gdt->sc_scratch + GDT_CPAR_VERSION); gdt->sc_cpar.cp_state = gdt_dec16(gdt->sc_scratch + GDT_CPAR_STATE); gdt->sc_cpar.cp_strategy = gdt_dec16(gdt->sc_scratch + GDT_CPAR_STRATEGY); gdt->sc_cpar.cp_write_back = gdt_dec16(gdt->sc_scratch + GDT_CPAR_WRITE_BACK); gdt->sc_cpar.cp_block_size = gdt_dec16(gdt->sc_scratch + GDT_CPAR_BLOCK_SIZE); /* Read board information and features */ gdt->sc_more_proc = 0; if (gdt_internal_cmd(gdt, GDT_CACHESERVICE, GDT_IOCTL, GDT_BOARD_INFO, GDT_INVALID_CHANNEL, GDT_BINFO_SZ)) { /* XXX A lot of these assignments can probably go later */ gdt->sc_binfo.bi_ser_no = gdt_dec32(gdt->sc_scratch + GDT_BINFO_SER_NO); bcopy(gdt->sc_scratch + GDT_BINFO_OEM_ID, gdt->sc_binfo.bi_oem_id, sizeof gdt->sc_binfo.bi_oem_id); gdt->sc_binfo.bi_ep_flags = gdt_dec16(gdt->sc_scratch + GDT_BINFO_EP_FLAGS); gdt->sc_binfo.bi_proc_id = gdt_dec32(gdt->sc_scratch + GDT_BINFO_PROC_ID); gdt->sc_binfo.bi_memsize = gdt_dec32(gdt->sc_scratch + GDT_BINFO_MEMSIZE); gdt->sc_binfo.bi_mem_banks = gdt->sc_scratch[GDT_BINFO_MEM_BANKS]; gdt->sc_binfo.bi_chan_type = gdt->sc_scratch[GDT_BINFO_CHAN_TYPE]; gdt->sc_binfo.bi_chan_count = gdt->sc_scratch[GDT_BINFO_CHAN_COUNT]; gdt->sc_binfo.bi_rdongle_pres = gdt->sc_scratch[GDT_BINFO_RDONGLE_PRES]; gdt->sc_binfo.bi_epr_fw_ver = gdt_dec32(gdt->sc_scratch + GDT_BINFO_EPR_FW_VER); gdt->sc_binfo.bi_upd_fw_ver = gdt_dec32(gdt->sc_scratch + GDT_BINFO_UPD_FW_VER); gdt->sc_binfo.bi_upd_revision = gdt_dec32(gdt->sc_scratch + GDT_BINFO_UPD_REVISION); bcopy(gdt->sc_scratch + GDT_BINFO_TYPE_STRING, gdt->sc_binfo.bi_type_string, sizeof gdt->sc_binfo.bi_type_string); bcopy(gdt->sc_scratch + GDT_BINFO_RAID_STRING, gdt->sc_binfo.bi_raid_string, sizeof gdt->sc_binfo.bi_raid_string); gdt->sc_binfo.bi_update_pres = gdt->sc_scratch[GDT_BINFO_UPDATE_PRES]; gdt->sc_binfo.bi_xor_pres = gdt->sc_scratch[GDT_BINFO_XOR_PRES]; gdt->sc_binfo.bi_prom_type = gdt->sc_scratch[GDT_BINFO_PROM_TYPE]; gdt->sc_binfo.bi_prom_count = gdt->sc_scratch[GDT_BINFO_PROM_COUNT]; gdt->sc_binfo.bi_dup_pres = gdt_dec32(gdt->sc_scratch + GDT_BINFO_DUP_PRES); gdt->sc_binfo.bi_chan_pres = gdt_dec32(gdt->sc_scratch + GDT_BINFO_CHAN_PRES); gdt->sc_binfo.bi_mem_pres = gdt_dec32(gdt->sc_scratch + GDT_BINFO_MEM_PRES); gdt->sc_binfo.bi_ft_bus_system = gdt->sc_scratch[GDT_BINFO_FT_BUS_SYSTEM]; gdt->sc_binfo.bi_subtype_valid = gdt->sc_scratch[GDT_BINFO_SUBTYPE_VALID]; gdt->sc_binfo.bi_board_subtype = gdt->sc_scratch[GDT_BINFO_BOARD_SUBTYPE]; gdt->sc_binfo.bi_rampar_pres = gdt->sc_scratch[GDT_BINFO_RAMPAR_PRES]; if (gdt_internal_cmd(gdt, GDT_CACHESERVICE, GDT_IOCTL, GDT_BOARD_FEATURES, GDT_INVALID_CHANNEL, GDT_BFEAT_SZ)) { gdt->sc_bfeat.bf_chaining = gdt->sc_scratch[GDT_BFEAT_CHAINING]; gdt->sc_bfeat.bf_striping = gdt->sc_scratch[GDT_BFEAT_STRIPING]; gdt->sc_bfeat.bf_mirroring = gdt->sc_scratch[GDT_BFEAT_MIRRORING]; gdt->sc_bfeat.bf_raid = gdt->sc_scratch[GDT_BFEAT_RAID]; gdt->sc_more_proc = 1; } } else { /* XXX Not implemented yet */ } /* Read more information */ if (gdt->sc_more_proc) { /* XXX Not implemented yet */ } if (!gdt_internal_cmd(gdt, GDT_SCSIRAWSERVICE, GDT_INIT, 0, 0, 0)) { printf("raw service initialization error %d\n", gdt->sc_status); return (1); } /* Set/get features raw service (scatter/gather) */ gdt->sc_raw_feat = 0; if (gdt_internal_cmd(gdt, GDT_SCSIRAWSERVICE, GDT_SET_FEAT, GDT_SCATTER_GATHER, 0, 0)) if (gdt_internal_cmd(gdt, GDT_SCSIRAWSERVICE, GDT_GET_FEAT, 0, 0, 0)) gdt->sc_raw_feat = gdt->sc_info; /* Set/get features cache service (scatter/gather) */ gdt->sc_cache_feat = 0; if (gdt_internal_cmd(gdt, GDT_CACHESERVICE, GDT_SET_FEAT, 0, GDT_SCATTER_GATHER, 0)) if (gdt_internal_cmd(gdt, GDT_CACHESERVICE, GDT_GET_FEAT, 0, 0, 0)) gdt->sc_cache_feat = gdt->sc_info; /* XXX Linux reserve drives here, potentially */ /* Scan for cache devices */ for (i = 0; i < cdev_cnt && i < GDT_MAX_HDRIVES; i++) if (gdt_internal_cmd(gdt, GDT_CACHESERVICE, GDT_INFO, i, 0, 0)) { gdt->sc_hdr[i].hd_present = 1; gdt->sc_hdr[i].hd_size = gdt->sc_info; /* * Evaluate mapping (sectors per head, heads per cyl) */ gdt->sc_hdr[i].hd_size &= ~GDT_SECS32; if (gdt->sc_info2 == 0) gdt_eval_mapping(gdt->sc_hdr[i].hd_size, &drv_cyls, &drv_hds, &drv_secs); else { drv_hds = gdt->sc_info2 & 0xff; drv_secs = (gdt->sc_info2 >> 8) & 0xff; drv_cyls = gdt->sc_hdr[i].hd_size / drv_hds / drv_secs; } gdt->sc_hdr[i].hd_heads = drv_hds; gdt->sc_hdr[i].hd_secs = drv_secs; /* Round the size */ gdt->sc_hdr[i].hd_size = drv_cyls * drv_hds * drv_secs; if (gdt_internal_cmd(gdt, GDT_CACHESERVICE, GDT_DEVTYPE, i, 0, 0)) gdt->sc_hdr[i].hd_devtype = gdt->sc_info; } printf("dpmem %x %d-bus %d cache device%s\n", gdt->sc_dpmembase, gdt->sc_bus_cnt, cdev_cnt, cdev_cnt == 1 ? "" : "s"); printf("%s: ver %x, cache %s, strategy %d, writeback %s, blksz %d\n", gdt->sc_dev.dv_xname, gdt->sc_cpar.cp_version, gdt->sc_cpar.cp_state ? "on" : "off", gdt->sc_cpar.cp_strategy, gdt->sc_cpar.cp_write_back ? "on" : "off", gdt->sc_cpar.cp_block_size); #if 1 printf("%s: raw feat %x cache feat %x\n", gdt->sc_dev.dv_xname, gdt->sc_raw_feat, gdt->sc_cache_feat); #endif config_found(&gdt->sc_dev, &gdt->sc_link, scsiprint); MALLOC(gdt->sc_raw_link, struct scsi_link *, gdt->sc_bus_cnt * sizeof (struct scsi_link), M_DEVBUF, M_NOWAIT); bzero(gdt->sc_raw_link, gdt->sc_bus_cnt * sizeof (struct scsi_link)); for (i = 0; i < gdt->sc_bus_cnt; i++) { /* Fill in the prototype scsi_link. */ gdt->sc_raw_link[i].adapter_softc = gdt; gdt->sc_raw_link[i].adapter = &gdt_raw_switch; gdt->sc_raw_link[i].adapter_target = 7; gdt->sc_raw_link[i].device = &gdt_dev; gdt->sc_raw_link[i].openings = 4; /* XXX a guess */ gdt->sc_raw_link[i].adapter_buswidth = (gdt->sc_class & GDT_FC) ? GDT_MAXID : 16; /* XXX */ config_found(&gdt->sc_dev, &gdt->sc_raw_link[i], scsiprint); } gdt_polling = 0; return (0); } void gdt_eval_mapping(size, cyls, heads, secs) u_int32_t size; int *cyls, *heads, *secs; { *cyls = size / GDT_HEADS / GDT_SECS; if (*cyls < GDT_MAXCYLS) { *heads = GDT_HEADS; *secs = GDT_SECS; } else { /* Too high for 64 * 32 */ *cyls = size / GDT_MEDHEADS / GDT_MEDSECS; if (*cyls < GDT_MAXCYLS) { *heads = GDT_MEDHEADS; *secs = GDT_MEDSECS; } else { /* Too high for 127 * 63 */ *cyls = size / GDT_BIGHEADS / GDT_BIGSECS; *heads = GDT_BIGHEADS; *secs = GDT_BIGSECS; } } } /* * Insert a command into the driver queue, either at the front or at the tail. * It's ok to overload the freelist link as these structures are never on * the freelist at this time. */ void gdt_enqueue(gdt, xs, infront) struct gdt_softc *gdt; struct scsi_xfer *xs; int infront; { if (infront || LIST_FIRST(&gdt->sc_queue) == NULL) { if (LIST_FIRST(&gdt->sc_queue) == NULL) gdt->sc_queuelast = xs; LIST_INSERT_HEAD(&gdt->sc_queue, xs, free_list); return; } LIST_INSERT_AFTER(gdt->sc_queuelast, xs, free_list); gdt->sc_queuelast = xs; } /* * Pull a command off the front of the driver queue. */ struct scsi_xfer * gdt_dequeue(gdt) struct gdt_softc *gdt; { struct scsi_xfer *xs; xs = LIST_FIRST(&gdt->sc_queue); LIST_REMOVE(xs, free_list); if (LIST_FIRST(&gdt->sc_queue) == NULL) gdt->sc_queuelast = NULL; return (xs); } /* Start a SCSI operation on a cache device */ int gdt_scsi_cmd(xs) struct scsi_xfer *xs; { struct scsi_link *link = xs->sc_link; struct gdt_softc *gdt = link->adapter_softc; u_int8_t target = link->target; struct gdt_ccb *ccb; int dontqueue = 0; u_int32_t blockno, blockcnt; struct scsi_rw *rw; struct scsi_rw_big *rwb; GDT_DPRINTF(GDT_D_CMD, ("gdt_scsi_cmd ")); if (target >= GDT_MAX_HDRIVES || !gdt->sc_hdr[target].hd_present || link->lun != 0) { xs->error = XS_DRIVER_STUFFUP; return (COMPLETE); } xs->error = XS_NOERROR; ccb = NULL; GDT_LOCK_GDT(gdt); if (!gdt_polling && gdt->sc_test_busy(gdt)) { /* Don't double enqueue if we came from gdt_chain. */ if (xs != LIST_FIRST(&gdt->sc_queue)) gdt_enqueue(gdt, xs, 0); GDT_UNLOCK_GDT(gdt); return (SUCCESSFULLY_QUEUED); } GDT_UNLOCK_GDT(gdt); switch (xs->cmd->opcode) { case TEST_UNIT_READY: case REQUEST_SENSE: case INQUIRY: case MODE_SENSE: case START_STOP: case READ_CAPACITY: case SYNCHRONIZE_CACHE: #if 0 case VERIFY: #endif return (gdt_internal_cache_cmd(xs) ? COMPLETE : TRY_AGAIN_LATER); case PREVENT_ALLOW: GDT_DPRINTF(GDT_D_CMD, ("PREVENT/ALLOW ")); /* XXX Not yet implemented */ xs->error = XS_NOERROR; return (COMPLETE); case READ_COMMAND: case READ_BIG: case WRITE_COMMAND: case WRITE_BIG: GDT_LOCK_GDT(gdt); /* * When chaining commands we may get called with the * first command in the queue, recognize this case * easily. */ if (xs == LIST_FIRST(&gdt->sc_queue)) xs = gdt_dequeue(gdt); else { /* A new command chain, start from the beginning. */ gdt->sc_cmd_off = 0; /* Don't resort to queuing if we are polling. */ dontqueue = xs->flags & SCSI_POLL; /* * The queue, if existent, must be processed first, * before the new command can be run. */ if (LIST_FIRST(&gdt->sc_queue) != NULL) { /* If order cannot be preserved, punt. */ if (dontqueue) { GDT_UNLOCK_GDT(gdt); xs->error = XS_DRIVER_STUFFUP; return (TRY_AGAIN_LATER); } /* * Enqueue the new command, ponder on the front * command of the queue instead. */ gdt_enqueue(gdt, xs, 0); xs = gdt_dequeue(gdt); } } if (xs->cmdlen == 6) { rw = (struct scsi_rw *)xs->cmd; blockno = _3btol(rw->addr) & (SRW_TOPADDR << 16 | 0xffff); blockcnt = rw->length ? rw->length : 0x100; } else { rwb = (struct scsi_rw_big *)xs->cmd; blockno = _4btol(rwb->addr); blockcnt = _2btol(rwb->length); } if (blockno >= gdt->sc_hdr[target].hd_size || blockno + blockcnt > gdt->sc_hdr[target].hd_size) { printf("%s: out of bounds %d-%d >= %d\n", gdt->sc_dev.dv_xname, blockno, blockcnt, gdt->sc_hdr[target].hd_size); xs->error = XS_DRIVER_STUFFUP; return (COMPLETE); } ccb = gdt_get_ccb(gdt, xs->flags); /* * Are we out of commands, then queue. If we cannot queue, * then punt. */ if (ccb == NULL) { if (dontqueue) { GDT_UNLOCK_GDT(gdt); scsi_done(xs); xs->error = XS_DRIVER_STUFFUP; return (COMPLETE); } if (xs->error) { GDT_UNLOCK_GDT(gdt); scsi_done(xs); return (COMPLETE); } /* Put back on the queue, in the front. */ gdt_enqueue(gdt, xs, 1); GDT_UNLOCK_GDT(gdt); return (SUCCESSFULLY_QUEUED); } ccb->gc_blockno = blockno; ccb->gc_blockcnt = blockcnt; ccb->gc_xs = xs; ccb->gc_timeout = xs->timeout; ccb->gc_service = GDT_CACHESERVICE; gdt_ccb_set_cmd(ccb, GDT_GCF_SCSI); gdt_enqueue_ccb(gdt, ccb); /* XXX what if enqueue did not start a transfer? */ if (gdt_polling) { if (!gdt_wait(gdt, ccb, ccb->gc_timeout)) { GDT_UNLOCK_GDT(gdt); printf("%s: command %d timed out\n", gdt->sc_dev.dv_xname, ccb->gc_cmd_index); xs->error = XS_TIMEOUT; return (TRY_AGAIN_LATER); } } GDT_UNLOCK_GDT(gdt); if (gdt_polling) { scsi_done(xs); return (COMPLETE); } return (SUCCESSFULLY_QUEUED); default: GDT_DPRINTF(GDT_D_CMD, ("unknown opc %d ", xs->cmd->opcode)); /* XXX Not yet implemented */ xs->error = XS_DRIVER_STUFFUP; return (COMPLETE); } } /* XXX Currently only for cacheservice, returns 0 if busy */ int gdt_exec_ccb(ccb) struct gdt_ccb *ccb; { struct scsi_xfer *xs = ccb->gc_xs; struct scsi_link *link = xs->sc_link; struct gdt_softc *gdt = link->adapter_softc; u_int8_t target = link->target; u_int32_t sg_canz; int i, len, off; u_int8_t *buf; paddr_t pa; GDT_DPRINTF(GDT_D_CMD, ("gdt_exec_ccb(%p, %p) ", xs, ccb)); gdt->sc_cmd_cnt = 0; /* * XXX Yeah I know it's a always-true condition, but that may change * later. */ if (gdt->sc_cmd_cnt == 0) gdt->sc_set_sema0(gdt); gdt_enc32(gdt->sc_cmd + GDT_CMD_COMMANDINDEX, ccb->gc_cmd_index); gdt_enc32(gdt->sc_cmd + GDT_CMD_BOARDNODE, GDT_LOCALBOARD); gdt_enc16(gdt->sc_cmd + GDT_CMD_UNION + GDT_CACHE_DEVICENO, target); switch (xs->cmd->opcode) { case PREVENT_ALLOW: /* XXX PREVENT_ALLOW support goes here */ gdt_enc32(gdt->sc_cmd + GDT_CMD_UNION + GDT_CACHE_BLOCKNO, 1); sg_canz = 0; break; case WRITE_COMMAND: case WRITE_BIG: /* XXX WRITE_THR could be supported too */ gdt->sc_cmd[GDT_CMD_OPCODE] = GDT_WRITE; break; case READ_COMMAND: case READ_BIG: gdt->sc_cmd[GDT_CMD_OPCODE] = GDT_READ; break; } if (xs->cmd->opcode != PREVENT_ALLOW) { gdt_enc32(gdt->sc_cmd + GDT_CMD_UNION + GDT_CACHE_BLOCKNO, ccb->gc_blockno); gdt_enc32(gdt->sc_cmd + GDT_CMD_UNION + GDT_CACHE_BLOCKCNT, ccb->gc_blockcnt); } if (gdt->sc_cache_feat & GDT_SCATTER_GATHER) { gdt_enc32(gdt->sc_cmd + GDT_CMD_UNION + GDT_CACHE_DESTADDR, 0xffffffff); len = xs->datalen; buf = xs->data; for (i = 0; len > 0; i++) { for (off = PAGE_SIZE, pa = vtophys(buf); off < len; off += PAGE_SIZE) if (pa + off != vtophys(buf + off)) break; gdt_enc32(gdt->sc_cmd + GDT_CMD_UNION + GDT_CACHE_SG_LST + i * GDT_SG_SZ + GDT_SG_PTR, pa); gdt_enc32(gdt->sc_cmd + GDT_CMD_UNION + GDT_CACHE_SG_LST + i * GDT_SG_SZ + GDT_SG_LEN, MIN(off, len)); GDT_DPRINTF(GDT_D_IO, ("#%d va %p pa %p len %x\n", i, buf, (void *)pa, MIN(off, len))); len -= off; buf += off; } sg_canz = i; gdt_enc32(gdt->sc_cmd + GDT_CMD_UNION + GDT_CACHE_SG_LST + sg_canz * GDT_SG_SZ + GDT_SG_LEN, 0); } else { gdt_enc32(gdt->sc_cmd + GDT_CMD_UNION + GDT_CACHE_DESTADDR, vtophys(xs->data)); sg_canz = 0; } gdt_enc32(gdt->sc_cmd + GDT_CMD_UNION + GDT_CACHE_SG_CANZ, sg_canz); gdt->sc_cmd_len = roundup(GDT_CMD_UNION + GDT_CACHE_SG_LST + sg_canz * GDT_SG_SZ, sizeof (u_int32_t)); if (gdt->sc_cmd_cnt > 0 && gdt->sc_cmd_off + gdt->sc_cmd_len + GDT_DPMEM_COMMAND_OFFSET > gdt->sc_ic_all_size) { printf("%s: DPMEM overflow\n", gdt->sc_dev.dv_xname); gdt_free_ccb(gdt, ccb); xs->error = XS_BUSY; return (0); } gdt->sc_copy_cmd(gdt, ccb); gdt->sc_release_event(gdt, ccb); xs->error = XS_NOERROR; xs->resid = 0; return (1); } void gdt_copy_internal_data(xs, data, size) struct scsi_xfer *xs; u_int8_t *data; size_t size; { size_t copy_cnt; GDT_DPRINTF(GDT_D_MISC, ("gdt_copy_internal_data ")); if (!xs->datalen) printf("uio move not yet supported\n"); else { copy_cnt = MIN(size, xs->datalen); bcopy(data, xs->data, copy_cnt); } } /* Emulated SCSI operation on cache device */ int gdt_internal_cache_cmd(xs) struct scsi_xfer *xs; { struct scsi_link *link = xs->sc_link; struct gdt_softc *gdt = link->adapter_softc; struct scsi_inquiry_data inq; struct scsi_sense_data sd; struct { struct scsi_mode_header hd; struct scsi_blk_desc bd; union scsi_disk_pages dp; } mpd; struct scsi_read_cap_data rcd; u_int8_t target = link->target; GDT_DPRINTF(GDT_D_CMD, ("gdt_internal_cache_cmd ")); switch (xs->cmd->opcode) { case TEST_UNIT_READY: case START_STOP: #if 0 case VERIFY: #endif GDT_DPRINTF(GDT_D_CMD, ("opc %d tgt %d ", xs->cmd->opcode, target)); break; case SYNCHRONIZE_CACHE: GDT_DPRINTF(GDT_D_CMD, ("SYNCHRONIZE CACHE tgt %d ", target)); break; case REQUEST_SENSE: GDT_DPRINTF(GDT_D_CMD, ("REQUEST SENSE tgt %d ", target)); bzero(&sd, sizeof sd); sd.error_code = 0x70; sd.segment = 0; sd.flags = SKEY_NO_SENSE; gdt_enc32(sd.info, 0); sd.extra_len = 0; gdt_copy_internal_data(xs, (u_int8_t *)&sd, sizeof sd); break; case INQUIRY: GDT_DPRINTF(GDT_D_CMD, ("INQUIRY tgt %d devtype %x ", target, gdt->sc_hdr[target].hd_devtype)); bzero(&inq, sizeof inq); inq.device = (gdt->sc_hdr[target].hd_devtype & 4) ? T_CDROM : T_DIRECT; inq.dev_qual2 = (gdt->sc_hdr[target].hd_devtype & 1) ? SID_REMOVABLE : 0; inq.version = 2; inq.response_format = 2; inq.additional_length = 32; strcpy(inq.vendor, "ICP "); sprintf(inq.product, "Host drive #%02d", target); strcpy(inq.revision, " "); gdt_copy_internal_data(xs, (u_int8_t *)&inq, sizeof inq); break; case MODE_SENSE: GDT_DPRINTF(GDT_D_CMD, ("MODE SENSE tgt %d ", target)); bzero(&mpd, sizeof mpd); switch (((struct scsi_mode_sense *)xs->cmd)->page) { case 4: /* scsi_disk.h says this should be 0x16 */ mpd.dp.rigid_geometry.pg_length = 0x16; mpd.hd.data_length = sizeof mpd.hd + sizeof mpd.bd + mpd.dp.rigid_geometry.pg_length; mpd.hd.blk_desc_len = sizeof mpd.bd; /* XXX */ mpd.hd.dev_spec = (gdt->sc_hdr[target].hd_devtype & 2) ? 0x80 : 0; _lto3b(GDT_SECTOR_SIZE, mpd.bd.blklen); mpd.dp.rigid_geometry.pg_code = 4; _lto3b(gdt->sc_hdr[target].hd_size / gdt->sc_hdr[target].hd_heads / gdt->sc_hdr[target].hd_secs, mpd.dp.rigid_geometry.ncyl); mpd.dp.rigid_geometry.nheads = gdt->sc_hdr[target].hd_heads; gdt_copy_internal_data(xs, (u_int8_t *)&mpd, sizeof mpd); break; default: printf("%s: mode sense page %d not simulated\n", gdt->sc_dev.dv_xname, ((struct scsi_mode_sense *)xs->cmd)->page); xs->error = XS_DRIVER_STUFFUP; return (0); } break; case READ_CAPACITY: GDT_DPRINTF(GDT_D_CMD, ("READ CAPACITY tgt %d ", target)); bzero(&rcd, sizeof rcd); _lto4b(gdt->sc_hdr[target].hd_size - 1, rcd.addr); _lto4b(GDT_SECTOR_SIZE, rcd.length); gdt_copy_internal_data(xs, (u_int8_t *)&rcd, sizeof rcd); break; default: printf("gdt_internal_cache_cmd got bad opcode: %d\n", xs->cmd->opcode); xs->error = XS_DRIVER_STUFFUP; return (0); } xs->error = XS_NOERROR; return (1); } /* Start a raw SCSI operation */ int gdt_raw_scsi_cmd(xs) struct scsi_xfer *xs; { GDT_DPRINTF(GDT_D_CMD, ("gdt_raw_scsi_cmd ")); /* XXX Not yet implemented */ xs->error = XS_DRIVER_STUFFUP; return (COMPLETE); } void gdt_clear_events(gdt) struct gdt_softc *gdt; { GDT_DPRINTF(GDT_D_MISC, ("gdt_clear_events(%p) ", gdt)); /* XXX To be implemented */ } int gdt_async_event(gdt, service) struct gdt_softc *gdt; int service; { GDT_DPRINTF(GDT_D_INTR, ("gdt_async_event(%p, %d) ", gdt, service)); if (service == GDT_SCREENSERVICE) { /* XXX To be implemented */ } else { /* XXX To be implemented */ } return (0); } int gdt_sync_event(gdt, service, index, xs) struct gdt_softc *gdt; int service; u_int8_t index; struct scsi_xfer *xs; { GDT_DPRINTF(GDT_D_INTR, ("gdt_sync_event(%p, %d, %d, %p) ", gdt, service, index, xs)); if (service == GDT_SCREENSERVICE) { /* XXX To be implemented */ return (0); } else { if (gdt->sc_status == GDT_S_OK) { /* XXX To be implemented */ } else { /* XXX To be implemented */ return (0); } } return (1); } int gdt_intr(arg) void *arg; { struct gdt_softc *gdt = arg; struct gdt_intr_ctx ctx; int chain = 1, sync_val = 0; struct scsi_xfer *xs; int prev_cmd; struct gdt_ccb *ccb; GDT_DPRINTF(GDT_D_INTR, ("gdt_intr(%p) ", gdt)); /* If polling and we were not called from gdt_wait, just return */ if (gdt_polling && !gdt_from_wait) return (0); if (!gdt_polling) GDT_LOCK_GDT(gdt); ctx.istatus = gdt->sc_get_status(gdt); if (!ctx.istatus) { GDT_UNLOCK_GDT(gdt); gdt->sc_status = GDT_S_NO_STATUS; return (0); } gdt_wait_index = 0; ctx.service = ctx.info2 = 0; gdt->sc_intr(gdt, &ctx); gdt->sc_status = ctx.cmd_status; gdt->sc_info = ctx.info; gdt->sc_info2 = ctx.info2; if (gdt_from_wait) { gdt_wait_gdt = gdt; gdt_wait_index = ctx.istatus; } switch (ctx.istatus) { case GDT_ASYNCINDEX: gdt_async_event(gdt, ctx.service); goto finish; case GDT_SPEZINDEX: /* XXX Not yet implemented */ chain = 0; goto finish; } ccb = &gdt->sc_ccbs[ctx.istatus - 2]; xs = ccb->gc_xs; if (!gdt_polling) untimeout(gdt_timeout, ccb); ctx.service = ccb->gc_service; prev_cmd = ccb->gc_flags & GDT_GCF_CMD_MASK; gdt_free_ccb(gdt, ccb); switch (prev_cmd) { case GDT_GCF_UNUSED: /* XXX Not yet implemented */ chain = 0; goto finish; case GDT_GCF_INTERNAL: chain = 0; goto finish; } sync_val = gdt_sync_event(gdt, ctx.service, ctx.istatus, xs); finish: if (!gdt_polling) GDT_UNLOCK_GDT(gdt); switch (sync_val) { case 1: xs->flags |= ITSDONE; scsi_done(xs); break; case 2: gdt_enqueue(gdt, xs, 0); } if (chain) gdt_chain(gdt); return (1); } void gdtminphys(bp) struct buf *bp; { #if 0 u_int8_t *buf = bp->b_data; paddr_t pa; long off; #endif GDT_DPRINTF(GDT_D_MISC, ("gdtminphys(0x%x) ", bp)); #if 1 /* As this is way more than MAXPHYS it's really not necessary. */ if (bp->b_bcount > ((GDT_MAXOFFSETS - 1) * PAGE_SIZE)) bp->b_bcount = ((GDT_MAXOFFSETS - 1) * PAGE_SIZE); #else for (off = PAGE_SIZE, pa = vtophys(buf); off < bp->b_bcount; off += PAGE_SIZE) if (pa + off != vtophys(buf + off)) { bp->b_bcount = off; break; } #endif minphys(bp); } int gdt_wait(gdt, ccb, timeout) struct gdt_softc *gdt; struct gdt_ccb *ccb; int timeout; { int rv = 0; GDT_DPRINTF(GDT_D_MISC, ("gdt_wait(%p, %p, %d) ", gdt, ccb, timeout)); gdt_from_wait = 1; do { if (gdt_intr(gdt) && gdt == gdt_wait_gdt && ccb->gc_cmd_index == gdt_wait_index) { rv = 1; break; } DELAY(1); } while (--timeout); gdt_from_wait = 0; while (gdt->sc_test_busy(gdt)) DELAY(0); /* XXX correct? */ return (rv); } int gdt_internal_cmd(gdt, service, opcode, arg1, arg2, arg3) struct gdt_softc *gdt; u_int8_t service; u_int16_t opcode; u_int32_t arg1, arg2, arg3; { int retries; struct gdt_ccb *ccb; GDT_DPRINTF(GDT_D_CMD, ("gdt_internal_cmd(%p, %d, %d, %d, %d, %d) ", gdt, service, opcode, arg1, arg2, arg3)); bzero(gdt->sc_cmd, GDT_CMD_SZ); for (retries = GDT_RETRIES; ; ) { ccb = gdt_get_ccb(gdt, SCSI_NOSLEEP); if (ccb == NULL) { printf("%s: no free command index found\n", gdt->sc_dev.dv_xname); return (0); } ccb->gc_service = service; gdt_ccb_set_cmd(ccb, GDT_GCF_INTERNAL); gdt->sc_set_sema0(gdt); gdt_enc32(gdt->sc_cmd + GDT_CMD_COMMANDINDEX, ccb->gc_cmd_index); gdt_enc16(gdt->sc_cmd + GDT_CMD_OPCODE, opcode); gdt_enc32(gdt->sc_cmd + GDT_CMD_BOARDNODE, GDT_LOCALBOARD); switch (service) { case GDT_CACHESERVICE: if (opcode == GDT_IOCTL) { gdt_enc32(gdt->sc_cmd + GDT_CMD_UNION + GDT_IOCTL_SUBFUNC, arg1); gdt_enc32(gdt->sc_cmd + GDT_CMD_UNION + GDT_IOCTL_CHANNEL, arg2); gdt_enc16(gdt->sc_cmd + GDT_CMD_UNION + GDT_IOCTL_PARAM_SIZE, (u_int16_t)arg3); gdt_enc32(gdt->sc_cmd + GDT_CMD_UNION + GDT_IOCTL_P_PARAM, vtophys(gdt->sc_scratch)); } else { gdt_enc16(gdt->sc_cmd + GDT_CMD_UNION + GDT_CACHE_DEVICENO, (u_int16_t)arg1); gdt_enc32(gdt->sc_cmd + GDT_CMD_UNION + GDT_CACHE_BLOCKNO, arg2); } break; case GDT_SCSIRAWSERVICE: gdt_enc32(gdt->sc_cmd + GDT_CMD_UNION + GDT_RAW_DIRECTION, arg1); gdt->sc_cmd[GDT_CMD_UNION + GDT_RAW_BUS] = (u_int8_t)arg2; gdt->sc_cmd[GDT_CMD_UNION + GDT_RAW_TARGET] = (u_int8_t)arg3; gdt->sc_cmd[GDT_CMD_UNION + GDT_RAW_LUN] = (u_int8_t)(arg3 >> 8); } gdt->sc_cmd_len = GDT_CMD_SZ; gdt->sc_cmd_off = 0; gdt->sc_cmd_cnt = 0; gdt->sc_copy_cmd(gdt, ccb); gdt->sc_release_event(gdt, ccb); DELAY(20); if (!gdt_wait(gdt, ccb, GDT_POLL_TIMEOUT)) return (0); if (gdt->sc_status != GDT_S_BSY || --retries == 0) break; DELAY(1); } return (gdt->sc_status == GDT_S_OK); } struct gdt_ccb * gdt_get_ccb(gdt, flags) struct gdt_softc *gdt; int flags; { struct gdt_ccb *ccb; GDT_DPRINTF(GDT_D_QUEUE, ("gdt_get_ccb(%p, 0x%x) ", gdt, flags)); GDT_LOCK_GDT(gdt); for (;;) { ccb = TAILQ_FIRST(&gdt->sc_free_ccb); if (ccb != NULL) break; if (flags & SCSI_NOSLEEP) goto bail_out; tsleep(&gdt->sc_free_ccb, PRIBIO, "gdt_ccb", 0); } TAILQ_REMOVE(&gdt->sc_free_ccb, ccb, gc_chain); bail_out: GDT_UNLOCK_GDT(gdt); return (ccb); } void gdt_free_ccb(gdt, ccb) struct gdt_softc *gdt; struct gdt_ccb *ccb; { GDT_DPRINTF(GDT_D_QUEUE, ("gdt_free_ccb(%p, %p) ", gdt, ccb)); GDT_LOCK_GDT(gdt); TAILQ_INSERT_HEAD(&gdt->sc_free_ccb, ccb, gc_chain); /* If the free list was empty, wake up potential waiters. */ if (TAILQ_NEXT(ccb, gc_chain) == NULL) wakeup(&gdt->sc_free_ccb); GDT_UNLOCK_GDT(gdt); } void gdt_enqueue_ccb(gdt, ccb) struct gdt_softc *gdt; struct gdt_ccb *ccb; { GDT_DPRINTF(GDT_D_QUEUE, ("gdt_enqueue_ccb(%p, %p) ", gdt, ccb)); TAILQ_INSERT_TAIL(&gdt->sc_ccbq, ccb, gc_chain); gdt_start_ccbs(gdt); } void gdt_start_ccbs(gdt) struct gdt_softc *gdt; { struct gdt_ccb *ccb; GDT_DPRINTF(GDT_D_QUEUE, ("gdt_start_ccbs(%p) ", gdt)); while ((ccb = TAILQ_FIRST(&gdt->sc_ccbq)) != NULL) { if (ccb->gc_flags & GDT_GCF_WATCHDOG) untimeout(gdt_watchdog, ccb); if (gdt_exec_ccb(ccb) == 0) { ccb->gc_flags |= GDT_GCF_WATCHDOG; timeout(gdt_watchdog, ccb, (GDT_WATCH_TIMEOUT * hz) / 1000); break; } TAILQ_REMOVE(&gdt->sc_ccbq, ccb, gc_chain); if ((ccb->gc_xs->flags & SCSI_POLL) == 0) timeout(gdt_timeout, ccb, (ccb->gc_timeout * hz) / 1000); } } void gdt_chain(gdt) struct gdt_softc *gdt; { GDT_DPRINTF(GDT_D_INTR, ("gdt_chain(%p) ", gdt)); if (LIST_FIRST(&gdt->sc_queue)) gdt_scsi_cmd(LIST_FIRST(&gdt->sc_queue)); } void gdt_timeout(arg) void *arg; { struct gdt_ccb *ccb = arg; struct scsi_link *link = ccb->gc_xs->sc_link; struct gdt_softc *gdt = link->adapter_softc; sc_print_addr(link); printf("timed out\n"); /* XXX Test for multiple timeouts */ ccb->gc_xs->error = XS_TIMEOUT; GDT_LOCK_GDT(gdt); gdt_enqueue_ccb(gdt, ccb); GDT_UNLOCK_GDT(gdt); } void gdt_watchdog(arg) void *arg; { struct gdt_ccb *ccb = arg; struct scsi_link *link = ccb->gc_xs->sc_link; struct gdt_softc *gdt = link->adapter_softc; GDT_LOCK_GDT(gdt); ccb->gc_flags &= ~GDT_GCF_WATCHDOG; gdt_start_ccbs(gdt); GDT_UNLOCK_GDT(gdt); }