diff --git a/sys/cam/ata/ata_xpt.c b/sys/cam/ata/ata_xpt.c index a442ec15ca30..3429bb29a688 100644 --- a/sys/cam/ata/ata_xpt.c +++ b/sys/cam/ata/ata_xpt.c @@ -1,2092 +1,2093 @@ /*- * Copyright (c) 2009 Alexander Motin * 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, * without modification, immediately at the beginning of the file. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE 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 __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* for xpt_print below */ #include "opt_cam.h" struct ata_quirk_entry { struct scsi_inquiry_pattern inq_pat; u_int8_t quirks; #define CAM_QUIRK_MAXTAGS 0x01 u_int mintags; u_int maxtags; }; static periph_init_t probe_periph_init; static struct periph_driver probe_driver = { probe_periph_init, "aprobe", TAILQ_HEAD_INITIALIZER(probe_driver.units), /* generation */ 0, CAM_PERIPH_DRV_EARLY }; PERIPHDRIVER_DECLARE(aprobe, probe_driver); typedef enum { PROBE_RESET, PROBE_IDENTIFY, PROBE_SPINUP, PROBE_SETMODE, PROBE_SETPM, PROBE_SETAPST, PROBE_SETDMAAA, PROBE_SETAN, PROBE_SET_MULTI, PROBE_INQUIRY, PROBE_FULL_INQUIRY, PROBE_PM_PID, PROBE_PM_PRV, PROBE_IDENTIFY_SES, PROBE_IDENTIFY_SAFTE, PROBE_DONE, PROBE_INVALID } probe_action; static char *probe_action_text[] = { "PROBE_RESET", "PROBE_IDENTIFY", "PROBE_SPINUP", "PROBE_SETMODE", "PROBE_SETPM", "PROBE_SETAPST", "PROBE_SETDMAAA", "PROBE_SETAN", "PROBE_SET_MULTI", "PROBE_INQUIRY", "PROBE_FULL_INQUIRY", "PROBE_PM_PID", "PROBE_PM_PRV", "PROBE_IDENTIFY_SES", "PROBE_IDENTIFY_SAFTE", "PROBE_DONE", "PROBE_INVALID" }; #define PROBE_SET_ACTION(softc, newaction) \ do { \ char **text; \ text = probe_action_text; \ CAM_DEBUG((softc)->periph->path, CAM_DEBUG_PROBE, \ ("Probe %s to %s\n", text[(softc)->action], \ text[(newaction)])); \ (softc)->action = (newaction); \ } while(0) typedef enum { PROBE_NO_ANNOUNCE = 0x04 } probe_flags; typedef struct { TAILQ_HEAD(, ccb_hdr) request_ccbs; struct ata_params ident_data; probe_action action; probe_flags flags; uint32_t pm_pid; uint32_t pm_prv; int restart; int spinup; int faults; u_int caps; struct cam_periph *periph; } probe_softc; static struct ata_quirk_entry ata_quirk_table[] = { { /* Default tagged queuing parameters for all devices */ { T_ANY, SIP_MEDIA_REMOVABLE|SIP_MEDIA_FIXED, /*vendor*/"*", /*product*/"*", /*revision*/"*" }, /*quirks*/0, /*mintags*/0, /*maxtags*/0 }, }; static const int ata_quirk_table_size = sizeof(ata_quirk_table) / sizeof(*ata_quirk_table); static cam_status proberegister(struct cam_periph *periph, void *arg); static void probeschedule(struct cam_periph *probe_periph); static void probestart(struct cam_periph *periph, union ccb *start_ccb); static void proberequestdefaultnegotiation(struct cam_periph *periph); static void probedone(struct cam_periph *periph, union ccb *done_ccb); static void probecleanup(struct cam_periph *periph); static void ata_find_quirk(struct cam_ed *device); static void ata_scan_bus(struct cam_periph *periph, union ccb *ccb); static void ata_scan_lun(struct cam_periph *periph, struct cam_path *path, cam_flags flags, union ccb *ccb); static void xptscandone(struct cam_periph *periph, union ccb *done_ccb); static struct cam_ed * ata_alloc_device(struct cam_eb *bus, struct cam_et *target, lun_id_t lun_id); static void ata_device_transport(struct cam_path *path); static void ata_get_transfer_settings(struct ccb_trans_settings *cts); static void ata_set_transfer_settings(struct ccb_trans_settings *cts, struct cam_path *path, int async_update); static void ata_dev_async(u_int32_t async_code, struct cam_eb *bus, struct cam_et *target, struct cam_ed *device, void *async_arg); static void ata_action(union ccb *start_ccb); static void ata_announce_periph(struct cam_periph *periph); static int ata_dma = 1; static int atapi_dma = 1; TUNABLE_INT("hw.ata.ata_dma", &ata_dma); TUNABLE_INT("hw.ata.atapi_dma", &atapi_dma); static struct xpt_xport ata_xport = { .alloc_device = ata_alloc_device, .action = ata_action, .async = ata_dev_async, .announce = ata_announce_periph, }; struct xpt_xport * ata_get_xport(void) { return (&ata_xport); } static void probe_periph_init() { } static cam_status proberegister(struct cam_periph *periph, void *arg) { union ccb *request_ccb; /* CCB representing the probe request */ cam_status status; probe_softc *softc; request_ccb = (union ccb *)arg; if (request_ccb == NULL) { printf("proberegister: no probe CCB, " "can't register device\n"); return(CAM_REQ_CMP_ERR); } softc = (probe_softc *)malloc(sizeof(*softc), M_CAMXPT, M_ZERO | M_NOWAIT); if (softc == NULL) { printf("proberegister: Unable to probe new device. " "Unable to allocate softc\n"); return(CAM_REQ_CMP_ERR); } TAILQ_INIT(&softc->request_ccbs); TAILQ_INSERT_TAIL(&softc->request_ccbs, &request_ccb->ccb_h, periph_links.tqe); softc->flags = 0; periph->softc = softc; softc->periph = periph; softc->action = PROBE_INVALID; status = cam_periph_acquire(periph); if (status != CAM_REQ_CMP) { return (status); } CAM_DEBUG(periph->path, CAM_DEBUG_PROBE, ("Probe started\n")); ata_device_transport(periph->path); probeschedule(periph); return(CAM_REQ_CMP); } static void probeschedule(struct cam_periph *periph) { union ccb *ccb; probe_softc *softc; softc = (probe_softc *)periph->softc; ccb = (union ccb *)TAILQ_FIRST(&softc->request_ccbs); if ((periph->path->device->flags & CAM_DEV_UNCONFIGURED) || periph->path->device->protocol == PROTO_SATAPM || periph->path->device->protocol == PROTO_SEMB) PROBE_SET_ACTION(softc, PROBE_RESET); else PROBE_SET_ACTION(softc, PROBE_IDENTIFY); if (ccb->crcn.flags & CAM_EXPECT_INQ_CHANGE) softc->flags |= PROBE_NO_ANNOUNCE; else softc->flags &= ~PROBE_NO_ANNOUNCE; xpt_schedule(periph, CAM_PRIORITY_XPT); } static void probestart(struct cam_periph *periph, union ccb *start_ccb) { struct ccb_trans_settings cts; struct ccb_ataio *ataio; struct ccb_scsiio *csio; probe_softc *softc; struct cam_path *path; struct ata_params *ident_buf; CAM_DEBUG(start_ccb->ccb_h.path, CAM_DEBUG_TRACE, ("probestart\n")); softc = (probe_softc *)periph->softc; path = start_ccb->ccb_h.path; ataio = &start_ccb->ataio; csio = &start_ccb->csio; ident_buf = &periph->path->device->ident_data; if (softc->restart) { softc->restart = 0; if ((path->device->flags & CAM_DEV_UNCONFIGURED) || path->device->protocol == PROTO_SATAPM || path->device->protocol == PROTO_SEMB) softc->action = PROBE_RESET; else softc->action = PROBE_IDENTIFY; } switch (softc->action) { case PROBE_RESET: cam_fill_ataio(ataio, 0, probedone, /*flags*/CAM_DIR_NONE, 0, /*data_ptr*/NULL, /*dxfer_len*/0, 15 * 1000); ata_reset_cmd(ataio); break; case PROBE_IDENTIFY: cam_fill_ataio(ataio, 1, probedone, /*flags*/CAM_DIR_IN, 0, /*data_ptr*/(u_int8_t *)&softc->ident_data, /*dxfer_len*/sizeof(softc->ident_data), 30 * 1000); if (periph->path->device->protocol == PROTO_ATA) ata_28bit_cmd(ataio, ATA_ATA_IDENTIFY, 0, 0, 0); else ata_28bit_cmd(ataio, ATA_ATAPI_IDENTIFY, 0, 0, 0); break; case PROBE_SPINUP: if (bootverbose) xpt_print(path, "Spinning up device\n"); cam_fill_ataio(ataio, 1, probedone, /*flags*/CAM_DIR_NONE | CAM_HIGH_POWER, 0, /*data_ptr*/NULL, /*dxfer_len*/0, 30 * 1000); ata_28bit_cmd(ataio, ATA_SETFEATURES, ATA_SF_PUIS_SPINUP, 0, 0); break; case PROBE_SETMODE: { int mode, wantmode; mode = 0; /* Fetch user modes from SIM. */ bzero(&cts, sizeof(cts)); xpt_setup_ccb(&cts.ccb_h, path, CAM_PRIORITY_NONE); cts.ccb_h.func_code = XPT_GET_TRAN_SETTINGS; cts.type = CTS_TYPE_USER_SETTINGS; xpt_action((union ccb *)&cts); if (path->device->transport == XPORT_ATA) { if (cts.xport_specific.ata.valid & CTS_ATA_VALID_MODE) mode = cts.xport_specific.ata.mode; } else { if (cts.xport_specific.sata.valid & CTS_SATA_VALID_MODE) mode = cts.xport_specific.sata.mode; } if (periph->path->device->protocol == PROTO_ATA) { if (ata_dma == 0 && (mode == 0 || mode > ATA_PIO_MAX)) mode = ATA_PIO_MAX; } else { if (atapi_dma == 0 && (mode == 0 || mode > ATA_PIO_MAX)) mode = ATA_PIO_MAX; } negotiate: /* Honor device capabilities. */ wantmode = mode = ata_max_mode(ident_buf, mode); /* Report modes to SIM. */ bzero(&cts, sizeof(cts)); xpt_setup_ccb(&cts.ccb_h, path, CAM_PRIORITY_NONE); cts.ccb_h.func_code = XPT_SET_TRAN_SETTINGS; cts.type = CTS_TYPE_CURRENT_SETTINGS; if (path->device->transport == XPORT_ATA) { cts.xport_specific.ata.mode = mode; cts.xport_specific.ata.valid = CTS_ATA_VALID_MODE; } else { cts.xport_specific.sata.mode = mode; cts.xport_specific.sata.valid = CTS_SATA_VALID_MODE; } xpt_action((union ccb *)&cts); /* Fetch current modes from SIM. */ bzero(&cts, sizeof(cts)); xpt_setup_ccb(&cts.ccb_h, path, CAM_PRIORITY_NONE); cts.ccb_h.func_code = XPT_GET_TRAN_SETTINGS; cts.type = CTS_TYPE_CURRENT_SETTINGS; xpt_action((union ccb *)&cts); if (path->device->transport == XPORT_ATA) { if (cts.xport_specific.ata.valid & CTS_ATA_VALID_MODE) mode = cts.xport_specific.ata.mode; } else { if (cts.xport_specific.ata.valid & CTS_SATA_VALID_MODE) mode = cts.xport_specific.sata.mode; } /* If SIM disagree - renegotiate. */ if (mode != wantmode) goto negotiate; /* Remember what transport thinks about DMA. */ if (mode < ATA_DMA) path->device->inq_flags &= ~SID_DMA; else path->device->inq_flags |= SID_DMA; xpt_async(AC_GETDEV_CHANGED, path, NULL); cam_fill_ataio(ataio, 1, probedone, /*flags*/CAM_DIR_NONE, 0, /*data_ptr*/NULL, /*dxfer_len*/0, 30 * 1000); ata_28bit_cmd(ataio, ATA_SETFEATURES, ATA_SF_SETXFER, 0, mode); break; } case PROBE_SETPM: cam_fill_ataio(ataio, 1, probedone, CAM_DIR_NONE, 0, NULL, 0, 30*1000); ata_28bit_cmd(ataio, ATA_SETFEATURES, (softc->caps & CTS_SATA_CAPS_H_PMREQ) ? 0x10 : 0x90, 0, 0x03); break; case PROBE_SETAPST: cam_fill_ataio(ataio, 1, probedone, CAM_DIR_NONE, 0, NULL, 0, 30*1000); ata_28bit_cmd(ataio, ATA_SETFEATURES, (softc->caps & CTS_SATA_CAPS_H_APST) ? 0x10 : 0x90, 0, 0x07); break; case PROBE_SETDMAAA: cam_fill_ataio(ataio, 1, probedone, CAM_DIR_NONE, 0, NULL, 0, 30*1000); ata_28bit_cmd(ataio, ATA_SETFEATURES, (softc->caps & CTS_SATA_CAPS_H_DMAAA) ? 0x10 : 0x90, 0, 0x02); break; case PROBE_SETAN: /* Remember what transport thinks about AEN. */ if (softc->caps & CTS_SATA_CAPS_H_AN) path->device->inq_flags |= SID_AEN; else path->device->inq_flags &= ~SID_AEN; xpt_async(AC_GETDEV_CHANGED, path, NULL); cam_fill_ataio(ataio, 1, probedone, CAM_DIR_NONE, 0, NULL, 0, 30*1000); ata_28bit_cmd(ataio, ATA_SETFEATURES, (softc->caps & CTS_SATA_CAPS_H_AN) ? 0x10 : 0x90, 0, 0x05); break; case PROBE_SET_MULTI: { u_int sectors, bytecount; bytecount = 8192; /* SATA maximum */ /* Fetch user bytecount from SIM. */ bzero(&cts, sizeof(cts)); xpt_setup_ccb(&cts.ccb_h, path, CAM_PRIORITY_NONE); cts.ccb_h.func_code = XPT_GET_TRAN_SETTINGS; cts.type = CTS_TYPE_USER_SETTINGS; xpt_action((union ccb *)&cts); if (path->device->transport == XPORT_ATA) { if (cts.xport_specific.ata.valid & CTS_ATA_VALID_BYTECOUNT) bytecount = cts.xport_specific.ata.bytecount; } else { if (cts.xport_specific.sata.valid & CTS_SATA_VALID_BYTECOUNT) bytecount = cts.xport_specific.sata.bytecount; } /* Honor device capabilities. */ sectors = max(1, min(ident_buf->sectors_intr & 0xff, bytecount / ata_logical_sector_size(ident_buf))); /* Report bytecount to SIM. */ bzero(&cts, sizeof(cts)); xpt_setup_ccb(&cts.ccb_h, path, CAM_PRIORITY_NONE); cts.ccb_h.func_code = XPT_SET_TRAN_SETTINGS; cts.type = CTS_TYPE_CURRENT_SETTINGS; if (path->device->transport == XPORT_ATA) { cts.xport_specific.ata.bytecount = sectors * ata_logical_sector_size(ident_buf); cts.xport_specific.ata.valid = CTS_ATA_VALID_BYTECOUNT; } else { cts.xport_specific.sata.bytecount = sectors * ata_logical_sector_size(ident_buf); cts.xport_specific.sata.valid = CTS_SATA_VALID_BYTECOUNT; } xpt_action((union ccb *)&cts); /* Fetch current bytecount from SIM. */ bzero(&cts, sizeof(cts)); xpt_setup_ccb(&cts.ccb_h, path, CAM_PRIORITY_NONE); cts.ccb_h.func_code = XPT_GET_TRAN_SETTINGS; cts.type = CTS_TYPE_CURRENT_SETTINGS; xpt_action((union ccb *)&cts); if (path->device->transport == XPORT_ATA) { if (cts.xport_specific.ata.valid & CTS_ATA_VALID_BYTECOUNT) bytecount = cts.xport_specific.ata.bytecount; } else { if (cts.xport_specific.sata.valid & CTS_SATA_VALID_BYTECOUNT) bytecount = cts.xport_specific.sata.bytecount; } sectors = bytecount / ata_logical_sector_size(ident_buf); cam_fill_ataio(ataio, 1, probedone, CAM_DIR_NONE, 0, NULL, 0, 30*1000); ata_28bit_cmd(ataio, ATA_SET_MULTI, 0, 0, sectors); break; } case PROBE_INQUIRY: { u_int bytecount; bytecount = 8192; /* SATA maximum */ /* Fetch user bytecount from SIM. */ bzero(&cts, sizeof(cts)); xpt_setup_ccb(&cts.ccb_h, path, CAM_PRIORITY_NONE); cts.ccb_h.func_code = XPT_GET_TRAN_SETTINGS; cts.type = CTS_TYPE_USER_SETTINGS; xpt_action((union ccb *)&cts); if (path->device->transport == XPORT_ATA) { if (cts.xport_specific.ata.valid & CTS_ATA_VALID_BYTECOUNT) bytecount = cts.xport_specific.ata.bytecount; } else { if (cts.xport_specific.sata.valid & CTS_SATA_VALID_BYTECOUNT) bytecount = cts.xport_specific.sata.bytecount; } /* Honor device capabilities. */ bytecount &= ~1; bytecount = max(2, min(65534, bytecount)); if (ident_buf->satacapabilities != 0x0000 && ident_buf->satacapabilities != 0xffff) { bytecount = min(8192, bytecount); } /* Report bytecount to SIM. */ bzero(&cts, sizeof(cts)); xpt_setup_ccb(&cts.ccb_h, path, CAM_PRIORITY_NONE); cts.ccb_h.func_code = XPT_SET_TRAN_SETTINGS; cts.type = CTS_TYPE_CURRENT_SETTINGS; if (path->device->transport == XPORT_ATA) { cts.xport_specific.ata.bytecount = bytecount; cts.xport_specific.ata.valid = CTS_ATA_VALID_BYTECOUNT; } else { cts.xport_specific.sata.bytecount = bytecount; cts.xport_specific.sata.valid = CTS_SATA_VALID_BYTECOUNT; } xpt_action((union ccb *)&cts); /* FALLTHROUGH */ } case PROBE_FULL_INQUIRY: { u_int inquiry_len; struct scsi_inquiry_data *inq_buf = &periph->path->device->inq_data; if (softc->action == PROBE_INQUIRY) inquiry_len = SHORT_INQUIRY_LENGTH; else inquiry_len = SID_ADDITIONAL_LENGTH(inq_buf); /* * Some parallel SCSI devices fail to send an * ignore wide residue message when dealing with * odd length inquiry requests. Round up to be * safe. */ inquiry_len = roundup2(inquiry_len, 2); scsi_inquiry(csio, /*retries*/1, probedone, MSG_SIMPLE_Q_TAG, (u_int8_t *)inq_buf, inquiry_len, /*evpd*/FALSE, /*page_code*/0, SSD_MIN_SIZE, /*timeout*/60 * 1000); break; } case PROBE_PM_PID: cam_fill_ataio(ataio, 1, probedone, /*flags*/CAM_DIR_NONE, 0, /*data_ptr*/NULL, /*dxfer_len*/0, 10 * 1000); ata_pm_read_cmd(ataio, 0, 15); break; case PROBE_PM_PRV: cam_fill_ataio(ataio, 1, probedone, /*flags*/CAM_DIR_NONE, 0, /*data_ptr*/NULL, /*dxfer_len*/0, 10 * 1000); ata_pm_read_cmd(ataio, 1, 15); break; case PROBE_IDENTIFY_SES: cam_fill_ataio(ataio, 1, probedone, /*flags*/CAM_DIR_IN, 0, /*data_ptr*/(u_int8_t *)&softc->ident_data, /*dxfer_len*/sizeof(softc->ident_data), 30 * 1000); ata_28bit_cmd(ataio, ATA_SEP_ATTN, 0xEC, 0x02, sizeof(softc->ident_data) / 4); break; case PROBE_IDENTIFY_SAFTE: cam_fill_ataio(ataio, 1, probedone, /*flags*/CAM_DIR_IN, 0, /*data_ptr*/(u_int8_t *)&softc->ident_data, /*dxfer_len*/sizeof(softc->ident_data), 30 * 1000); ata_28bit_cmd(ataio, ATA_SEP_ATTN, 0xEC, 0x00, sizeof(softc->ident_data) / 4); break; default: panic("probestart: invalid action state 0x%x\n", softc->action); } start_ccb->ccb_h.flags |= CAM_DEV_QFREEZE; xpt_action(start_ccb); } static void proberequestdefaultnegotiation(struct cam_periph *periph) { struct ccb_trans_settings cts; xpt_setup_ccb(&cts.ccb_h, periph->path, CAM_PRIORITY_NONE); cts.ccb_h.func_code = XPT_GET_TRAN_SETTINGS; cts.type = CTS_TYPE_USER_SETTINGS; xpt_action((union ccb *)&cts); if ((cts.ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) return; cts.xport_specific.valid = 0; cts.ccb_h.func_code = XPT_SET_TRAN_SETTINGS; cts.type = CTS_TYPE_CURRENT_SETTINGS; xpt_action((union ccb *)&cts); } static void probedone(struct cam_periph *periph, union ccb *done_ccb) { struct ccb_trans_settings cts; struct ata_params *ident_buf; struct scsi_inquiry_data *inq_buf; probe_softc *softc; struct cam_path *path; cam_status status; u_int32_t priority; u_int caps; int changed = 1, found = 1; static const uint8_t fake_device_id_hdr[8] = {0, SVPD_DEVICE_ID, 0, 12, SVPD_ID_CODESET_BINARY, SVPD_ID_TYPE_NAA, 0, 8}; CAM_DEBUG(done_ccb->ccb_h.path, CAM_DEBUG_TRACE, ("probedone\n")); softc = (probe_softc *)periph->softc; path = done_ccb->ccb_h.path; priority = done_ccb->ccb_h.pinfo.priority; ident_buf = &path->device->ident_data; inq_buf = &path->device->inq_data; if ((done_ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) { if (cam_periph_error(done_ccb, 0, softc->restart ? (SF_NO_RECOVERY | SF_NO_RETRY) : 0, NULL) == ERESTART) { out: /* Drop freeze taken due to CAM_DEV_QFREEZE flag set. */ cam_release_devq(path, 0, 0, 0, FALSE); return; } if ((done_ccb->ccb_h.status & CAM_DEV_QFRZN) != 0) { /* Don't wedge the queue */ xpt_release_devq(path, /*count*/1, /*run_queue*/TRUE); } status = done_ccb->ccb_h.status & CAM_STATUS_MASK; if (softc->restart) { softc->faults++; if ((done_ccb->ccb_h.status & CAM_STATUS_MASK) == CAM_CMD_TIMEOUT) softc->faults += 4; if (softc->faults < 10) goto done; else softc->restart = 0; /* Old PIO2 devices may not support mode setting. */ } else if (softc->action == PROBE_SETMODE && status == CAM_ATA_STATUS_ERROR && ata_max_pmode(ident_buf) <= ATA_PIO2 && (ident_buf->capabilities1 & ATA_SUPPORT_IORDY) == 0) { goto noerror; /* * Some old WD SATA disks report supported and enabled * device-initiated interface power management, but return * ABORT on attempt to disable it. */ } else if (softc->action == PROBE_SETPM && status == CAM_ATA_STATUS_ERROR) { goto noerror; /* * Some HP SATA disks report supported DMA Auto-Activation, * but return ABORT on attempt to enable it. */ } else if (softc->action == PROBE_SETDMAAA && status == CAM_ATA_STATUS_ERROR) { goto noerror; /* * SES and SAF-TE SEPs have different IDENTIFY commands, * but SATA specification doesn't tell how to identify them. * Until better way found, just try another if first fail. */ } else if (softc->action == PROBE_IDENTIFY_SES && status == CAM_ATA_STATUS_ERROR) { PROBE_SET_ACTION(softc, PROBE_IDENTIFY_SAFTE); xpt_release_ccb(done_ccb); xpt_schedule(periph, priority); goto out; } /* * If we get to this point, we got an error status back * from the inquiry and the error status doesn't require * automatically retrying the command. Therefore, the * inquiry failed. If we had inquiry information before * for this device, but this latest inquiry command failed, * the device has probably gone away. If this device isn't * already marked unconfigured, notify the peripheral * drivers that this device is no more. */ device_fail: if ((path->device->flags & CAM_DEV_UNCONFIGURED) == 0) xpt_async(AC_LOST_DEVICE, path, NULL); PROBE_SET_ACTION(softc, PROBE_INVALID); found = 0; goto done; } noerror: if (softc->restart) goto done; switch (softc->action) { case PROBE_RESET: { int sign = (done_ccb->ataio.res.lba_high << 8) + done_ccb->ataio.res.lba_mid; CAM_DEBUG(path, CAM_DEBUG_PROBE, ("SIGNATURE: %04x\n", sign)); if (sign == 0x0000 && done_ccb->ccb_h.target_id != 15) { path->device->protocol = PROTO_ATA; PROBE_SET_ACTION(softc, PROBE_IDENTIFY); } else if (sign == 0x9669 && done_ccb->ccb_h.target_id == 15) { /* Report SIM that PM is present. */ bzero(&cts, sizeof(cts)); xpt_setup_ccb(&cts.ccb_h, path, CAM_PRIORITY_NONE); cts.ccb_h.func_code = XPT_SET_TRAN_SETTINGS; cts.type = CTS_TYPE_CURRENT_SETTINGS; cts.xport_specific.sata.pm_present = 1; cts.xport_specific.sata.valid = CTS_SATA_VALID_PM; xpt_action((union ccb *)&cts); path->device->protocol = PROTO_SATAPM; PROBE_SET_ACTION(softc, PROBE_PM_PID); } else if (sign == 0xc33c && done_ccb->ccb_h.target_id != 15) { path->device->protocol = PROTO_SEMB; PROBE_SET_ACTION(softc, PROBE_IDENTIFY_SES); } else if (sign == 0xeb14 && done_ccb->ccb_h.target_id != 15) { path->device->protocol = PROTO_SCSI; PROBE_SET_ACTION(softc, PROBE_IDENTIFY); } else { if (done_ccb->ccb_h.target_id != 15) { xpt_print(path, "Unexpected signature 0x%04x\n", sign); } goto device_fail; } xpt_release_ccb(done_ccb); xpt_schedule(periph, priority); goto out; } case PROBE_IDENTIFY: { struct ccb_pathinq cpi; int16_t *ptr; ident_buf = &softc->ident_data; for (ptr = (int16_t *)ident_buf; ptr < (int16_t *)ident_buf + sizeof(struct ata_params)/2; ptr++) { *ptr = le16toh(*ptr); } if (strncmp(ident_buf->model, "FX", 2) && strncmp(ident_buf->model, "NEC", 3) && strncmp(ident_buf->model, "Pioneer", 7) && strncmp(ident_buf->model, "SHARP", 5)) { ata_bswap(ident_buf->model, sizeof(ident_buf->model)); ata_bswap(ident_buf->revision, sizeof(ident_buf->revision)); ata_bswap(ident_buf->serial, sizeof(ident_buf->serial)); } ata_btrim(ident_buf->model, sizeof(ident_buf->model)); ata_bpack(ident_buf->model, ident_buf->model, sizeof(ident_buf->model)); ata_btrim(ident_buf->revision, sizeof(ident_buf->revision)); ata_bpack(ident_buf->revision, ident_buf->revision, sizeof(ident_buf->revision)); ata_btrim(ident_buf->serial, sizeof(ident_buf->serial)); ata_bpack(ident_buf->serial, ident_buf->serial, sizeof(ident_buf->serial)); /* Device may need spin-up before IDENTIFY become valid. */ if ((ident_buf->specconf == 0x37c8 || ident_buf->specconf == 0x738c) && ((ident_buf->config & ATA_RESP_INCOMPLETE) || softc->spinup == 0)) { PROBE_SET_ACTION(softc, PROBE_SPINUP); xpt_release_ccb(done_ccb); xpt_schedule(periph, priority); goto out; } ident_buf = &path->device->ident_data; if ((periph->path->device->flags & CAM_DEV_UNCONFIGURED) == 0) { /* Check that it is the same device. */ if (bcmp(softc->ident_data.model, ident_buf->model, sizeof(ident_buf->model)) || bcmp(softc->ident_data.revision, ident_buf->revision, sizeof(ident_buf->revision)) || bcmp(softc->ident_data.serial, ident_buf->serial, sizeof(ident_buf->serial))) { /* Device changed. */ xpt_async(AC_LOST_DEVICE, path, NULL); } else { bcopy(&softc->ident_data, ident_buf, sizeof(struct ata_params)); changed = 0; } } if (changed) { bcopy(&softc->ident_data, ident_buf, sizeof(struct ata_params)); /* Clean up from previous instance of this device */ if (path->device->serial_num != NULL) { free(path->device->serial_num, M_CAMXPT); path->device->serial_num = NULL; path->device->serial_num_len = 0; } if (path->device->device_id != NULL) { free(path->device->device_id, M_CAMXPT); path->device->device_id = NULL; path->device->device_id_len = 0; } path->device->serial_num = (u_int8_t *)malloc((sizeof(ident_buf->serial) + 1), M_CAMXPT, M_NOWAIT); if (path->device->serial_num != NULL) { bcopy(ident_buf->serial, path->device->serial_num, sizeof(ident_buf->serial)); path->device->serial_num[sizeof(ident_buf->serial)] = '\0'; path->device->serial_num_len = strlen(path->device->serial_num); } if (ident_buf->enabled.extension & ATA_SUPPORT_64BITWWN) { path->device->device_id = malloc(16, M_CAMXPT, M_NOWAIT); if (path->device->device_id != NULL) { path->device->device_id_len = 16; bcopy(&fake_device_id_hdr, path->device->device_id, 8); bcopy(ident_buf->wwn, path->device->device_id + 8, 8); ata_bswap(path->device->device_id + 8, 8); } } path->device->flags |= CAM_DEV_IDENTIFY_DATA_VALID; xpt_async(AC_GETDEV_CHANGED, path, NULL); } if (ident_buf->satacapabilities & ATA_SUPPORT_NCQ) { path->device->mintags = 2; path->device->maxtags = ATA_QUEUE_LEN(ident_buf->queue) + 1; } ata_find_quirk(path->device); if (path->device->mintags != 0 && path->bus->sim->max_tagged_dev_openings != 0) { /* Check if the SIM does not want queued commands. */ bzero(&cpi, sizeof(cpi)); xpt_setup_ccb(&cpi.ccb_h, path, CAM_PRIORITY_NONE); cpi.ccb_h.func_code = XPT_PATH_INQ; xpt_action((union ccb *)&cpi); if (cpi.ccb_h.status == CAM_REQ_CMP && (cpi.hba_inquiry & PI_TAG_ABLE)) { /* Report SIM which tags are allowed. */ bzero(&cts, sizeof(cts)); xpt_setup_ccb(&cts.ccb_h, path, CAM_PRIORITY_NONE); cts.ccb_h.func_code = XPT_SET_TRAN_SETTINGS; cts.type = CTS_TYPE_CURRENT_SETTINGS; cts.xport_specific.sata.tags = path->device->maxtags; cts.xport_specific.sata.valid = CTS_SATA_VALID_TAGS; xpt_action((union ccb *)&cts); } } ata_device_transport(path); if (changed) proberequestdefaultnegotiation(periph); PROBE_SET_ACTION(softc, PROBE_SETMODE); xpt_release_ccb(done_ccb); xpt_schedule(periph, priority); goto out; } case PROBE_SPINUP: if (bootverbose) xpt_print(path, "Spin-up done\n"); softc->spinup = 1; PROBE_SET_ACTION(softc, PROBE_IDENTIFY); xpt_release_ccb(done_ccb); xpt_schedule(periph, priority); goto out; case PROBE_SETMODE: /* Set supported bits. */ bzero(&cts, sizeof(cts)); xpt_setup_ccb(&cts.ccb_h, path, CAM_PRIORITY_NONE); cts.ccb_h.func_code = XPT_GET_TRAN_SETTINGS; cts.type = CTS_TYPE_CURRENT_SETTINGS; xpt_action((union ccb *)&cts); if (path->device->transport == XPORT_SATA && cts.xport_specific.sata.valid & CTS_SATA_VALID_CAPS) caps = cts.xport_specific.sata.caps & CTS_SATA_CAPS_H; else if (path->device->transport == XPORT_ATA && cts.xport_specific.ata.valid & CTS_ATA_VALID_CAPS) caps = cts.xport_specific.ata.caps & CTS_ATA_CAPS_H; else caps = 0; if (path->device->transport == XPORT_SATA && ident_buf->satacapabilities != 0xffff) { if (ident_buf->satacapabilities & ATA_SUPPORT_IFPWRMNGTRCV) caps |= CTS_SATA_CAPS_D_PMREQ; if (ident_buf->satacapabilities & ATA_SUPPORT_HAPST) caps |= CTS_SATA_CAPS_D_APST; } /* Mask unwanted bits. */ bzero(&cts, sizeof(cts)); xpt_setup_ccb(&cts.ccb_h, path, CAM_PRIORITY_NONE); cts.ccb_h.func_code = XPT_GET_TRAN_SETTINGS; cts.type = CTS_TYPE_USER_SETTINGS; xpt_action((union ccb *)&cts); if (path->device->transport == XPORT_SATA && cts.xport_specific.sata.valid & CTS_SATA_VALID_CAPS) caps &= cts.xport_specific.sata.caps; else if (path->device->transport == XPORT_ATA && cts.xport_specific.ata.valid & CTS_ATA_VALID_CAPS) caps &= cts.xport_specific.ata.caps; else caps = 0; /* * Remember what transport thinks about 48-bit DMA. If * capability information is not provided or transport is * SATA, we take support for granted. */ if (!(path->device->inq_flags & SID_DMA) || (path->device->transport == XPORT_ATA && (cts.xport_specific.ata.valid & CTS_ATA_VALID_CAPS) && !(caps & CTS_ATA_CAPS_H_DMA48))) path->device->inq_flags &= ~SID_DMA48; else path->device->inq_flags |= SID_DMA48; /* Store result to SIM. */ bzero(&cts, sizeof(cts)); xpt_setup_ccb(&cts.ccb_h, path, CAM_PRIORITY_NONE); cts.ccb_h.func_code = XPT_SET_TRAN_SETTINGS; cts.type = CTS_TYPE_CURRENT_SETTINGS; if (path->device->transport == XPORT_SATA) { cts.xport_specific.sata.caps = caps; cts.xport_specific.sata.valid = CTS_SATA_VALID_CAPS; } else { cts.xport_specific.ata.caps = caps; cts.xport_specific.ata.valid = CTS_ATA_VALID_CAPS; } xpt_action((union ccb *)&cts); softc->caps = caps; if (path->device->transport != XPORT_SATA) goto notsata; if ((ident_buf->satasupport & ATA_SUPPORT_IFPWRMNGT) && (!(softc->caps & CTS_SATA_CAPS_H_PMREQ)) != (!(ident_buf->sataenabled & ATA_SUPPORT_IFPWRMNGT))) { PROBE_SET_ACTION(softc, PROBE_SETPM); xpt_release_ccb(done_ccb); xpt_schedule(periph, priority); goto out; } /* FALLTHROUGH */ case PROBE_SETPM: if (ident_buf->satacapabilities != 0xffff && (ident_buf->satacapabilities & ATA_SUPPORT_DAPST) && (!(softc->caps & CTS_SATA_CAPS_H_APST)) != (!(ident_buf->sataenabled & ATA_ENABLED_DAPST))) { PROBE_SET_ACTION(softc, PROBE_SETAPST); xpt_release_ccb(done_ccb); xpt_schedule(periph, priority); goto out; } /* FALLTHROUGH */ case PROBE_SETAPST: if ((ident_buf->satasupport & ATA_SUPPORT_AUTOACTIVATE) && (!(softc->caps & CTS_SATA_CAPS_H_DMAAA)) != (!(ident_buf->sataenabled & ATA_SUPPORT_AUTOACTIVATE))) { PROBE_SET_ACTION(softc, PROBE_SETDMAAA); xpt_release_ccb(done_ccb); xpt_schedule(periph, priority); goto out; } /* FALLTHROUGH */ case PROBE_SETDMAAA: if (path->device->protocol != PROTO_ATA && (ident_buf->satasupport & ATA_SUPPORT_ASYNCNOTIF) && (!(softc->caps & CTS_SATA_CAPS_H_AN)) != (!(ident_buf->sataenabled & ATA_SUPPORT_ASYNCNOTIF))) { PROBE_SET_ACTION(softc, PROBE_SETAN); xpt_release_ccb(done_ccb); xpt_schedule(periph, priority); goto out; } /* FALLTHROUGH */ case PROBE_SETAN: notsata: if (path->device->protocol == PROTO_ATA) { PROBE_SET_ACTION(softc, PROBE_SET_MULTI); } else { PROBE_SET_ACTION(softc, PROBE_INQUIRY); } xpt_release_ccb(done_ccb); xpt_schedule(periph, priority); goto out; case PROBE_SET_MULTI: if (periph->path->device->flags & CAM_DEV_UNCONFIGURED) { path->device->flags &= ~CAM_DEV_UNCONFIGURED; xpt_acquire_device(path->device); done_ccb->ccb_h.func_code = XPT_GDEV_TYPE; xpt_action(done_ccb); xpt_async(AC_FOUND_DEVICE, path, done_ccb); } PROBE_SET_ACTION(softc, PROBE_DONE); break; case PROBE_INQUIRY: case PROBE_FULL_INQUIRY: { u_int8_t periph_qual, len; path->device->flags |= CAM_DEV_INQUIRY_DATA_VALID; periph_qual = SID_QUAL(inq_buf); - if (periph_qual != SID_QUAL_LU_CONNECTED) + if (periph_qual != SID_QUAL_LU_CONNECTED && + periph_qual != SID_QUAL_LU_OFFLINE) break; /* * We conservatively request only * SHORT_INQUIRY_LEN bytes of inquiry * information during our first try * at sending an INQUIRY. If the device * has more information to give, * perform a second request specifying * the amount of information the device * is willing to give. */ len = inq_buf->additional_length + offsetof(struct scsi_inquiry_data, additional_length) + 1; if (softc->action == PROBE_INQUIRY && len > SHORT_INQUIRY_LENGTH) { PROBE_SET_ACTION(softc, PROBE_FULL_INQUIRY); xpt_release_ccb(done_ccb); xpt_schedule(periph, priority); goto out; } ata_device_transport(path); if (periph->path->device->flags & CAM_DEV_UNCONFIGURED) { path->device->flags &= ~CAM_DEV_UNCONFIGURED; xpt_acquire_device(path->device); done_ccb->ccb_h.func_code = XPT_GDEV_TYPE; xpt_action(done_ccb); xpt_async(AC_FOUND_DEVICE, path, done_ccb); } PROBE_SET_ACTION(softc, PROBE_DONE); break; } case PROBE_PM_PID: if ((path->device->flags & CAM_DEV_IDENTIFY_DATA_VALID) == 0) bzero(ident_buf, sizeof(*ident_buf)); softc->pm_pid = (done_ccb->ataio.res.lba_high << 24) + (done_ccb->ataio.res.lba_mid << 16) + (done_ccb->ataio.res.lba_low << 8) + done_ccb->ataio.res.sector_count; ((uint32_t *)ident_buf)[0] = softc->pm_pid; snprintf(ident_buf->model, sizeof(ident_buf->model), "Port Multiplier %08x", softc->pm_pid); PROBE_SET_ACTION(softc, PROBE_PM_PRV); xpt_release_ccb(done_ccb); xpt_schedule(periph, priority); goto out; case PROBE_PM_PRV: softc->pm_prv = (done_ccb->ataio.res.lba_high << 24) + (done_ccb->ataio.res.lba_mid << 16) + (done_ccb->ataio.res.lba_low << 8) + done_ccb->ataio.res.sector_count; ((uint32_t *)ident_buf)[1] = softc->pm_prv; snprintf(ident_buf->revision, sizeof(ident_buf->revision), "%04x", softc->pm_prv); path->device->flags |= CAM_DEV_IDENTIFY_DATA_VALID; ata_device_transport(path); if (periph->path->device->flags & CAM_DEV_UNCONFIGURED) proberequestdefaultnegotiation(periph); /* Set supported bits. */ bzero(&cts, sizeof(cts)); xpt_setup_ccb(&cts.ccb_h, path, CAM_PRIORITY_NONE); cts.ccb_h.func_code = XPT_GET_TRAN_SETTINGS; cts.type = CTS_TYPE_CURRENT_SETTINGS; xpt_action((union ccb *)&cts); if (cts.xport_specific.sata.valid & CTS_SATA_VALID_CAPS) caps = cts.xport_specific.sata.caps & CTS_SATA_CAPS_H; else caps = 0; /* All PMPs must support PM requests. */ caps |= CTS_SATA_CAPS_D_PMREQ; /* Mask unwanted bits. */ bzero(&cts, sizeof(cts)); xpt_setup_ccb(&cts.ccb_h, path, CAM_PRIORITY_NONE); cts.ccb_h.func_code = XPT_GET_TRAN_SETTINGS; cts.type = CTS_TYPE_USER_SETTINGS; xpt_action((union ccb *)&cts); if (cts.xport_specific.sata.valid & CTS_SATA_VALID_CAPS) caps &= cts.xport_specific.sata.caps; else caps = 0; /* Remember what transport thinks about AEN. */ if ((caps & CTS_SATA_CAPS_H_AN) && path->device->protocol != PROTO_ATA) path->device->inq_flags |= SID_AEN; else path->device->inq_flags &= ~SID_AEN; /* Store result to SIM. */ bzero(&cts, sizeof(cts)); xpt_setup_ccb(&cts.ccb_h, path, CAM_PRIORITY_NONE); cts.ccb_h.func_code = XPT_SET_TRAN_SETTINGS; cts.type = CTS_TYPE_CURRENT_SETTINGS; cts.xport_specific.sata.caps = caps; cts.xport_specific.sata.valid = CTS_SATA_VALID_CAPS; xpt_action((union ccb *)&cts); softc->caps = caps; xpt_async(AC_GETDEV_CHANGED, path, NULL); if (periph->path->device->flags & CAM_DEV_UNCONFIGURED) { path->device->flags &= ~CAM_DEV_UNCONFIGURED; xpt_acquire_device(path->device); done_ccb->ccb_h.func_code = XPT_GDEV_TYPE; xpt_action(done_ccb); xpt_async(AC_FOUND_DEVICE, path, done_ccb); } else { done_ccb->ccb_h.func_code = XPT_GDEV_TYPE; xpt_action(done_ccb); xpt_async(AC_SCSI_AEN, path, done_ccb); } PROBE_SET_ACTION(softc, PROBE_DONE); break; case PROBE_IDENTIFY_SES: case PROBE_IDENTIFY_SAFTE: if ((periph->path->device->flags & CAM_DEV_UNCONFIGURED) == 0) { /* Check that it is the same device. */ if (bcmp(&softc->ident_data, ident_buf, 53)) { /* Device changed. */ xpt_async(AC_LOST_DEVICE, path, NULL); } else { bcopy(&softc->ident_data, ident_buf, sizeof(struct ata_params)); changed = 0; } } if (changed) { bcopy(&softc->ident_data, ident_buf, sizeof(struct ata_params)); /* Clean up from previous instance of this device */ if (path->device->device_id != NULL) { free(path->device->device_id, M_CAMXPT); path->device->device_id = NULL; path->device->device_id_len = 0; } path->device->device_id = malloc(16, M_CAMXPT, M_NOWAIT); if (path->device->device_id != NULL) { path->device->device_id_len = 16; bcopy(&fake_device_id_hdr, path->device->device_id, 8); bcopy(((uint8_t*)ident_buf) + 2, path->device->device_id + 8, 8); } path->device->flags |= CAM_DEV_IDENTIFY_DATA_VALID; } ata_device_transport(path); if (changed) proberequestdefaultnegotiation(periph); if (periph->path->device->flags & CAM_DEV_UNCONFIGURED) { path->device->flags &= ~CAM_DEV_UNCONFIGURED; xpt_acquire_device(path->device); done_ccb->ccb_h.func_code = XPT_GDEV_TYPE; xpt_action(done_ccb); xpt_async(AC_FOUND_DEVICE, path, done_ccb); } PROBE_SET_ACTION(softc, PROBE_DONE); break; default: panic("probedone: invalid action state 0x%x\n", softc->action); } done: if (softc->restart) { softc->restart = 0; xpt_release_ccb(done_ccb); probeschedule(periph); goto out; } xpt_release_ccb(done_ccb); CAM_DEBUG(periph->path, CAM_DEBUG_PROBE, ("Probe completed\n")); while ((done_ccb = (union ccb *)TAILQ_FIRST(&softc->request_ccbs))) { TAILQ_REMOVE(&softc->request_ccbs, &done_ccb->ccb_h, periph_links.tqe); done_ccb->ccb_h.status = found ? CAM_REQ_CMP : CAM_REQ_CMP_ERR; xpt_done(done_ccb); } /* Drop freeze taken due to CAM_DEV_QFREEZE flag set. */ cam_release_devq(path, 0, 0, 0, FALSE); cam_periph_invalidate(periph); cam_periph_release_locked(periph); } static void probecleanup(struct cam_periph *periph) { free(periph->softc, M_CAMXPT); } static void ata_find_quirk(struct cam_ed *device) { struct ata_quirk_entry *quirk; caddr_t match; match = cam_quirkmatch((caddr_t)&device->ident_data, (caddr_t)ata_quirk_table, ata_quirk_table_size, sizeof(*ata_quirk_table), ata_identify_match); if (match == NULL) panic("xpt_find_quirk: device didn't match wildcard entry!!"); quirk = (struct ata_quirk_entry *)match; device->quirk = quirk; if (quirk->quirks & CAM_QUIRK_MAXTAGS) { device->mintags = quirk->mintags; device->maxtags = quirk->maxtags; } } typedef struct { union ccb *request_ccb; struct ccb_pathinq *cpi; int counter; } ata_scan_bus_info; /* * To start a scan, request_ccb is an XPT_SCAN_BUS ccb. * As the scan progresses, xpt_scan_bus is used as the * callback on completion function. */ static void ata_scan_bus(struct cam_periph *periph, union ccb *request_ccb) { struct cam_path *path; ata_scan_bus_info *scan_info; union ccb *work_ccb, *reset_ccb; struct mtx *mtx; cam_status status; CAM_DEBUG(request_ccb->ccb_h.path, CAM_DEBUG_TRACE, ("xpt_scan_bus\n")); switch (request_ccb->ccb_h.func_code) { case XPT_SCAN_BUS: case XPT_SCAN_TGT: /* Find out the characteristics of the bus */ work_ccb = xpt_alloc_ccb_nowait(); if (work_ccb == NULL) { request_ccb->ccb_h.status = CAM_RESRC_UNAVAIL; xpt_done(request_ccb); return; } xpt_setup_ccb(&work_ccb->ccb_h, request_ccb->ccb_h.path, request_ccb->ccb_h.pinfo.priority); work_ccb->ccb_h.func_code = XPT_PATH_INQ; xpt_action(work_ccb); if (work_ccb->ccb_h.status != CAM_REQ_CMP) { request_ccb->ccb_h.status = work_ccb->ccb_h.status; xpt_free_ccb(work_ccb); xpt_done(request_ccb); return; } /* We may need to reset bus first, if we haven't done it yet. */ if ((work_ccb->cpi.hba_inquiry & (PI_WIDE_32|PI_WIDE_16|PI_SDTR_ABLE)) && !(work_ccb->cpi.hba_misc & PIM_NOBUSRESET) && !timevalisset(&request_ccb->ccb_h.path->bus->last_reset)) { reset_ccb = xpt_alloc_ccb_nowait(); if (reset_ccb == NULL) { request_ccb->ccb_h.status = CAM_RESRC_UNAVAIL; xpt_free_ccb(work_ccb); xpt_done(request_ccb); return; } xpt_setup_ccb(&reset_ccb->ccb_h, request_ccb->ccb_h.path, CAM_PRIORITY_NONE); reset_ccb->ccb_h.func_code = XPT_RESET_BUS; xpt_action(reset_ccb); if (reset_ccb->ccb_h.status != CAM_REQ_CMP) { request_ccb->ccb_h.status = reset_ccb->ccb_h.status; xpt_free_ccb(reset_ccb); xpt_free_ccb(work_ccb); xpt_done(request_ccb); return; } xpt_free_ccb(reset_ccb); } /* Save some state for use while we probe for devices */ scan_info = (ata_scan_bus_info *) malloc(sizeof(ata_scan_bus_info), M_CAMXPT, M_NOWAIT); if (scan_info == NULL) { request_ccb->ccb_h.status = CAM_RESRC_UNAVAIL; xpt_free_ccb(work_ccb); xpt_done(request_ccb); return; } scan_info->request_ccb = request_ccb; scan_info->cpi = &work_ccb->cpi; /* If PM supported, probe it first. */ if (scan_info->cpi->hba_inquiry & PI_SATAPM) scan_info->counter = scan_info->cpi->max_target; else scan_info->counter = 0; work_ccb = xpt_alloc_ccb_nowait(); if (work_ccb == NULL) { free(scan_info, M_CAMXPT); request_ccb->ccb_h.status = CAM_RESRC_UNAVAIL; xpt_done(request_ccb); break; } mtx = xpt_path_mtx(scan_info->request_ccb->ccb_h.path); goto scan_next; case XPT_SCAN_LUN: work_ccb = request_ccb; /* Reuse the same CCB to query if a device was really found */ scan_info = (ata_scan_bus_info *)work_ccb->ccb_h.ppriv_ptr0; mtx = xpt_path_mtx(scan_info->request_ccb->ccb_h.path); mtx_lock(mtx); /* If there is PMP... */ if ((scan_info->cpi->hba_inquiry & PI_SATAPM) && (scan_info->counter == scan_info->cpi->max_target)) { if (work_ccb->ccb_h.status == CAM_REQ_CMP) { /* everything else will be probed by it */ /* Free the current request path- we're done with it. */ xpt_free_path(work_ccb->ccb_h.path); goto done; } else { struct ccb_trans_settings cts; /* Report SIM that PM is absent. */ bzero(&cts, sizeof(cts)); xpt_setup_ccb(&cts.ccb_h, work_ccb->ccb_h.path, CAM_PRIORITY_NONE); cts.ccb_h.func_code = XPT_SET_TRAN_SETTINGS; cts.type = CTS_TYPE_CURRENT_SETTINGS; cts.xport_specific.sata.pm_present = 0; cts.xport_specific.sata.valid = CTS_SATA_VALID_PM; xpt_action((union ccb *)&cts); } } /* Free the current request path- we're done with it. */ xpt_free_path(work_ccb->ccb_h.path); if (scan_info->counter == ((scan_info->cpi->hba_inquiry & PI_SATAPM) ? 0 : scan_info->cpi->max_target)) { done: mtx_unlock(mtx); xpt_free_ccb(work_ccb); xpt_free_ccb((union ccb *)scan_info->cpi); request_ccb = scan_info->request_ccb; free(scan_info, M_CAMXPT); request_ccb->ccb_h.status = CAM_REQ_CMP; xpt_done(request_ccb); break; } /* Take next device. Wrap from max (PMP) to 0. */ scan_info->counter = (scan_info->counter + 1 ) % (scan_info->cpi->max_target + 1); scan_next: status = xpt_create_path(&path, NULL, scan_info->request_ccb->ccb_h.path_id, scan_info->counter, 0); if (status != CAM_REQ_CMP) { if (request_ccb->ccb_h.func_code == XPT_SCAN_LUN) mtx_unlock(mtx); printf("xpt_scan_bus: xpt_create_path failed" " with status %#x, bus scan halted\n", status); xpt_free_ccb(work_ccb); xpt_free_ccb((union ccb *)scan_info->cpi); request_ccb = scan_info->request_ccb; free(scan_info, M_CAMXPT); request_ccb->ccb_h.status = status; xpt_done(request_ccb); break; } xpt_setup_ccb(&work_ccb->ccb_h, path, scan_info->request_ccb->ccb_h.pinfo.priority); work_ccb->ccb_h.func_code = XPT_SCAN_LUN; work_ccb->ccb_h.cbfcnp = ata_scan_bus; work_ccb->ccb_h.flags |= CAM_UNLOCKED; work_ccb->ccb_h.ppriv_ptr0 = scan_info; work_ccb->crcn.flags = scan_info->request_ccb->crcn.flags; mtx_unlock(mtx); if (request_ccb->ccb_h.func_code == XPT_SCAN_LUN) mtx = NULL; xpt_action(work_ccb); if (mtx != NULL) mtx_lock(mtx); break; default: break; } } static void ata_scan_lun(struct cam_periph *periph, struct cam_path *path, cam_flags flags, union ccb *request_ccb) { struct ccb_pathinq cpi; cam_status status; struct cam_path *new_path; struct cam_periph *old_periph; int lock; CAM_DEBUG(path, CAM_DEBUG_TRACE, ("xpt_scan_lun\n")); xpt_setup_ccb(&cpi.ccb_h, path, CAM_PRIORITY_NONE); cpi.ccb_h.func_code = XPT_PATH_INQ; xpt_action((union ccb *)&cpi); if (cpi.ccb_h.status != CAM_REQ_CMP) { if (request_ccb != NULL) { request_ccb->ccb_h.status = cpi.ccb_h.status; xpt_done(request_ccb); } return; } if (request_ccb == NULL) { request_ccb = xpt_alloc_ccb_nowait(); if (request_ccb == NULL) { xpt_print(path, "xpt_scan_lun: can't allocate CCB, " "can't continue\n"); return; } status = xpt_create_path(&new_path, NULL, path->bus->path_id, path->target->target_id, path->device->lun_id); if (status != CAM_REQ_CMP) { xpt_print(path, "xpt_scan_lun: can't create path, " "can't continue\n"); xpt_free_ccb(request_ccb); return; } xpt_setup_ccb(&request_ccb->ccb_h, new_path, CAM_PRIORITY_XPT); request_ccb->ccb_h.cbfcnp = xptscandone; request_ccb->ccb_h.flags |= CAM_UNLOCKED; request_ccb->ccb_h.func_code = XPT_SCAN_LUN; request_ccb->crcn.flags = flags; } lock = (xpt_path_owned(path) == 0); if (lock) xpt_path_lock(path); if ((old_periph = cam_periph_find(path, "aprobe")) != NULL) { if ((old_periph->flags & CAM_PERIPH_INVALID) == 0) { probe_softc *softc; softc = (probe_softc *)old_periph->softc; TAILQ_INSERT_TAIL(&softc->request_ccbs, &request_ccb->ccb_h, periph_links.tqe); softc->restart = 1; } else { request_ccb->ccb_h.status = CAM_REQ_CMP_ERR; xpt_done(request_ccb); } } else { status = cam_periph_alloc(proberegister, NULL, probecleanup, probestart, "aprobe", CAM_PERIPH_BIO, request_ccb->ccb_h.path, NULL, 0, request_ccb); if (status != CAM_REQ_CMP) { xpt_print(path, "xpt_scan_lun: cam_alloc_periph " "returned an error, can't continue probe\n"); request_ccb->ccb_h.status = status; xpt_done(request_ccb); } } if (lock) xpt_path_unlock(path); } static void xptscandone(struct cam_periph *periph, union ccb *done_ccb) { xpt_free_path(done_ccb->ccb_h.path); xpt_free_ccb(done_ccb); } static struct cam_ed * ata_alloc_device(struct cam_eb *bus, struct cam_et *target, lun_id_t lun_id) { struct ata_quirk_entry *quirk; struct cam_ed *device; device = xpt_alloc_device(bus, target, lun_id); if (device == NULL) return (NULL); /* * Take the default quirk entry until we have inquiry * data and can determine a better quirk to use. */ quirk = &ata_quirk_table[ata_quirk_table_size - 1]; device->quirk = (void *)quirk; device->mintags = 0; device->maxtags = 0; bzero(&device->inq_data, sizeof(device->inq_data)); device->inq_flags = 0; device->queue_flags = 0; device->serial_num = NULL; device->serial_num_len = 0; return (device); } static void ata_device_transport(struct cam_path *path) { struct ccb_pathinq cpi; struct ccb_trans_settings cts; struct scsi_inquiry_data *inq_buf = NULL; struct ata_params *ident_buf = NULL; /* Get transport information from the SIM */ xpt_setup_ccb(&cpi.ccb_h, path, CAM_PRIORITY_NONE); cpi.ccb_h.func_code = XPT_PATH_INQ; xpt_action((union ccb *)&cpi); path->device->transport = cpi.transport; if ((path->device->flags & CAM_DEV_INQUIRY_DATA_VALID) != 0) inq_buf = &path->device->inq_data; if ((path->device->flags & CAM_DEV_IDENTIFY_DATA_VALID) != 0) ident_buf = &path->device->ident_data; if (path->device->protocol == PROTO_ATA) { path->device->protocol_version = ident_buf ? ata_version(ident_buf->version_major) : cpi.protocol_version; } else if (path->device->protocol == PROTO_SCSI) { path->device->protocol_version = inq_buf ? SID_ANSI_REV(inq_buf) : cpi.protocol_version; } path->device->transport_version = ident_buf ? ata_version(ident_buf->version_major) : cpi.transport_version; /* Tell the controller what we think */ xpt_setup_ccb(&cts.ccb_h, path, CAM_PRIORITY_NONE); cts.ccb_h.func_code = XPT_SET_TRAN_SETTINGS; cts.type = CTS_TYPE_CURRENT_SETTINGS; cts.transport = path->device->transport; cts.transport_version = path->device->transport_version; cts.protocol = path->device->protocol; cts.protocol_version = path->device->protocol_version; cts.proto_specific.valid = 0; if (ident_buf) { if (path->device->transport == XPORT_ATA) { cts.xport_specific.ata.atapi = (ident_buf->config == ATA_PROTO_CFA) ? 0 : ((ident_buf->config & ATA_PROTO_MASK) == ATA_PROTO_ATAPI_16) ? 16 : ((ident_buf->config & ATA_PROTO_MASK) == ATA_PROTO_ATAPI_12) ? 12 : 0; cts.xport_specific.ata.valid = CTS_ATA_VALID_ATAPI; } else { cts.xport_specific.sata.atapi = (ident_buf->config == ATA_PROTO_CFA) ? 0 : ((ident_buf->config & ATA_PROTO_MASK) == ATA_PROTO_ATAPI_16) ? 16 : ((ident_buf->config & ATA_PROTO_MASK) == ATA_PROTO_ATAPI_12) ? 12 : 0; cts.xport_specific.sata.valid = CTS_SATA_VALID_ATAPI; } } else cts.xport_specific.valid = 0; xpt_action((union ccb *)&cts); } static void ata_dev_advinfo(union ccb *start_ccb) { struct cam_ed *device; struct ccb_dev_advinfo *cdai; off_t amt; start_ccb->ccb_h.status = CAM_REQ_INVALID; device = start_ccb->ccb_h.path->device; cdai = &start_ccb->cdai; switch(cdai->buftype) { case CDAI_TYPE_SCSI_DEVID: if (cdai->flags & CDAI_FLAG_STORE) return; cdai->provsiz = device->device_id_len; if (device->device_id_len == 0) break; amt = device->device_id_len; if (cdai->provsiz > cdai->bufsiz) amt = cdai->bufsiz; memcpy(cdai->buf, device->device_id, amt); break; case CDAI_TYPE_SERIAL_NUM: if (cdai->flags & CDAI_FLAG_STORE) return; cdai->provsiz = device->serial_num_len; if (device->serial_num_len == 0) break; amt = device->serial_num_len; if (cdai->provsiz > cdai->bufsiz) amt = cdai->bufsiz; memcpy(cdai->buf, device->serial_num, amt); break; case CDAI_TYPE_PHYS_PATH: if (cdai->flags & CDAI_FLAG_STORE) { if (device->physpath != NULL) free(device->physpath, M_CAMXPT); device->physpath_len = cdai->bufsiz; /* Clear existing buffer if zero length */ if (cdai->bufsiz == 0) break; device->physpath = malloc(cdai->bufsiz, M_CAMXPT, M_NOWAIT); if (device->physpath == NULL) { start_ccb->ccb_h.status = CAM_REQ_ABORTED; return; } memcpy(device->physpath, cdai->buf, cdai->bufsiz); } else { cdai->provsiz = device->physpath_len; if (device->physpath_len == 0) break; amt = device->physpath_len; if (cdai->provsiz > cdai->bufsiz) amt = cdai->bufsiz; memcpy(cdai->buf, device->physpath, amt); } break; default: return; } start_ccb->ccb_h.status = CAM_REQ_CMP; if (cdai->flags & CDAI_FLAG_STORE) { xpt_async(AC_ADVINFO_CHANGED, start_ccb->ccb_h.path, (void *)(uintptr_t)cdai->buftype); } } static void ata_action(union ccb *start_ccb) { switch (start_ccb->ccb_h.func_code) { case XPT_SET_TRAN_SETTINGS: { ata_set_transfer_settings(&start_ccb->cts, start_ccb->ccb_h.path, /*async_update*/FALSE); break; } case XPT_SCAN_BUS: case XPT_SCAN_TGT: ata_scan_bus(start_ccb->ccb_h.path->periph, start_ccb); break; case XPT_SCAN_LUN: ata_scan_lun(start_ccb->ccb_h.path->periph, start_ccb->ccb_h.path, start_ccb->crcn.flags, start_ccb); break; case XPT_GET_TRAN_SETTINGS: { ata_get_transfer_settings(&start_ccb->cts); break; } case XPT_SCSI_IO: { struct cam_ed *device; u_int maxlen = 0; device = start_ccb->ccb_h.path->device; if (device->protocol == PROTO_SCSI && (device->flags & CAM_DEV_IDENTIFY_DATA_VALID)) { uint16_t p = device->ident_data.config & ATA_PROTO_MASK; maxlen = (device->ident_data.config == ATA_PROTO_CFA) ? 0 : (p == ATA_PROTO_ATAPI_16) ? 16 : (p == ATA_PROTO_ATAPI_12) ? 12 : 0; } if (start_ccb->csio.cdb_len > maxlen) { start_ccb->ccb_h.status = CAM_REQ_INVALID; xpt_done(start_ccb); break; } xpt_action_default(start_ccb); break; } case XPT_DEV_ADVINFO: { ata_dev_advinfo(start_ccb); break; } default: xpt_action_default(start_ccb); break; } } static void ata_get_transfer_settings(struct ccb_trans_settings *cts) { struct ccb_trans_settings_ata *ata; struct ccb_trans_settings_scsi *scsi; struct cam_ed *device; device = cts->ccb_h.path->device; xpt_action_default((union ccb *)cts); if (cts->protocol == PROTO_UNKNOWN || cts->protocol == PROTO_UNSPECIFIED) { cts->protocol = device->protocol; cts->protocol_version = device->protocol_version; } if (cts->protocol == PROTO_ATA) { ata = &cts->proto_specific.ata; if ((ata->valid & CTS_ATA_VALID_TQ) == 0) { ata->valid |= CTS_ATA_VALID_TQ; if (cts->type == CTS_TYPE_USER_SETTINGS || (device->flags & CAM_DEV_TAG_AFTER_COUNT) != 0 || (device->inq_flags & SID_CmdQue) != 0) ata->flags |= CTS_ATA_FLAGS_TAG_ENB; } } if (cts->protocol == PROTO_SCSI) { scsi = &cts->proto_specific.scsi; if ((scsi->valid & CTS_SCSI_VALID_TQ) == 0) { scsi->valid |= CTS_SCSI_VALID_TQ; if (cts->type == CTS_TYPE_USER_SETTINGS || (device->flags & CAM_DEV_TAG_AFTER_COUNT) != 0 || (device->inq_flags & SID_CmdQue) != 0) scsi->flags |= CTS_SCSI_FLAGS_TAG_ENB; } } if (cts->transport == XPORT_UNKNOWN || cts->transport == XPORT_UNSPECIFIED) { cts->transport = device->transport; cts->transport_version = device->transport_version; } } static void ata_set_transfer_settings(struct ccb_trans_settings *cts, struct cam_path *path, int async_update) { struct ccb_pathinq cpi; struct ccb_trans_settings_ata *ata; struct ccb_trans_settings_scsi *scsi; struct ata_params *ident_data; struct scsi_inquiry_data *inq_data; struct cam_ed *device; if (path == NULL || (device = path->device) == NULL) { cts->ccb_h.status = CAM_PATH_INVALID; xpt_done((union ccb *)cts); return; } if (cts->protocol == PROTO_UNKNOWN || cts->protocol == PROTO_UNSPECIFIED) { cts->protocol = device->protocol; cts->protocol_version = device->protocol_version; } if (cts->protocol_version == PROTO_VERSION_UNKNOWN || cts->protocol_version == PROTO_VERSION_UNSPECIFIED) cts->protocol_version = device->protocol_version; if (cts->protocol != device->protocol) { xpt_print(path, "Uninitialized Protocol %x:%x?\n", cts->protocol, device->protocol); cts->protocol = device->protocol; } if (cts->protocol_version > device->protocol_version) { if (bootverbose) { xpt_print(path, "Down reving Protocol " "Version from %d to %d?\n", cts->protocol_version, device->protocol_version); } cts->protocol_version = device->protocol_version; } if (cts->transport == XPORT_UNKNOWN || cts->transport == XPORT_UNSPECIFIED) { cts->transport = device->transport; cts->transport_version = device->transport_version; } if (cts->transport_version == XPORT_VERSION_UNKNOWN || cts->transport_version == XPORT_VERSION_UNSPECIFIED) cts->transport_version = device->transport_version; if (cts->transport != device->transport) { xpt_print(path, "Uninitialized Transport %x:%x?\n", cts->transport, device->transport); cts->transport = device->transport; } if (cts->transport_version > device->transport_version) { if (bootverbose) { xpt_print(path, "Down reving Transport " "Version from %d to %d?\n", cts->transport_version, device->transport_version); } cts->transport_version = device->transport_version; } ident_data = &device->ident_data; inq_data = &device->inq_data; if (cts->protocol == PROTO_ATA) ata = &cts->proto_specific.ata; else ata = NULL; if (cts->protocol == PROTO_SCSI) scsi = &cts->proto_specific.scsi; else scsi = NULL; xpt_setup_ccb(&cpi.ccb_h, path, CAM_PRIORITY_NONE); cpi.ccb_h.func_code = XPT_PATH_INQ; xpt_action((union ccb *)&cpi); /* Sanity checking */ if ((cpi.hba_inquiry & PI_TAG_ABLE) == 0 || (ata && (ident_data->satacapabilities & ATA_SUPPORT_NCQ) == 0) || (scsi && (INQ_DATA_TQ_ENABLED(inq_data)) == 0) || (device->queue_flags & SCP_QUEUE_DQUE) != 0 || (device->mintags == 0)) { /* * Can't tag on hardware that doesn't support tags, * doesn't have it enabled, or has broken tag support. */ if (ata) ata->flags &= ~CTS_ATA_FLAGS_TAG_ENB; if (scsi) scsi->flags &= ~CTS_SCSI_FLAGS_TAG_ENB; } /* Start/stop tags use. */ if (cts->type == CTS_TYPE_CURRENT_SETTINGS && ((ata && (ata->valid & CTS_ATA_VALID_TQ) != 0) || (scsi && (scsi->valid & CTS_SCSI_VALID_TQ) != 0))) { int nowt, newt = 0; nowt = ((device->flags & CAM_DEV_TAG_AFTER_COUNT) != 0 || (device->inq_flags & SID_CmdQue) != 0); if (ata) newt = (ata->flags & CTS_ATA_FLAGS_TAG_ENB) != 0; if (scsi) newt = (scsi->flags & CTS_SCSI_FLAGS_TAG_ENB) != 0; if (newt && !nowt) { /* * Delay change to use tags until after a * few commands have gone to this device so * the controller has time to perform transfer * negotiations without tagged messages getting * in the way. */ device->tag_delay_count = CAM_TAG_DELAY_COUNT; device->flags |= CAM_DEV_TAG_AFTER_COUNT; } else if (nowt && !newt) xpt_stop_tags(path); } if (async_update == FALSE) xpt_action_default((union ccb *)cts); } /* * Handle any per-device event notifications that require action by the XPT. */ static void ata_dev_async(u_int32_t async_code, struct cam_eb *bus, struct cam_et *target, struct cam_ed *device, void *async_arg) { cam_status status; struct cam_path newpath; /* * We only need to handle events for real devices. */ if (target->target_id == CAM_TARGET_WILDCARD || device->lun_id == CAM_LUN_WILDCARD) return; /* * We need our own path with wildcards expanded to * handle certain types of events. */ if ((async_code == AC_SENT_BDR) || (async_code == AC_BUS_RESET) || (async_code == AC_INQ_CHANGED)) status = xpt_compile_path(&newpath, NULL, bus->path_id, target->target_id, device->lun_id); else status = CAM_REQ_CMP_ERR; if (status == CAM_REQ_CMP) { if (async_code == AC_INQ_CHANGED) { /* * We've sent a start unit command, or * something similar to a device that * may have caused its inquiry data to * change. So we re-scan the device to * refresh the inquiry data for it. */ ata_scan_lun(newpath.periph, &newpath, CAM_EXPECT_INQ_CHANGE, NULL); } else { /* We need to reinitialize device after reset. */ ata_scan_lun(newpath.periph, &newpath, 0, NULL); } xpt_release_path(&newpath); } else if (async_code == AC_LOST_DEVICE && (device->flags & CAM_DEV_UNCONFIGURED) == 0) { device->flags |= CAM_DEV_UNCONFIGURED; xpt_release_device(device); } else if (async_code == AC_TRANSFER_NEG) { struct ccb_trans_settings *settings; struct cam_path path; settings = (struct ccb_trans_settings *)async_arg; xpt_compile_path(&path, NULL, bus->path_id, target->target_id, device->lun_id); ata_set_transfer_settings(settings, &path, /*async_update*/TRUE); xpt_release_path(&path); } } static void ata_announce_periph(struct cam_periph *periph) { struct ccb_pathinq cpi; struct ccb_trans_settings cts; struct cam_path *path = periph->path; u_int speed; u_int mb; cam_periph_assert(periph, MA_OWNED); xpt_setup_ccb(&cts.ccb_h, path, CAM_PRIORITY_NORMAL); cts.ccb_h.func_code = XPT_GET_TRAN_SETTINGS; cts.type = CTS_TYPE_CURRENT_SETTINGS; xpt_action((union ccb*)&cts); if ((cts.ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) return; /* Ask the SIM for its base transfer speed */ xpt_setup_ccb(&cpi.ccb_h, path, CAM_PRIORITY_NORMAL); cpi.ccb_h.func_code = XPT_PATH_INQ; xpt_action((union ccb *)&cpi); /* Report connection speed */ speed = cpi.base_transfer_speed; if (cts.ccb_h.status == CAM_REQ_CMP && cts.transport == XPORT_ATA) { struct ccb_trans_settings_pata *pata = &cts.xport_specific.ata; if (pata->valid & CTS_ATA_VALID_MODE) speed = ata_mode2speed(pata->mode); } if (cts.ccb_h.status == CAM_REQ_CMP && cts.transport == XPORT_SATA) { struct ccb_trans_settings_sata *sata = &cts.xport_specific.sata; if (sata->valid & CTS_SATA_VALID_REVISION) speed = ata_revision2speed(sata->revision); } mb = speed / 1000; if (mb > 0) printf("%s%d: %d.%03dMB/s transfers", periph->periph_name, periph->unit_number, mb, speed % 1000); else printf("%s%d: %dKB/s transfers", periph->periph_name, periph->unit_number, speed); /* Report additional information about connection */ if (cts.ccb_h.status == CAM_REQ_CMP && cts.transport == XPORT_ATA) { struct ccb_trans_settings_pata *pata = &cts.xport_specific.ata; printf(" ("); if (pata->valid & CTS_ATA_VALID_MODE) printf("%s, ", ata_mode2string(pata->mode)); if ((pata->valid & CTS_ATA_VALID_ATAPI) && pata->atapi != 0) printf("ATAPI %dbytes, ", pata->atapi); if (pata->valid & CTS_ATA_VALID_BYTECOUNT) printf("PIO %dbytes", pata->bytecount); printf(")"); } if (cts.ccb_h.status == CAM_REQ_CMP && cts.transport == XPORT_SATA) { struct ccb_trans_settings_sata *sata = &cts.xport_specific.sata; printf(" ("); if (sata->valid & CTS_SATA_VALID_REVISION) printf("SATA %d.x, ", sata->revision); else printf("SATA, "); if (sata->valid & CTS_SATA_VALID_MODE) printf("%s, ", ata_mode2string(sata->mode)); if ((sata->valid & CTS_ATA_VALID_ATAPI) && sata->atapi != 0) printf("ATAPI %dbytes, ", sata->atapi); if (sata->valid & CTS_SATA_VALID_BYTECOUNT) printf("PIO %dbytes", sata->bytecount); printf(")"); } printf("\n"); } diff --git a/sys/cam/scsi/scsi_cd.c b/sys/cam/scsi/scsi_cd.c index f252b862b10f..1dc5f8050841 100644 --- a/sys/cam/scsi/scsi_cd.c +++ b/sys/cam/scsi/scsi_cd.c @@ -1,3718 +1,3719 @@ /*- * Copyright (c) 1997 Justin T. Gibbs. * Copyright (c) 1997, 1998, 1999, 2000, 2001, 2002, 2003 Kenneth D. Merry. * 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, * without modification, immediately at the beginning of the file. * 2. 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 AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /*- * Portions of this driver taken from the original FreeBSD cd driver. * Written by Julian Elischer (julian@tfs.com) * for TRW Financial Systems for use under the MACH(2.5) operating system. * * TRW Financial Systems, in accordance with their agreement with Carnegie * Mellon University, makes this software available to CMU to distribute * or use in any manner that they see fit as long as this message is kept with * the software. For this reason TFS also grants any other persons or * organisations permission to use or modify this software. * * TFS supplies this software to be publicly redistributed * on the understanding that TFS is not responsible for the correct * functioning of this software in any circumstances. * * Ported to run under 386BSD by Julian Elischer (julian@tfs.com) Sept 1992 * * from: cd.c,v 1.83 1997/05/04 15:24:22 joerg Exp $ */ #include __FBSDID("$FreeBSD$"); #include "opt_cd.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define LEADOUT 0xaa /* leadout toc entry */ struct cd_params { u_int32_t blksize; u_long disksize; }; typedef enum { CD_Q_NONE = 0x00, CD_Q_NO_TOUCH = 0x01, CD_Q_BCD_TRACKS = 0x02, CD_Q_10_BYTE_ONLY = 0x10, CD_Q_RETRY_BUSY = 0x40 } cd_quirks; #define CD_Q_BIT_STRING \ "\020" \ "\001NO_TOUCH" \ "\002BCD_TRACKS" \ "\00510_BYTE_ONLY" \ "\007RETRY_BUSY" typedef enum { CD_FLAG_INVALID = 0x0001, CD_FLAG_NEW_DISC = 0x0002, CD_FLAG_DISC_LOCKED = 0x0004, CD_FLAG_DISC_REMOVABLE = 0x0008, CD_FLAG_SAW_MEDIA = 0x0010, CD_FLAG_ACTIVE = 0x0080, CD_FLAG_SCHED_ON_COMP = 0x0100, CD_FLAG_RETRY_UA = 0x0200, CD_FLAG_VALID_MEDIA = 0x0400, CD_FLAG_VALID_TOC = 0x0800, CD_FLAG_SCTX_INIT = 0x1000 } cd_flags; typedef enum { CD_CCB_PROBE = 0x01, CD_CCB_BUFFER_IO = 0x02, CD_CCB_TUR = 0x04, CD_CCB_TYPE_MASK = 0x0F, CD_CCB_RETRY_UA = 0x10 } cd_ccb_state; #define ccb_state ppriv_field0 #define ccb_bp ppriv_ptr1 struct cd_tocdata { struct ioc_toc_header header; struct cd_toc_entry entries[100]; }; struct cd_toc_single { struct ioc_toc_header header; struct cd_toc_entry entry; }; typedef enum { CD_STATE_PROBE, CD_STATE_NORMAL } cd_state; struct cd_softc { cam_pinfo pinfo; cd_state state; volatile cd_flags flags; struct bio_queue_head bio_queue; LIST_HEAD(, ccb_hdr) pending_ccbs; struct cd_params params; union ccb saved_ccb; cd_quirks quirks; struct cam_periph *periph; int minimum_command_size; int outstanding_cmds; int tur; struct task sysctl_task; struct sysctl_ctx_list sysctl_ctx; struct sysctl_oid *sysctl_tree; STAILQ_HEAD(, cd_mode_params) mode_queue; struct cd_tocdata toc; struct disk *disk; struct callout mediapoll_c; }; struct cd_page_sizes { int page; int page_size; }; static struct cd_page_sizes cd_page_size_table[] = { { AUDIO_PAGE, sizeof(struct cd_audio_page)} }; struct cd_quirk_entry { struct scsi_inquiry_pattern inq_pat; cd_quirks quirks; }; /* * NOTE ON 10_BYTE_ONLY quirks: Any 10_BYTE_ONLY quirks MUST be because * your device hangs when it gets a 10 byte command. Adding a quirk just * to get rid of the informative diagnostic message is not acceptable. All * 10_BYTE_ONLY quirks must be documented in full in a PR (which should be * referenced in a comment along with the quirk) , and must be approved by * ken@FreeBSD.org. Any quirks added that don't adhere to this policy may * be removed until the submitter can explain why they are needed. * 10_BYTE_ONLY quirks will be removed (as they will no longer be necessary) * when the CAM_NEW_TRAN_CODE work is done. */ static struct cd_quirk_entry cd_quirk_table[] = { { { T_CDROM, SIP_MEDIA_REMOVABLE, "CHINON", "CD-ROM CDS-535","*"}, /* quirks */ CD_Q_BCD_TRACKS }, { /* * VMware returns BUSY status when storage has transient * connectivity problems, so better wait. */ {T_CDROM, SIP_MEDIA_REMOVABLE, "NECVMWar", "VMware IDE CDR10", "*"}, /*quirks*/ CD_Q_RETRY_BUSY } }; static disk_open_t cdopen; static disk_close_t cdclose; static disk_ioctl_t cdioctl; static disk_strategy_t cdstrategy; static periph_init_t cdinit; static periph_ctor_t cdregister; static periph_dtor_t cdcleanup; static periph_start_t cdstart; static periph_oninv_t cdoninvalidate; static void cdasync(void *callback_arg, u_int32_t code, struct cam_path *path, void *arg); static int cdcmdsizesysctl(SYSCTL_HANDLER_ARGS); static int cdrunccb(union ccb *ccb, int (*error_routine)(union ccb *ccb, u_int32_t cam_flags, u_int32_t sense_flags), u_int32_t cam_flags, u_int32_t sense_flags); static void cddone(struct cam_periph *periph, union ccb *start_ccb); static union cd_pages *cdgetpage(struct cd_mode_params *mode_params); static int cdgetpagesize(int page_num); static void cdprevent(struct cam_periph *periph, int action); static int cdcheckmedia(struct cam_periph *periph); static int cdsize(struct cam_periph *periph, u_int32_t *size); static int cd6byteworkaround(union ccb *ccb); static int cderror(union ccb *ccb, u_int32_t cam_flags, u_int32_t sense_flags); static int cdreadtoc(struct cam_periph *periph, u_int32_t mode, u_int32_t start, u_int8_t *data, u_int32_t len, u_int32_t sense_flags); static int cdgetmode(struct cam_periph *periph, struct cd_mode_params *data, u_int32_t page); static int cdsetmode(struct cam_periph *periph, struct cd_mode_params *data); static int cdplay(struct cam_periph *periph, u_int32_t blk, u_int32_t len); static int cdreadsubchannel(struct cam_periph *periph, u_int32_t mode, u_int32_t format, int track, struct cd_sub_channel_info *data, u_int32_t len); static int cdplaymsf(struct cam_periph *periph, u_int32_t startm, u_int32_t starts, u_int32_t startf, u_int32_t endm, u_int32_t ends, u_int32_t endf); static int cdplaytracks(struct cam_periph *periph, u_int32_t strack, u_int32_t sindex, u_int32_t etrack, u_int32_t eindex); static int cdpause(struct cam_periph *periph, u_int32_t go); static int cdstopunit(struct cam_periph *periph, u_int32_t eject); static int cdstartunit(struct cam_periph *periph, int load); static int cdsetspeed(struct cam_periph *periph, u_int32_t rdspeed, u_int32_t wrspeed); static int cdreportkey(struct cam_periph *periph, struct dvd_authinfo *authinfo); static int cdsendkey(struct cam_periph *periph, struct dvd_authinfo *authinfo); static int cdreaddvdstructure(struct cam_periph *periph, struct dvd_struct *dvdstruct); static timeout_t cdmediapoll; static struct periph_driver cddriver = { cdinit, "cd", TAILQ_HEAD_INITIALIZER(cddriver.units), /* generation */ 0 }; PERIPHDRIVER_DECLARE(cd, cddriver); #ifndef CD_DEFAULT_POLL_PERIOD #define CD_DEFAULT_POLL_PERIOD 3 #endif #ifndef CD_DEFAULT_RETRY #define CD_DEFAULT_RETRY 4 #endif #ifndef CD_DEFAULT_TIMEOUT #define CD_DEFAULT_TIMEOUT 30000 #endif static int cd_poll_period = CD_DEFAULT_POLL_PERIOD; static int cd_retry_count = CD_DEFAULT_RETRY; static int cd_timeout = CD_DEFAULT_TIMEOUT; static SYSCTL_NODE(_kern_cam, OID_AUTO, cd, CTLFLAG_RD, 0, "CAM CDROM driver"); SYSCTL_INT(_kern_cam_cd, OID_AUTO, poll_period, CTLFLAG_RWTUN, &cd_poll_period, 0, "Media polling period in seconds"); SYSCTL_INT(_kern_cam_cd, OID_AUTO, retry_count, CTLFLAG_RWTUN, &cd_retry_count, 0, "Normal I/O retry count"); SYSCTL_INT(_kern_cam_cd, OID_AUTO, timeout, CTLFLAG_RWTUN, &cd_timeout, 0, "Timeout, in us, for read operations"); static MALLOC_DEFINE(M_SCSICD, "scsi_cd", "scsi_cd buffers"); static void cdinit(void) { cam_status status; /* * Install a global async callback. This callback will * receive async callbacks like "new device found". */ status = xpt_register_async(AC_FOUND_DEVICE, cdasync, NULL, NULL); if (status != CAM_REQ_CMP) { printf("cd: Failed to attach master async callback " "due to status 0x%x!\n", status); } } /* * Callback from GEOM, called when it has finished cleaning up its * resources. */ static void cddiskgonecb(struct disk *dp) { struct cam_periph *periph; periph = (struct cam_periph *)dp->d_drv1; cam_periph_release(periph); } static void cdoninvalidate(struct cam_periph *periph) { struct cd_softc *softc; softc = (struct cd_softc *)periph->softc; /* * De-register any async callbacks. */ xpt_register_async(0, cdasync, periph, periph->path); softc->flags |= CD_FLAG_INVALID; /* * Return all queued I/O with ENXIO. * XXX Handle any transactions queued to the card * with XPT_ABORT_CCB. */ bioq_flush(&softc->bio_queue, NULL, ENXIO); disk_gone(softc->disk); } static void cdcleanup(struct cam_periph *periph) { struct cd_softc *softc; softc = (struct cd_softc *)periph->softc; cam_periph_unlock(periph); if ((softc->flags & CD_FLAG_SCTX_INIT) != 0 && sysctl_ctx_free(&softc->sysctl_ctx) != 0) { xpt_print(periph->path, "can't remove sysctl context\n"); } callout_drain(&softc->mediapoll_c); disk_destroy(softc->disk); free(softc, M_DEVBUF); cam_periph_lock(periph); } static void cdasync(void *callback_arg, u_int32_t code, struct cam_path *path, void *arg) { struct cam_periph *periph; struct cd_softc *softc; periph = (struct cam_periph *)callback_arg; switch (code) { case AC_FOUND_DEVICE: { struct ccb_getdev *cgd; cam_status status; cgd = (struct ccb_getdev *)arg; if (cgd == NULL) break; if (cgd->protocol != PROTO_SCSI) break; - + if (SID_QUAL(&cgd->inq_data) != SID_QUAL_LU_CONNECTED) + break; if (SID_TYPE(&cgd->inq_data) != T_CDROM && SID_TYPE(&cgd->inq_data) != T_WORM) break; /* * Allocate a peripheral instance for * this device and start the probe * process. */ status = cam_periph_alloc(cdregister, cdoninvalidate, cdcleanup, cdstart, "cd", CAM_PERIPH_BIO, path, cdasync, AC_FOUND_DEVICE, cgd); if (status != CAM_REQ_CMP && status != CAM_REQ_INPROG) printf("cdasync: Unable to attach new device " "due to status 0x%x\n", status); break; } case AC_UNIT_ATTENTION: { union ccb *ccb; int error_code, sense_key, asc, ascq; softc = (struct cd_softc *)periph->softc; ccb = (union ccb *)arg; /* * Handle all media change UNIT ATTENTIONs except * our own, as they will be handled by cderror(). */ if (xpt_path_periph(ccb->ccb_h.path) != periph && scsi_extract_sense_ccb(ccb, &error_code, &sense_key, &asc, &ascq)) { if (asc == 0x28 && ascq == 0x00) disk_media_changed(softc->disk, M_NOWAIT); } cam_periph_async(periph, code, path, arg); break; } case AC_SCSI_AEN: softc = (struct cd_softc *)periph->softc; if (softc->state == CD_STATE_NORMAL && !softc->tur) { if (cam_periph_acquire(periph) == CAM_REQ_CMP) { softc->tur = 1; xpt_schedule(periph, CAM_PRIORITY_NORMAL); } } /* FALLTHROUGH */ case AC_SENT_BDR: case AC_BUS_RESET: { struct ccb_hdr *ccbh; softc = (struct cd_softc *)periph->softc; /* * Don't fail on the expected unit attention * that will occur. */ softc->flags |= CD_FLAG_RETRY_UA; LIST_FOREACH(ccbh, &softc->pending_ccbs, periph_links.le) ccbh->ccb_state |= CD_CCB_RETRY_UA; /* FALLTHROUGH */ } default: cam_periph_async(periph, code, path, arg); break; } } static void cdsysctlinit(void *context, int pending) { struct cam_periph *periph; struct cd_softc *softc; char tmpstr[80], tmpstr2[80]; periph = (struct cam_periph *)context; if (cam_periph_acquire(periph) != CAM_REQ_CMP) return; softc = (struct cd_softc *)periph->softc; snprintf(tmpstr, sizeof(tmpstr), "CAM CD unit %d", periph->unit_number); snprintf(tmpstr2, sizeof(tmpstr2), "%d", periph->unit_number); sysctl_ctx_init(&softc->sysctl_ctx); softc->flags |= CD_FLAG_SCTX_INIT; softc->sysctl_tree = SYSCTL_ADD_NODE(&softc->sysctl_ctx, SYSCTL_STATIC_CHILDREN(_kern_cam_cd), OID_AUTO, tmpstr2, CTLFLAG_RD, 0, tmpstr); if (softc->sysctl_tree == NULL) { printf("cdsysctlinit: unable to allocate sysctl tree\n"); cam_periph_release(periph); return; } /* * Now register the sysctl handler, so the user can the value on * the fly. */ SYSCTL_ADD_PROC(&softc->sysctl_ctx,SYSCTL_CHILDREN(softc->sysctl_tree), OID_AUTO, "minimum_cmd_size", CTLTYPE_INT | CTLFLAG_RW, &softc->minimum_command_size, 0, cdcmdsizesysctl, "I", "Minimum CDB size"); cam_periph_release(periph); } /* * We have a handler function for this so we can check the values when the * user sets them, instead of every time we look at them. */ static int cdcmdsizesysctl(SYSCTL_HANDLER_ARGS) { int error, value; value = *(int *)arg1; error = sysctl_handle_int(oidp, &value, 0, req); if ((error != 0) || (req->newptr == NULL)) return (error); /* * The only real values we can have here are 6 or 10. I don't * really forsee having 12 be an option at any time in the future. * So if the user sets something less than or equal to 6, we'll set * it to 6. If he sets something greater than 6, we'll set it to 10. * * I suppose we could just return an error here for the wrong values, * but I don't think it's necessary to do so, as long as we can * determine the user's intent without too much trouble. */ if (value < 6) value = 6; else if (value > 6) value = 10; *(int *)arg1 = value; return (0); } static cam_status cdregister(struct cam_periph *periph, void *arg) { struct cd_softc *softc; struct ccb_pathinq cpi; struct ccb_getdev *cgd; char tmpstr[80]; caddr_t match; cgd = (struct ccb_getdev *)arg; if (cgd == NULL) { printf("cdregister: no getdev CCB, can't register device\n"); return(CAM_REQ_CMP_ERR); } softc = (struct cd_softc *)malloc(sizeof(*softc),M_DEVBUF, M_NOWAIT | M_ZERO); if (softc == NULL) { printf("cdregister: Unable to probe new device. " "Unable to allocate softc\n"); return(CAM_REQ_CMP_ERR); } LIST_INIT(&softc->pending_ccbs); STAILQ_INIT(&softc->mode_queue); softc->state = CD_STATE_PROBE; bioq_init(&softc->bio_queue); if (SID_IS_REMOVABLE(&cgd->inq_data)) softc->flags |= CD_FLAG_DISC_REMOVABLE; periph->softc = softc; softc->periph = periph; /* * See if this device has any quirks. */ match = cam_quirkmatch((caddr_t)&cgd->inq_data, (caddr_t)cd_quirk_table, sizeof(cd_quirk_table)/sizeof(*cd_quirk_table), sizeof(*cd_quirk_table), scsi_inquiry_match); if (match != NULL) softc->quirks = ((struct cd_quirk_entry *)match)->quirks; else softc->quirks = CD_Q_NONE; /* Check if the SIM does not want 6 byte commands */ bzero(&cpi, sizeof(cpi)); xpt_setup_ccb(&cpi.ccb_h, periph->path, CAM_PRIORITY_NORMAL); cpi.ccb_h.func_code = XPT_PATH_INQ; xpt_action((union ccb *)&cpi); if (cpi.ccb_h.status == CAM_REQ_CMP && (cpi.hba_misc & PIM_NO_6_BYTE)) softc->quirks |= CD_Q_10_BYTE_ONLY; TASK_INIT(&softc->sysctl_task, 0, cdsysctlinit, periph); /* The default is 6 byte commands, unless quirked otherwise */ if (softc->quirks & CD_Q_10_BYTE_ONLY) softc->minimum_command_size = 10; else softc->minimum_command_size = 6; /* * Refcount and block open attempts until we are setup * Can't block */ (void)cam_periph_hold(periph, PRIBIO); cam_periph_unlock(periph); /* * Load the user's default, if any. */ snprintf(tmpstr, sizeof(tmpstr), "kern.cam.cd.%d.minimum_cmd_size", periph->unit_number); TUNABLE_INT_FETCH(tmpstr, &softc->minimum_command_size); /* 6 and 10 are the only permissible values here. */ if (softc->minimum_command_size < 6) softc->minimum_command_size = 6; else if (softc->minimum_command_size > 6) softc->minimum_command_size = 10; /* * We need to register the statistics structure for this device, * but we don't have the blocksize yet for it. So, we register * the structure and indicate that we don't have the blocksize * yet. Unlike other SCSI peripheral drivers, we explicitly set * the device type here to be CDROM, rather than just ORing in * the device type. This is because this driver can attach to either * CDROM or WORM devices, and we want this peripheral driver to * show up in the devstat list as a CD peripheral driver, not a * WORM peripheral driver. WORM drives will also have the WORM * driver attached to them. */ softc->disk = disk_alloc(); softc->disk->d_devstat = devstat_new_entry("cd", periph->unit_number, 0, DEVSTAT_BS_UNAVAILABLE, DEVSTAT_TYPE_CDROM | XPORT_DEVSTAT_TYPE(cpi.transport), DEVSTAT_PRIORITY_CD); softc->disk->d_open = cdopen; softc->disk->d_close = cdclose; softc->disk->d_strategy = cdstrategy; softc->disk->d_gone = cddiskgonecb; softc->disk->d_ioctl = cdioctl; softc->disk->d_name = "cd"; cam_strvis(softc->disk->d_descr, cgd->inq_data.vendor, sizeof(cgd->inq_data.vendor), sizeof(softc->disk->d_descr)); strlcat(softc->disk->d_descr, " ", sizeof(softc->disk->d_descr)); cam_strvis(&softc->disk->d_descr[strlen(softc->disk->d_descr)], cgd->inq_data.product, sizeof(cgd->inq_data.product), sizeof(softc->disk->d_descr) - strlen(softc->disk->d_descr)); softc->disk->d_unit = periph->unit_number; softc->disk->d_drv1 = periph; if (cpi.maxio == 0) softc->disk->d_maxsize = DFLTPHYS; /* traditional default */ else if (cpi.maxio > MAXPHYS) softc->disk->d_maxsize = MAXPHYS; /* for safety */ else softc->disk->d_maxsize = cpi.maxio; softc->disk->d_flags = 0; softc->disk->d_hba_vendor = cpi.hba_vendor; softc->disk->d_hba_device = cpi.hba_device; softc->disk->d_hba_subvendor = cpi.hba_subvendor; softc->disk->d_hba_subdevice = cpi.hba_subdevice; /* * Acquire a reference to the periph before we register with GEOM. * We'll release this reference once GEOM calls us back (via * dadiskgonecb()) telling us that our provider has been freed. */ if (cam_periph_acquire(periph) != CAM_REQ_CMP) { xpt_print(periph->path, "%s: lost periph during " "registration!\n", __func__); cam_periph_lock(periph); return (CAM_REQ_CMP_ERR); } disk_create(softc->disk, DISK_VERSION); cam_periph_lock(periph); /* * Add an async callback so that we get * notified if this device goes away. */ xpt_register_async(AC_SENT_BDR | AC_BUS_RESET | AC_LOST_DEVICE | AC_SCSI_AEN | AC_UNIT_ATTENTION, cdasync, periph, periph->path); /* * Schedule a periodic media polling events. */ callout_init_mtx(&softc->mediapoll_c, cam_periph_mtx(periph), 0); if ((softc->flags & CD_FLAG_DISC_REMOVABLE) && (cgd->inq_flags & SID_AEN) == 0 && cd_poll_period != 0) callout_reset(&softc->mediapoll_c, cd_poll_period * hz, cdmediapoll, periph); xpt_schedule(periph, CAM_PRIORITY_DEV); return(CAM_REQ_CMP); } static int cdopen(struct disk *dp) { struct cam_periph *periph; struct cd_softc *softc; int error; periph = (struct cam_periph *)dp->d_drv1; softc = (struct cd_softc *)periph->softc; if (cam_periph_acquire(periph) != CAM_REQ_CMP) return(ENXIO); cam_periph_lock(periph); if (softc->flags & CD_FLAG_INVALID) { cam_periph_release_locked(periph); cam_periph_unlock(periph); return(ENXIO); } if ((error = cam_periph_hold(periph, PRIBIO | PCATCH)) != 0) { cam_periph_release_locked(periph); cam_periph_unlock(periph); return (error); } CAM_DEBUG(periph->path, CAM_DEBUG_TRACE | CAM_DEBUG_PERIPH, ("cdopen\n")); /* * Check for media, and set the appropriate flags. We don't bail * if we don't have media, but then we don't allow anything but the * CDIOCEJECT/CDIOCCLOSE ioctls if there is no media. */ cdcheckmedia(periph); CAM_DEBUG(periph->path, CAM_DEBUG_TRACE, ("leaving cdopen\n")); cam_periph_unhold(periph); cam_periph_unlock(periph); return (0); } static int cdclose(struct disk *dp) { struct cam_periph *periph; struct cd_softc *softc; periph = (struct cam_periph *)dp->d_drv1; softc = (struct cd_softc *)periph->softc; cam_periph_lock(periph); if (cam_periph_hold(periph, PRIBIO) != 0) { cam_periph_unlock(periph); cam_periph_release(periph); return (0); } CAM_DEBUG(periph->path, CAM_DEBUG_TRACE | CAM_DEBUG_PERIPH, ("cdclose\n")); if ((softc->flags & CD_FLAG_DISC_REMOVABLE) != 0) cdprevent(periph, PR_ALLOW); /* * Since we're closing this CD, mark the blocksize as unavailable. * It will be marked as available when the CD is opened again. */ softc->disk->d_devstat->flags |= DEVSTAT_BS_UNAVAILABLE; /* * We'll check the media and toc again at the next open(). */ softc->flags &= ~(CD_FLAG_VALID_MEDIA|CD_FLAG_VALID_TOC); cam_periph_unhold(periph); cam_periph_release_locked(periph); cam_periph_unlock(periph); return (0); } static int cdrunccb(union ccb *ccb, int (*error_routine)(union ccb *ccb, u_int32_t cam_flags, u_int32_t sense_flags), u_int32_t cam_flags, u_int32_t sense_flags) { struct cd_softc *softc; struct cam_periph *periph; int error; periph = xpt_path_periph(ccb->ccb_h.path); softc = (struct cd_softc *)periph->softc; error = cam_periph_runccb(ccb, error_routine, cam_flags, sense_flags, softc->disk->d_devstat); return(error); } /* * Actually translate the requested transfer into one the physical driver * can understand. The transfer is described by a buf and will include * only one physical transfer. */ static void cdstrategy(struct bio *bp) { struct cam_periph *periph; struct cd_softc *softc; periph = (struct cam_periph *)bp->bio_disk->d_drv1; cam_periph_lock(periph); CAM_DEBUG(periph->path, CAM_DEBUG_TRACE, ("cdstrategy(%p)\n", bp)); softc = (struct cd_softc *)periph->softc; /* * If the device has been made invalid, error out */ if ((softc->flags & CD_FLAG_INVALID)) { cam_periph_unlock(periph); biofinish(bp, NULL, ENXIO); return; } /* * If we don't have valid media, look for it before trying to * schedule the I/O. */ if ((softc->flags & CD_FLAG_VALID_MEDIA) == 0) { int error; error = cdcheckmedia(periph); if (error != 0) { cam_periph_unlock(periph); biofinish(bp, NULL, error); return; } } /* * Place it in the queue of disk activities for this disk */ bioq_disksort(&softc->bio_queue, bp); xpt_schedule(periph, CAM_PRIORITY_NORMAL); cam_periph_unlock(periph); return; } static void cdstart(struct cam_periph *periph, union ccb *start_ccb) { struct cd_softc *softc; struct bio *bp; struct ccb_scsiio *csio; struct scsi_read_capacity_data *rcap; softc = (struct cd_softc *)periph->softc; CAM_DEBUG(periph->path, CAM_DEBUG_TRACE, ("entering cdstart\n")); switch (softc->state) { case CD_STATE_NORMAL: { bp = bioq_first(&softc->bio_queue); if (bp == NULL) { if (softc->tur) { softc->tur = 0; csio = &start_ccb->csio; scsi_test_unit_ready(csio, /*retries*/ cd_retry_count, cddone, MSG_SIMPLE_Q_TAG, SSD_FULL_SIZE, cd_timeout); start_ccb->ccb_h.ccb_bp = NULL; start_ccb->ccb_h.ccb_state = CD_CCB_TUR; xpt_action(start_ccb); } else xpt_release_ccb(start_ccb); } else { if (softc->tur) { softc->tur = 0; cam_periph_release_locked(periph); } bioq_remove(&softc->bio_queue, bp); scsi_read_write(&start_ccb->csio, /*retries*/ cd_retry_count, /* cbfcnp */ cddone, MSG_SIMPLE_Q_TAG, /* read */bp->bio_cmd == BIO_READ ? SCSI_RW_READ : SCSI_RW_WRITE, /* byte2 */ 0, /* minimum_cmd_size */ 10, /* lba */ bp->bio_offset / softc->params.blksize, bp->bio_bcount / softc->params.blksize, /* data_ptr */ bp->bio_data, /* dxfer_len */ bp->bio_bcount, /* sense_len */ cd_retry_count ? SSD_FULL_SIZE : SF_NO_PRINT, /* timeout */ cd_timeout); /* Use READ CD command for audio tracks. */ if (softc->params.blksize == 2352) { start_ccb->csio.cdb_io.cdb_bytes[0] = READ_CD; start_ccb->csio.cdb_io.cdb_bytes[9] = 0xf8; start_ccb->csio.cdb_io.cdb_bytes[10] = 0; start_ccb->csio.cdb_io.cdb_bytes[11] = 0; start_ccb->csio.cdb_len = 12; } start_ccb->ccb_h.ccb_state = CD_CCB_BUFFER_IO; LIST_INSERT_HEAD(&softc->pending_ccbs, &start_ccb->ccb_h, periph_links.le); softc->outstanding_cmds++; /* We expect a unit attention from this device */ if ((softc->flags & CD_FLAG_RETRY_UA) != 0) { start_ccb->ccb_h.ccb_state |= CD_CCB_RETRY_UA; softc->flags &= ~CD_FLAG_RETRY_UA; } start_ccb->ccb_h.ccb_bp = bp; bp = bioq_first(&softc->bio_queue); xpt_action(start_ccb); } if (bp != NULL || softc->tur) { /* Have more work to do, so ensure we stay scheduled */ xpt_schedule(periph, CAM_PRIORITY_NORMAL); } break; } case CD_STATE_PROBE: { rcap = (struct scsi_read_capacity_data *)malloc(sizeof(*rcap), M_SCSICD, M_NOWAIT | M_ZERO); if (rcap == NULL) { xpt_print(periph->path, "cdstart: Couldn't malloc read_capacity data\n"); /* cd_free_periph??? */ break; } csio = &start_ccb->csio; scsi_read_capacity(csio, /*retries*/ cd_retry_count, cddone, MSG_SIMPLE_Q_TAG, rcap, SSD_FULL_SIZE, /*timeout*/20000); start_ccb->ccb_h.ccb_bp = NULL; start_ccb->ccb_h.ccb_state = CD_CCB_PROBE; xpt_action(start_ccb); break; } } } static void cddone(struct cam_periph *periph, union ccb *done_ccb) { struct cd_softc *softc; struct ccb_scsiio *csio; CAM_DEBUG(periph->path, CAM_DEBUG_TRACE, ("entering cddone\n")); softc = (struct cd_softc *)periph->softc; csio = &done_ccb->csio; switch (csio->ccb_h.ccb_state & CD_CCB_TYPE_MASK) { case CD_CCB_BUFFER_IO: { struct bio *bp; int error; bp = (struct bio *)done_ccb->ccb_h.ccb_bp; error = 0; if ((done_ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) { int sf; if ((done_ccb->ccb_h.ccb_state & CD_CCB_RETRY_UA) != 0) sf = SF_RETRY_UA; else sf = 0; error = cderror(done_ccb, CAM_RETRY_SELTO, sf); if (error == ERESTART) { /* * A retry was scheuled, so * just return. */ return; } } if (error != 0) { xpt_print(periph->path, "cddone: got error %#x back\n", error); bioq_flush(&softc->bio_queue, NULL, EIO); bp->bio_resid = bp->bio_bcount; bp->bio_error = error; bp->bio_flags |= BIO_ERROR; if ((done_ccb->ccb_h.status & CAM_DEV_QFRZN) != 0) cam_release_devq(done_ccb->ccb_h.path, /*relsim_flags*/0, /*reduction*/0, /*timeout*/0, /*getcount_only*/0); } else { bp->bio_resid = csio->resid; bp->bio_error = 0; if (bp->bio_resid != 0) { /* * Short transfer ??? * XXX: not sure this is correct for partial * transfers at EOM */ bp->bio_flags |= BIO_ERROR; } } LIST_REMOVE(&done_ccb->ccb_h, periph_links.le); softc->outstanding_cmds--; biofinish(bp, NULL, 0); break; } case CD_CCB_PROBE: { struct scsi_read_capacity_data *rdcap; char announce_buf[120]; /* * Currently (9/30/97) the * longest possible announce * buffer is 108 bytes, for the * first error case below. * That is 39 bytes for the * basic string, 16 bytes for the * biggest sense key (hardware * error), 52 bytes for the * text of the largest sense * qualifier valid for a CDROM, * (0x72, 0x03 or 0x04, * 0x03), and one byte for the * null terminating character. * To allow for longer strings, * the announce buffer is 120 * bytes. */ struct cd_params *cdp; int error; cdp = &softc->params; rdcap = (struct scsi_read_capacity_data *)csio->data_ptr; cdp->disksize = scsi_4btoul (rdcap->addr) + 1; cdp->blksize = scsi_4btoul (rdcap->length); /* * Retry any UNIT ATTENTION type errors. They * are expected at boot. */ if ((csio->ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_CMP || (error = cderror(done_ccb, CAM_RETRY_SELTO, SF_RETRY_UA | SF_NO_PRINT)) == 0) { snprintf(announce_buf, sizeof(announce_buf), "cd present [%lu x %lu byte records]", cdp->disksize, (u_long)cdp->blksize); } else { if (error == ERESTART) { /* * A retry was scheuled, so * just return. */ return; } else { int asc, ascq; int sense_key, error_code; int have_sense; cam_status status; struct ccb_getdev cgd; /* Don't wedge this device's queue */ if ((done_ccb->ccb_h.status & CAM_DEV_QFRZN) != 0) cam_release_devq(done_ccb->ccb_h.path, /*relsim_flags*/0, /*reduction*/0, /*timeout*/0, /*getcount_only*/0); status = done_ccb->ccb_h.status; xpt_setup_ccb(&cgd.ccb_h, done_ccb->ccb_h.path, CAM_PRIORITY_NORMAL); cgd.ccb_h.func_code = XPT_GDEV_TYPE; xpt_action((union ccb *)&cgd); if (scsi_extract_sense_ccb(done_ccb, &error_code, &sense_key, &asc, &ascq)) have_sense = TRUE; else have_sense = FALSE; /* * Attach to anything that claims to be a * CDROM or WORM device, as long as it * doesn't return a "Logical unit not * supported" (0x25) error. */ if ((have_sense) && (asc != 0x25) && (error_code == SSD_CURRENT_ERROR)) { const char *sense_key_desc; const char *asc_desc; scsi_sense_desc(sense_key, asc, ascq, &cgd.inq_data, &sense_key_desc, &asc_desc); snprintf(announce_buf, sizeof(announce_buf), "Attempt to query device " "size failed: %s, %s", sense_key_desc, asc_desc); } else if ((have_sense == 0) && ((status & CAM_STATUS_MASK) == CAM_SCSI_STATUS_ERROR) && (csio->scsi_status == SCSI_STATUS_BUSY)) { snprintf(announce_buf, sizeof(announce_buf), "Attempt to query device " "size failed: SCSI Status: %s", scsi_status_string(csio)); } else if (SID_TYPE(&cgd.inq_data) == T_CDROM) { /* * We only print out an error for * CDROM type devices. For WORM * devices, we don't print out an * error since a few WORM devices * don't support CDROM commands. * If we have sense information, go * ahead and print it out. * Otherwise, just say that we * couldn't attach. */ /* * Just print out the error, not * the full probe message, when we * don't attach. */ if (have_sense) scsi_sense_print( &done_ccb->csio); else { xpt_print(periph->path, "got CAM status %#x\n", done_ccb->ccb_h.status); } xpt_print(periph->path, "fatal error, " "failed to attach to device\n"); /* * Invalidate this peripheral. */ cam_periph_invalidate(periph); announce_buf[0] = '\0'; } else { /* * Invalidate this peripheral. */ cam_periph_invalidate(periph); announce_buf[0] = '\0'; } } } free(rdcap, M_SCSICD); if (announce_buf[0] != '\0') { xpt_announce_periph(periph, announce_buf); xpt_announce_quirks(periph, softc->quirks, CD_Q_BIT_STRING); /* * Create our sysctl variables, now that we know * we have successfully attached. */ taskqueue_enqueue(taskqueue_thread,&softc->sysctl_task); } softc->state = CD_STATE_NORMAL; /* * Since our peripheral may be invalidated by an error * above or an external event, we must release our CCB * before releasing the probe lock on the peripheral. * The peripheral will only go away once the last lock * is removed, and we need it around for the CCB release * operation. */ xpt_release_ccb(done_ccb); cam_periph_unhold(periph); return; } case CD_CCB_TUR: { if ((done_ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) { if (cderror(done_ccb, CAM_RETRY_SELTO, SF_RETRY_UA | SF_NO_RECOVERY | SF_NO_PRINT) == ERESTART) return; if ((done_ccb->ccb_h.status & CAM_DEV_QFRZN) != 0) cam_release_devq(done_ccb->ccb_h.path, /*relsim_flags*/0, /*reduction*/0, /*timeout*/0, /*getcount_only*/0); } xpt_release_ccb(done_ccb); cam_periph_release_locked(periph); return; } default: break; } xpt_release_ccb(done_ccb); } static union cd_pages * cdgetpage(struct cd_mode_params *mode_params) { union cd_pages *page; if (mode_params->cdb_size == 10) page = (union cd_pages *)find_mode_page_10( (struct scsi_mode_header_10 *)mode_params->mode_buf); else page = (union cd_pages *)find_mode_page_6( (struct scsi_mode_header_6 *)mode_params->mode_buf); return (page); } static int cdgetpagesize(int page_num) { int i; for (i = 0; i < (sizeof(cd_page_size_table)/ sizeof(cd_page_size_table[0])); i++) { if (cd_page_size_table[i].page == page_num) return (cd_page_size_table[i].page_size); } return (-1); } static int cdioctl(struct disk *dp, u_long cmd, void *addr, int flag, struct thread *td) { struct cam_periph *periph; struct cd_softc *softc; int nocopyout, error = 0; periph = (struct cam_periph *)dp->d_drv1; cam_periph_lock(periph); softc = (struct cd_softc *)periph->softc; CAM_DEBUG(periph->path, CAM_DEBUG_TRACE, ("cdioctl(%#lx)\n", cmd)); if ((error = cam_periph_hold(periph, PRIBIO | PCATCH)) != 0) { cam_periph_unlock(periph); cam_periph_release(periph); return (error); } /* * If we don't have media loaded, check for it. If still don't * have media loaded, we can only do a load or eject. * * We only care whether media is loaded if this is a cd-specific ioctl * (thus the IOCGROUP check below). Note that this will break if * anyone adds any ioctls into the switch statement below that don't * have their ioctl group set to 'c'. */ if (((softc->flags & CD_FLAG_VALID_MEDIA) == 0) && ((cmd != CDIOCCLOSE) && (cmd != CDIOCEJECT)) && (IOCGROUP(cmd) == 'c')) { error = cdcheckmedia(periph); if (error != 0) { cam_periph_unhold(periph); cam_periph_unlock(periph); return (error); } } /* * Drop the lock here so later mallocs can use WAITOK. The periph * is essentially locked still with the cam_periph_hold call above. */ cam_periph_unlock(periph); nocopyout = 0; switch (cmd) { case CDIOCPLAYTRACKS: { struct ioc_play_track *args = (struct ioc_play_track *) addr; struct cd_mode_params params; union cd_pages *page; params.alloc_len = sizeof(union cd_mode_data_6_10); params.mode_buf = malloc(params.alloc_len, M_SCSICD, M_WAITOK | M_ZERO); cam_periph_lock(periph); CAM_DEBUG(periph->path, CAM_DEBUG_SUBTRACE, ("trying to do CDIOCPLAYTRACKS\n")); error = cdgetmode(periph, ¶ms, AUDIO_PAGE); if (error) { free(params.mode_buf, M_SCSICD); cam_periph_unlock(periph); break; } page = cdgetpage(¶ms); page->audio.flags &= ~CD_PA_SOTC; page->audio.flags |= CD_PA_IMMED; error = cdsetmode(periph, ¶ms); free(params.mode_buf, M_SCSICD); if (error) { cam_periph_unlock(periph); break; } /* * This was originally implemented with the PLAY * AUDIO TRACK INDEX command, but that command was * deprecated after SCSI-2. Most (all?) SCSI CDROM * drives support it but ATAPI and ATAPI-derivative * drives don't seem to support it. So we keep a * cache of the table of contents and translate * track numbers to MSF format. */ if (softc->flags & CD_FLAG_VALID_TOC) { union msf_lba *sentry, *eentry; int st, et; if (args->end_track < softc->toc.header.ending_track + 1) args->end_track++; if (args->end_track > softc->toc.header.ending_track + 1) args->end_track = softc->toc.header.ending_track + 1; st = args->start_track - softc->toc.header.starting_track; et = args->end_track - softc->toc.header.starting_track; if ((st < 0) || (et < 0) || (st > (softc->toc.header.ending_track - softc->toc.header.starting_track))) { error = EINVAL; cam_periph_unlock(periph); break; } sentry = &softc->toc.entries[st].addr; eentry = &softc->toc.entries[et].addr; error = cdplaymsf(periph, sentry->msf.minute, sentry->msf.second, sentry->msf.frame, eentry->msf.minute, eentry->msf.second, eentry->msf.frame); } else { /* * If we don't have a valid TOC, try the * play track index command. It is part of * the SCSI-2 spec, but was removed in the * MMC specs. ATAPI and ATAPI-derived * drives don't support it. */ if (softc->quirks & CD_Q_BCD_TRACKS) { args->start_track = bin2bcd(args->start_track); args->end_track = bin2bcd(args->end_track); } error = cdplaytracks(periph, args->start_track, args->start_index, args->end_track, args->end_index); } cam_periph_unlock(periph); } break; case CDIOCPLAYMSF: { struct ioc_play_msf *args = (struct ioc_play_msf *) addr; struct cd_mode_params params; union cd_pages *page; params.alloc_len = sizeof(union cd_mode_data_6_10); params.mode_buf = malloc(params.alloc_len, M_SCSICD, M_WAITOK | M_ZERO); cam_periph_lock(periph); CAM_DEBUG(periph->path, CAM_DEBUG_SUBTRACE, ("trying to do CDIOCPLAYMSF\n")); error = cdgetmode(periph, ¶ms, AUDIO_PAGE); if (error) { free(params.mode_buf, M_SCSICD); cam_periph_unlock(periph); break; } page = cdgetpage(¶ms); page->audio.flags &= ~CD_PA_SOTC; page->audio.flags |= CD_PA_IMMED; error = cdsetmode(periph, ¶ms); free(params.mode_buf, M_SCSICD); if (error) { cam_periph_unlock(periph); break; } error = cdplaymsf(periph, args->start_m, args->start_s, args->start_f, args->end_m, args->end_s, args->end_f); cam_periph_unlock(periph); } break; case CDIOCPLAYBLOCKS: { struct ioc_play_blocks *args = (struct ioc_play_blocks *) addr; struct cd_mode_params params; union cd_pages *page; params.alloc_len = sizeof(union cd_mode_data_6_10); params.mode_buf = malloc(params.alloc_len, M_SCSICD, M_WAITOK | M_ZERO); cam_periph_lock(periph); CAM_DEBUG(periph->path, CAM_DEBUG_SUBTRACE, ("trying to do CDIOCPLAYBLOCKS\n")); error = cdgetmode(periph, ¶ms, AUDIO_PAGE); if (error) { free(params.mode_buf, M_SCSICD); cam_periph_unlock(periph); break; } page = cdgetpage(¶ms); page->audio.flags &= ~CD_PA_SOTC; page->audio.flags |= CD_PA_IMMED; error = cdsetmode(periph, ¶ms); free(params.mode_buf, M_SCSICD); if (error) { cam_periph_unlock(periph); break; } error = cdplay(periph, args->blk, args->len); cam_periph_unlock(periph); } break; case CDIOCREADSUBCHANNEL_SYSSPACE: nocopyout = 1; /* Fallthrough */ case CDIOCREADSUBCHANNEL: { struct ioc_read_subchannel *args = (struct ioc_read_subchannel *) addr; struct cd_sub_channel_info *data; u_int32_t len = args->data_len; data = malloc(sizeof(struct cd_sub_channel_info), M_SCSICD, M_WAITOK | M_ZERO); cam_periph_lock(periph); CAM_DEBUG(periph->path, CAM_DEBUG_SUBTRACE, ("trying to do CDIOCREADSUBCHANNEL\n")); if ((len > sizeof(struct cd_sub_channel_info)) || (len < sizeof(struct cd_sub_channel_header))) { printf( "scsi_cd: cdioctl: " "cdioreadsubchannel: error, len=%d\n", len); error = EINVAL; free(data, M_SCSICD); cam_periph_unlock(periph); break; } if (softc->quirks & CD_Q_BCD_TRACKS) args->track = bin2bcd(args->track); error = cdreadsubchannel(periph, args->address_format, args->data_format, args->track, data, len); if (error) { free(data, M_SCSICD); cam_periph_unlock(periph); break; } if (softc->quirks & CD_Q_BCD_TRACKS) data->what.track_info.track_number = bcd2bin(data->what.track_info.track_number); len = min(len, ((data->header.data_len[0] << 8) + data->header.data_len[1] + sizeof(struct cd_sub_channel_header))); cam_periph_unlock(periph); if (nocopyout == 0) { if (copyout(data, args->data, len) != 0) { error = EFAULT; } } else { bcopy(data, args->data, len); } free(data, M_SCSICD); } break; case CDIOREADTOCHEADER: { struct ioc_toc_header *th; th = malloc(sizeof(struct ioc_toc_header), M_SCSICD, M_WAITOK | M_ZERO); cam_periph_lock(periph); CAM_DEBUG(periph->path, CAM_DEBUG_SUBTRACE, ("trying to do CDIOREADTOCHEADER\n")); error = cdreadtoc(periph, 0, 0, (u_int8_t *)th, sizeof (*th), /*sense_flags*/SF_NO_PRINT); if (error) { free(th, M_SCSICD); cam_periph_unlock(periph); break; } if (softc->quirks & CD_Q_BCD_TRACKS) { /* we are going to have to convert the BCD * encoding on the cd to what is expected */ th->starting_track = bcd2bin(th->starting_track); th->ending_track = bcd2bin(th->ending_track); } th->len = ntohs(th->len); bcopy(th, addr, sizeof(*th)); free(th, M_SCSICD); cam_periph_unlock(periph); } break; case CDIOREADTOCENTRYS: { struct cd_tocdata *data; struct cd_toc_single *lead; struct ioc_read_toc_entry *te = (struct ioc_read_toc_entry *) addr; struct ioc_toc_header *th; u_int32_t len, readlen, idx, num; u_int32_t starting_track = te->starting_track; data = malloc(sizeof(*data), M_SCSICD, M_WAITOK | M_ZERO); lead = malloc(sizeof(*lead), M_SCSICD, M_WAITOK | M_ZERO); cam_periph_lock(periph); CAM_DEBUG(periph->path, CAM_DEBUG_SUBTRACE, ("trying to do CDIOREADTOCENTRYS\n")); if (te->data_len < sizeof(struct cd_toc_entry) || (te->data_len % sizeof(struct cd_toc_entry)) != 0 || (te->address_format != CD_MSF_FORMAT && te->address_format != CD_LBA_FORMAT)) { error = EINVAL; printf("scsi_cd: error in readtocentries, " "returning EINVAL\n"); free(data, M_SCSICD); free(lead, M_SCSICD); cam_periph_unlock(periph); break; } th = &data->header; error = cdreadtoc(periph, 0, 0, (u_int8_t *)th, sizeof (*th), /*sense_flags*/0); if (error) { free(data, M_SCSICD); free(lead, M_SCSICD); cam_periph_unlock(periph); break; } if (softc->quirks & CD_Q_BCD_TRACKS) { /* we are going to have to convert the BCD * encoding on the cd to what is expected */ th->starting_track = bcd2bin(th->starting_track); th->ending_track = bcd2bin(th->ending_track); } if (starting_track == 0) starting_track = th->starting_track; else if (starting_track == LEADOUT) starting_track = th->ending_track + 1; else if (starting_track < th->starting_track || starting_track > th->ending_track + 1) { printf("scsi_cd: error in readtocentries, " "returning EINVAL\n"); free(data, M_SCSICD); free(lead, M_SCSICD); cam_periph_unlock(periph); error = EINVAL; break; } /* calculate reading length without leadout entry */ readlen = (th->ending_track - starting_track + 1) * sizeof(struct cd_toc_entry); /* and with leadout entry */ len = readlen + sizeof(struct cd_toc_entry); if (te->data_len < len) { len = te->data_len; if (readlen > len) readlen = len; } if (len > sizeof(data->entries)) { printf("scsi_cd: error in readtocentries, " "returning EINVAL\n"); error = EINVAL; free(data, M_SCSICD); free(lead, M_SCSICD); cam_periph_unlock(periph); break; } num = len / sizeof(struct cd_toc_entry); if (readlen > 0) { error = cdreadtoc(periph, te->address_format, starting_track, (u_int8_t *)data, readlen + sizeof (*th), /*sense_flags*/0); if (error) { free(data, M_SCSICD); free(lead, M_SCSICD); cam_periph_unlock(periph); break; } } /* make leadout entry if needed */ idx = starting_track + num - 1; if (softc->quirks & CD_Q_BCD_TRACKS) th->ending_track = bcd2bin(th->ending_track); if (idx == th->ending_track + 1) { error = cdreadtoc(periph, te->address_format, LEADOUT, (u_int8_t *)lead, sizeof(*lead), /*sense_flags*/0); if (error) { free(data, M_SCSICD); free(lead, M_SCSICD); cam_periph_unlock(periph); break; } data->entries[idx - starting_track] = lead->entry; } if (softc->quirks & CD_Q_BCD_TRACKS) { for (idx = 0; idx < num - 1; idx++) { data->entries[idx].track = bcd2bin(data->entries[idx].track); } } cam_periph_unlock(periph); error = copyout(data->entries, te->data, len); free(data, M_SCSICD); free(lead, M_SCSICD); } break; case CDIOREADTOCENTRY: { struct cd_toc_single *data; struct ioc_read_toc_single_entry *te = (struct ioc_read_toc_single_entry *) addr; struct ioc_toc_header *th; u_int32_t track; data = malloc(sizeof(*data), M_SCSICD, M_WAITOK | M_ZERO); cam_periph_lock(periph); CAM_DEBUG(periph->path, CAM_DEBUG_SUBTRACE, ("trying to do CDIOREADTOCENTRY\n")); if (te->address_format != CD_MSF_FORMAT && te->address_format != CD_LBA_FORMAT) { printf("error in readtocentry, " " returning EINVAL\n"); free(data, M_SCSICD); error = EINVAL; cam_periph_unlock(periph); break; } th = &data->header; error = cdreadtoc(periph, 0, 0, (u_int8_t *)th, sizeof (*th), /*sense_flags*/0); if (error) { free(data, M_SCSICD); cam_periph_unlock(periph); break; } if (softc->quirks & CD_Q_BCD_TRACKS) { /* we are going to have to convert the BCD * encoding on the cd to what is expected */ th->starting_track = bcd2bin(th->starting_track); th->ending_track = bcd2bin(th->ending_track); } track = te->track; if (track == 0) track = th->starting_track; else if (track == LEADOUT) /* OK */; else if (track < th->starting_track || track > th->ending_track + 1) { printf("error in readtocentry, " " returning EINVAL\n"); free(data, M_SCSICD); error = EINVAL; cam_periph_unlock(periph); break; } error = cdreadtoc(periph, te->address_format, track, (u_int8_t *)data, sizeof(*data), /*sense_flags*/0); if (error) { free(data, M_SCSICD); cam_periph_unlock(periph); break; } if (softc->quirks & CD_Q_BCD_TRACKS) data->entry.track = bcd2bin(data->entry.track); bcopy(&data->entry, &te->entry, sizeof(struct cd_toc_entry)); free(data, M_SCSICD); cam_periph_unlock(periph); } break; case CDIOCSETPATCH: { struct ioc_patch *arg = (struct ioc_patch *)addr; struct cd_mode_params params; union cd_pages *page; params.alloc_len = sizeof(union cd_mode_data_6_10); params.mode_buf = malloc(params.alloc_len, M_SCSICD, M_WAITOK | M_ZERO); cam_periph_lock(periph); CAM_DEBUG(periph->path, CAM_DEBUG_SUBTRACE, ("trying to do CDIOCSETPATCH\n")); error = cdgetmode(periph, ¶ms, AUDIO_PAGE); if (error) { free(params.mode_buf, M_SCSICD); cam_periph_unlock(periph); break; } page = cdgetpage(¶ms); page->audio.port[LEFT_PORT].channels = arg->patch[0]; page->audio.port[RIGHT_PORT].channels = arg->patch[1]; page->audio.port[2].channels = arg->patch[2]; page->audio.port[3].channels = arg->patch[3]; error = cdsetmode(periph, ¶ms); free(params.mode_buf, M_SCSICD); cam_periph_unlock(periph); } break; case CDIOCGETVOL: { struct ioc_vol *arg = (struct ioc_vol *) addr; struct cd_mode_params params; union cd_pages *page; params.alloc_len = sizeof(union cd_mode_data_6_10); params.mode_buf = malloc(params.alloc_len, M_SCSICD, M_WAITOK | M_ZERO); cam_periph_lock(periph); CAM_DEBUG(periph->path, CAM_DEBUG_SUBTRACE, ("trying to do CDIOCGETVOL\n")); error = cdgetmode(periph, ¶ms, AUDIO_PAGE); if (error) { free(params.mode_buf, M_SCSICD); cam_periph_unlock(periph); break; } page = cdgetpage(¶ms); arg->vol[LEFT_PORT] = page->audio.port[LEFT_PORT].volume; arg->vol[RIGHT_PORT] = page->audio.port[RIGHT_PORT].volume; arg->vol[2] = page->audio.port[2].volume; arg->vol[3] = page->audio.port[3].volume; free(params.mode_buf, M_SCSICD); cam_periph_unlock(periph); } break; case CDIOCSETVOL: { struct ioc_vol *arg = (struct ioc_vol *) addr; struct cd_mode_params params; union cd_pages *page; params.alloc_len = sizeof(union cd_mode_data_6_10); params.mode_buf = malloc(params.alloc_len, M_SCSICD, M_WAITOK | M_ZERO); cam_periph_lock(periph); CAM_DEBUG(periph->path, CAM_DEBUG_SUBTRACE, ("trying to do CDIOCSETVOL\n")); error = cdgetmode(periph, ¶ms, AUDIO_PAGE); if (error) { free(params.mode_buf, M_SCSICD); cam_periph_unlock(periph); break; } page = cdgetpage(¶ms); page->audio.port[LEFT_PORT].channels = CHANNEL_0; page->audio.port[LEFT_PORT].volume = arg->vol[LEFT_PORT]; page->audio.port[RIGHT_PORT].channels = CHANNEL_1; page->audio.port[RIGHT_PORT].volume = arg->vol[RIGHT_PORT]; page->audio.port[2].volume = arg->vol[2]; page->audio.port[3].volume = arg->vol[3]; error = cdsetmode(periph, ¶ms); cam_periph_unlock(periph); free(params.mode_buf, M_SCSICD); } break; case CDIOCSETMONO: { struct cd_mode_params params; union cd_pages *page; params.alloc_len = sizeof(union cd_mode_data_6_10); params.mode_buf = malloc(params.alloc_len, M_SCSICD, M_WAITOK | M_ZERO); cam_periph_lock(periph); CAM_DEBUG(periph->path, CAM_DEBUG_SUBTRACE, ("trying to do CDIOCSETMONO\n")); error = cdgetmode(periph, ¶ms, AUDIO_PAGE); if (error) { free(params.mode_buf, M_SCSICD); cam_periph_unlock(periph); break; } page = cdgetpage(¶ms); page->audio.port[LEFT_PORT].channels = LEFT_CHANNEL | RIGHT_CHANNEL; page->audio.port[RIGHT_PORT].channels = LEFT_CHANNEL | RIGHT_CHANNEL; page->audio.port[2].channels = 0; page->audio.port[3].channels = 0; error = cdsetmode(periph, ¶ms); cam_periph_unlock(periph); free(params.mode_buf, M_SCSICD); } break; case CDIOCSETSTEREO: { struct cd_mode_params params; union cd_pages *page; params.alloc_len = sizeof(union cd_mode_data_6_10); params.mode_buf = malloc(params.alloc_len, M_SCSICD, M_WAITOK | M_ZERO); cam_periph_lock(periph); CAM_DEBUG(periph->path, CAM_DEBUG_SUBTRACE, ("trying to do CDIOCSETSTEREO\n")); error = cdgetmode(periph, ¶ms, AUDIO_PAGE); if (error) { free(params.mode_buf, M_SCSICD); cam_periph_unlock(periph); break; } page = cdgetpage(¶ms); page->audio.port[LEFT_PORT].channels = LEFT_CHANNEL; page->audio.port[RIGHT_PORT].channels = RIGHT_CHANNEL; page->audio.port[2].channels = 0; page->audio.port[3].channels = 0; error = cdsetmode(periph, ¶ms); free(params.mode_buf, M_SCSICD); cam_periph_unlock(periph); } break; case CDIOCSETMUTE: { struct cd_mode_params params; union cd_pages *page; params.alloc_len = sizeof(union cd_mode_data_6_10); params.mode_buf = malloc(params.alloc_len, M_SCSICD, M_WAITOK | M_ZERO); cam_periph_lock(periph); CAM_DEBUG(periph->path, CAM_DEBUG_SUBTRACE, ("trying to do CDIOCSETMUTE\n")); error = cdgetmode(periph, ¶ms, AUDIO_PAGE); if (error) { free(params.mode_buf, M_SCSICD); cam_periph_unlock(periph); break; } page = cdgetpage(¶ms); page->audio.port[LEFT_PORT].channels = 0; page->audio.port[RIGHT_PORT].channels = 0; page->audio.port[2].channels = 0; page->audio.port[3].channels = 0; error = cdsetmode(periph, ¶ms); free(params.mode_buf, M_SCSICD); cam_periph_unlock(periph); } break; case CDIOCSETLEFT: { struct cd_mode_params params; union cd_pages *page; params.alloc_len = sizeof(union cd_mode_data_6_10); params.mode_buf = malloc(params.alloc_len, M_SCSICD, M_WAITOK | M_ZERO); cam_periph_lock(periph); CAM_DEBUG(periph->path, CAM_DEBUG_SUBTRACE, ("trying to do CDIOCSETLEFT\n")); error = cdgetmode(periph, ¶ms, AUDIO_PAGE); if (error) { free(params.mode_buf, M_SCSICD); cam_periph_unlock(periph); break; } page = cdgetpage(¶ms); page->audio.port[LEFT_PORT].channels = LEFT_CHANNEL; page->audio.port[RIGHT_PORT].channels = LEFT_CHANNEL; page->audio.port[2].channels = 0; page->audio.port[3].channels = 0; error = cdsetmode(periph, ¶ms); free(params.mode_buf, M_SCSICD); cam_periph_unlock(periph); } break; case CDIOCSETRIGHT: { struct cd_mode_params params; union cd_pages *page; params.alloc_len = sizeof(union cd_mode_data_6_10); params.mode_buf = malloc(params.alloc_len, M_SCSICD, M_WAITOK | M_ZERO); cam_periph_lock(periph); CAM_DEBUG(periph->path, CAM_DEBUG_SUBTRACE, ("trying to do CDIOCSETRIGHT\n")); error = cdgetmode(periph, ¶ms, AUDIO_PAGE); if (error) { free(params.mode_buf, M_SCSICD); cam_periph_unlock(periph); break; } page = cdgetpage(¶ms); page->audio.port[LEFT_PORT].channels = RIGHT_CHANNEL; page->audio.port[RIGHT_PORT].channels = RIGHT_CHANNEL; page->audio.port[2].channels = 0; page->audio.port[3].channels = 0; error = cdsetmode(periph, ¶ms); free(params.mode_buf, M_SCSICD); cam_periph_unlock(periph); } break; case CDIOCRESUME: cam_periph_lock(periph); error = cdpause(periph, 1); cam_periph_unlock(periph); break; case CDIOCPAUSE: cam_periph_lock(periph); error = cdpause(periph, 0); cam_periph_unlock(periph); break; case CDIOCSTART: cam_periph_lock(periph); error = cdstartunit(periph, 0); cam_periph_unlock(periph); break; case CDIOCCLOSE: cam_periph_lock(periph); error = cdstartunit(periph, 1); cam_periph_unlock(periph); break; case CDIOCSTOP: cam_periph_lock(periph); error = cdstopunit(periph, 0); cam_periph_unlock(periph); break; case CDIOCEJECT: cam_periph_lock(periph); error = cdstopunit(periph, 1); cam_periph_unlock(periph); break; case CDIOCALLOW: cam_periph_lock(periph); cdprevent(periph, PR_ALLOW); cam_periph_unlock(periph); break; case CDIOCPREVENT: cam_periph_lock(periph); cdprevent(periph, PR_PREVENT); cam_periph_unlock(periph); break; case CDIOCSETDEBUG: /* sc_link->flags |= (SDEV_DB1 | SDEV_DB2); */ error = ENOTTY; break; case CDIOCCLRDEBUG: /* sc_link->flags &= ~(SDEV_DB1 | SDEV_DB2); */ error = ENOTTY; break; case CDIOCRESET: /* return (cd_reset(periph)); */ error = ENOTTY; break; case CDRIOCREADSPEED: cam_periph_lock(periph); error = cdsetspeed(periph, *(u_int32_t *)addr, CDR_MAX_SPEED); cam_periph_unlock(periph); break; case CDRIOCWRITESPEED: cam_periph_lock(periph); error = cdsetspeed(periph, CDR_MAX_SPEED, *(u_int32_t *)addr); cam_periph_unlock(periph); break; case CDRIOCGETBLOCKSIZE: *(int *)addr = softc->params.blksize; break; case CDRIOCSETBLOCKSIZE: if (*(int *)addr <= 0) { error = EINVAL; break; } softc->disk->d_sectorsize = softc->params.blksize = *(int *)addr; break; case DVDIOCSENDKEY: case DVDIOCREPORTKEY: { struct dvd_authinfo *authinfo; authinfo = (struct dvd_authinfo *)addr; if (cmd == DVDIOCREPORTKEY) error = cdreportkey(periph, authinfo); else error = cdsendkey(periph, authinfo); break; } case DVDIOCREADSTRUCTURE: { struct dvd_struct *dvdstruct; dvdstruct = (struct dvd_struct *)addr; error = cdreaddvdstructure(periph, dvdstruct); break; } default: cam_periph_lock(periph); error = cam_periph_ioctl(periph, cmd, addr, cderror); cam_periph_unlock(periph); break; } cam_periph_lock(periph); cam_periph_unhold(periph); CAM_DEBUG(periph->path, CAM_DEBUG_TRACE, ("leaving cdioctl\n")); if (error && bootverbose) { printf("scsi_cd.c::ioctl cmd=%08lx error=%d\n", cmd, error); } cam_periph_unlock(periph); return (error); } static void cdprevent(struct cam_periph *periph, int action) { union ccb *ccb; struct cd_softc *softc; int error; CAM_DEBUG(periph->path, CAM_DEBUG_TRACE, ("entering cdprevent\n")); softc = (struct cd_softc *)periph->softc; if (((action == PR_ALLOW) && (softc->flags & CD_FLAG_DISC_LOCKED) == 0) || ((action == PR_PREVENT) && (softc->flags & CD_FLAG_DISC_LOCKED) != 0)) { return; } ccb = cam_periph_getccb(periph, CAM_PRIORITY_NORMAL); scsi_prevent(&ccb->csio, /*retries*/ cd_retry_count, cddone, MSG_SIMPLE_Q_TAG, action, SSD_FULL_SIZE, /* timeout */60000); error = cdrunccb(ccb, cderror, /*cam_flags*/CAM_RETRY_SELTO, /*sense_flags*/SF_RETRY_UA|SF_NO_PRINT); xpt_release_ccb(ccb); if (error == 0) { if (action == PR_ALLOW) softc->flags &= ~CD_FLAG_DISC_LOCKED; else softc->flags |= CD_FLAG_DISC_LOCKED; } } /* * XXX: the disk media and sector size is only really able to change * XXX: while the device is closed. */ static int cdcheckmedia(struct cam_periph *periph) { struct cd_softc *softc; struct ioc_toc_header *toch; struct cd_toc_single leadout; u_int32_t size, toclen; int error, num_entries, cdindex; softc = (struct cd_softc *)periph->softc; cdprevent(periph, PR_PREVENT); softc->disk->d_sectorsize = 2048; softc->disk->d_mediasize = 0; /* * Get the disc size and block size. If we can't get it, we don't * have media, most likely. */ if ((error = cdsize(periph, &size)) != 0) { softc->flags &= ~(CD_FLAG_VALID_MEDIA|CD_FLAG_VALID_TOC); cdprevent(periph, PR_ALLOW); return (error); } else { softc->flags |= CD_FLAG_SAW_MEDIA | CD_FLAG_VALID_MEDIA; softc->disk->d_sectorsize = softc->params.blksize; softc->disk->d_mediasize = (off_t)softc->params.blksize * softc->params.disksize; } /* * Now we check the table of contents. This (currently) is only * used for the CDIOCPLAYTRACKS ioctl. It may be used later to do * things like present a separate entry in /dev for each track, * like that acd(4) driver does. */ bzero(&softc->toc, sizeof(softc->toc)); toch = &softc->toc.header; /* * We will get errors here for media that doesn't have a table of * contents. According to the MMC-3 spec: "When a Read TOC/PMA/ATIP * command is presented for a DDCD/CD-R/RW media, where the first TOC * has not been recorded (no complete session) and the Format codes * 0000b, 0001b, or 0010b are specified, this command shall be rejected * with an INVALID FIELD IN CDB. Devices that are not capable of * reading an incomplete session on DDC/CD-R/RW media shall report * CANNOT READ MEDIUM - INCOMPATIBLE FORMAT." * * So this isn't fatal if we can't read the table of contents, it * just means that the user won't be able to issue the play tracks * ioctl, and likely lots of other stuff won't work either. They * need to burn the CD before we can do a whole lot with it. So * we don't print anything here if we get an error back. */ error = cdreadtoc(periph, 0, 0, (u_int8_t *)toch, sizeof(*toch), SF_NO_PRINT); /* * Errors in reading the table of contents aren't fatal, we just * won't have a valid table of contents cached. */ if (error != 0) { error = 0; bzero(&softc->toc, sizeof(softc->toc)); goto bailout; } if (softc->quirks & CD_Q_BCD_TRACKS) { toch->starting_track = bcd2bin(toch->starting_track); toch->ending_track = bcd2bin(toch->ending_track); } /* Number of TOC entries, plus leadout */ num_entries = (toch->ending_track - toch->starting_track) + 2; if (num_entries <= 0) goto bailout; toclen = num_entries * sizeof(struct cd_toc_entry); error = cdreadtoc(periph, CD_MSF_FORMAT, toch->starting_track, (u_int8_t *)&softc->toc, toclen + sizeof(*toch), SF_NO_PRINT); if (error != 0) { error = 0; bzero(&softc->toc, sizeof(softc->toc)); goto bailout; } if (softc->quirks & CD_Q_BCD_TRACKS) { toch->starting_track = bcd2bin(toch->starting_track); toch->ending_track = bcd2bin(toch->ending_track); } /* * XXX KDM is this necessary? Probably only if the drive doesn't * return leadout information with the table of contents. */ cdindex = toch->starting_track + num_entries -1; if (cdindex == toch->ending_track + 1) { error = cdreadtoc(periph, CD_MSF_FORMAT, LEADOUT, (u_int8_t *)&leadout, sizeof(leadout), SF_NO_PRINT); if (error != 0) { error = 0; goto bailout; } softc->toc.entries[cdindex - toch->starting_track] = leadout.entry; } if (softc->quirks & CD_Q_BCD_TRACKS) { for (cdindex = 0; cdindex < num_entries - 1; cdindex++) { softc->toc.entries[cdindex].track = bcd2bin(softc->toc.entries[cdindex].track); } } softc->flags |= CD_FLAG_VALID_TOC; /* If the first track is audio, correct sector size. */ if ((softc->toc.entries[0].control & 4) == 0) { softc->disk->d_sectorsize = softc->params.blksize = 2352; softc->disk->d_mediasize = (off_t)softc->params.blksize * softc->params.disksize; } bailout: /* * We unconditionally (re)set the blocksize each time the * CD device is opened. This is because the CD can change, * and therefore the blocksize might change. * XXX problems here if some slice or partition is still * open with the old size? */ if ((softc->disk->d_devstat->flags & DEVSTAT_BS_UNAVAILABLE) != 0) softc->disk->d_devstat->flags &= ~DEVSTAT_BS_UNAVAILABLE; softc->disk->d_devstat->block_size = softc->params.blksize; return (error); } static int cdsize(struct cam_periph *periph, u_int32_t *size) { struct cd_softc *softc; union ccb *ccb; struct scsi_read_capacity_data *rcap_buf; int error; CAM_DEBUG(periph->path, CAM_DEBUG_TRACE, ("entering cdsize\n")); softc = (struct cd_softc *)periph->softc; ccb = cam_periph_getccb(periph, CAM_PRIORITY_NORMAL); /* XXX Should be M_WAITOK */ rcap_buf = malloc(sizeof(struct scsi_read_capacity_data), M_SCSICD, M_NOWAIT | M_ZERO); if (rcap_buf == NULL) return (ENOMEM); scsi_read_capacity(&ccb->csio, /*retries*/ cd_retry_count, cddone, MSG_SIMPLE_Q_TAG, rcap_buf, SSD_FULL_SIZE, /* timeout */20000); error = cdrunccb(ccb, cderror, /*cam_flags*/CAM_RETRY_SELTO, /*sense_flags*/SF_RETRY_UA|SF_NO_PRINT); xpt_release_ccb(ccb); softc->params.disksize = scsi_4btoul(rcap_buf->addr) + 1; softc->params.blksize = scsi_4btoul(rcap_buf->length); /* Make sure we got at least some block size. */ if (error == 0 && softc->params.blksize == 0) error = EIO; /* * SCSI-3 mandates that the reported blocksize shall be 2048. * Older drives sometimes report funny values, trim it down to * 2048, or other parts of the kernel will get confused. * * XXX we leave drives alone that might report 512 bytes, as * well as drives reporting more weird sizes like perhaps 4K. */ if (softc->params.blksize > 2048 && softc->params.blksize <= 2352) softc->params.blksize = 2048; free(rcap_buf, M_SCSICD); *size = softc->params.disksize; return (error); } static int cd6byteworkaround(union ccb *ccb) { u_int8_t *cdb; struct cam_periph *periph; struct cd_softc *softc; struct cd_mode_params *params; int frozen, found; periph = xpt_path_periph(ccb->ccb_h.path); softc = (struct cd_softc *)periph->softc; cdb = ccb->csio.cdb_io.cdb_bytes; if ((ccb->ccb_h.flags & CAM_CDB_POINTER) || ((cdb[0] != MODE_SENSE_6) && (cdb[0] != MODE_SELECT_6))) return (0); /* * Because there is no convenient place to stash the overall * cd_mode_params structure pointer, we have to grab it like this. * This means that ALL MODE_SENSE and MODE_SELECT requests in the * cd(4) driver MUST go through cdgetmode() and cdsetmode()! * * XXX It would be nice if, at some point, we could increase the * number of available peripheral private pointers. Both pointers * are currently used in most every peripheral driver. */ found = 0; STAILQ_FOREACH(params, &softc->mode_queue, links) { if (params->mode_buf == ccb->csio.data_ptr) { found = 1; break; } } /* * This shouldn't happen. All mode sense and mode select * operations in the cd(4) driver MUST go through cdgetmode() and * cdsetmode()! */ if (found == 0) { xpt_print(periph->path, "mode buffer not found in mode queue!\n"); return (0); } params->cdb_size = 10; softc->minimum_command_size = 10; xpt_print(ccb->ccb_h.path, "%s(6) failed, increasing minimum CDB size to 10 bytes\n", (cdb[0] == MODE_SENSE_6) ? "MODE_SENSE" : "MODE_SELECT"); if (cdb[0] == MODE_SENSE_6) { struct scsi_mode_sense_10 ms10; struct scsi_mode_sense_6 *ms6; int len; ms6 = (struct scsi_mode_sense_6 *)cdb; bzero(&ms10, sizeof(ms10)); ms10.opcode = MODE_SENSE_10; ms10.byte2 = ms6->byte2; ms10.page = ms6->page; /* * 10 byte mode header, block descriptor, * sizeof(union cd_pages) */ len = sizeof(struct cd_mode_data_10); ccb->csio.dxfer_len = len; scsi_ulto2b(len, ms10.length); ms10.control = ms6->control; bcopy(&ms10, cdb, 10); ccb->csio.cdb_len = 10; } else { struct scsi_mode_select_10 ms10; struct scsi_mode_select_6 *ms6; struct scsi_mode_header_6 *header6; struct scsi_mode_header_10 *header10; struct scsi_mode_page_header *page_header; int blk_desc_len, page_num, page_size, len; ms6 = (struct scsi_mode_select_6 *)cdb; bzero(&ms10, sizeof(ms10)); ms10.opcode = MODE_SELECT_10; ms10.byte2 = ms6->byte2; header6 = (struct scsi_mode_header_6 *)params->mode_buf; header10 = (struct scsi_mode_header_10 *)params->mode_buf; page_header = find_mode_page_6(header6); page_num = page_header->page_code; blk_desc_len = header6->blk_desc_len; page_size = cdgetpagesize(page_num); if (page_size != (page_header->page_length + sizeof(*page_header))) page_size = page_header->page_length + sizeof(*page_header); len = sizeof(*header10) + blk_desc_len + page_size; len = min(params->alloc_len, len); /* * Since the 6 byte parameter header is shorter than the 10 * byte parameter header, we need to copy the actual mode * page data, and the block descriptor, if any, so things wind * up in the right place. The regions will overlap, but * bcopy() does the right thing. */ bcopy(params->mode_buf + sizeof(*header6), params->mode_buf + sizeof(*header10), len - sizeof(*header10)); /* Make sure these fields are set correctly. */ scsi_ulto2b(0, header10->data_length); header10->medium_type = 0; scsi_ulto2b(blk_desc_len, header10->blk_desc_len); ccb->csio.dxfer_len = len; scsi_ulto2b(len, ms10.length); ms10.control = ms6->control; bcopy(&ms10, cdb, 10); ccb->csio.cdb_len = 10; } frozen = (ccb->ccb_h.status & CAM_DEV_QFRZN) != 0; ccb->ccb_h.status = CAM_REQUEUE_REQ; xpt_action(ccb); if (frozen) { cam_release_devq(ccb->ccb_h.path, /*relsim_flags*/0, /*openings*/0, /*timeout*/0, /*getcount_only*/0); } return (ERESTART); } static int cderror(union ccb *ccb, u_int32_t cam_flags, u_int32_t sense_flags) { struct cd_softc *softc; struct cam_periph *periph; int error, error_code, sense_key, asc, ascq; periph = xpt_path_periph(ccb->ccb_h.path); softc = (struct cd_softc *)periph->softc; error = 0; /* * We use a status of CAM_REQ_INVALID as shorthand -- if a 6 byte * CDB comes back with this particular error, try transforming it * into the 10 byte version. */ if ((ccb->ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_INVALID) { error = cd6byteworkaround(ccb); } else if (scsi_extract_sense_ccb(ccb, &error_code, &sense_key, &asc, &ascq)) { if (sense_key == SSD_KEY_ILLEGAL_REQUEST) error = cd6byteworkaround(ccb); else if (sense_key == SSD_KEY_UNIT_ATTENTION && asc == 0x28 && ascq == 0x00) disk_media_changed(softc->disk, M_NOWAIT); else if (sense_key == SSD_KEY_NOT_READY && asc == 0x3a && (softc->flags & CD_FLAG_SAW_MEDIA)) { softc->flags &= ~CD_FLAG_SAW_MEDIA; disk_media_gone(softc->disk, M_NOWAIT); } } if (error == ERESTART) return (error); /* * XXX * Until we have a better way of doing pack validation, * don't treat UAs as errors. */ sense_flags |= SF_RETRY_UA; if (softc->quirks & CD_Q_RETRY_BUSY) sense_flags |= SF_RETRY_BUSY; return (cam_periph_error(ccb, cam_flags, sense_flags, &softc->saved_ccb)); } static void cdmediapoll(void *arg) { struct cam_periph *periph = arg; struct cd_softc *softc = periph->softc; if (softc->state == CD_STATE_NORMAL && !softc->tur && softc->outstanding_cmds == 0) { if (cam_periph_acquire(periph) == CAM_REQ_CMP) { softc->tur = 1; xpt_schedule(periph, CAM_PRIORITY_NORMAL); } } /* Queue us up again */ if (cd_poll_period != 0) callout_schedule(&softc->mediapoll_c, cd_poll_period * hz); } /* * Read table of contents */ static int cdreadtoc(struct cam_periph *periph, u_int32_t mode, u_int32_t start, u_int8_t *data, u_int32_t len, u_int32_t sense_flags) { struct scsi_read_toc *scsi_cmd; u_int32_t ntoc; struct ccb_scsiio *csio; union ccb *ccb; int error; ntoc = len; error = 0; ccb = cam_periph_getccb(periph, CAM_PRIORITY_NORMAL); csio = &ccb->csio; cam_fill_csio(csio, /* retries */ cd_retry_count, /* cbfcnp */ cddone, /* flags */ CAM_DIR_IN, /* tag_action */ MSG_SIMPLE_Q_TAG, /* data_ptr */ data, /* dxfer_len */ len, /* sense_len */ SSD_FULL_SIZE, sizeof(struct scsi_read_toc), /* timeout */ 50000); scsi_cmd = (struct scsi_read_toc *)&csio->cdb_io.cdb_bytes; bzero (scsi_cmd, sizeof(*scsi_cmd)); if (mode == CD_MSF_FORMAT) scsi_cmd->byte2 |= CD_MSF; scsi_cmd->from_track = start; /* scsi_ulto2b(ntoc, (u_int8_t *)scsi_cmd->data_len); */ scsi_cmd->data_len[0] = (ntoc) >> 8; scsi_cmd->data_len[1] = (ntoc) & 0xff; scsi_cmd->op_code = READ_TOC; error = cdrunccb(ccb, cderror, /*cam_flags*/CAM_RETRY_SELTO, /*sense_flags*/SF_RETRY_UA | sense_flags); xpt_release_ccb(ccb); return(error); } static int cdreadsubchannel(struct cam_periph *periph, u_int32_t mode, u_int32_t format, int track, struct cd_sub_channel_info *data, u_int32_t len) { struct scsi_read_subchannel *scsi_cmd; struct ccb_scsiio *csio; union ccb *ccb; int error; error = 0; ccb = cam_periph_getccb(periph, CAM_PRIORITY_NORMAL); csio = &ccb->csio; cam_fill_csio(csio, /* retries */ cd_retry_count, /* cbfcnp */ cddone, /* flags */ CAM_DIR_IN, /* tag_action */ MSG_SIMPLE_Q_TAG, /* data_ptr */ (u_int8_t *)data, /* dxfer_len */ len, /* sense_len */ SSD_FULL_SIZE, sizeof(struct scsi_read_subchannel), /* timeout */ 50000); scsi_cmd = (struct scsi_read_subchannel *)&csio->cdb_io.cdb_bytes; bzero (scsi_cmd, sizeof(*scsi_cmd)); scsi_cmd->op_code = READ_SUBCHANNEL; if (mode == CD_MSF_FORMAT) scsi_cmd->byte1 |= CD_MSF; scsi_cmd->byte2 = SRS_SUBQ; scsi_cmd->subchan_format = format; scsi_cmd->track = track; scsi_ulto2b(len, (u_int8_t *)scsi_cmd->data_len); scsi_cmd->control = 0; error = cdrunccb(ccb, cderror, /*cam_flags*/CAM_RETRY_SELTO, /*sense_flags*/SF_RETRY_UA); xpt_release_ccb(ccb); return(error); } /* * All MODE_SENSE requests in the cd(4) driver MUST go through this * routine. See comments in cd6byteworkaround() for details. */ static int cdgetmode(struct cam_periph *periph, struct cd_mode_params *data, u_int32_t page) { struct ccb_scsiio *csio; struct cd_softc *softc; union ccb *ccb; int param_len; int error; softc = (struct cd_softc *)periph->softc; ccb = cam_periph_getccb(periph, CAM_PRIORITY_NORMAL); csio = &ccb->csio; data->cdb_size = softc->minimum_command_size; if (data->cdb_size < 10) param_len = sizeof(struct cd_mode_data); else param_len = sizeof(struct cd_mode_data_10); /* Don't say we've got more room than we actually allocated */ param_len = min(param_len, data->alloc_len); scsi_mode_sense_len(csio, /* retries */ cd_retry_count, /* cbfcnp */ cddone, /* tag_action */ MSG_SIMPLE_Q_TAG, /* dbd */ 0, /* page_code */ SMS_PAGE_CTRL_CURRENT, /* page */ page, /* param_buf */ data->mode_buf, /* param_len */ param_len, /* minimum_cmd_size */ softc->minimum_command_size, /* sense_len */ SSD_FULL_SIZE, /* timeout */ 50000); /* * It would be nice not to have to do this, but there's no * available pointer in the CCB that would allow us to stuff the * mode params structure in there and retrieve it in * cd6byteworkaround(), so we can set the cdb size. The cdb size * lets the caller know what CDB size we ended up using, so they * can find the actual mode page offset. */ STAILQ_INSERT_TAIL(&softc->mode_queue, data, links); error = cdrunccb(ccb, cderror, /*cam_flags*/CAM_RETRY_SELTO, /*sense_flags*/SF_RETRY_UA); xpt_release_ccb(ccb); STAILQ_REMOVE(&softc->mode_queue, data, cd_mode_params, links); /* * This is a bit of belt-and-suspenders checking, but if we run * into a situation where the target sends back multiple block * descriptors, we might not have enough space in the buffer to * see the whole mode page. Better to return an error than * potentially access memory beyond our malloced region. */ if (error == 0) { u_int32_t data_len; if (data->cdb_size == 10) { struct scsi_mode_header_10 *hdr10; hdr10 = (struct scsi_mode_header_10 *)data->mode_buf; data_len = scsi_2btoul(hdr10->data_length); data_len += sizeof(hdr10->data_length); } else { struct scsi_mode_header_6 *hdr6; hdr6 = (struct scsi_mode_header_6 *)data->mode_buf; data_len = hdr6->data_length; data_len += sizeof(hdr6->data_length); } /* * Complain if there is more mode data available than we * allocated space for. This could potentially happen if * we miscalculated the page length for some reason, if the * drive returns multiple block descriptors, or if it sets * the data length incorrectly. */ if (data_len > data->alloc_len) { xpt_print(periph->path, "allocated modepage %d length " "%d < returned length %d\n", page, data->alloc_len, data_len); error = ENOSPC; } } return (error); } /* * All MODE_SELECT requests in the cd(4) driver MUST go through this * routine. See comments in cd6byteworkaround() for details. */ static int cdsetmode(struct cam_periph *periph, struct cd_mode_params *data) { struct ccb_scsiio *csio; struct cd_softc *softc; union ccb *ccb; int cdb_size, param_len; int error; softc = (struct cd_softc *)periph->softc; ccb = cam_periph_getccb(periph, CAM_PRIORITY_NORMAL); csio = &ccb->csio; error = 0; /* * If the data is formatted for the 10 byte version of the mode * select parameter list, we need to use the 10 byte CDB. * Otherwise, we use whatever the stored minimum command size. */ if (data->cdb_size == 10) cdb_size = data->cdb_size; else cdb_size = softc->minimum_command_size; if (cdb_size >= 10) { struct scsi_mode_header_10 *mode_header; u_int32_t data_len; mode_header = (struct scsi_mode_header_10 *)data->mode_buf; data_len = scsi_2btoul(mode_header->data_length); scsi_ulto2b(0, mode_header->data_length); /* * SONY drives do not allow a mode select with a medium_type * value that has just been returned by a mode sense; use a * medium_type of 0 (Default) instead. */ mode_header->medium_type = 0; /* * Pass back whatever the drive passed to us, plus the size * of the data length field. */ param_len = data_len + sizeof(mode_header->data_length); } else { struct scsi_mode_header_6 *mode_header; mode_header = (struct scsi_mode_header_6 *)data->mode_buf; param_len = mode_header->data_length + 1; mode_header->data_length = 0; /* * SONY drives do not allow a mode select with a medium_type * value that has just been returned by a mode sense; use a * medium_type of 0 (Default) instead. */ mode_header->medium_type = 0; } /* Don't say we've got more room than we actually allocated */ param_len = min(param_len, data->alloc_len); scsi_mode_select_len(csio, /* retries */ cd_retry_count, /* cbfcnp */ cddone, /* tag_action */ MSG_SIMPLE_Q_TAG, /* scsi_page_fmt */ 1, /* save_pages */ 0, /* param_buf */ data->mode_buf, /* param_len */ param_len, /* minimum_cmd_size */ cdb_size, /* sense_len */ SSD_FULL_SIZE, /* timeout */ 50000); /* See comments in cdgetmode() and cd6byteworkaround(). */ STAILQ_INSERT_TAIL(&softc->mode_queue, data, links); error = cdrunccb(ccb, cderror, /*cam_flags*/CAM_RETRY_SELTO, /*sense_flags*/SF_RETRY_UA); xpt_release_ccb(ccb); STAILQ_REMOVE(&softc->mode_queue, data, cd_mode_params, links); return (error); } static int cdplay(struct cam_periph *periph, u_int32_t blk, u_int32_t len) { struct ccb_scsiio *csio; union ccb *ccb; int error; u_int8_t cdb_len; error = 0; ccb = cam_periph_getccb(periph, CAM_PRIORITY_NORMAL); csio = &ccb->csio; /* * Use the smallest possible command to perform the operation. */ if ((len & 0xffff0000) == 0) { /* * We can fit in a 10 byte cdb. */ struct scsi_play_10 *scsi_cmd; scsi_cmd = (struct scsi_play_10 *)&csio->cdb_io.cdb_bytes; bzero (scsi_cmd, sizeof(*scsi_cmd)); scsi_cmd->op_code = PLAY_10; scsi_ulto4b(blk, (u_int8_t *)scsi_cmd->blk_addr); scsi_ulto2b(len, (u_int8_t *)scsi_cmd->xfer_len); cdb_len = sizeof(*scsi_cmd); } else { struct scsi_play_12 *scsi_cmd; scsi_cmd = (struct scsi_play_12 *)&csio->cdb_io.cdb_bytes; bzero (scsi_cmd, sizeof(*scsi_cmd)); scsi_cmd->op_code = PLAY_12; scsi_ulto4b(blk, (u_int8_t *)scsi_cmd->blk_addr); scsi_ulto4b(len, (u_int8_t *)scsi_cmd->xfer_len); cdb_len = sizeof(*scsi_cmd); } cam_fill_csio(csio, /*retries*/ cd_retry_count, cddone, /*flags*/CAM_DIR_NONE, MSG_SIMPLE_Q_TAG, /*dataptr*/NULL, /*datalen*/0, /*sense_len*/SSD_FULL_SIZE, cdb_len, /*timeout*/50 * 1000); error = cdrunccb(ccb, cderror, /*cam_flags*/CAM_RETRY_SELTO, /*sense_flags*/SF_RETRY_UA); xpt_release_ccb(ccb); return(error); } static int cdplaymsf(struct cam_periph *periph, u_int32_t startm, u_int32_t starts, u_int32_t startf, u_int32_t endm, u_int32_t ends, u_int32_t endf) { struct scsi_play_msf *scsi_cmd; struct ccb_scsiio *csio; union ccb *ccb; int error; error = 0; ccb = cam_periph_getccb(periph, CAM_PRIORITY_NORMAL); csio = &ccb->csio; cam_fill_csio(csio, /* retries */ cd_retry_count, /* cbfcnp */ cddone, /* flags */ CAM_DIR_NONE, /* tag_action */ MSG_SIMPLE_Q_TAG, /* data_ptr */ NULL, /* dxfer_len */ 0, /* sense_len */ SSD_FULL_SIZE, sizeof(struct scsi_play_msf), /* timeout */ 50000); scsi_cmd = (struct scsi_play_msf *)&csio->cdb_io.cdb_bytes; bzero (scsi_cmd, sizeof(*scsi_cmd)); scsi_cmd->op_code = PLAY_MSF; scsi_cmd->start_m = startm; scsi_cmd->start_s = starts; scsi_cmd->start_f = startf; scsi_cmd->end_m = endm; scsi_cmd->end_s = ends; scsi_cmd->end_f = endf; error = cdrunccb(ccb, cderror, /*cam_flags*/CAM_RETRY_SELTO, /*sense_flags*/SF_RETRY_UA); xpt_release_ccb(ccb); return(error); } static int cdplaytracks(struct cam_periph *periph, u_int32_t strack, u_int32_t sindex, u_int32_t etrack, u_int32_t eindex) { struct scsi_play_track *scsi_cmd; struct ccb_scsiio *csio; union ccb *ccb; int error; error = 0; ccb = cam_periph_getccb(periph, CAM_PRIORITY_NORMAL); csio = &ccb->csio; cam_fill_csio(csio, /* retries */ cd_retry_count, /* cbfcnp */ cddone, /* flags */ CAM_DIR_NONE, /* tag_action */ MSG_SIMPLE_Q_TAG, /* data_ptr */ NULL, /* dxfer_len */ 0, /* sense_len */ SSD_FULL_SIZE, sizeof(struct scsi_play_track), /* timeout */ 50000); scsi_cmd = (struct scsi_play_track *)&csio->cdb_io.cdb_bytes; bzero (scsi_cmd, sizeof(*scsi_cmd)); scsi_cmd->op_code = PLAY_TRACK; scsi_cmd->start_track = strack; scsi_cmd->start_index = sindex; scsi_cmd->end_track = etrack; scsi_cmd->end_index = eindex; error = cdrunccb(ccb, cderror, /*cam_flags*/CAM_RETRY_SELTO, /*sense_flags*/SF_RETRY_UA); xpt_release_ccb(ccb); return(error); } static int cdpause(struct cam_periph *periph, u_int32_t go) { struct scsi_pause *scsi_cmd; struct ccb_scsiio *csio; union ccb *ccb; int error; error = 0; ccb = cam_periph_getccb(periph, CAM_PRIORITY_NORMAL); csio = &ccb->csio; cam_fill_csio(csio, /* retries */ cd_retry_count, /* cbfcnp */ cddone, /* flags */ CAM_DIR_NONE, /* tag_action */ MSG_SIMPLE_Q_TAG, /* data_ptr */ NULL, /* dxfer_len */ 0, /* sense_len */ SSD_FULL_SIZE, sizeof(struct scsi_pause), /* timeout */ 50000); scsi_cmd = (struct scsi_pause *)&csio->cdb_io.cdb_bytes; bzero (scsi_cmd, sizeof(*scsi_cmd)); scsi_cmd->op_code = PAUSE; scsi_cmd->resume = go; error = cdrunccb(ccb, cderror, /*cam_flags*/CAM_RETRY_SELTO, /*sense_flags*/SF_RETRY_UA); xpt_release_ccb(ccb); return(error); } static int cdstartunit(struct cam_periph *periph, int load) { union ccb *ccb; int error; error = 0; ccb = cam_periph_getccb(periph, CAM_PRIORITY_NORMAL); scsi_start_stop(&ccb->csio, /* retries */ cd_retry_count, /* cbfcnp */ cddone, /* tag_action */ MSG_SIMPLE_Q_TAG, /* start */ TRUE, /* load_eject */ load, /* immediate */ FALSE, /* sense_len */ SSD_FULL_SIZE, /* timeout */ 50000); error = cdrunccb(ccb, cderror, /*cam_flags*/CAM_RETRY_SELTO, /*sense_flags*/SF_RETRY_UA); xpt_release_ccb(ccb); return(error); } static int cdstopunit(struct cam_periph *periph, u_int32_t eject) { union ccb *ccb; int error; error = 0; ccb = cam_periph_getccb(periph, CAM_PRIORITY_NORMAL); scsi_start_stop(&ccb->csio, /* retries */ cd_retry_count, /* cbfcnp */ cddone, /* tag_action */ MSG_SIMPLE_Q_TAG, /* start */ FALSE, /* load_eject */ eject, /* immediate */ FALSE, /* sense_len */ SSD_FULL_SIZE, /* timeout */ 50000); error = cdrunccb(ccb, cderror, /*cam_flags*/CAM_RETRY_SELTO, /*sense_flags*/SF_RETRY_UA); xpt_release_ccb(ccb); return(error); } static int cdsetspeed(struct cam_periph *periph, u_int32_t rdspeed, u_int32_t wrspeed) { struct scsi_set_speed *scsi_cmd; struct ccb_scsiio *csio; union ccb *ccb; int error; error = 0; ccb = cam_periph_getccb(periph, CAM_PRIORITY_NORMAL); csio = &ccb->csio; /* Preserve old behavior: units in multiples of CDROM speed */ if (rdspeed < 177) rdspeed *= 177; if (wrspeed < 177) wrspeed *= 177; cam_fill_csio(csio, /* retries */ cd_retry_count, /* cbfcnp */ cddone, /* flags */ CAM_DIR_NONE, /* tag_action */ MSG_SIMPLE_Q_TAG, /* data_ptr */ NULL, /* dxfer_len */ 0, /* sense_len */ SSD_FULL_SIZE, sizeof(struct scsi_set_speed), /* timeout */ 50000); scsi_cmd = (struct scsi_set_speed *)&csio->cdb_io.cdb_bytes; bzero(scsi_cmd, sizeof(*scsi_cmd)); scsi_cmd->opcode = SET_CD_SPEED; scsi_ulto2b(rdspeed, scsi_cmd->readspeed); scsi_ulto2b(wrspeed, scsi_cmd->writespeed); error = cdrunccb(ccb, cderror, /*cam_flags*/CAM_RETRY_SELTO, /*sense_flags*/SF_RETRY_UA); xpt_release_ccb(ccb); return(error); } static int cdreportkey(struct cam_periph *periph, struct dvd_authinfo *authinfo) { union ccb *ccb; u_int8_t *databuf; u_int32_t lba; int error; int length; error = 0; databuf = NULL; lba = 0; switch (authinfo->format) { case DVD_REPORT_AGID: length = sizeof(struct scsi_report_key_data_agid); break; case DVD_REPORT_CHALLENGE: length = sizeof(struct scsi_report_key_data_challenge); break; case DVD_REPORT_KEY1: length = sizeof(struct scsi_report_key_data_key1_key2); break; case DVD_REPORT_TITLE_KEY: length = sizeof(struct scsi_report_key_data_title); /* The lba field is only set for the title key */ lba = authinfo->lba; break; case DVD_REPORT_ASF: length = sizeof(struct scsi_report_key_data_asf); break; case DVD_REPORT_RPC: length = sizeof(struct scsi_report_key_data_rpc); break; case DVD_INVALIDATE_AGID: length = 0; break; default: return (EINVAL); } if (length != 0) { databuf = malloc(length, M_DEVBUF, M_WAITOK | M_ZERO); } else databuf = NULL; cam_periph_lock(periph); ccb = cam_periph_getccb(periph, CAM_PRIORITY_NORMAL); scsi_report_key(&ccb->csio, /* retries */ cd_retry_count, /* cbfcnp */ cddone, /* tag_action */ MSG_SIMPLE_Q_TAG, /* lba */ lba, /* agid */ authinfo->agid, /* key_format */ authinfo->format, /* data_ptr */ databuf, /* dxfer_len */ length, /* sense_len */ SSD_FULL_SIZE, /* timeout */ 50000); error = cdrunccb(ccb, cderror, /*cam_flags*/CAM_RETRY_SELTO, /*sense_flags*/SF_RETRY_UA); if (error != 0) goto bailout; if (ccb->csio.resid != 0) { xpt_print(periph->path, "warning, residual for report key " "command is %d\n", ccb->csio.resid); } switch(authinfo->format) { case DVD_REPORT_AGID: { struct scsi_report_key_data_agid *agid_data; agid_data = (struct scsi_report_key_data_agid *)databuf; authinfo->agid = (agid_data->agid & RKD_AGID_MASK) >> RKD_AGID_SHIFT; break; } case DVD_REPORT_CHALLENGE: { struct scsi_report_key_data_challenge *chal_data; chal_data = (struct scsi_report_key_data_challenge *)databuf; bcopy(chal_data->challenge_key, authinfo->keychal, min(sizeof(chal_data->challenge_key), sizeof(authinfo->keychal))); break; } case DVD_REPORT_KEY1: { struct scsi_report_key_data_key1_key2 *key1_data; key1_data = (struct scsi_report_key_data_key1_key2 *)databuf; bcopy(key1_data->key1, authinfo->keychal, min(sizeof(key1_data->key1), sizeof(authinfo->keychal))); break; } case DVD_REPORT_TITLE_KEY: { struct scsi_report_key_data_title *title_data; title_data = (struct scsi_report_key_data_title *)databuf; authinfo->cpm = (title_data->byte0 & RKD_TITLE_CPM) >> RKD_TITLE_CPM_SHIFT; authinfo->cp_sec = (title_data->byte0 & RKD_TITLE_CP_SEC) >> RKD_TITLE_CP_SEC_SHIFT; authinfo->cgms = (title_data->byte0 & RKD_TITLE_CMGS_MASK) >> RKD_TITLE_CMGS_SHIFT; bcopy(title_data->title_key, authinfo->keychal, min(sizeof(title_data->title_key), sizeof(authinfo->keychal))); break; } case DVD_REPORT_ASF: { struct scsi_report_key_data_asf *asf_data; asf_data = (struct scsi_report_key_data_asf *)databuf; authinfo->asf = asf_data->success & RKD_ASF_SUCCESS; break; } case DVD_REPORT_RPC: { struct scsi_report_key_data_rpc *rpc_data; rpc_data = (struct scsi_report_key_data_rpc *)databuf; authinfo->reg_type = (rpc_data->byte4 & RKD_RPC_TYPE_MASK) >> RKD_RPC_TYPE_SHIFT; authinfo->vend_rsts = (rpc_data->byte4 & RKD_RPC_VENDOR_RESET_MASK) >> RKD_RPC_VENDOR_RESET_SHIFT; authinfo->user_rsts = rpc_data->byte4 & RKD_RPC_USER_RESET_MASK; authinfo->region = rpc_data->region_mask; authinfo->rpc_scheme = rpc_data->rpc_scheme1; break; } case DVD_INVALIDATE_AGID: break; default: /* This should be impossible, since we checked above */ error = EINVAL; goto bailout; break; /* NOTREACHED */ } bailout: xpt_release_ccb(ccb); cam_periph_unlock(periph); if (databuf != NULL) free(databuf, M_DEVBUF); return(error); } static int cdsendkey(struct cam_periph *periph, struct dvd_authinfo *authinfo) { union ccb *ccb; u_int8_t *databuf; int length; int error; error = 0; databuf = NULL; switch(authinfo->format) { case DVD_SEND_CHALLENGE: { struct scsi_report_key_data_challenge *challenge_data; length = sizeof(*challenge_data); challenge_data = malloc(length, M_DEVBUF, M_WAITOK | M_ZERO); databuf = (u_int8_t *)challenge_data; scsi_ulto2b(length - sizeof(challenge_data->data_len), challenge_data->data_len); bcopy(authinfo->keychal, challenge_data->challenge_key, min(sizeof(authinfo->keychal), sizeof(challenge_data->challenge_key))); break; } case DVD_SEND_KEY2: { struct scsi_report_key_data_key1_key2 *key2_data; length = sizeof(*key2_data); key2_data = malloc(length, M_DEVBUF, M_WAITOK | M_ZERO); databuf = (u_int8_t *)key2_data; scsi_ulto2b(length - sizeof(key2_data->data_len), key2_data->data_len); bcopy(authinfo->keychal, key2_data->key1, min(sizeof(authinfo->keychal), sizeof(key2_data->key1))); break; } case DVD_SEND_RPC: { struct scsi_send_key_data_rpc *rpc_data; length = sizeof(*rpc_data); rpc_data = malloc(length, M_DEVBUF, M_WAITOK | M_ZERO); databuf = (u_int8_t *)rpc_data; scsi_ulto2b(length - sizeof(rpc_data->data_len), rpc_data->data_len); rpc_data->region_code = authinfo->region; break; } default: return (EINVAL); } cam_periph_lock(periph); ccb = cam_periph_getccb(periph, CAM_PRIORITY_NORMAL); scsi_send_key(&ccb->csio, /* retries */ cd_retry_count, /* cbfcnp */ cddone, /* tag_action */ MSG_SIMPLE_Q_TAG, /* agid */ authinfo->agid, /* key_format */ authinfo->format, /* data_ptr */ databuf, /* dxfer_len */ length, /* sense_len */ SSD_FULL_SIZE, /* timeout */ 50000); error = cdrunccb(ccb, cderror, /*cam_flags*/CAM_RETRY_SELTO, /*sense_flags*/SF_RETRY_UA); xpt_release_ccb(ccb); cam_periph_unlock(periph); if (databuf != NULL) free(databuf, M_DEVBUF); return(error); } static int cdreaddvdstructure(struct cam_periph *periph, struct dvd_struct *dvdstruct) { union ccb *ccb; u_int8_t *databuf; u_int32_t address; int error; int length; error = 0; databuf = NULL; /* The address is reserved for many of the formats */ address = 0; switch(dvdstruct->format) { case DVD_STRUCT_PHYSICAL: length = sizeof(struct scsi_read_dvd_struct_data_physical); break; case DVD_STRUCT_COPYRIGHT: length = sizeof(struct scsi_read_dvd_struct_data_copyright); break; case DVD_STRUCT_DISCKEY: length = sizeof(struct scsi_read_dvd_struct_data_disc_key); break; case DVD_STRUCT_BCA: length = sizeof(struct scsi_read_dvd_struct_data_bca); break; case DVD_STRUCT_MANUFACT: length = sizeof(struct scsi_read_dvd_struct_data_manufacturer); break; case DVD_STRUCT_CMI: return (ENODEV); case DVD_STRUCT_PROTDISCID: length = sizeof(struct scsi_read_dvd_struct_data_prot_discid); break; case DVD_STRUCT_DISCKEYBLOCK: length = sizeof(struct scsi_read_dvd_struct_data_disc_key_blk); break; case DVD_STRUCT_DDS: length = sizeof(struct scsi_read_dvd_struct_data_dds); break; case DVD_STRUCT_MEDIUM_STAT: length = sizeof(struct scsi_read_dvd_struct_data_medium_status); break; case DVD_STRUCT_SPARE_AREA: length = sizeof(struct scsi_read_dvd_struct_data_spare_area); break; case DVD_STRUCT_RMD_LAST: return (ENODEV); case DVD_STRUCT_RMD_RMA: return (ENODEV); case DVD_STRUCT_PRERECORDED: length = sizeof(struct scsi_read_dvd_struct_data_leadin); break; case DVD_STRUCT_UNIQUEID: length = sizeof(struct scsi_read_dvd_struct_data_disc_id); break; case DVD_STRUCT_DCB: return (ENODEV); case DVD_STRUCT_LIST: /* * This is the maximum allocation length for the READ DVD * STRUCTURE command. There's nothing in the MMC3 spec * that indicates a limit in the amount of data that can * be returned from this call, other than the limits * imposed by the 2-byte length variables. */ length = 65535; break; default: return (EINVAL); } if (length != 0) { databuf = malloc(length, M_DEVBUF, M_WAITOK | M_ZERO); } else databuf = NULL; cam_periph_lock(periph); ccb = cam_periph_getccb(periph, CAM_PRIORITY_NORMAL); scsi_read_dvd_structure(&ccb->csio, /* retries */ cd_retry_count, /* cbfcnp */ cddone, /* tag_action */ MSG_SIMPLE_Q_TAG, /* lba */ address, /* layer_number */ dvdstruct->layer_num, /* key_format */ dvdstruct->format, /* agid */ dvdstruct->agid, /* data_ptr */ databuf, /* dxfer_len */ length, /* sense_len */ SSD_FULL_SIZE, /* timeout */ 50000); error = cdrunccb(ccb, cderror, /*cam_flags*/CAM_RETRY_SELTO, /*sense_flags*/SF_RETRY_UA); if (error != 0) goto bailout; switch(dvdstruct->format) { case DVD_STRUCT_PHYSICAL: { struct scsi_read_dvd_struct_data_layer_desc *inlayer; struct dvd_layer *outlayer; struct scsi_read_dvd_struct_data_physical *phys_data; phys_data = (struct scsi_read_dvd_struct_data_physical *)databuf; inlayer = &phys_data->layer_desc; outlayer = (struct dvd_layer *)&dvdstruct->data; dvdstruct->length = sizeof(*inlayer); outlayer->book_type = (inlayer->book_type_version & RDSD_BOOK_TYPE_MASK) >> RDSD_BOOK_TYPE_SHIFT; outlayer->book_version = (inlayer->book_type_version & RDSD_BOOK_VERSION_MASK); outlayer->disc_size = (inlayer->disc_size_max_rate & RDSD_DISC_SIZE_MASK) >> RDSD_DISC_SIZE_SHIFT; outlayer->max_rate = (inlayer->disc_size_max_rate & RDSD_MAX_RATE_MASK); outlayer->nlayers = (inlayer->layer_info & RDSD_NUM_LAYERS_MASK) >> RDSD_NUM_LAYERS_SHIFT; outlayer->track_path = (inlayer->layer_info & RDSD_TRACK_PATH_MASK) >> RDSD_TRACK_PATH_SHIFT; outlayer->layer_type = (inlayer->layer_info & RDSD_LAYER_TYPE_MASK); outlayer->linear_density = (inlayer->density & RDSD_LIN_DENSITY_MASK) >> RDSD_LIN_DENSITY_SHIFT; outlayer->track_density = (inlayer->density & RDSD_TRACK_DENSITY_MASK); outlayer->bca = (inlayer->bca & RDSD_BCA_MASK) >> RDSD_BCA_SHIFT; outlayer->start_sector = scsi_3btoul(inlayer->main_data_start); outlayer->end_sector = scsi_3btoul(inlayer->main_data_end); outlayer->end_sector_l0 = scsi_3btoul(inlayer->end_sector_layer0); break; } case DVD_STRUCT_COPYRIGHT: { struct scsi_read_dvd_struct_data_copyright *copy_data; copy_data = (struct scsi_read_dvd_struct_data_copyright *) databuf; dvdstruct->cpst = copy_data->cps_type; dvdstruct->rmi = copy_data->region_info; dvdstruct->length = 0; break; } default: /* * Tell the user what the overall length is, no matter * what we can actually fit in the data buffer. */ dvdstruct->length = length - ccb->csio.resid - sizeof(struct scsi_read_dvd_struct_data_header); /* * But only actually copy out the smaller of what we read * in or what the structure can take. */ bcopy(databuf + sizeof(struct scsi_read_dvd_struct_data_header), dvdstruct->data, min(sizeof(dvdstruct->data), dvdstruct->length)); break; } bailout: xpt_release_ccb(ccb); cam_periph_unlock(periph); if (databuf != NULL) free(databuf, M_DEVBUF); return(error); } void scsi_report_key(struct ccb_scsiio *csio, u_int32_t retries, void (*cbfcnp)(struct cam_periph *, union ccb *), u_int8_t tag_action, u_int32_t lba, u_int8_t agid, u_int8_t key_format, u_int8_t *data_ptr, u_int32_t dxfer_len, u_int8_t sense_len, u_int32_t timeout) { struct scsi_report_key *scsi_cmd; scsi_cmd = (struct scsi_report_key *)&csio->cdb_io.cdb_bytes; bzero(scsi_cmd, sizeof(*scsi_cmd)); scsi_cmd->opcode = REPORT_KEY; scsi_ulto4b(lba, scsi_cmd->lba); scsi_ulto2b(dxfer_len, scsi_cmd->alloc_len); scsi_cmd->agid_keyformat = (agid << RK_KF_AGID_SHIFT) | (key_format & RK_KF_KEYFORMAT_MASK); cam_fill_csio(csio, retries, cbfcnp, /*flags*/ (dxfer_len == 0) ? CAM_DIR_NONE : CAM_DIR_IN, tag_action, /*data_ptr*/ data_ptr, /*dxfer_len*/ dxfer_len, sense_len, sizeof(*scsi_cmd), timeout); } void scsi_send_key(struct ccb_scsiio *csio, u_int32_t retries, void (*cbfcnp)(struct cam_periph *, union ccb *), u_int8_t tag_action, u_int8_t agid, u_int8_t key_format, u_int8_t *data_ptr, u_int32_t dxfer_len, u_int8_t sense_len, u_int32_t timeout) { struct scsi_send_key *scsi_cmd; scsi_cmd = (struct scsi_send_key *)&csio->cdb_io.cdb_bytes; bzero(scsi_cmd, sizeof(*scsi_cmd)); scsi_cmd->opcode = SEND_KEY; scsi_ulto2b(dxfer_len, scsi_cmd->param_len); scsi_cmd->agid_keyformat = (agid << RK_KF_AGID_SHIFT) | (key_format & RK_KF_KEYFORMAT_MASK); cam_fill_csio(csio, retries, cbfcnp, /*flags*/ CAM_DIR_OUT, tag_action, /*data_ptr*/ data_ptr, /*dxfer_len*/ dxfer_len, sense_len, sizeof(*scsi_cmd), timeout); } void scsi_read_dvd_structure(struct ccb_scsiio *csio, u_int32_t retries, void (*cbfcnp)(struct cam_periph *, union ccb *), u_int8_t tag_action, u_int32_t address, u_int8_t layer_number, u_int8_t format, u_int8_t agid, u_int8_t *data_ptr, u_int32_t dxfer_len, u_int8_t sense_len, u_int32_t timeout) { struct scsi_read_dvd_structure *scsi_cmd; scsi_cmd = (struct scsi_read_dvd_structure *)&csio->cdb_io.cdb_bytes; bzero(scsi_cmd, sizeof(*scsi_cmd)); scsi_cmd->opcode = READ_DVD_STRUCTURE; scsi_ulto4b(address, scsi_cmd->address); scsi_cmd->layer_number = layer_number; scsi_cmd->format = format; scsi_ulto2b(dxfer_len, scsi_cmd->alloc_len); /* The AGID is the top two bits of this byte */ scsi_cmd->agid = agid << 6; cam_fill_csio(csio, retries, cbfcnp, /*flags*/ CAM_DIR_IN, tag_action, /*data_ptr*/ data_ptr, /*dxfer_len*/ dxfer_len, sense_len, sizeof(*scsi_cmd), timeout); } diff --git a/sys/cam/scsi/scsi_ch.c b/sys/cam/scsi/scsi_ch.c index b83bc53f07d9..38acb0197213 100644 --- a/sys/cam/scsi/scsi_ch.c +++ b/sys/cam/scsi/scsi_ch.c @@ -1,1930 +1,1931 @@ /*- * Copyright (c) 1997 Justin T. Gibbs. * Copyright (c) 1997, 1998, 1999 Kenneth D. Merry. * 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, * without modification, immediately at the beginning of the file. * 2. 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 AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * Derived from the NetBSD SCSI changer driver. * * $NetBSD: ch.c,v 1.32 1998/01/12 09:49:12 thorpej Exp $ * */ /*- * Copyright (c) 1996, 1997 Jason R. Thorpe * All rights reserved. * * Partially based on an autochanger driver written by Stefan Grefen * and on an autochanger driver written by the Systems Programming Group * at the University of Utah Computer Science Department. * * 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 acknowledgements: * This product includes software developed by Jason R. Thorpe * for And Communications, http://www.and.com/ * 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 __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * Timeout definitions for various changer related commands. They may * be too short for some devices (especially the timeout for INITIALIZE * ELEMENT STATUS). */ static const u_int32_t CH_TIMEOUT_MODE_SENSE = 6000; static const u_int32_t CH_TIMEOUT_MOVE_MEDIUM = 15 * 60 * 1000; static const u_int32_t CH_TIMEOUT_EXCHANGE_MEDIUM = 15 * 60 * 1000; static const u_int32_t CH_TIMEOUT_POSITION_TO_ELEMENT = 15 * 60 * 1000; static const u_int32_t CH_TIMEOUT_READ_ELEMENT_STATUS = 5 * 60 * 1000; static const u_int32_t CH_TIMEOUT_SEND_VOLTAG = 10000; static const u_int32_t CH_TIMEOUT_INITIALIZE_ELEMENT_STATUS = 500000; typedef enum { CH_FLAG_INVALID = 0x001 } ch_flags; typedef enum { CH_STATE_PROBE, CH_STATE_NORMAL } ch_state; typedef enum { CH_CCB_PROBE } ch_ccb_types; typedef enum { CH_Q_NONE = 0x00, CH_Q_NO_DBD = 0x01, CH_Q_NO_DVCID = 0x02 } ch_quirks; #define CH_Q_BIT_STRING \ "\020" \ "\001NO_DBD" \ "\002NO_DVCID" #define ccb_state ppriv_field0 #define ccb_bp ppriv_ptr1 struct scsi_mode_sense_data { struct scsi_mode_header_6 header; struct scsi_mode_blk_desc blk_desc; union { struct page_element_address_assignment ea; struct page_transport_geometry_parameters tg; struct page_device_capabilities cap; } pages; }; struct ch_softc { ch_flags flags; ch_state state; ch_quirks quirks; union ccb saved_ccb; struct devstat *device_stats; struct cdev *dev; int open_count; int sc_picker; /* current picker */ /* * The following information is obtained from the * element address assignment page. */ int sc_firsts[CHET_MAX + 1]; /* firsts */ int sc_counts[CHET_MAX + 1]; /* counts */ /* * The following mask defines the legal combinations * of elements for the MOVE MEDIUM command. */ u_int8_t sc_movemask[CHET_MAX + 1]; /* * As above, but for EXCHANGE MEDIUM. */ u_int8_t sc_exchangemask[CHET_MAX + 1]; /* * Quirks; see below. XXX KDM not implemented yet */ int sc_settledelay; /* delay for settle */ }; static d_open_t chopen; static d_close_t chclose; static d_ioctl_t chioctl; static periph_init_t chinit; static periph_ctor_t chregister; static periph_oninv_t choninvalidate; static periph_dtor_t chcleanup; static periph_start_t chstart; static void chasync(void *callback_arg, u_int32_t code, struct cam_path *path, void *arg); static void chdone(struct cam_periph *periph, union ccb *done_ccb); static int cherror(union ccb *ccb, u_int32_t cam_flags, u_int32_t sense_flags); static int chmove(struct cam_periph *periph, struct changer_move *cm); static int chexchange(struct cam_periph *periph, struct changer_exchange *ce); static int chposition(struct cam_periph *periph, struct changer_position *cp); static int chgetelemstatus(struct cam_periph *periph, int scsi_version, u_long cmd, struct changer_element_status_request *csr); static int chsetvoltag(struct cam_periph *periph, struct changer_set_voltag_request *csvr); static int chielem(struct cam_periph *periph, unsigned int timeout); static int chgetparams(struct cam_periph *periph); static int chscsiversion(struct cam_periph *periph); static struct periph_driver chdriver = { chinit, "ch", TAILQ_HEAD_INITIALIZER(chdriver.units), /* generation */ 0 }; PERIPHDRIVER_DECLARE(ch, chdriver); static struct cdevsw ch_cdevsw = { .d_version = D_VERSION, .d_flags = D_TRACKCLOSE, .d_open = chopen, .d_close = chclose, .d_ioctl = chioctl, .d_name = "ch", }; static MALLOC_DEFINE(M_SCSICH, "scsi_ch", "scsi_ch buffers"); static void chinit(void) { cam_status status; /* * Install a global async callback. This callback will * receive async callbacks like "new device found". */ status = xpt_register_async(AC_FOUND_DEVICE, chasync, NULL, NULL); if (status != CAM_REQ_CMP) { printf("ch: Failed to attach master async callback " "due to status 0x%x!\n", status); } } static void chdevgonecb(void *arg) { struct ch_softc *softc; struct cam_periph *periph; struct mtx *mtx; int i; periph = (struct cam_periph *)arg; mtx = cam_periph_mtx(periph); mtx_lock(mtx); softc = (struct ch_softc *)periph->softc; KASSERT(softc->open_count >= 0, ("Negative open count %d", softc->open_count)); /* * When we get this callback, we will get no more close calls from * devfs. So if we have any dangling opens, we need to release the * reference held for that particular context. */ for (i = 0; i < softc->open_count; i++) cam_periph_release_locked(periph); softc->open_count = 0; /* * Release the reference held for the device node, it is gone now. */ cam_periph_release_locked(periph); /* * We reference the lock directly here, instead of using * cam_periph_unlock(). The reason is that the final call to * cam_periph_release_locked() above could result in the periph * getting freed. If that is the case, dereferencing the periph * with a cam_periph_unlock() call would cause a page fault. */ mtx_unlock(mtx); } static void choninvalidate(struct cam_periph *periph) { struct ch_softc *softc; softc = (struct ch_softc *)periph->softc; /* * De-register any async callbacks. */ xpt_register_async(0, chasync, periph, periph->path); softc->flags |= CH_FLAG_INVALID; /* * Tell devfs this device has gone away, and ask for a callback * when it has cleaned up its state. */ destroy_dev_sched_cb(softc->dev, chdevgonecb, periph); } static void chcleanup(struct cam_periph *periph) { struct ch_softc *softc; softc = (struct ch_softc *)periph->softc; devstat_remove_entry(softc->device_stats); free(softc, M_DEVBUF); } static void chasync(void *callback_arg, u_int32_t code, struct cam_path *path, void *arg) { struct cam_periph *periph; periph = (struct cam_periph *)callback_arg; switch(code) { case AC_FOUND_DEVICE: { struct ccb_getdev *cgd; cam_status status; cgd = (struct ccb_getdev *)arg; if (cgd == NULL) break; if (cgd->protocol != PROTO_SCSI) break; - + if (SID_QUAL(&cgd->inq_data) != SID_QUAL_LU_CONNECTED) + break; if (SID_TYPE(&cgd->inq_data)!= T_CHANGER) break; /* * Allocate a peripheral instance for * this device and start the probe * process. */ status = cam_periph_alloc(chregister, choninvalidate, chcleanup, chstart, "ch", CAM_PERIPH_BIO, path, chasync, AC_FOUND_DEVICE, cgd); if (status != CAM_REQ_CMP && status != CAM_REQ_INPROG) printf("chasync: Unable to probe new device " "due to status 0x%x\n", status); break; } default: cam_periph_async(periph, code, path, arg); break; } } static cam_status chregister(struct cam_periph *periph, void *arg) { struct ch_softc *softc; struct ccb_getdev *cgd; struct ccb_pathinq cpi; cgd = (struct ccb_getdev *)arg; if (cgd == NULL) { printf("chregister: no getdev CCB, can't register device\n"); return(CAM_REQ_CMP_ERR); } softc = (struct ch_softc *)malloc(sizeof(*softc),M_DEVBUF,M_NOWAIT); if (softc == NULL) { printf("chregister: Unable to probe new device. " "Unable to allocate softc\n"); return(CAM_REQ_CMP_ERR); } bzero(softc, sizeof(*softc)); softc->state = CH_STATE_PROBE; periph->softc = softc; softc->quirks = CH_Q_NONE; /* * The DVCID and CURDATA bits were not introduced until the SMC * spec. If this device claims SCSI-2 or earlier support, then it * very likely does not support these bits. */ if (cgd->inq_data.version <= SCSI_REV_2) softc->quirks |= CH_Q_NO_DVCID; bzero(&cpi, sizeof(cpi)); xpt_setup_ccb(&cpi.ccb_h, periph->path, CAM_PRIORITY_NORMAL); cpi.ccb_h.func_code = XPT_PATH_INQ; xpt_action((union ccb *)&cpi); /* * Changers don't have a blocksize, and obviously don't support * tagged queueing. */ cam_periph_unlock(periph); softc->device_stats = devstat_new_entry("ch", periph->unit_number, 0, DEVSTAT_NO_BLOCKSIZE | DEVSTAT_NO_ORDERED_TAGS, SID_TYPE(&cgd->inq_data) | XPORT_DEVSTAT_TYPE(cpi.transport), DEVSTAT_PRIORITY_OTHER); /* * Acquire a reference to the periph before we create the devfs * instance for it. We'll release this reference once the devfs * instance has been freed. */ if (cam_periph_acquire(periph) != CAM_REQ_CMP) { xpt_print(periph->path, "%s: lost periph during " "registration!\n", __func__); cam_periph_lock(periph); return (CAM_REQ_CMP_ERR); } /* Register the device */ softc->dev = make_dev(&ch_cdevsw, periph->unit_number, UID_ROOT, GID_OPERATOR, 0600, "%s%d", periph->periph_name, periph->unit_number); cam_periph_lock(periph); softc->dev->si_drv1 = periph; /* * Add an async callback so that we get * notified if this device goes away. */ xpt_register_async(AC_LOST_DEVICE, chasync, periph, periph->path); /* * Lock this periph until we are setup. * This first call can't block */ (void)cam_periph_hold(periph, PRIBIO); xpt_schedule(periph, CAM_PRIORITY_DEV); return(CAM_REQ_CMP); } static int chopen(struct cdev *dev, int flags, int fmt, struct thread *td) { struct cam_periph *periph; struct ch_softc *softc; int error; periph = (struct cam_periph *)dev->si_drv1; if (cam_periph_acquire(periph) != CAM_REQ_CMP) return (ENXIO); softc = (struct ch_softc *)periph->softc; cam_periph_lock(periph); if (softc->flags & CH_FLAG_INVALID) { cam_periph_release_locked(periph); cam_periph_unlock(periph); return(ENXIO); } if ((error = cam_periph_hold(periph, PRIBIO | PCATCH)) != 0) { cam_periph_unlock(periph); cam_periph_release(periph); return (error); } /* * Load information about this changer device into the softc. */ if ((error = chgetparams(periph)) != 0) { cam_periph_unhold(periph); cam_periph_release_locked(periph); cam_periph_unlock(periph); return(error); } cam_periph_unhold(periph); softc->open_count++; cam_periph_unlock(periph); return(error); } static int chclose(struct cdev *dev, int flag, int fmt, struct thread *td) { struct cam_periph *periph; struct ch_softc *softc; struct mtx *mtx; periph = (struct cam_periph *)dev->si_drv1; if (periph == NULL) return(ENXIO); mtx = cam_periph_mtx(periph); mtx_lock(mtx); softc = (struct ch_softc *)periph->softc; softc->open_count--; cam_periph_release_locked(periph); /* * We reference the lock directly here, instead of using * cam_periph_unlock(). The reason is that the call to * cam_periph_release_locked() above could result in the periph * getting freed. If that is the case, dereferencing the periph * with a cam_periph_unlock() call would cause a page fault. * * cam_periph_release() avoids this problem using the same method, * but we're manually acquiring and dropping the lock here to * protect the open count and avoid another lock acquisition and * release. */ mtx_unlock(mtx); return(0); } static void chstart(struct cam_periph *periph, union ccb *start_ccb) { struct ch_softc *softc; softc = (struct ch_softc *)periph->softc; switch (softc->state) { case CH_STATE_NORMAL: { xpt_release_ccb(start_ccb); break; } case CH_STATE_PROBE: { int mode_buffer_len; void *mode_buffer; /* * Include the block descriptor when calculating the mode * buffer length, */ mode_buffer_len = sizeof(struct scsi_mode_header_6) + sizeof(struct scsi_mode_blk_desc) + sizeof(struct page_element_address_assignment); mode_buffer = malloc(mode_buffer_len, M_SCSICH, M_NOWAIT); if (mode_buffer == NULL) { printf("chstart: couldn't malloc mode sense data\n"); break; } bzero(mode_buffer, mode_buffer_len); /* * Get the element address assignment page. */ scsi_mode_sense(&start_ccb->csio, /* retries */ 1, /* cbfcnp */ chdone, /* tag_action */ MSG_SIMPLE_Q_TAG, /* dbd */ (softc->quirks & CH_Q_NO_DBD) ? FALSE : TRUE, /* page_code */ SMS_PAGE_CTRL_CURRENT, /* page */ CH_ELEMENT_ADDR_ASSIGN_PAGE, /* param_buf */ (u_int8_t *)mode_buffer, /* param_len */ mode_buffer_len, /* sense_len */ SSD_FULL_SIZE, /* timeout */ CH_TIMEOUT_MODE_SENSE); start_ccb->ccb_h.ccb_bp = NULL; start_ccb->ccb_h.ccb_state = CH_CCB_PROBE; xpt_action(start_ccb); break; } } } static void chdone(struct cam_periph *periph, union ccb *done_ccb) { struct ch_softc *softc; struct ccb_scsiio *csio; softc = (struct ch_softc *)periph->softc; csio = &done_ccb->csio; switch(done_ccb->ccb_h.ccb_state) { case CH_CCB_PROBE: { struct scsi_mode_header_6 *mode_header; struct page_element_address_assignment *ea; char announce_buf[80]; mode_header = (struct scsi_mode_header_6 *)csio->data_ptr; ea = (struct page_element_address_assignment *) find_mode_page_6(mode_header); if ((done_ccb->ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_CMP){ softc->sc_firsts[CHET_MT] = scsi_2btoul(ea->mtea); softc->sc_counts[CHET_MT] = scsi_2btoul(ea->nmte); softc->sc_firsts[CHET_ST] = scsi_2btoul(ea->fsea); softc->sc_counts[CHET_ST] = scsi_2btoul(ea->nse); softc->sc_firsts[CHET_IE] = scsi_2btoul(ea->fieea); softc->sc_counts[CHET_IE] = scsi_2btoul(ea->niee); softc->sc_firsts[CHET_DT] = scsi_2btoul(ea->fdtea); softc->sc_counts[CHET_DT] = scsi_2btoul(ea->ndte); softc->sc_picker = softc->sc_firsts[CHET_MT]; #define PLURAL(c) (c) == 1 ? "" : "s" snprintf(announce_buf, sizeof(announce_buf), "%d slot%s, %d drive%s, " "%d picker%s, %d portal%s", softc->sc_counts[CHET_ST], PLURAL(softc->sc_counts[CHET_ST]), softc->sc_counts[CHET_DT], PLURAL(softc->sc_counts[CHET_DT]), softc->sc_counts[CHET_MT], PLURAL(softc->sc_counts[CHET_MT]), softc->sc_counts[CHET_IE], PLURAL(softc->sc_counts[CHET_IE])); #undef PLURAL } else { int error; error = cherror(done_ccb, CAM_RETRY_SELTO, SF_RETRY_UA | SF_NO_PRINT); /* * Retry any UNIT ATTENTION type errors. They * are expected at boot. */ if (error == ERESTART) { /* * A retry was scheuled, so * just return. */ return; } else if (error != 0) { int retry_scheduled; struct scsi_mode_sense_6 *sms; sms = (struct scsi_mode_sense_6 *) done_ccb->csio.cdb_io.cdb_bytes; /* * Check to see if block descriptors were * disabled. Some devices don't like that. * We're taking advantage of the fact that * the first few bytes of the 6 and 10 byte * mode sense commands are the same. If * block descriptors were disabled, enable * them and re-send the command. */ if (sms->byte2 & SMS_DBD) { sms->byte2 &= ~SMS_DBD; xpt_action(done_ccb); softc->quirks |= CH_Q_NO_DBD; retry_scheduled = 1; } else retry_scheduled = 0; /* Don't wedge this device's queue */ if ((done_ccb->ccb_h.status & CAM_DEV_QFRZN) != 0) cam_release_devq(done_ccb->ccb_h.path, /*relsim_flags*/0, /*reduction*/0, /*timeout*/0, /*getcount_only*/0); if (retry_scheduled) return; if ((done_ccb->ccb_h.status & CAM_STATUS_MASK) == CAM_SCSI_STATUS_ERROR) scsi_sense_print(&done_ccb->csio); else { xpt_print(periph->path, "got CAM status %#x\n", done_ccb->ccb_h.status); } xpt_print(periph->path, "fatal error, failed " "to attach to device\n"); cam_periph_invalidate(periph); announce_buf[0] = '\0'; } } if (announce_buf[0] != '\0') { xpt_announce_periph(periph, announce_buf); xpt_announce_quirks(periph, softc->quirks, CH_Q_BIT_STRING); } softc->state = CH_STATE_NORMAL; free(mode_header, M_SCSICH); /* * Since our peripheral may be invalidated by an error * above or an external event, we must release our CCB * before releasing the probe lock on the peripheral. * The peripheral will only go away once the last lock * is removed, and we need it around for the CCB release * operation. */ xpt_release_ccb(done_ccb); cam_periph_unhold(periph); return; } default: break; } xpt_release_ccb(done_ccb); } static int cherror(union ccb *ccb, u_int32_t cam_flags, u_int32_t sense_flags) { struct ch_softc *softc; struct cam_periph *periph; periph = xpt_path_periph(ccb->ccb_h.path); softc = (struct ch_softc *)periph->softc; return (cam_periph_error(ccb, cam_flags, sense_flags, &softc->saved_ccb)); } static int chioctl(struct cdev *dev, u_long cmd, caddr_t addr, int flag, struct thread *td) { struct cam_periph *periph; struct ch_softc *softc; int error; periph = (struct cam_periph *)dev->si_drv1; if (periph == NULL) return(ENXIO); cam_periph_lock(periph); CAM_DEBUG(periph->path, CAM_DEBUG_TRACE, ("entering chioctl\n")); softc = (struct ch_softc *)periph->softc; error = 0; CAM_DEBUG(periph->path, CAM_DEBUG_TRACE, ("trying to do ioctl %#lx\n", cmd)); /* * If this command can change the device's state, we must * have the device open for writing. */ switch (cmd) { case CHIOGPICKER: case CHIOGPARAMS: case OCHIOGSTATUS: case CHIOGSTATUS: break; default: if ((flag & FWRITE) == 0) { cam_periph_unlock(periph); return (EBADF); } } switch (cmd) { case CHIOMOVE: error = chmove(periph, (struct changer_move *)addr); break; case CHIOEXCHANGE: error = chexchange(periph, (struct changer_exchange *)addr); break; case CHIOPOSITION: error = chposition(periph, (struct changer_position *)addr); break; case CHIOGPICKER: *(int *)addr = softc->sc_picker - softc->sc_firsts[CHET_MT]; break; case CHIOSPICKER: { int new_picker = *(int *)addr; if (new_picker > (softc->sc_counts[CHET_MT] - 1)) { error = EINVAL; break; } softc->sc_picker = softc->sc_firsts[CHET_MT] + new_picker; break; } case CHIOGPARAMS: { struct changer_params *cp = (struct changer_params *)addr; cp->cp_npickers = softc->sc_counts[CHET_MT]; cp->cp_nslots = softc->sc_counts[CHET_ST]; cp->cp_nportals = softc->sc_counts[CHET_IE]; cp->cp_ndrives = softc->sc_counts[CHET_DT]; break; } case CHIOIELEM: error = chielem(periph, *(unsigned int *)addr); break; case OCHIOGSTATUS: { error = chgetelemstatus(periph, SCSI_REV_2, cmd, (struct changer_element_status_request *)addr); break; } case CHIOGSTATUS: { int scsi_version; scsi_version = chscsiversion(periph); if (scsi_version >= SCSI_REV_0) { error = chgetelemstatus(periph, scsi_version, cmd, (struct changer_element_status_request *)addr); } else { /* unable to determine the SCSI version */ cam_periph_unlock(periph); return (ENXIO); } break; } case CHIOSETVOLTAG: { error = chsetvoltag(periph, (struct changer_set_voltag_request *) addr); break; } /* Implement prevent/allow? */ default: error = cam_periph_ioctl(periph, cmd, addr, cherror); break; } cam_periph_unlock(periph); return (error); } static int chmove(struct cam_periph *periph, struct changer_move *cm) { struct ch_softc *softc; u_int16_t fromelem, toelem; union ccb *ccb; int error; error = 0; softc = (struct ch_softc *)periph->softc; /* * Check arguments. */ if ((cm->cm_fromtype > CHET_DT) || (cm->cm_totype > CHET_DT)) return (EINVAL); if ((cm->cm_fromunit > (softc->sc_counts[cm->cm_fromtype] - 1)) || (cm->cm_tounit > (softc->sc_counts[cm->cm_totype] - 1))) return (ENODEV); /* * Check the request against the changer's capabilities. */ if ((softc->sc_movemask[cm->cm_fromtype] & (1 << cm->cm_totype)) == 0) return (ENODEV); /* * Calculate the source and destination elements. */ fromelem = softc->sc_firsts[cm->cm_fromtype] + cm->cm_fromunit; toelem = softc->sc_firsts[cm->cm_totype] + cm->cm_tounit; ccb = cam_periph_getccb(periph, CAM_PRIORITY_NORMAL); scsi_move_medium(&ccb->csio, /* retries */ 1, /* cbfcnp */ chdone, /* tag_action */ MSG_SIMPLE_Q_TAG, /* tea */ softc->sc_picker, /* src */ fromelem, /* dst */ toelem, /* invert */ (cm->cm_flags & CM_INVERT) ? TRUE : FALSE, /* sense_len */ SSD_FULL_SIZE, /* timeout */ CH_TIMEOUT_MOVE_MEDIUM); error = cam_periph_runccb(ccb, cherror, /*cam_flags*/CAM_RETRY_SELTO, /*sense_flags*/ SF_RETRY_UA, softc->device_stats); xpt_release_ccb(ccb); return(error); } static int chexchange(struct cam_periph *periph, struct changer_exchange *ce) { struct ch_softc *softc; u_int16_t src, dst1, dst2; union ccb *ccb; int error; error = 0; softc = (struct ch_softc *)periph->softc; /* * Check arguments. */ if ((ce->ce_srctype > CHET_DT) || (ce->ce_fdsttype > CHET_DT) || (ce->ce_sdsttype > CHET_DT)) return (EINVAL); if ((ce->ce_srcunit > (softc->sc_counts[ce->ce_srctype] - 1)) || (ce->ce_fdstunit > (softc->sc_counts[ce->ce_fdsttype] - 1)) || (ce->ce_sdstunit > (softc->sc_counts[ce->ce_sdsttype] - 1))) return (ENODEV); /* * Check the request against the changer's capabilities. */ if (((softc->sc_exchangemask[ce->ce_srctype] & (1 << ce->ce_fdsttype)) == 0) || ((softc->sc_exchangemask[ce->ce_fdsttype] & (1 << ce->ce_sdsttype)) == 0)) return (ENODEV); /* * Calculate the source and destination elements. */ src = softc->sc_firsts[ce->ce_srctype] + ce->ce_srcunit; dst1 = softc->sc_firsts[ce->ce_fdsttype] + ce->ce_fdstunit; dst2 = softc->sc_firsts[ce->ce_sdsttype] + ce->ce_sdstunit; ccb = cam_periph_getccb(periph, CAM_PRIORITY_NORMAL); scsi_exchange_medium(&ccb->csio, /* retries */ 1, /* cbfcnp */ chdone, /* tag_action */ MSG_SIMPLE_Q_TAG, /* tea */ softc->sc_picker, /* src */ src, /* dst1 */ dst1, /* dst2 */ dst2, /* invert1 */ (ce->ce_flags & CE_INVERT1) ? TRUE : FALSE, /* invert2 */ (ce->ce_flags & CE_INVERT2) ? TRUE : FALSE, /* sense_len */ SSD_FULL_SIZE, /* timeout */ CH_TIMEOUT_EXCHANGE_MEDIUM); error = cam_periph_runccb(ccb, cherror, /*cam_flags*/CAM_RETRY_SELTO, /*sense_flags*/ SF_RETRY_UA, softc->device_stats); xpt_release_ccb(ccb); return(error); } static int chposition(struct cam_periph *periph, struct changer_position *cp) { struct ch_softc *softc; u_int16_t dst; union ccb *ccb; int error; error = 0; softc = (struct ch_softc *)periph->softc; /* * Check arguments. */ if (cp->cp_type > CHET_DT) return (EINVAL); if (cp->cp_unit > (softc->sc_counts[cp->cp_type] - 1)) return (ENODEV); /* * Calculate the destination element. */ dst = softc->sc_firsts[cp->cp_type] + cp->cp_unit; ccb = cam_periph_getccb(periph, CAM_PRIORITY_NORMAL); scsi_position_to_element(&ccb->csio, /* retries */ 1, /* cbfcnp */ chdone, /* tag_action */ MSG_SIMPLE_Q_TAG, /* tea */ softc->sc_picker, /* dst */ dst, /* invert */ (cp->cp_flags & CP_INVERT) ? TRUE : FALSE, /* sense_len */ SSD_FULL_SIZE, /* timeout */ CH_TIMEOUT_POSITION_TO_ELEMENT); error = cam_periph_runccb(ccb, cherror, /*cam_flags*/ CAM_RETRY_SELTO, /*sense_flags*/ SF_RETRY_UA, softc->device_stats); xpt_release_ccb(ccb); return(error); } /* * Copy a volume tag to a volume_tag struct, converting SCSI byte order * to host native byte order in the volume serial number. The volume * label as returned by the changer is transferred to user mode as * nul-terminated string. Volume labels are truncated at the first * space, as suggested by SCSI-2. */ static void copy_voltag(struct changer_voltag *uvoltag, struct volume_tag *voltag) { int i; for (i=0; ivif[i]; if (c && c != ' ') uvoltag->cv_volid[i] = c; else break; } uvoltag->cv_serial = scsi_2btoul(voltag->vsn); } /* * Copy an element status descriptor to a user-mode * changer_element_status structure. */ static void copy_element_status(struct ch_softc *softc, u_int16_t flags, struct read_element_status_descriptor *desc, struct changer_element_status *ces, int scsi_version) { u_int16_t eaddr = scsi_2btoul(desc->eaddr); u_int16_t et; struct volume_tag *pvol_tag = NULL, *avol_tag = NULL; struct read_element_status_device_id *devid = NULL; ces->ces_int_addr = eaddr; /* set up logical address in element status */ for (et = CHET_MT; et <= CHET_DT; et++) { if ((softc->sc_firsts[et] <= eaddr) && ((softc->sc_firsts[et] + softc->sc_counts[et]) > eaddr)) { ces->ces_addr = eaddr - softc->sc_firsts[et]; ces->ces_type = et; break; } } ces->ces_flags = desc->flags1; ces->ces_sensecode = desc->sense_code; ces->ces_sensequal = desc->sense_qual; if (desc->flags2 & READ_ELEMENT_STATUS_INVERT) ces->ces_flags |= CES_INVERT; if (desc->flags2 & READ_ELEMENT_STATUS_SVALID) { eaddr = scsi_2btoul(desc->ssea); /* convert source address to logical format */ for (et = CHET_MT; et <= CHET_DT; et++) { if ((softc->sc_firsts[et] <= eaddr) && ((softc->sc_firsts[et] + softc->sc_counts[et]) > eaddr)) { ces->ces_source_addr = eaddr - softc->sc_firsts[et]; ces->ces_source_type = et; ces->ces_flags |= CES_SOURCE_VALID; break; } } if (!(ces->ces_flags & CES_SOURCE_VALID)) printf("ch: warning: could not map element source " "address %ud to a valid element type\n", eaddr); } /* * pvoltag and avoltag are common between SCSI-2 and later versions */ if (flags & READ_ELEMENT_STATUS_PVOLTAG) pvol_tag = &desc->voltag_devid.pvoltag; if (flags & READ_ELEMENT_STATUS_AVOLTAG) avol_tag = (flags & READ_ELEMENT_STATUS_PVOLTAG) ? &desc->voltag_devid.voltag[1] :&desc->voltag_devid.pvoltag; /* * For SCSI-3 and later, element status can carry designator and * other information. */ if (scsi_version >= SCSI_REV_SPC) { if ((flags & READ_ELEMENT_STATUS_PVOLTAG) ^ (flags & READ_ELEMENT_STATUS_AVOLTAG)) devid = &desc->voltag_devid.pvol_and_devid.devid; else if (!(flags & READ_ELEMENT_STATUS_PVOLTAG) && !(flags & READ_ELEMENT_STATUS_AVOLTAG)) devid = &desc->voltag_devid.devid; else /* Have both PVOLTAG and AVOLTAG */ devid = &desc->voltag_devid.vol_tags_and_devid.devid; } if (pvol_tag) copy_voltag(&(ces->ces_pvoltag), pvol_tag); if (avol_tag) copy_voltag(&(ces->ces_pvoltag), avol_tag); if (devid != NULL) { if (devid->designator_length > 0) { bcopy((void *)devid->designator, (void *)ces->ces_designator, devid->designator_length); ces->ces_designator_length = devid->designator_length; /* * Make sure we are always NUL terminated. The * This won't matter for the binary code set, * since the user will only pay attention to the * length field. */ ces->ces_designator[devid->designator_length]= '\0'; } if (devid->piv_assoc_designator_type & READ_ELEMENT_STATUS_PIV_SET) { ces->ces_flags |= CES_PIV; ces->ces_protocol_id = READ_ELEMENT_STATUS_PROTOCOL_ID( devid->prot_code_set); } ces->ces_code_set = READ_ELEMENT_STATUS_CODE_SET(devid->prot_code_set); ces->ces_assoc = READ_ELEMENT_STATUS_ASSOCIATION( devid->piv_assoc_designator_type); ces->ces_designator_type = READ_ELEMENT_STATUS_DESIGNATOR_TYPE( devid->piv_assoc_designator_type); } else if (scsi_version > SCSI_REV_2) { /* SCSI-SPC and No devid, no designator */ ces->ces_designator_length = 0; ces->ces_designator[0] = '\0'; ces->ces_protocol_id = CES_PROTOCOL_ID_FCP_4; } if (scsi_version <= SCSI_REV_2) { if (desc->dt_or_obsolete.scsi_2.dt_scsi_flags & READ_ELEMENT_STATUS_DT_IDVALID) { ces->ces_flags |= CES_SCSIID_VALID; ces->ces_scsi_id = desc->dt_or_obsolete.scsi_2.dt_scsi_addr; } if (desc->dt_or_obsolete.scsi_2.dt_scsi_addr & READ_ELEMENT_STATUS_DT_LUVALID) { ces->ces_flags |= CES_LUN_VALID; ces->ces_scsi_lun = desc->dt_or_obsolete.scsi_2.dt_scsi_flags & READ_ELEMENT_STATUS_DT_LUNMASK; } } } static int chgetelemstatus(struct cam_periph *periph, int scsi_version, u_long cmd, struct changer_element_status_request *cesr) { struct read_element_status_header *st_hdr; struct read_element_status_page_header *pg_hdr; struct read_element_status_descriptor *desc; caddr_t data = NULL; size_t size, desclen; int avail, i, error = 0; int curdata, dvcid, sense_flags; int try_no_dvcid = 0; struct changer_element_status *user_data = NULL; struct ch_softc *softc; union ccb *ccb; int chet = cesr->cesr_element_type; int want_voltags = (cesr->cesr_flags & CESR_VOLTAGS) ? 1 : 0; softc = (struct ch_softc *)periph->softc; /* perform argument checking */ /* * Perform a range check on the cesr_element_{base,count} * request argument fields. */ if ((softc->sc_counts[chet] - cesr->cesr_element_base) <= 0 || (cesr->cesr_element_base + cesr->cesr_element_count) > softc->sc_counts[chet]) return (EINVAL); /* * Request one descriptor for the given element type. This * is used to determine the size of the descriptor so that * we can allocate enough storage for all of them. We assume * that the first one can fit into 1k. */ cam_periph_unlock(periph); data = (caddr_t)malloc(1024, M_DEVBUF, M_WAITOK); cam_periph_lock(periph); ccb = cam_periph_getccb(periph, CAM_PRIORITY_NORMAL); sense_flags = SF_RETRY_UA; if (softc->quirks & CH_Q_NO_DVCID) { dvcid = 0; curdata = 0; } else { dvcid = 1; curdata = 1; /* * Don't print anything for an Illegal Request, because * these flags can cause some changers to complain. We'll * retry without them if we get an error. */ sense_flags |= SF_QUIET_IR; } retry_einval: scsi_read_element_status(&ccb->csio, /* retries */ 1, /* cbfcnp */ chdone, /* tag_action */ MSG_SIMPLE_Q_TAG, /* voltag */ want_voltags, /* sea */ softc->sc_firsts[chet], /* curdata */ curdata, /* dvcid */ dvcid, /* count */ 1, /* data_ptr */ data, /* dxfer_len */ 1024, /* sense_len */ SSD_FULL_SIZE, /* timeout */ CH_TIMEOUT_READ_ELEMENT_STATUS); error = cam_periph_runccb(ccb, cherror, /*cam_flags*/ CAM_RETRY_SELTO, /*sense_flags*/ sense_flags, softc->device_stats); /* * An Illegal Request sense key (only used if there is no asc/ascq) * or 0x24,0x00 for an ASC/ASCQ both map to EINVAL. If dvcid or * curdata are set (we set both or neither), try turning them off * and see if the command is successful. */ if ((error == EINVAL) && (dvcid || curdata)) { dvcid = 0; curdata = 0; error = 0; /* At this point we want to report any Illegal Request */ sense_flags &= ~SF_QUIET_IR; try_no_dvcid = 1; goto retry_einval; } /* * In this case, we tried a read element status with dvcid and * curdata set, and it failed. We retried without those bits, and * it succeeded. Suggest to the user that he set a quirk, so we * don't go through the retry process the first time in the future. * This should only happen on changers that claim SCSI-3 or higher, * but don't support these bits. */ if ((try_no_dvcid != 0) && (error == 0)) softc->quirks |= CH_Q_NO_DVCID; if (error) goto done; cam_periph_unlock(periph); st_hdr = (struct read_element_status_header *)data; pg_hdr = (struct read_element_status_page_header *)((uintptr_t)st_hdr + sizeof(struct read_element_status_header)); desclen = scsi_2btoul(pg_hdr->edl); size = sizeof(struct read_element_status_header) + sizeof(struct read_element_status_page_header) + (desclen * cesr->cesr_element_count); /* * Reallocate storage for descriptors and get them from the * device. */ free(data, M_DEVBUF); data = (caddr_t)malloc(size, M_DEVBUF, M_WAITOK); cam_periph_lock(periph); scsi_read_element_status(&ccb->csio, /* retries */ 1, /* cbfcnp */ chdone, /* tag_action */ MSG_SIMPLE_Q_TAG, /* voltag */ want_voltags, /* sea */ softc->sc_firsts[chet] + cesr->cesr_element_base, /* curdata */ curdata, /* dvcid */ dvcid, /* count */ cesr->cesr_element_count, /* data_ptr */ data, /* dxfer_len */ size, /* sense_len */ SSD_FULL_SIZE, /* timeout */ CH_TIMEOUT_READ_ELEMENT_STATUS); error = cam_periph_runccb(ccb, cherror, /*cam_flags*/ CAM_RETRY_SELTO, /*sense_flags*/ SF_RETRY_UA, softc->device_stats); if (error) goto done; cam_periph_unlock(periph); /* * Fill in the user status array. */ st_hdr = (struct read_element_status_header *)data; pg_hdr = (struct read_element_status_page_header *)((uintptr_t)st_hdr + sizeof(struct read_element_status_header)); avail = scsi_2btoul(st_hdr->count); if (avail != cesr->cesr_element_count) { xpt_print(periph->path, "warning, READ ELEMENT STATUS avail != count\n"); } user_data = (struct changer_element_status *) malloc(avail * sizeof(struct changer_element_status), M_DEVBUF, M_WAITOK | M_ZERO); desc = (struct read_element_status_descriptor *)((uintptr_t)data + sizeof(struct read_element_status_header) + sizeof(struct read_element_status_page_header)); /* * Set up the individual element status structures */ for (i = 0; i < avail; ++i) { struct changer_element_status *ces; /* * In the changer_element_status structure, fields from * the beginning to the field of ces_scsi_lun are common * between SCSI-2 and SCSI-3, while all the rest are new * from SCSI-3. In order to maintain backward compatibility * of the chio command, the ces pointer, below, is computed * such that it lines up with the structure boundary * corresponding to the SCSI version. */ ces = cmd == OCHIOGSTATUS ? (struct changer_element_status *) ((unsigned char *)user_data + i * (offsetof(struct changer_element_status,ces_scsi_lun)+1)): &user_data[i]; copy_element_status(softc, pg_hdr->flags, desc, ces, scsi_version); desc = (struct read_element_status_descriptor *) ((unsigned char *)desc + desclen); } /* Copy element status structures out to userspace. */ if (cmd == OCHIOGSTATUS) error = copyout(user_data, cesr->cesr_element_status, avail* (offsetof(struct changer_element_status, ces_scsi_lun) + 1)); else error = copyout(user_data, cesr->cesr_element_status, avail * sizeof(struct changer_element_status)); cam_periph_lock(periph); done: xpt_release_ccb(ccb); if (data != NULL) free(data, M_DEVBUF); if (user_data != NULL) free(user_data, M_DEVBUF); return (error); } static int chielem(struct cam_periph *periph, unsigned int timeout) { union ccb *ccb; struct ch_softc *softc; int error; if (!timeout) { timeout = CH_TIMEOUT_INITIALIZE_ELEMENT_STATUS; } else { timeout *= 1000; } error = 0; softc = (struct ch_softc *)periph->softc; ccb = cam_periph_getccb(periph, CAM_PRIORITY_NORMAL); scsi_initialize_element_status(&ccb->csio, /* retries */ 1, /* cbfcnp */ chdone, /* tag_action */ MSG_SIMPLE_Q_TAG, /* sense_len */ SSD_FULL_SIZE, /* timeout */ timeout); error = cam_periph_runccb(ccb, cherror, /*cam_flags*/ CAM_RETRY_SELTO, /*sense_flags*/ SF_RETRY_UA, softc->device_stats); xpt_release_ccb(ccb); return(error); } static int chsetvoltag(struct cam_periph *periph, struct changer_set_voltag_request *csvr) { union ccb *ccb; struct ch_softc *softc; u_int16_t ea; u_int8_t sac; struct scsi_send_volume_tag_parameters ssvtp; int error; int i; error = 0; softc = (struct ch_softc *)periph->softc; bzero(&ssvtp, sizeof(ssvtp)); for (i=0; icsvr_type > CHET_DT) return EINVAL; if (csvr->csvr_addr > (softc->sc_counts[csvr->csvr_type] - 1)) return ENODEV; ea = softc->sc_firsts[csvr->csvr_type] + csvr->csvr_addr; if (csvr->csvr_flags & CSVR_ALTERNATE) { switch (csvr->csvr_flags & CSVR_MODE_MASK) { case CSVR_MODE_SET: sac = SEND_VOLUME_TAG_ASSERT_ALTERNATE; break; case CSVR_MODE_REPLACE: sac = SEND_VOLUME_TAG_REPLACE_ALTERNATE; break; case CSVR_MODE_CLEAR: sac = SEND_VOLUME_TAG_UNDEFINED_ALTERNATE; break; default: error = EINVAL; goto out; } } else { switch (csvr->csvr_flags & CSVR_MODE_MASK) { case CSVR_MODE_SET: sac = SEND_VOLUME_TAG_ASSERT_PRIMARY; break; case CSVR_MODE_REPLACE: sac = SEND_VOLUME_TAG_REPLACE_PRIMARY; break; case CSVR_MODE_CLEAR: sac = SEND_VOLUME_TAG_UNDEFINED_PRIMARY; break; default: error = EINVAL; goto out; } } memcpy(ssvtp.vitf, csvr->csvr_voltag.cv_volid, min(strlen(csvr->csvr_voltag.cv_volid), sizeof(ssvtp.vitf))); scsi_ulto2b(csvr->csvr_voltag.cv_serial, ssvtp.minvsn); ccb = cam_periph_getccb(periph, CAM_PRIORITY_NORMAL); scsi_send_volume_tag(&ccb->csio, /* retries */ 1, /* cbfcnp */ chdone, /* tag_action */ MSG_SIMPLE_Q_TAG, /* element_address */ ea, /* send_action_code */ sac, /* parameters */ &ssvtp, /* sense_len */ SSD_FULL_SIZE, /* timeout */ CH_TIMEOUT_SEND_VOLTAG); error = cam_periph_runccb(ccb, cherror, /*cam_flags*/ CAM_RETRY_SELTO, /*sense_flags*/ SF_RETRY_UA, softc->device_stats); xpt_release_ccb(ccb); out: return error; } static int chgetparams(struct cam_periph *periph) { union ccb *ccb; struct ch_softc *softc; void *mode_buffer; int mode_buffer_len; struct page_element_address_assignment *ea; struct page_device_capabilities *cap; int error, from, dbd; u_int8_t *moves, *exchanges; error = 0; softc = (struct ch_softc *)periph->softc; ccb = cam_periph_getccb(periph, CAM_PRIORITY_NORMAL); /* * The scsi_mode_sense_data structure is just a convenience * structure that allows us to easily calculate the worst-case * storage size of the mode sense buffer. */ mode_buffer_len = sizeof(struct scsi_mode_sense_data); mode_buffer = malloc(mode_buffer_len, M_SCSICH, M_NOWAIT); if (mode_buffer == NULL) { printf("chgetparams: couldn't malloc mode sense data\n"); return(ENOSPC); } bzero(mode_buffer, mode_buffer_len); if (softc->quirks & CH_Q_NO_DBD) dbd = FALSE; else dbd = TRUE; /* * Get the element address assignment page. */ scsi_mode_sense(&ccb->csio, /* retries */ 1, /* cbfcnp */ chdone, /* tag_action */ MSG_SIMPLE_Q_TAG, /* dbd */ dbd, /* page_code */ SMS_PAGE_CTRL_CURRENT, /* page */ CH_ELEMENT_ADDR_ASSIGN_PAGE, /* param_buf */ (u_int8_t *)mode_buffer, /* param_len */ mode_buffer_len, /* sense_len */ SSD_FULL_SIZE, /* timeout */ CH_TIMEOUT_MODE_SENSE); error = cam_periph_runccb(ccb, cherror, /*cam_flags*/ CAM_RETRY_SELTO, /* sense_flags */ SF_RETRY_UA|SF_NO_PRINT, softc->device_stats); if (error) { if (dbd) { struct scsi_mode_sense_6 *sms; sms = (struct scsi_mode_sense_6 *) ccb->csio.cdb_io.cdb_bytes; sms->byte2 &= ~SMS_DBD; error = cam_periph_runccb(ccb, cherror, /*cam_flags*/ CAM_RETRY_SELTO, /*sense_flags*/ SF_RETRY_UA, softc->device_stats); } else { /* * Since we disabled sense printing above, print * out the sense here since we got an error. */ scsi_sense_print(&ccb->csio); } if (error) { xpt_print(periph->path, "chgetparams: error getting element " "address page\n"); xpt_release_ccb(ccb); free(mode_buffer, M_SCSICH); return(error); } } ea = (struct page_element_address_assignment *) find_mode_page_6((struct scsi_mode_header_6 *)mode_buffer); softc->sc_firsts[CHET_MT] = scsi_2btoul(ea->mtea); softc->sc_counts[CHET_MT] = scsi_2btoul(ea->nmte); softc->sc_firsts[CHET_ST] = scsi_2btoul(ea->fsea); softc->sc_counts[CHET_ST] = scsi_2btoul(ea->nse); softc->sc_firsts[CHET_IE] = scsi_2btoul(ea->fieea); softc->sc_counts[CHET_IE] = scsi_2btoul(ea->niee); softc->sc_firsts[CHET_DT] = scsi_2btoul(ea->fdtea); softc->sc_counts[CHET_DT] = scsi_2btoul(ea->ndte); bzero(mode_buffer, mode_buffer_len); /* * Now get the device capabilities page. */ scsi_mode_sense(&ccb->csio, /* retries */ 1, /* cbfcnp */ chdone, /* tag_action */ MSG_SIMPLE_Q_TAG, /* dbd */ dbd, /* page_code */ SMS_PAGE_CTRL_CURRENT, /* page */ CH_DEVICE_CAP_PAGE, /* param_buf */ (u_int8_t *)mode_buffer, /* param_len */ mode_buffer_len, /* sense_len */ SSD_FULL_SIZE, /* timeout */ CH_TIMEOUT_MODE_SENSE); error = cam_periph_runccb(ccb, cherror, /*cam_flags*/ CAM_RETRY_SELTO, /* sense_flags */ SF_RETRY_UA | SF_NO_PRINT, softc->device_stats); if (error) { if (dbd) { struct scsi_mode_sense_6 *sms; sms = (struct scsi_mode_sense_6 *) ccb->csio.cdb_io.cdb_bytes; sms->byte2 &= ~SMS_DBD; error = cam_periph_runccb(ccb, cherror, /*cam_flags*/ CAM_RETRY_SELTO, /*sense_flags*/ SF_RETRY_UA, softc->device_stats); } else { /* * Since we disabled sense printing above, print * out the sense here since we got an error. */ scsi_sense_print(&ccb->csio); } if (error) { xpt_print(periph->path, "chgetparams: error getting device " "capabilities page\n"); xpt_release_ccb(ccb); free(mode_buffer, M_SCSICH); return(error); } } xpt_release_ccb(ccb); cap = (struct page_device_capabilities *) find_mode_page_6((struct scsi_mode_header_6 *)mode_buffer); bzero(softc->sc_movemask, sizeof(softc->sc_movemask)); bzero(softc->sc_exchangemask, sizeof(softc->sc_exchangemask)); moves = cap->move_from; exchanges = cap->exchange_with; for (from = CHET_MT; from <= CHET_MAX; ++from) { softc->sc_movemask[from] = moves[from]; softc->sc_exchangemask[from] = exchanges[from]; } free(mode_buffer, M_SCSICH); return(error); } static int chscsiversion(struct cam_periph *periph) { struct scsi_inquiry_data *inq_data; struct ccb_getdev *cgd; int dev_scsi_version; cam_periph_assert(periph, MA_OWNED); if ((cgd = (struct ccb_getdev *)xpt_alloc_ccb_nowait()) == NULL) return (-1); /* * Get the device information. */ xpt_setup_ccb(&cgd->ccb_h, periph->path, CAM_PRIORITY_NORMAL); cgd->ccb_h.func_code = XPT_GDEV_TYPE; xpt_action((union ccb *)cgd); if (cgd->ccb_h.status != CAM_REQ_CMP) { xpt_free_ccb((union ccb *)cgd); return -1; } inq_data = &cgd->inq_data; dev_scsi_version = inq_data->version; xpt_free_ccb((union ccb *)cgd); return dev_scsi_version; } void scsi_move_medium(struct ccb_scsiio *csio, u_int32_t retries, void (*cbfcnp)(struct cam_periph *, union ccb *), u_int8_t tag_action, u_int32_t tea, u_int32_t src, u_int32_t dst, int invert, u_int8_t sense_len, u_int32_t timeout) { struct scsi_move_medium *scsi_cmd; scsi_cmd = (struct scsi_move_medium *)&csio->cdb_io.cdb_bytes; bzero(scsi_cmd, sizeof(*scsi_cmd)); scsi_cmd->opcode = MOVE_MEDIUM; scsi_ulto2b(tea, scsi_cmd->tea); scsi_ulto2b(src, scsi_cmd->src); scsi_ulto2b(dst, scsi_cmd->dst); if (invert) scsi_cmd->invert |= MOVE_MEDIUM_INVERT; cam_fill_csio(csio, retries, cbfcnp, /*flags*/ CAM_DIR_NONE, tag_action, /*data_ptr*/ NULL, /*dxfer_len*/ 0, sense_len, sizeof(*scsi_cmd), timeout); } void scsi_exchange_medium(struct ccb_scsiio *csio, u_int32_t retries, void (*cbfcnp)(struct cam_periph *, union ccb *), u_int8_t tag_action, u_int32_t tea, u_int32_t src, u_int32_t dst1, u_int32_t dst2, int invert1, int invert2, u_int8_t sense_len, u_int32_t timeout) { struct scsi_exchange_medium *scsi_cmd; scsi_cmd = (struct scsi_exchange_medium *)&csio->cdb_io.cdb_bytes; bzero(scsi_cmd, sizeof(*scsi_cmd)); scsi_cmd->opcode = EXCHANGE_MEDIUM; scsi_ulto2b(tea, scsi_cmd->tea); scsi_ulto2b(src, scsi_cmd->src); scsi_ulto2b(dst1, scsi_cmd->fdst); scsi_ulto2b(dst2, scsi_cmd->sdst); if (invert1) scsi_cmd->invert |= EXCHANGE_MEDIUM_INV1; if (invert2) scsi_cmd->invert |= EXCHANGE_MEDIUM_INV2; cam_fill_csio(csio, retries, cbfcnp, /*flags*/ CAM_DIR_NONE, tag_action, /*data_ptr*/ NULL, /*dxfer_len*/ 0, sense_len, sizeof(*scsi_cmd), timeout); } void scsi_position_to_element(struct ccb_scsiio *csio, u_int32_t retries, void (*cbfcnp)(struct cam_periph *, union ccb *), u_int8_t tag_action, u_int32_t tea, u_int32_t dst, int invert, u_int8_t sense_len, u_int32_t timeout) { struct scsi_position_to_element *scsi_cmd; scsi_cmd = (struct scsi_position_to_element *)&csio->cdb_io.cdb_bytes; bzero(scsi_cmd, sizeof(*scsi_cmd)); scsi_cmd->opcode = POSITION_TO_ELEMENT; scsi_ulto2b(tea, scsi_cmd->tea); scsi_ulto2b(dst, scsi_cmd->dst); if (invert) scsi_cmd->invert |= POSITION_TO_ELEMENT_INVERT; cam_fill_csio(csio, retries, cbfcnp, /*flags*/ CAM_DIR_NONE, tag_action, /*data_ptr*/ NULL, /*dxfer_len*/ 0, sense_len, sizeof(*scsi_cmd), timeout); } void scsi_read_element_status(struct ccb_scsiio *csio, u_int32_t retries, void (*cbfcnp)(struct cam_periph *, union ccb *), u_int8_t tag_action, int voltag, u_int32_t sea, int curdata, int dvcid, u_int32_t count, u_int8_t *data_ptr, u_int32_t dxfer_len, u_int8_t sense_len, u_int32_t timeout) { struct scsi_read_element_status *scsi_cmd; scsi_cmd = (struct scsi_read_element_status *)&csio->cdb_io.cdb_bytes; bzero(scsi_cmd, sizeof(*scsi_cmd)); scsi_cmd->opcode = READ_ELEMENT_STATUS; scsi_ulto2b(sea, scsi_cmd->sea); scsi_ulto2b(count, scsi_cmd->count); scsi_ulto3b(dxfer_len, scsi_cmd->len); if (dvcid) scsi_cmd->flags |= READ_ELEMENT_STATUS_DVCID; if (curdata) scsi_cmd->flags |= READ_ELEMENT_STATUS_CURDATA; if (voltag) scsi_cmd->byte2 |= READ_ELEMENT_STATUS_VOLTAG; cam_fill_csio(csio, retries, cbfcnp, /*flags*/ CAM_DIR_IN, tag_action, data_ptr, dxfer_len, sense_len, sizeof(*scsi_cmd), timeout); } void scsi_initialize_element_status(struct ccb_scsiio *csio, u_int32_t retries, void (*cbfcnp)(struct cam_periph *, union ccb *), u_int8_t tag_action, u_int8_t sense_len, u_int32_t timeout) { struct scsi_initialize_element_status *scsi_cmd; scsi_cmd = (struct scsi_initialize_element_status *) &csio->cdb_io.cdb_bytes; bzero(scsi_cmd, sizeof(*scsi_cmd)); scsi_cmd->opcode = INITIALIZE_ELEMENT_STATUS; cam_fill_csio(csio, retries, cbfcnp, /*flags*/ CAM_DIR_NONE, tag_action, /* data_ptr */ NULL, /* dxfer_len */ 0, sense_len, sizeof(*scsi_cmd), timeout); } void scsi_send_volume_tag(struct ccb_scsiio *csio, u_int32_t retries, void (*cbfcnp)(struct cam_periph *, union ccb *), u_int8_t tag_action, u_int16_t element_address, u_int8_t send_action_code, struct scsi_send_volume_tag_parameters *parameters, u_int8_t sense_len, u_int32_t timeout) { struct scsi_send_volume_tag *scsi_cmd; scsi_cmd = (struct scsi_send_volume_tag *) &csio->cdb_io.cdb_bytes; bzero(scsi_cmd, sizeof(*scsi_cmd)); scsi_cmd->opcode = SEND_VOLUME_TAG; scsi_ulto2b(element_address, scsi_cmd->ea); scsi_cmd->sac = send_action_code; scsi_ulto2b(sizeof(*parameters), scsi_cmd->pll); cam_fill_csio(csio, retries, cbfcnp, /*flags*/ CAM_DIR_OUT, tag_action, /* data_ptr */ (u_int8_t *) parameters, sizeof(*parameters), sense_len, sizeof(*scsi_cmd), timeout); } diff --git a/sys/cam/scsi/scsi_da.c b/sys/cam/scsi/scsi_da.c index 74dd3b9ef260..3ebbb713c001 100644 --- a/sys/cam/scsi/scsi_da.c +++ b/sys/cam/scsi/scsi_da.c @@ -1,4022 +1,4023 @@ /*- * Implementation of SCSI Direct Access Peripheral driver for CAM. * * Copyright (c) 1997 Justin T. Gibbs. * 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, * without modification, immediately at the beginning of the file. * 2. 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 AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include #ifdef _KERNEL #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #endif /* _KERNEL */ #ifndef _KERNEL #include #include #endif /* _KERNEL */ #include #include #include #include #include #include #ifndef _KERNEL #include #endif /* !_KERNEL */ #ifdef _KERNEL typedef enum { DA_STATE_PROBE_RC, DA_STATE_PROBE_RC16, DA_STATE_PROBE_LBP, DA_STATE_PROBE_BLK_LIMITS, DA_STATE_PROBE_BDC, DA_STATE_PROBE_ATA, DA_STATE_NORMAL } da_state; typedef enum { DA_FLAG_PACK_INVALID = 0x001, DA_FLAG_NEW_PACK = 0x002, DA_FLAG_PACK_LOCKED = 0x004, DA_FLAG_PACK_REMOVABLE = 0x008, DA_FLAG_NEED_OTAG = 0x020, DA_FLAG_WAS_OTAG = 0x040, DA_FLAG_RETRY_UA = 0x080, DA_FLAG_OPEN = 0x100, DA_FLAG_SCTX_INIT = 0x200, DA_FLAG_CAN_RC16 = 0x400, DA_FLAG_PROBED = 0x800, DA_FLAG_DIRTY = 0x1000, DA_FLAG_ANNOUNCED = 0x2000 } da_flags; typedef enum { DA_Q_NONE = 0x00, DA_Q_NO_SYNC_CACHE = 0x01, DA_Q_NO_6_BYTE = 0x02, DA_Q_NO_PREVENT = 0x04, DA_Q_4K = 0x08, DA_Q_NO_RC16 = 0x10, DA_Q_NO_UNMAP = 0x20, DA_Q_RETRY_BUSY = 0x40 } da_quirks; #define DA_Q_BIT_STRING \ "\020" \ "\001NO_SYNC_CACHE" \ "\002NO_6_BYTE" \ "\003NO_PREVENT" \ "\0044K" \ "\005NO_RC16" \ "\006NO_UNMAP" \ "\007RETRY_BUSY" typedef enum { DA_CCB_PROBE_RC = 0x01, DA_CCB_PROBE_RC16 = 0x02, DA_CCB_PROBE_LBP = 0x03, DA_CCB_PROBE_BLK_LIMITS = 0x04, DA_CCB_PROBE_BDC = 0x05, DA_CCB_PROBE_ATA = 0x06, DA_CCB_BUFFER_IO = 0x07, DA_CCB_DUMP = 0x0A, DA_CCB_DELETE = 0x0B, DA_CCB_TUR = 0x0C, DA_CCB_TYPE_MASK = 0x0F, DA_CCB_RETRY_UA = 0x10 } da_ccb_state; /* * Order here is important for method choice * * We prefer ATA_TRIM as tests run against a Sandforce 2281 SSD attached to * LSI 2008 (mps) controller (FW: v12, Drv: v14) resulted 20% quicker deletes * using ATA_TRIM than the corresponding UNMAP results for a real world mysql * import taking 5mins. * */ typedef enum { DA_DELETE_NONE, DA_DELETE_DISABLE, DA_DELETE_ATA_TRIM, DA_DELETE_UNMAP, DA_DELETE_WS16, DA_DELETE_WS10, DA_DELETE_ZERO, DA_DELETE_MIN = DA_DELETE_ATA_TRIM, DA_DELETE_MAX = DA_DELETE_ZERO } da_delete_methods; typedef void da_delete_func_t (struct cam_periph *periph, union ccb *ccb, struct bio *bp); static da_delete_func_t da_delete_trim; static da_delete_func_t da_delete_unmap; static da_delete_func_t da_delete_ws; static const void * da_delete_functions[] = { NULL, NULL, da_delete_trim, da_delete_unmap, da_delete_ws, da_delete_ws, da_delete_ws }; static const char *da_delete_method_names[] = { "NONE", "DISABLE", "ATA_TRIM", "UNMAP", "WS16", "WS10", "ZERO" }; static const char *da_delete_method_desc[] = { "NONE", "DISABLED", "ATA TRIM", "UNMAP", "WRITE SAME(16) with UNMAP", "WRITE SAME(10) with UNMAP", "ZERO" }; /* Offsets into our private area for storing information */ #define ccb_state ppriv_field0 #define ccb_bp ppriv_ptr1 struct disk_params { u_int8_t heads; u_int32_t cylinders; u_int8_t secs_per_track; u_int32_t secsize; /* Number of bytes/sector */ u_int64_t sectors; /* total number sectors */ u_int stripesize; u_int stripeoffset; }; #define UNMAP_RANGE_MAX 0xffffffff #define UNMAP_HEAD_SIZE 8 #define UNMAP_RANGE_SIZE 16 #define UNMAP_MAX_RANGES 2048 /* Protocol Max is 4095 */ #define UNMAP_BUF_SIZE ((UNMAP_MAX_RANGES * UNMAP_RANGE_SIZE) + \ UNMAP_HEAD_SIZE) #define WS10_MAX_BLKS 0xffff #define WS16_MAX_BLKS 0xffffffff #define ATA_TRIM_MAX_RANGES ((UNMAP_BUF_SIZE / \ (ATA_DSM_RANGE_SIZE * ATA_DSM_BLK_SIZE)) * ATA_DSM_BLK_SIZE) struct da_softc { struct bio_queue_head bio_queue; struct bio_queue_head delete_queue; struct bio_queue_head delete_run_queue; LIST_HEAD(, ccb_hdr) pending_ccbs; int tur; /* TEST UNIT READY should be sent */ int refcount; /* Active xpt_action() calls */ da_state state; da_flags flags; da_quirks quirks; int sort_io_queue; int minimum_cmd_size; int error_inject; int trim_max_ranges; int delete_running; int delete_available; /* Delete methods possibly available */ u_int maxio; uint32_t unmap_max_ranges; uint32_t unmap_max_lba; /* Max LBAs in UNMAP req */ uint64_t ws_max_blks; da_delete_methods delete_method; da_delete_func_t *delete_func; struct disk_params params; struct disk *disk; union ccb saved_ccb; struct task sysctl_task; struct sysctl_ctx_list sysctl_ctx; struct sysctl_oid *sysctl_tree; struct callout sendordered_c; uint64_t wwpn; uint8_t unmap_buf[UNMAP_BUF_SIZE]; struct scsi_read_capacity_data_long rcaplong; struct callout mediapoll_c; }; #define dadeleteflag(softc, delete_method, enable) \ if (enable) { \ softc->delete_available |= (1 << delete_method); \ } else { \ softc->delete_available &= ~(1 << delete_method); \ } struct da_quirk_entry { struct scsi_inquiry_pattern inq_pat; da_quirks quirks; }; static const char quantum[] = "QUANTUM"; static const char microp[] = "MICROP"; static struct da_quirk_entry da_quirk_table[] = { /* SPI, FC devices */ { /* * Fujitsu M2513A MO drives. * Tested devices: M2513A2 firmware versions 1200 & 1300. * (dip switch selects whether T_DIRECT or T_OPTICAL device) * Reported by: W.Scholten */ {T_DIRECT, SIP_MEDIA_REMOVABLE, "FUJITSU", "M2513A", "*"}, /*quirks*/ DA_Q_NO_SYNC_CACHE }, { /* See above. */ {T_OPTICAL, SIP_MEDIA_REMOVABLE, "FUJITSU", "M2513A", "*"}, /*quirks*/ DA_Q_NO_SYNC_CACHE }, { /* * This particular Fujitsu drive doesn't like the * synchronize cache command. * Reported by: Tom Jackson */ {T_DIRECT, SIP_MEDIA_FIXED, "FUJITSU", "M2954*", "*"}, /*quirks*/ DA_Q_NO_SYNC_CACHE }, { /* * This drive doesn't like the synchronize cache command * either. Reported by: Matthew Jacob * in NetBSD PR kern/6027, August 24, 1998. */ {T_DIRECT, SIP_MEDIA_FIXED, microp, "2217*", "*"}, /*quirks*/ DA_Q_NO_SYNC_CACHE }, { /* * This drive doesn't like the synchronize cache command * either. Reported by: Hellmuth Michaelis (hm@kts.org) * (PR 8882). */ {T_DIRECT, SIP_MEDIA_FIXED, microp, "2112*", "*"}, /*quirks*/ DA_Q_NO_SYNC_CACHE }, { /* * Doesn't like the synchronize cache command. * Reported by: Blaz Zupan */ {T_DIRECT, SIP_MEDIA_FIXED, "NEC", "D3847*", "*"}, /*quirks*/ DA_Q_NO_SYNC_CACHE }, { /* * Doesn't like the synchronize cache command. * Reported by: Blaz Zupan */ {T_DIRECT, SIP_MEDIA_FIXED, quantum, "MAVERICK 540S", "*"}, /*quirks*/ DA_Q_NO_SYNC_CACHE }, { /* * Doesn't like the synchronize cache command. */ {T_DIRECT, SIP_MEDIA_FIXED, quantum, "LPS525S", "*"}, /*quirks*/ DA_Q_NO_SYNC_CACHE }, { /* * Doesn't like the synchronize cache command. * Reported by: walter@pelissero.de */ {T_DIRECT, SIP_MEDIA_FIXED, quantum, "LPS540S", "*"}, /*quirks*/ DA_Q_NO_SYNC_CACHE }, { /* * Doesn't work correctly with 6 byte reads/writes. * Returns illegal request, and points to byte 9 of the * 6-byte CDB. * Reported by: Adam McDougall */ {T_DIRECT, SIP_MEDIA_FIXED, quantum, "VIKING 4*", "*"}, /*quirks*/ DA_Q_NO_6_BYTE }, { /* See above. */ {T_DIRECT, SIP_MEDIA_FIXED, quantum, "VIKING 2*", "*"}, /*quirks*/ DA_Q_NO_6_BYTE }, { /* * Doesn't like the synchronize cache command. * Reported by: walter@pelissero.de */ {T_DIRECT, SIP_MEDIA_FIXED, "CONNER", "CP3500*", "*"}, /*quirks*/ DA_Q_NO_SYNC_CACHE }, { /* * The CISS RAID controllers do not support SYNC_CACHE */ {T_DIRECT, SIP_MEDIA_FIXED, "COMPAQ", "RAID*", "*"}, /*quirks*/ DA_Q_NO_SYNC_CACHE }, { /* * The STEC SSDs sometimes hang on UNMAP. */ {T_DIRECT, SIP_MEDIA_FIXED, "STEC", "*", "*"}, /*quirks*/ DA_Q_NO_UNMAP }, { /* * VMware returns BUSY status when storage has transient * connectivity problems, so better wait. */ {T_DIRECT, SIP_MEDIA_FIXED, "VMware", "Virtual disk", "*"}, /*quirks*/ DA_Q_RETRY_BUSY }, /* USB mass storage devices supported by umass(4) */ { /* * EXATELECOM (Sigmatel) i-Bead 100/105 USB Flash MP3 Player * PR: kern/51675 */ {T_DIRECT, SIP_MEDIA_REMOVABLE, "EXATEL", "i-BEAD10*", "*"}, /*quirks*/ DA_Q_NO_SYNC_CACHE }, { /* * Power Quotient Int. (PQI) USB flash key * PR: kern/53067 */ {T_DIRECT, SIP_MEDIA_REMOVABLE, "Generic*", "USB Flash Disk*", "*"}, /*quirks*/ DA_Q_NO_SYNC_CACHE }, { /* * Creative Nomad MUVO mp3 player (USB) * PR: kern/53094 */ {T_DIRECT, SIP_MEDIA_REMOVABLE, "CREATIVE", "NOMAD_MUVO", "*"}, /*quirks*/ DA_Q_NO_SYNC_CACHE|DA_Q_NO_PREVENT }, { /* * Jungsoft NEXDISK USB flash key * PR: kern/54737 */ {T_DIRECT, SIP_MEDIA_REMOVABLE, "JUNGSOFT", "NEXDISK*", "*"}, /*quirks*/ DA_Q_NO_SYNC_CACHE }, { /* * FreeDik USB Mini Data Drive * PR: kern/54786 */ {T_DIRECT, SIP_MEDIA_REMOVABLE, "FreeDik*", "Mini Data Drive", "*"}, /*quirks*/ DA_Q_NO_SYNC_CACHE }, { /* * Sigmatel USB Flash MP3 Player * PR: kern/57046 */ {T_DIRECT, SIP_MEDIA_REMOVABLE, "SigmaTel", "MSCN", "*"}, /*quirks*/ DA_Q_NO_SYNC_CACHE|DA_Q_NO_PREVENT }, { /* * Neuros USB Digital Audio Computer * PR: kern/63645 */ {T_DIRECT, SIP_MEDIA_REMOVABLE, "NEUROS", "dig. audio comp.", "*"}, /*quirks*/ DA_Q_NO_SYNC_CACHE }, { /* * SEAGRAND NP-900 MP3 Player * PR: kern/64563 */ {T_DIRECT, SIP_MEDIA_REMOVABLE, "SEAGRAND", "NP-900*", "*"}, /*quirks*/ DA_Q_NO_SYNC_CACHE|DA_Q_NO_PREVENT }, { /* * iRiver iFP MP3 player (with UMS Firmware) * PR: kern/54881, i386/63941, kern/66124 */ {T_DIRECT, SIP_MEDIA_REMOVABLE, "iRiver", "iFP*", "*"}, /*quirks*/ DA_Q_NO_SYNC_CACHE }, { /* * Frontier Labs NEX IA+ Digital Audio Player, rev 1.10/0.01 * PR: kern/70158 */ {T_DIRECT, SIP_MEDIA_REMOVABLE, "FL" , "Nex*", "*"}, /*quirks*/ DA_Q_NO_SYNC_CACHE }, { /* * ZICPlay USB MP3 Player with FM * PR: kern/75057 */ {T_DIRECT, SIP_MEDIA_REMOVABLE, "ACTIONS*" , "USB DISK*", "*"}, /*quirks*/ DA_Q_NO_SYNC_CACHE }, { /* * TEAC USB floppy mechanisms */ {T_DIRECT, SIP_MEDIA_REMOVABLE, "TEAC" , "FD-05*", "*"}, /*quirks*/ DA_Q_NO_SYNC_CACHE }, { /* * Kingston DataTraveler II+ USB Pen-Drive. * Reported by: Pawel Jakub Dawidek */ {T_DIRECT, SIP_MEDIA_REMOVABLE, "Kingston" , "DataTraveler II+", "*"}, /*quirks*/ DA_Q_NO_SYNC_CACHE }, { /* * USB DISK Pro PMAP * Reported by: jhs * PR: usb/96381 */ {T_DIRECT, SIP_MEDIA_REMOVABLE, " ", "USB DISK Pro", "PMAP"}, /*quirks*/ DA_Q_NO_SYNC_CACHE }, { /* * Motorola E398 Mobile Phone (TransFlash memory card). * Reported by: Wojciech A. Koszek * PR: usb/89889 */ {T_DIRECT, SIP_MEDIA_REMOVABLE, "Motorola" , "Motorola Phone", "*"}, /*quirks*/ DA_Q_NO_SYNC_CACHE }, { /* * Qware BeatZkey! Pro * PR: usb/79164 */ {T_DIRECT, SIP_MEDIA_REMOVABLE, "GENERIC", "USB DISK DEVICE", "*"}, /*quirks*/ DA_Q_NO_SYNC_CACHE }, { /* * Time DPA20B 1GB MP3 Player * PR: usb/81846 */ {T_DIRECT, SIP_MEDIA_REMOVABLE, "USB2.0*", "(FS) FLASH DISK*", "*"}, /*quirks*/ DA_Q_NO_SYNC_CACHE }, { /* * Samsung USB key 128Mb * PR: usb/90081 */ {T_DIRECT, SIP_MEDIA_REMOVABLE, "USB-DISK", "FreeDik-FlashUsb", "*"}, /*quirks*/ DA_Q_NO_SYNC_CACHE }, { /* * Kingston DataTraveler 2.0 USB Flash memory. * PR: usb/89196 */ {T_DIRECT, SIP_MEDIA_REMOVABLE, "Kingston", "DataTraveler 2.0", "*"}, /*quirks*/ DA_Q_NO_SYNC_CACHE }, { /* * Creative MUVO Slim mp3 player (USB) * PR: usb/86131 */ {T_DIRECT, SIP_MEDIA_REMOVABLE, "CREATIVE", "MuVo Slim", "*"}, /*quirks*/ DA_Q_NO_SYNC_CACHE|DA_Q_NO_PREVENT }, { /* * United MP5512 Portable MP3 Player (2-in-1 USB DISK/MP3) * PR: usb/80487 */ {T_DIRECT, SIP_MEDIA_REMOVABLE, "Generic*", "MUSIC DISK", "*"}, /*quirks*/ DA_Q_NO_SYNC_CACHE }, { /* * SanDisk Micro Cruzer 128MB * PR: usb/75970 */ {T_DIRECT, SIP_MEDIA_REMOVABLE, "SanDisk" , "Micro Cruzer", "*"}, /*quirks*/ DA_Q_NO_SYNC_CACHE }, { /* * TOSHIBA TransMemory USB sticks * PR: kern/94660 */ {T_DIRECT, SIP_MEDIA_REMOVABLE, "TOSHIBA", "TransMemory", "*"}, /*quirks*/ DA_Q_NO_SYNC_CACHE }, { /* * PNY USB 3.0 Flash Drives */ {T_DIRECT, SIP_MEDIA_REMOVABLE, "PNY", "USB 3.0 FD*", "*"}, /*quirks*/ DA_Q_NO_SYNC_CACHE | DA_Q_NO_RC16 }, { /* * PNY USB Flash keys * PR: usb/75578, usb/72344, usb/65436 */ {T_DIRECT, SIP_MEDIA_REMOVABLE, "*" , "USB DISK*", "*"}, /*quirks*/ DA_Q_NO_SYNC_CACHE }, { /* * Genesys 6-in-1 Card Reader * PR: usb/94647 */ {T_DIRECT, SIP_MEDIA_REMOVABLE, "Generic*", "STORAGE DEVICE*", "*"}, /*quirks*/ DA_Q_NO_SYNC_CACHE }, { /* * Rekam Digital CAMERA * PR: usb/98713 */ {T_DIRECT, SIP_MEDIA_REMOVABLE, "CAMERA*", "4MP-9J6*", "*"}, /*quirks*/ DA_Q_NO_SYNC_CACHE }, { /* * iRiver H10 MP3 player * PR: usb/102547 */ {T_DIRECT, SIP_MEDIA_REMOVABLE, "iriver", "H10*", "*"}, /*quirks*/ DA_Q_NO_SYNC_CACHE }, { /* * iRiver U10 MP3 player * PR: usb/92306 */ {T_DIRECT, SIP_MEDIA_REMOVABLE, "iriver", "U10*", "*"}, /*quirks*/ DA_Q_NO_SYNC_CACHE }, { /* * X-Micro Flash Disk * PR: usb/96901 */ {T_DIRECT, SIP_MEDIA_REMOVABLE, "X-Micro", "Flash Disk", "*"}, /*quirks*/ DA_Q_NO_SYNC_CACHE }, { /* * EasyMP3 EM732X USB 2.0 Flash MP3 Player * PR: usb/96546 */ {T_DIRECT, SIP_MEDIA_REMOVABLE, "EM732X", "MP3 Player*", "1.00"}, /*quirks*/ DA_Q_NO_SYNC_CACHE }, { /* * Denver MP3 player * PR: usb/107101 */ {T_DIRECT, SIP_MEDIA_REMOVABLE, "DENVER", "MP3 PLAYER", "*"}, /*quirks*/ DA_Q_NO_SYNC_CACHE }, { /* * Philips USB Key Audio KEY013 * PR: usb/68412 */ {T_DIRECT, SIP_MEDIA_REMOVABLE, "PHILIPS", "Key*", "*"}, /*quirks*/ DA_Q_NO_SYNC_CACHE | DA_Q_NO_PREVENT }, { /* * JNC MP3 Player * PR: usb/94439 */ {T_DIRECT, SIP_MEDIA_REMOVABLE, "JNC*" , "MP3 Player*", "*"}, /*quirks*/ DA_Q_NO_SYNC_CACHE }, { /* * SAMSUNG MP0402H * PR: usb/108427 */ {T_DIRECT, SIP_MEDIA_FIXED, "SAMSUNG", "MP0402H", "*"}, /*quirks*/ DA_Q_NO_SYNC_CACHE }, { /* * I/O Magic USB flash - Giga Bank * PR: usb/108810 */ {T_DIRECT, SIP_MEDIA_FIXED, "GS-Magic", "stor*", "*"}, /*quirks*/ DA_Q_NO_SYNC_CACHE }, { /* * JoyFly 128mb USB Flash Drive * PR: 96133 */ {T_DIRECT, SIP_MEDIA_REMOVABLE, "USB 2.0", "Flash Disk*", "*"}, /*quirks*/ DA_Q_NO_SYNC_CACHE }, { /* * ChipsBnk usb stick * PR: 103702 */ {T_DIRECT, SIP_MEDIA_REMOVABLE, "ChipsBnk", "USB*", "*"}, /*quirks*/ DA_Q_NO_SYNC_CACHE }, { /* * Storcase (Kingston) InfoStation IFS FC2/SATA-R 201A * PR: 129858 */ {T_DIRECT, SIP_MEDIA_FIXED, "IFS", "FC2/SATA-R*", "*"}, /*quirks*/ DA_Q_NO_SYNC_CACHE }, { /* * Samsung YP-U3 mp3-player * PR: 125398 */ {T_DIRECT, SIP_MEDIA_REMOVABLE, "Samsung", "YP-U3", "*"}, /*quirks*/ DA_Q_NO_SYNC_CACHE }, { {T_DIRECT, SIP_MEDIA_REMOVABLE, "Netac", "OnlyDisk*", "2000"}, /*quirks*/ DA_Q_NO_SYNC_CACHE }, { /* * Sony Cyber-Shot DSC cameras * PR: usb/137035 */ {T_DIRECT, SIP_MEDIA_REMOVABLE, "Sony", "Sony DSC", "*"}, /*quirks*/ DA_Q_NO_SYNC_CACHE | DA_Q_NO_PREVENT }, { {T_DIRECT, SIP_MEDIA_REMOVABLE, "Kingston", "DataTraveler G3", "1.00"}, /*quirks*/ DA_Q_NO_PREVENT }, { /* At least several Transcent USB sticks lie on RC16. */ {T_DIRECT, SIP_MEDIA_REMOVABLE, "JetFlash", "Transcend*", "*"}, /*quirks*/ DA_Q_NO_RC16 }, /* ATA/SATA devices over SAS/USB/... */ { /* Hitachi Advanced Format (4k) drives */ { T_DIRECT, SIP_MEDIA_FIXED, "Hitachi", "H??????????E3*", "*" }, /*quirks*/DA_Q_4K }, { /* Samsung Advanced Format (4k) drives */ { T_DIRECT, SIP_MEDIA_FIXED, "ATA", "SAMSUNG HD155UI*", "*" }, /*quirks*/DA_Q_4K }, { /* Samsung Advanced Format (4k) drives */ { T_DIRECT, SIP_MEDIA_FIXED, "SAMSUNG", "HD155UI*", "*" }, /*quirks*/DA_Q_4K }, { /* Samsung Advanced Format (4k) drives */ { T_DIRECT, SIP_MEDIA_FIXED, "ATA", "SAMSUNG HD204UI*", "*" }, /*quirks*/DA_Q_4K }, { /* Samsung Advanced Format (4k) drives */ { T_DIRECT, SIP_MEDIA_FIXED, "SAMSUNG", "HD204UI*", "*" }, /*quirks*/DA_Q_4K }, { /* Seagate Barracuda Green Advanced Format (4k) drives */ { T_DIRECT, SIP_MEDIA_FIXED, "ATA", "ST????DL*", "*" }, /*quirks*/DA_Q_4K }, { /* Seagate Barracuda Green Advanced Format (4k) drives */ { T_DIRECT, SIP_MEDIA_FIXED, "ST????DL", "*", "*" }, /*quirks*/DA_Q_4K }, { /* Seagate Barracuda Green Advanced Format (4k) drives */ { T_DIRECT, SIP_MEDIA_FIXED, "ATA", "ST???DM*", "*" }, /*quirks*/DA_Q_4K }, { /* Seagate Barracuda Green Advanced Format (4k) drives */ { T_DIRECT, SIP_MEDIA_FIXED, "ST???DM*", "*", "*" }, /*quirks*/DA_Q_4K }, { /* Seagate Barracuda Green Advanced Format (4k) drives */ { T_DIRECT, SIP_MEDIA_FIXED, "ATA", "ST????DM*", "*" }, /*quirks*/DA_Q_4K }, { /* Seagate Barracuda Green Advanced Format (4k) drives */ { T_DIRECT, SIP_MEDIA_FIXED, "ST????DM", "*", "*" }, /*quirks*/DA_Q_4K }, { /* Seagate Momentus Advanced Format (4k) drives */ { T_DIRECT, SIP_MEDIA_FIXED, "ATA", "ST9500423AS*", "*" }, /*quirks*/DA_Q_4K }, { /* Seagate Momentus Advanced Format (4k) drives */ { T_DIRECT, SIP_MEDIA_FIXED, "ST950042", "3AS*", "*" }, /*quirks*/DA_Q_4K }, { /* Seagate Momentus Advanced Format (4k) drives */ { T_DIRECT, SIP_MEDIA_FIXED, "ATA", "ST9500424AS*", "*" }, /*quirks*/DA_Q_4K }, { /* Seagate Momentus Advanced Format (4k) drives */ { T_DIRECT, SIP_MEDIA_FIXED, "ST950042", "4AS*", "*" }, /*quirks*/DA_Q_4K }, { /* Seagate Momentus Advanced Format (4k) drives */ { T_DIRECT, SIP_MEDIA_FIXED, "ATA", "ST9640423AS*", "*" }, /*quirks*/DA_Q_4K }, { /* Seagate Momentus Advanced Format (4k) drives */ { T_DIRECT, SIP_MEDIA_FIXED, "ST964042", "3AS*", "*" }, /*quirks*/DA_Q_4K }, { /* Seagate Momentus Advanced Format (4k) drives */ { T_DIRECT, SIP_MEDIA_FIXED, "ATA", "ST9640424AS*", "*" }, /*quirks*/DA_Q_4K }, { /* Seagate Momentus Advanced Format (4k) drives */ { T_DIRECT, SIP_MEDIA_FIXED, "ST964042", "4AS*", "*" }, /*quirks*/DA_Q_4K }, { /* Seagate Momentus Advanced Format (4k) drives */ { T_DIRECT, SIP_MEDIA_FIXED, "ATA", "ST9750420AS*", "*" }, /*quirks*/DA_Q_4K }, { /* Seagate Momentus Advanced Format (4k) drives */ { T_DIRECT, SIP_MEDIA_FIXED, "ST975042", "0AS*", "*" }, /*quirks*/DA_Q_4K }, { /* Seagate Momentus Advanced Format (4k) drives */ { T_DIRECT, SIP_MEDIA_FIXED, "ATA", "ST9750422AS*", "*" }, /*quirks*/DA_Q_4K }, { /* Seagate Momentus Advanced Format (4k) drives */ { T_DIRECT, SIP_MEDIA_FIXED, "ST975042", "2AS*", "*" }, /*quirks*/DA_Q_4K }, { /* Seagate Momentus Advanced Format (4k) drives */ { T_DIRECT, SIP_MEDIA_FIXED, "ATA", "ST9750423AS*", "*" }, /*quirks*/DA_Q_4K }, { /* Seagate Momentus Advanced Format (4k) drives */ { T_DIRECT, SIP_MEDIA_FIXED, "ST975042", "3AS*", "*" }, /*quirks*/DA_Q_4K }, { /* Seagate Momentus Thin Advanced Format (4k) drives */ { T_DIRECT, SIP_MEDIA_FIXED, "ATA", "ST???LT*", "*" }, /*quirks*/DA_Q_4K }, { /* Seagate Momentus Thin Advanced Format (4k) drives */ { T_DIRECT, SIP_MEDIA_FIXED, "ST???LT*", "*", "*" }, /*quirks*/DA_Q_4K }, { /* WDC Caviar Green Advanced Format (4k) drives */ { T_DIRECT, SIP_MEDIA_FIXED, "ATA", "WDC WD????RS*", "*" }, /*quirks*/DA_Q_4K }, { /* WDC Caviar Green Advanced Format (4k) drives */ { T_DIRECT, SIP_MEDIA_FIXED, "WDC WD??", "??RS*", "*" }, /*quirks*/DA_Q_4K }, { /* WDC Caviar Green Advanced Format (4k) drives */ { T_DIRECT, SIP_MEDIA_FIXED, "ATA", "WDC WD????RX*", "*" }, /*quirks*/DA_Q_4K }, { /* WDC Caviar Green Advanced Format (4k) drives */ { T_DIRECT, SIP_MEDIA_FIXED, "WDC WD??", "??RX*", "*" }, /*quirks*/DA_Q_4K }, { /* WDC Caviar Green Advanced Format (4k) drives */ { T_DIRECT, SIP_MEDIA_FIXED, "ATA", "WDC WD??????RS*", "*" }, /*quirks*/DA_Q_4K }, { /* WDC Caviar Green Advanced Format (4k) drives */ { T_DIRECT, SIP_MEDIA_FIXED, "WDC WD??", "????RS*", "*" }, /*quirks*/DA_Q_4K }, { /* WDC Caviar Green Advanced Format (4k) drives */ { T_DIRECT, SIP_MEDIA_FIXED, "ATA", "WDC WD??????RX*", "*" }, /*quirks*/DA_Q_4K }, { /* WDC Caviar Green Advanced Format (4k) drives */ { T_DIRECT, SIP_MEDIA_FIXED, "WDC WD??", "????RX*", "*" }, /*quirks*/DA_Q_4K }, { /* WDC Scorpio Black Advanced Format (4k) drives */ { T_DIRECT, SIP_MEDIA_FIXED, "ATA", "WDC WD???PKT*", "*" }, /*quirks*/DA_Q_4K }, { /* WDC Scorpio Black Advanced Format (4k) drives */ { T_DIRECT, SIP_MEDIA_FIXED, "WDC WD??", "?PKT*", "*" }, /*quirks*/DA_Q_4K }, { /* WDC Scorpio Black Advanced Format (4k) drives */ { T_DIRECT, SIP_MEDIA_FIXED, "ATA", "WDC WD?????PKT*", "*" }, /*quirks*/DA_Q_4K }, { /* WDC Scorpio Black Advanced Format (4k) drives */ { T_DIRECT, SIP_MEDIA_FIXED, "WDC WD??", "???PKT*", "*" }, /*quirks*/DA_Q_4K }, { /* WDC Scorpio Blue Advanced Format (4k) drives */ { T_DIRECT, SIP_MEDIA_FIXED, "ATA", "WDC WD???PVT*", "*" }, /*quirks*/DA_Q_4K }, { /* WDC Scorpio Blue Advanced Format (4k) drives */ { T_DIRECT, SIP_MEDIA_FIXED, "WDC WD??", "?PVT*", "*" }, /*quirks*/DA_Q_4K }, { /* WDC Scorpio Blue Advanced Format (4k) drives */ { T_DIRECT, SIP_MEDIA_FIXED, "ATA", "WDC WD?????PVT*", "*" }, /*quirks*/DA_Q_4K }, { /* WDC Scorpio Blue Advanced Format (4k) drives */ { T_DIRECT, SIP_MEDIA_FIXED, "WDC WD??", "???PVT*", "*" }, /*quirks*/DA_Q_4K }, { /* * Olympus FE-210 camera */ {T_DIRECT, SIP_MEDIA_REMOVABLE, "OLYMPUS", "FE210*", "*"}, /*quirks*/ DA_Q_NO_SYNC_CACHE }, { /* * LG UP3S MP3 player */ {T_DIRECT, SIP_MEDIA_REMOVABLE, "LG", "UP3S", "*"}, /*quirks*/ DA_Q_NO_SYNC_CACHE }, { /* * Laser MP3-2GA13 MP3 player */ {T_DIRECT, SIP_MEDIA_REMOVABLE, "USB 2.0", "(HS) Flash Disk", "*"}, /*quirks*/ DA_Q_NO_SYNC_CACHE }, { /* * LaCie external 250GB Hard drive des by Porsche * Submitted by: Ben Stuyts * PR: 121474 */ {T_DIRECT, SIP_MEDIA_FIXED, "SAMSUNG", "HM250JI", "*"}, /*quirks*/ DA_Q_NO_SYNC_CACHE }, /* SATA SSDs */ { /* * Corsair Force 2 SSDs * 4k optimised & trim only works in 4k requests + 4k aligned */ { T_DIRECT, SIP_MEDIA_FIXED, "ATA", "Corsair CSSD-F*", "*" }, /*quirks*/DA_Q_4K }, { /* * Corsair Force 3 SSDs * 4k optimised & trim only works in 4k requests + 4k aligned */ { T_DIRECT, SIP_MEDIA_FIXED, "ATA", "Corsair Force 3*", "*" }, /*quirks*/DA_Q_4K }, { /* * Corsair Neutron GTX SSDs * 4k optimised & trim only works in 4k requests + 4k aligned */ { T_DIRECT, SIP_MEDIA_FIXED, "*", "Corsair Neutron GTX*", "*" }, /*quirks*/DA_Q_4K }, { /* * Corsair Force GT & GS SSDs * 4k optimised & trim only works in 4k requests + 4k aligned */ { T_DIRECT, SIP_MEDIA_FIXED, "ATA", "Corsair Force G*", "*" }, /*quirks*/DA_Q_4K }, { /* * Crucial M4 SSDs * 4k optimised & trim only works in 4k requests + 4k aligned */ { T_DIRECT, SIP_MEDIA_FIXED, "ATA", "M4-CT???M4SSD2*", "*" }, /*quirks*/DA_Q_4K }, { /* * Crucial RealSSD C300 SSDs * 4k optimised */ { T_DIRECT, SIP_MEDIA_FIXED, "ATA", "C300-CTFDDAC???MAG*", "*" }, /*quirks*/DA_Q_4K }, { /* * Intel 320 Series SSDs * 4k optimised & trim only works in 4k requests + 4k aligned */ { T_DIRECT, SIP_MEDIA_FIXED, "ATA", "INTEL SSDSA2CW*", "*" }, /*quirks*/DA_Q_4K }, { /* * Intel 330 Series SSDs * 4k optimised & trim only works in 4k requests + 4k aligned */ { T_DIRECT, SIP_MEDIA_FIXED, "ATA", "INTEL SSDSC2CT*", "*" }, /*quirks*/DA_Q_4K }, { /* * Intel 510 Series SSDs * 4k optimised & trim only works in 4k requests + 4k aligned */ { T_DIRECT, SIP_MEDIA_FIXED, "ATA", "INTEL SSDSC2MH*", "*" }, /*quirks*/DA_Q_4K }, { /* * Intel 520 Series SSDs * 4k optimised & trim only works in 4k requests + 4k aligned */ { T_DIRECT, SIP_MEDIA_FIXED, "ATA", "INTEL SSDSC2BW*", "*" }, /*quirks*/DA_Q_4K }, { /* * Intel X25-M Series SSDs * 4k optimised & trim only works in 4k requests + 4k aligned */ { T_DIRECT, SIP_MEDIA_FIXED, "ATA", "INTEL SSDSA2M*", "*" }, /*quirks*/DA_Q_4K }, { /* * Kingston E100 Series SSDs * 4k optimised & trim only works in 4k requests + 4k aligned */ { T_DIRECT, SIP_MEDIA_FIXED, "ATA", "KINGSTON SE100S3*", "*" }, /*quirks*/DA_Q_4K }, { /* * Kingston HyperX 3k SSDs * 4k optimised & trim only works in 4k requests + 4k aligned */ { T_DIRECT, SIP_MEDIA_FIXED, "ATA", "KINGSTON SH103S3*", "*" }, /*quirks*/DA_Q_4K }, { /* * Marvell SSDs (entry taken from OpenSolaris) * 4k optimised & trim only works in 4k requests + 4k aligned */ { T_DIRECT, SIP_MEDIA_FIXED, "ATA", "MARVELL SD88SA02*", "*" }, /*quirks*/DA_Q_4K }, { /* * OCZ Agility 2 SSDs * 4k optimised & trim only works in 4k requests + 4k aligned */ { T_DIRECT, SIP_MEDIA_FIXED, "*", "OCZ-AGILITY2*", "*" }, /*quirks*/DA_Q_4K }, { /* * OCZ Agility 3 SSDs * 4k optimised & trim only works in 4k requests + 4k aligned */ { T_DIRECT, SIP_MEDIA_FIXED, "ATA", "OCZ-AGILITY3*", "*" }, /*quirks*/DA_Q_4K }, { /* * OCZ Deneva R Series SSDs * 4k optimised & trim only works in 4k requests + 4k aligned */ { T_DIRECT, SIP_MEDIA_FIXED, "ATA", "DENRSTE251M45*", "*" }, /*quirks*/DA_Q_4K }, { /* * OCZ Vertex 2 SSDs (inc pro series) * 4k optimised & trim only works in 4k requests + 4k aligned */ { T_DIRECT, SIP_MEDIA_FIXED, "ATA", "OCZ?VERTEX2*", "*" }, /*quirks*/DA_Q_4K }, { /* * OCZ Vertex 3 SSDs * 4k optimised & trim only works in 4k requests + 4k aligned */ { T_DIRECT, SIP_MEDIA_FIXED, "ATA", "OCZ-VERTEX3*", "*" }, /*quirks*/DA_Q_4K }, { /* * OCZ Vertex 4 SSDs * 4k optimised & trim only works in 4k requests + 4k aligned */ { T_DIRECT, SIP_MEDIA_FIXED, "ATA", "OCZ-VERTEX4*", "*" }, /*quirks*/DA_Q_4K }, { /* * Samsung 830 Series SSDs * 4k optimised & trim only works in 4k requests + 4k aligned */ { T_DIRECT, SIP_MEDIA_FIXED, "ATA", "SAMSUNG SSD 830 Series*", "*" }, /*quirks*/DA_Q_4K }, { /* * Samsung 840 SSDs * 4k optimised & trim only works in 4k requests + 4k aligned */ { T_DIRECT, SIP_MEDIA_FIXED, "ATA", "Samsung SSD 840*", "*" }, /*quirks*/DA_Q_4K }, { /* * Samsung 843T Series SSDs * 4k optimised */ { T_DIRECT, SIP_MEDIA_FIXED, "ATA", "SAMSUNG MZ7WD*", "*" }, /*quirks*/DA_Q_4K }, { /* * Samsung 850 SSDs * 4k optimised & trim only works in 4k requests + 4k aligned */ { T_DIRECT, SIP_MEDIA_FIXED, "ATA", "Samsung SSD 850*", "*" }, /*quirks*/DA_Q_4K }, { /* * Samsung PM853T Series SSDs * 4k optimised */ { T_DIRECT, SIP_MEDIA_FIXED, "ATA", "SAMSUNG MZ7GE*", "*" }, /*quirks*/DA_Q_4K }, { /* * SuperTalent TeraDrive CT SSDs * 4k optimised & trim only works in 4k requests + 4k aligned */ { T_DIRECT, SIP_MEDIA_FIXED, "ATA", "FTM??CT25H*", "*" }, /*quirks*/DA_Q_4K }, { /* * XceedIOPS SATA SSDs * 4k optimised */ { T_DIRECT, SIP_MEDIA_FIXED, "ATA", "SG9XCS2D*", "*" }, /*quirks*/DA_Q_4K }, { /* * Hama Innostor USB-Stick */ { T_DIRECT, SIP_MEDIA_REMOVABLE, "Innostor", "Innostor*", "*" }, /*quirks*/DA_Q_NO_RC16 }, { /* * MX-ES USB Drive by Mach Xtreme */ { T_DIRECT, SIP_MEDIA_REMOVABLE, "MX", "MXUB3*", "*"}, /*quirks*/DA_Q_NO_RC16 }, }; static disk_strategy_t dastrategy; static dumper_t dadump; static periph_init_t dainit; static void daasync(void *callback_arg, u_int32_t code, struct cam_path *path, void *arg); static void dasysctlinit(void *context, int pending); static int dacmdsizesysctl(SYSCTL_HANDLER_ARGS); static int dadeletemethodsysctl(SYSCTL_HANDLER_ARGS); static int dadeletemaxsysctl(SYSCTL_HANDLER_ARGS); static void dadeletemethodset(struct da_softc *softc, da_delete_methods delete_method); static off_t dadeletemaxsize(struct da_softc *softc, da_delete_methods delete_method); static void dadeletemethodchoose(struct da_softc *softc, da_delete_methods default_method); static void daprobedone(struct cam_periph *periph, union ccb *ccb); static periph_ctor_t daregister; static periph_dtor_t dacleanup; static periph_start_t dastart; static periph_oninv_t daoninvalidate; static void dadone(struct cam_periph *periph, union ccb *done_ccb); static int daerror(union ccb *ccb, u_int32_t cam_flags, u_int32_t sense_flags); static void daprevent(struct cam_periph *periph, int action); static void dareprobe(struct cam_periph *periph); static void dasetgeom(struct cam_periph *periph, uint32_t block_len, uint64_t maxsector, struct scsi_read_capacity_data_long *rcaplong, size_t rcap_size); static timeout_t dasendorderedtag; static void dashutdown(void *arg, int howto); static timeout_t damediapoll; #ifndef DA_DEFAULT_POLL_PERIOD #define DA_DEFAULT_POLL_PERIOD 3 #endif #ifndef DA_DEFAULT_TIMEOUT #define DA_DEFAULT_TIMEOUT 60 /* Timeout in seconds */ #endif #ifndef DA_DEFAULT_RETRY #define DA_DEFAULT_RETRY 4 #endif #ifndef DA_DEFAULT_SEND_ORDERED #define DA_DEFAULT_SEND_ORDERED 1 #endif #define DA_SIO (softc->sort_io_queue >= 0 ? \ softc->sort_io_queue : cam_sort_io_queues) static int da_poll_period = DA_DEFAULT_POLL_PERIOD; static int da_retry_count = DA_DEFAULT_RETRY; static int da_default_timeout = DA_DEFAULT_TIMEOUT; static int da_send_ordered = DA_DEFAULT_SEND_ORDERED; static SYSCTL_NODE(_kern_cam, OID_AUTO, da, CTLFLAG_RD, 0, "CAM Direct Access Disk driver"); SYSCTL_INT(_kern_cam_da, OID_AUTO, poll_period, CTLFLAG_RWTUN, &da_poll_period, 0, "Media polling period in seconds"); SYSCTL_INT(_kern_cam_da, OID_AUTO, retry_count, CTLFLAG_RWTUN, &da_retry_count, 0, "Normal I/O retry count"); SYSCTL_INT(_kern_cam_da, OID_AUTO, default_timeout, CTLFLAG_RWTUN, &da_default_timeout, 0, "Normal I/O timeout (in seconds)"); SYSCTL_INT(_kern_cam_da, OID_AUTO, send_ordered, CTLFLAG_RWTUN, &da_send_ordered, 0, "Send Ordered Tags"); /* * DA_ORDEREDTAG_INTERVAL determines how often, relative * to the default timeout, we check to see whether an ordered * tagged transaction is appropriate to prevent simple tag * starvation. Since we'd like to ensure that there is at least * 1/2 of the timeout length left for a starved transaction to * complete after we've sent an ordered tag, we must poll at least * four times in every timeout period. This takes care of the worst * case where a starved transaction starts during an interval that * meets the requirement "don't send an ordered tag" test so it takes * us two intervals to determine that a tag must be sent. */ #ifndef DA_ORDEREDTAG_INTERVAL #define DA_ORDEREDTAG_INTERVAL 4 #endif static struct periph_driver dadriver = { dainit, "da", TAILQ_HEAD_INITIALIZER(dadriver.units), /* generation */ 0 }; PERIPHDRIVER_DECLARE(da, dadriver); static MALLOC_DEFINE(M_SCSIDA, "scsi_da", "scsi_da buffers"); static int daopen(struct disk *dp) { struct cam_periph *periph; struct da_softc *softc; int error; periph = (struct cam_periph *)dp->d_drv1; if (cam_periph_acquire(periph) != CAM_REQ_CMP) { return (ENXIO); } cam_periph_lock(periph); if ((error = cam_periph_hold(periph, PRIBIO|PCATCH)) != 0) { cam_periph_unlock(periph); cam_periph_release(periph); return (error); } CAM_DEBUG(periph->path, CAM_DEBUG_TRACE | CAM_DEBUG_PERIPH, ("daopen\n")); softc = (struct da_softc *)periph->softc; dareprobe(periph); /* Wait for the disk size update. */ error = cam_periph_sleep(periph, &softc->disk->d_mediasize, PRIBIO, "dareprobe", 0); if (error != 0) xpt_print(periph->path, "unable to retrieve capacity data\n"); if (periph->flags & CAM_PERIPH_INVALID) error = ENXIO; if (error == 0 && (softc->flags & DA_FLAG_PACK_REMOVABLE) != 0 && (softc->quirks & DA_Q_NO_PREVENT) == 0) daprevent(periph, PR_PREVENT); if (error == 0) { softc->flags &= ~DA_FLAG_PACK_INVALID; softc->flags |= DA_FLAG_OPEN; } cam_periph_unhold(periph); cam_periph_unlock(periph); if (error != 0) cam_periph_release(periph); return (error); } static int daclose(struct disk *dp) { struct cam_periph *periph; struct da_softc *softc; union ccb *ccb; int error; periph = (struct cam_periph *)dp->d_drv1; softc = (struct da_softc *)periph->softc; cam_periph_lock(periph); CAM_DEBUG(periph->path, CAM_DEBUG_TRACE | CAM_DEBUG_PERIPH, ("daclose\n")); if (cam_periph_hold(periph, PRIBIO) == 0) { /* Flush disk cache. */ if ((softc->flags & DA_FLAG_DIRTY) != 0 && (softc->quirks & DA_Q_NO_SYNC_CACHE) == 0 && (softc->flags & DA_FLAG_PACK_INVALID) == 0) { ccb = cam_periph_getccb(periph, CAM_PRIORITY_NORMAL); scsi_synchronize_cache(&ccb->csio, /*retries*/1, /*cbfcnp*/dadone, MSG_SIMPLE_Q_TAG, /*begin_lba*/0, /*lb_count*/0, SSD_FULL_SIZE, 5 * 60 * 1000); error = cam_periph_runccb(ccb, daerror, /*cam_flags*/0, /*sense_flags*/SF_RETRY_UA | SF_QUIET_IR, softc->disk->d_devstat); if (error == 0) softc->flags &= ~DA_FLAG_DIRTY; xpt_release_ccb(ccb); } /* Allow medium removal. */ if ((softc->flags & DA_FLAG_PACK_REMOVABLE) != 0 && (softc->quirks & DA_Q_NO_PREVENT) == 0) daprevent(periph, PR_ALLOW); cam_periph_unhold(periph); } /* * If we've got removeable media, mark the blocksize as * unavailable, since it could change when new media is * inserted. */ if ((softc->flags & DA_FLAG_PACK_REMOVABLE) != 0) softc->disk->d_devstat->flags |= DEVSTAT_BS_UNAVAILABLE; softc->flags &= ~DA_FLAG_OPEN; while (softc->refcount != 0) cam_periph_sleep(periph, &softc->refcount, PRIBIO, "daclose", 1); cam_periph_unlock(periph); cam_periph_release(periph); return (0); } static void daschedule(struct cam_periph *periph) { struct da_softc *softc = (struct da_softc *)periph->softc; if (softc->state != DA_STATE_NORMAL) return; /* Check if we have more work to do. */ if (bioq_first(&softc->bio_queue) || (!softc->delete_running && bioq_first(&softc->delete_queue)) || softc->tur) { xpt_schedule(periph, CAM_PRIORITY_NORMAL); } } /* * Actually translate the requested transfer into one the physical driver * can understand. The transfer is described by a buf and will include * only one physical transfer. */ static void dastrategy(struct bio *bp) { struct cam_periph *periph; struct da_softc *softc; periph = (struct cam_periph *)bp->bio_disk->d_drv1; softc = (struct da_softc *)periph->softc; cam_periph_lock(periph); /* * If the device has been made invalid, error out */ if ((softc->flags & DA_FLAG_PACK_INVALID)) { cam_periph_unlock(periph); biofinish(bp, NULL, ENXIO); return; } CAM_DEBUG(periph->path, CAM_DEBUG_TRACE, ("dastrategy(%p)\n", bp)); /* * Place it in the queue of disk activities for this disk */ if (bp->bio_cmd == BIO_DELETE) { bioq_disksort(&softc->delete_queue, bp); } else if (DA_SIO) { bioq_disksort(&softc->bio_queue, bp); } else { bioq_insert_tail(&softc->bio_queue, bp); } /* * Schedule ourselves for performing the work. */ daschedule(periph); cam_periph_unlock(periph); return; } static int dadump(void *arg, void *virtual, vm_offset_t physical, off_t offset, size_t length) { struct cam_periph *periph; struct da_softc *softc; u_int secsize; struct ccb_scsiio csio; struct disk *dp; int error = 0; dp = arg; periph = dp->d_drv1; softc = (struct da_softc *)periph->softc; cam_periph_lock(periph); secsize = softc->params.secsize; if ((softc->flags & DA_FLAG_PACK_INVALID) != 0) { cam_periph_unlock(periph); return (ENXIO); } if (length > 0) { xpt_setup_ccb(&csio.ccb_h, periph->path, CAM_PRIORITY_NORMAL); csio.ccb_h.ccb_state = DA_CCB_DUMP; scsi_read_write(&csio, /*retries*/0, dadone, MSG_ORDERED_Q_TAG, /*read*/SCSI_RW_WRITE, /*byte2*/0, /*minimum_cmd_size*/ softc->minimum_cmd_size, offset / secsize, length / secsize, /*data_ptr*/(u_int8_t *) virtual, /*dxfer_len*/length, /*sense_len*/SSD_FULL_SIZE, da_default_timeout * 1000); xpt_polled_action((union ccb *)&csio); error = cam_periph_error((union ccb *)&csio, 0, SF_NO_RECOVERY | SF_NO_RETRY, NULL); if ((csio.ccb_h.status & CAM_DEV_QFRZN) != 0) cam_release_devq(csio.ccb_h.path, /*relsim_flags*/0, /*reduction*/0, /*timeout*/0, /*getcount_only*/0); if (error != 0) printf("Aborting dump due to I/O error.\n"); cam_periph_unlock(periph); return (error); } /* * Sync the disk cache contents to the physical media. */ if ((softc->quirks & DA_Q_NO_SYNC_CACHE) == 0) { xpt_setup_ccb(&csio.ccb_h, periph->path, CAM_PRIORITY_NORMAL); csio.ccb_h.ccb_state = DA_CCB_DUMP; scsi_synchronize_cache(&csio, /*retries*/0, /*cbfcnp*/dadone, MSG_SIMPLE_Q_TAG, /*begin_lba*/0,/* Cover the whole disk */ /*lb_count*/0, SSD_FULL_SIZE, 5 * 60 * 1000); xpt_polled_action((union ccb *)&csio); error = cam_periph_error((union ccb *)&csio, 0, SF_NO_RECOVERY | SF_NO_RETRY | SF_QUIET_IR, NULL); if ((csio.ccb_h.status & CAM_DEV_QFRZN) != 0) cam_release_devq(csio.ccb_h.path, /*relsim_flags*/0, /*reduction*/0, /*timeout*/0, /*getcount_only*/0); if (error != 0) xpt_print(periph->path, "Synchronize cache failed\n"); } cam_periph_unlock(periph); return (error); } static int dagetattr(struct bio *bp) { int ret; struct cam_periph *periph; periph = (struct cam_periph *)bp->bio_disk->d_drv1; cam_periph_lock(periph); ret = xpt_getattr(bp->bio_data, bp->bio_length, bp->bio_attribute, periph->path); cam_periph_unlock(periph); if (ret == 0) bp->bio_completed = bp->bio_length; return ret; } static void dainit(void) { cam_status status; /* * Install a global async callback. This callback will * receive async callbacks like "new device found". */ status = xpt_register_async(AC_FOUND_DEVICE, daasync, NULL, NULL); if (status != CAM_REQ_CMP) { printf("da: Failed to attach master async callback " "due to status 0x%x!\n", status); } else if (da_send_ordered) { /* Register our shutdown event handler */ if ((EVENTHANDLER_REGISTER(shutdown_post_sync, dashutdown, NULL, SHUTDOWN_PRI_DEFAULT)) == NULL) printf("dainit: shutdown event registration failed!\n"); } } /* * Callback from GEOM, called when it has finished cleaning up its * resources. */ static void dadiskgonecb(struct disk *dp) { struct cam_periph *periph; periph = (struct cam_periph *)dp->d_drv1; cam_periph_release(periph); } static void daoninvalidate(struct cam_periph *periph) { struct da_softc *softc; softc = (struct da_softc *)periph->softc; /* * De-register any async callbacks. */ xpt_register_async(0, daasync, periph, periph->path); softc->flags |= DA_FLAG_PACK_INVALID; /* * Return all queued I/O with ENXIO. * XXX Handle any transactions queued to the card * with XPT_ABORT_CCB. */ bioq_flush(&softc->bio_queue, NULL, ENXIO); bioq_flush(&softc->delete_queue, NULL, ENXIO); /* * Tell GEOM that we've gone away, we'll get a callback when it is * done cleaning up its resources. */ disk_gone(softc->disk); } static void dacleanup(struct cam_periph *periph) { struct da_softc *softc; softc = (struct da_softc *)periph->softc; cam_periph_unlock(periph); /* * If we can't free the sysctl tree, oh well... */ if ((softc->flags & DA_FLAG_SCTX_INIT) != 0 && sysctl_ctx_free(&softc->sysctl_ctx) != 0) { xpt_print(periph->path, "can't remove sysctl context\n"); } callout_drain(&softc->mediapoll_c); disk_destroy(softc->disk); callout_drain(&softc->sendordered_c); free(softc, M_DEVBUF); cam_periph_lock(periph); } static void daasync(void *callback_arg, u_int32_t code, struct cam_path *path, void *arg) { struct cam_periph *periph; struct da_softc *softc; periph = (struct cam_periph *)callback_arg; switch (code) { case AC_FOUND_DEVICE: { struct ccb_getdev *cgd; cam_status status; cgd = (struct ccb_getdev *)arg; if (cgd == NULL) break; if (cgd->protocol != PROTO_SCSI) break; - + if (SID_QUAL(&cgd->inq_data) != SID_QUAL_LU_CONNECTED) + break; if (SID_TYPE(&cgd->inq_data) != T_DIRECT && SID_TYPE(&cgd->inq_data) != T_RBC && SID_TYPE(&cgd->inq_data) != T_OPTICAL) break; /* * Allocate a peripheral instance for * this device and start the probe * process. */ status = cam_periph_alloc(daregister, daoninvalidate, dacleanup, dastart, "da", CAM_PERIPH_BIO, path, daasync, AC_FOUND_DEVICE, cgd); if (status != CAM_REQ_CMP && status != CAM_REQ_INPROG) printf("daasync: Unable to attach to new device " "due to status 0x%x\n", status); return; } case AC_ADVINFO_CHANGED: { uintptr_t buftype; buftype = (uintptr_t)arg; if (buftype == CDAI_TYPE_PHYS_PATH) { struct da_softc *softc; softc = periph->softc; disk_attr_changed(softc->disk, "GEOM::physpath", M_NOWAIT); } break; } case AC_UNIT_ATTENTION: { union ccb *ccb; int error_code, sense_key, asc, ascq; softc = (struct da_softc *)periph->softc; ccb = (union ccb *)arg; /* * Handle all UNIT ATTENTIONs except our own, * as they will be handled by daerror(). */ if (xpt_path_periph(ccb->ccb_h.path) != periph && scsi_extract_sense_ccb(ccb, &error_code, &sense_key, &asc, &ascq)) { if (asc == 0x2A && ascq == 0x09) { xpt_print(ccb->ccb_h.path, "Capacity data has changed\n"); softc->flags &= ~DA_FLAG_PROBED; dareprobe(periph); } else if (asc == 0x28 && ascq == 0x00) { softc->flags &= ~DA_FLAG_PROBED; disk_media_changed(softc->disk, M_NOWAIT); } else if (asc == 0x3F && ascq == 0x03) { xpt_print(ccb->ccb_h.path, "INQUIRY data has changed\n"); softc->flags &= ~DA_FLAG_PROBED; dareprobe(periph); } } cam_periph_async(periph, code, path, arg); break; } case AC_SCSI_AEN: softc = (struct da_softc *)periph->softc; if (!softc->tur) { if (cam_periph_acquire(periph) == CAM_REQ_CMP) { softc->tur = 1; daschedule(periph); } } /* FALLTHROUGH */ case AC_SENT_BDR: case AC_BUS_RESET: { struct ccb_hdr *ccbh; softc = (struct da_softc *)periph->softc; /* * Don't fail on the expected unit attention * that will occur. */ softc->flags |= DA_FLAG_RETRY_UA; LIST_FOREACH(ccbh, &softc->pending_ccbs, periph_links.le) ccbh->ccb_state |= DA_CCB_RETRY_UA; break; } default: break; } cam_periph_async(periph, code, path, arg); } static void dasysctlinit(void *context, int pending) { struct cam_periph *periph; struct da_softc *softc; char tmpstr[80], tmpstr2[80]; struct ccb_trans_settings cts; periph = (struct cam_periph *)context; /* * periph was held for us when this task was enqueued */ if (periph->flags & CAM_PERIPH_INVALID) { cam_periph_release(periph); return; } softc = (struct da_softc *)periph->softc; snprintf(tmpstr, sizeof(tmpstr), "CAM DA unit %d", periph->unit_number); snprintf(tmpstr2, sizeof(tmpstr2), "%d", periph->unit_number); sysctl_ctx_init(&softc->sysctl_ctx); softc->flags |= DA_FLAG_SCTX_INIT; softc->sysctl_tree = SYSCTL_ADD_NODE(&softc->sysctl_ctx, SYSCTL_STATIC_CHILDREN(_kern_cam_da), OID_AUTO, tmpstr2, CTLFLAG_RD, 0, tmpstr); if (softc->sysctl_tree == NULL) { printf("dasysctlinit: unable to allocate sysctl tree\n"); cam_periph_release(periph); return; } /* * Now register the sysctl handler, so the user can change the value on * the fly. */ SYSCTL_ADD_PROC(&softc->sysctl_ctx, SYSCTL_CHILDREN(softc->sysctl_tree), OID_AUTO, "delete_method", CTLTYPE_STRING | CTLFLAG_RW, softc, 0, dadeletemethodsysctl, "A", "BIO_DELETE execution method"); SYSCTL_ADD_PROC(&softc->sysctl_ctx, SYSCTL_CHILDREN(softc->sysctl_tree), OID_AUTO, "delete_max", CTLTYPE_U64 | CTLFLAG_RW, softc, 0, dadeletemaxsysctl, "Q", "Maximum BIO_DELETE size"); SYSCTL_ADD_PROC(&softc->sysctl_ctx, SYSCTL_CHILDREN(softc->sysctl_tree), OID_AUTO, "minimum_cmd_size", CTLTYPE_INT | CTLFLAG_RW, &softc->minimum_cmd_size, 0, dacmdsizesysctl, "I", "Minimum CDB size"); SYSCTL_ADD_INT(&softc->sysctl_ctx, SYSCTL_CHILDREN(softc->sysctl_tree), OID_AUTO, "sort_io_queue", CTLFLAG_RW, &softc->sort_io_queue, 0, "Sort IO queue to try and optimise disk access patterns"); SYSCTL_ADD_INT(&softc->sysctl_ctx, SYSCTL_CHILDREN(softc->sysctl_tree), OID_AUTO, "error_inject", CTLFLAG_RW, &softc->error_inject, 0, "error_inject leaf"); /* * Add some addressing info. */ memset(&cts, 0, sizeof (cts)); xpt_setup_ccb(&cts.ccb_h, periph->path, CAM_PRIORITY_NONE); cts.ccb_h.func_code = XPT_GET_TRAN_SETTINGS; cts.type = CTS_TYPE_CURRENT_SETTINGS; cam_periph_lock(periph); xpt_action((union ccb *)&cts); cam_periph_unlock(periph); if (cts.ccb_h.status != CAM_REQ_CMP) { cam_periph_release(periph); return; } if (cts.protocol == PROTO_SCSI && cts.transport == XPORT_FC) { struct ccb_trans_settings_fc *fc = &cts.xport_specific.fc; if (fc->valid & CTS_FC_VALID_WWPN) { softc->wwpn = fc->wwpn; SYSCTL_ADD_UQUAD(&softc->sysctl_ctx, SYSCTL_CHILDREN(softc->sysctl_tree), OID_AUTO, "wwpn", CTLFLAG_RD, &softc->wwpn, "World Wide Port Name"); } } cam_periph_release(periph); } static int dadeletemaxsysctl(SYSCTL_HANDLER_ARGS) { int error; uint64_t value; struct da_softc *softc; softc = (struct da_softc *)arg1; value = softc->disk->d_delmaxsize; error = sysctl_handle_64(oidp, &value, 0, req); if ((error != 0) || (req->newptr == NULL)) return (error); /* only accept values smaller than the calculated value */ if (value > dadeletemaxsize(softc, softc->delete_method)) { return (EINVAL); } softc->disk->d_delmaxsize = value; return (0); } static int dacmdsizesysctl(SYSCTL_HANDLER_ARGS) { int error, value; value = *(int *)arg1; error = sysctl_handle_int(oidp, &value, 0, req); if ((error != 0) || (req->newptr == NULL)) return (error); /* * Acceptable values here are 6, 10, 12 or 16. */ if (value < 6) value = 6; else if ((value > 6) && (value <= 10)) value = 10; else if ((value > 10) && (value <= 12)) value = 12; else if (value > 12) value = 16; *(int *)arg1 = value; return (0); } static void dadeletemethodset(struct da_softc *softc, da_delete_methods delete_method) { softc->delete_method = delete_method; softc->disk->d_delmaxsize = dadeletemaxsize(softc, delete_method); softc->delete_func = da_delete_functions[delete_method]; if (softc->delete_method > DA_DELETE_DISABLE) softc->disk->d_flags |= DISKFLAG_CANDELETE; else softc->disk->d_flags &= ~DISKFLAG_CANDELETE; } static off_t dadeletemaxsize(struct da_softc *softc, da_delete_methods delete_method) { off_t sectors; switch(delete_method) { case DA_DELETE_UNMAP: sectors = (off_t)softc->unmap_max_lba; break; case DA_DELETE_ATA_TRIM: sectors = (off_t)ATA_DSM_RANGE_MAX * softc->trim_max_ranges; break; case DA_DELETE_WS16: sectors = omin(softc->ws_max_blks, WS16_MAX_BLKS); break; case DA_DELETE_ZERO: case DA_DELETE_WS10: sectors = omin(softc->ws_max_blks, WS10_MAX_BLKS); break; default: return 0; } return (off_t)softc->params.secsize * omin(sectors, softc->params.sectors); } static void daprobedone(struct cam_periph *periph, union ccb *ccb) { struct da_softc *softc; softc = (struct da_softc *)periph->softc; dadeletemethodchoose(softc, DA_DELETE_NONE); if (bootverbose && (softc->flags & DA_FLAG_ANNOUNCED) == 0) { char buf[80]; int i, sep; snprintf(buf, sizeof(buf), "Delete methods: <"); sep = 0; for (i = DA_DELETE_MIN; i <= DA_DELETE_MAX; i++) { if (softc->delete_available & (1 << i)) { if (sep) { strlcat(buf, ",", sizeof(buf)); } else { sep = 1; } strlcat(buf, da_delete_method_names[i], sizeof(buf)); if (i == softc->delete_method) { strlcat(buf, "(*)", sizeof(buf)); } } } if (sep == 0) { if (softc->delete_method == DA_DELETE_NONE) strlcat(buf, "NONE(*)", sizeof(buf)); else strlcat(buf, "DISABLED(*)", sizeof(buf)); } strlcat(buf, ">", sizeof(buf)); printf("%s%d: %s\n", periph->periph_name, periph->unit_number, buf); } /* * Since our peripheral may be invalidated by an error * above or an external event, we must release our CCB * before releasing the probe lock on the peripheral. * The peripheral will only go away once the last lock * is removed, and we need it around for the CCB release * operation. */ xpt_release_ccb(ccb); softc->state = DA_STATE_NORMAL; softc->flags |= DA_FLAG_PROBED; daschedule(periph); wakeup(&softc->disk->d_mediasize); if ((softc->flags & DA_FLAG_ANNOUNCED) == 0) { softc->flags |= DA_FLAG_ANNOUNCED; cam_periph_unhold(periph); } else cam_periph_release_locked(periph); } static void dadeletemethodchoose(struct da_softc *softc, da_delete_methods default_method) { int i, delete_method; delete_method = default_method; /* * Use the pre-defined order to choose the best * performing delete. */ for (i = DA_DELETE_MIN; i <= DA_DELETE_MAX; i++) { if (softc->delete_available & (1 << i)) { dadeletemethodset(softc, i); return; } } dadeletemethodset(softc, delete_method); } static int dadeletemethodsysctl(SYSCTL_HANDLER_ARGS) { char buf[16]; const char *p; struct da_softc *softc; int i, error, methods, value; softc = (struct da_softc *)arg1; value = softc->delete_method; if (value < 0 || value > DA_DELETE_MAX) p = "UNKNOWN"; else p = da_delete_method_names[value]; strncpy(buf, p, sizeof(buf)); error = sysctl_handle_string(oidp, buf, sizeof(buf), req); if (error != 0 || req->newptr == NULL) return (error); methods = softc->delete_available | (1 << DA_DELETE_DISABLE); for (i = 0; i <= DA_DELETE_MAX; i++) { if (!(methods & (1 << i)) || strcmp(buf, da_delete_method_names[i]) != 0) continue; dadeletemethodset(softc, i); return (0); } return (EINVAL); } static cam_status daregister(struct cam_periph *periph, void *arg) { struct da_softc *softc; struct ccb_pathinq cpi; struct ccb_getdev *cgd; char tmpstr[80]; caddr_t match; cgd = (struct ccb_getdev *)arg; if (cgd == NULL) { printf("daregister: no getdev CCB, can't register device\n"); return(CAM_REQ_CMP_ERR); } softc = (struct da_softc *)malloc(sizeof(*softc), M_DEVBUF, M_NOWAIT|M_ZERO); if (softc == NULL) { printf("daregister: Unable to probe new device. " "Unable to allocate softc\n"); return(CAM_REQ_CMP_ERR); } LIST_INIT(&softc->pending_ccbs); softc->state = DA_STATE_PROBE_RC; bioq_init(&softc->bio_queue); bioq_init(&softc->delete_queue); bioq_init(&softc->delete_run_queue); if (SID_IS_REMOVABLE(&cgd->inq_data)) softc->flags |= DA_FLAG_PACK_REMOVABLE; softc->unmap_max_ranges = UNMAP_MAX_RANGES; softc->unmap_max_lba = UNMAP_RANGE_MAX; softc->ws_max_blks = WS16_MAX_BLKS; softc->trim_max_ranges = ATA_TRIM_MAX_RANGES; softc->sort_io_queue = -1; periph->softc = softc; /* * See if this device has any quirks. */ match = cam_quirkmatch((caddr_t)&cgd->inq_data, (caddr_t)da_quirk_table, sizeof(da_quirk_table)/sizeof(*da_quirk_table), sizeof(*da_quirk_table), scsi_inquiry_match); if (match != NULL) softc->quirks = ((struct da_quirk_entry *)match)->quirks; else softc->quirks = DA_Q_NONE; /* Check if the SIM does not want 6 byte commands */ bzero(&cpi, sizeof(cpi)); xpt_setup_ccb(&cpi.ccb_h, periph->path, CAM_PRIORITY_NORMAL); cpi.ccb_h.func_code = XPT_PATH_INQ; xpt_action((union ccb *)&cpi); if (cpi.ccb_h.status == CAM_REQ_CMP && (cpi.hba_misc & PIM_NO_6_BYTE)) softc->quirks |= DA_Q_NO_6_BYTE; TASK_INIT(&softc->sysctl_task, 0, dasysctlinit, periph); /* * Take an exclusive refcount on the periph while dastart is called * to finish the probe. The reference will be dropped in dadone at * the end of probe. */ (void)cam_periph_hold(periph, PRIBIO); /* * Schedule a periodic event to occasionally send an * ordered tag to a device. */ callout_init_mtx(&softc->sendordered_c, cam_periph_mtx(periph), 0); callout_reset(&softc->sendordered_c, (da_default_timeout * hz) / DA_ORDEREDTAG_INTERVAL, dasendorderedtag, softc); cam_periph_unlock(periph); /* * RBC devices don't have to support READ(6), only READ(10). */ if (softc->quirks & DA_Q_NO_6_BYTE || SID_TYPE(&cgd->inq_data) == T_RBC) softc->minimum_cmd_size = 10; else softc->minimum_cmd_size = 6; /* * Load the user's default, if any. */ snprintf(tmpstr, sizeof(tmpstr), "kern.cam.da.%d.minimum_cmd_size", periph->unit_number); TUNABLE_INT_FETCH(tmpstr, &softc->minimum_cmd_size); /* * 6, 10, 12 and 16 are the currently permissible values. */ if (softc->minimum_cmd_size < 6) softc->minimum_cmd_size = 6; else if ((softc->minimum_cmd_size > 6) && (softc->minimum_cmd_size <= 10)) softc->minimum_cmd_size = 10; else if ((softc->minimum_cmd_size > 10) && (softc->minimum_cmd_size <= 12)) softc->minimum_cmd_size = 12; else if (softc->minimum_cmd_size > 12) softc->minimum_cmd_size = 16; /* Predict whether device may support READ CAPACITY(16). */ if (SID_ANSI_REV(&cgd->inq_data) >= SCSI_REV_SPC3 && (softc->quirks & DA_Q_NO_RC16) == 0) { softc->flags |= DA_FLAG_CAN_RC16; softc->state = DA_STATE_PROBE_RC16; } /* * Register this media as a disk. */ softc->disk = disk_alloc(); softc->disk->d_devstat = devstat_new_entry(periph->periph_name, periph->unit_number, 0, DEVSTAT_BS_UNAVAILABLE, SID_TYPE(&cgd->inq_data) | XPORT_DEVSTAT_TYPE(cpi.transport), DEVSTAT_PRIORITY_DISK); softc->disk->d_open = daopen; softc->disk->d_close = daclose; softc->disk->d_strategy = dastrategy; softc->disk->d_dump = dadump; softc->disk->d_getattr = dagetattr; softc->disk->d_gone = dadiskgonecb; softc->disk->d_name = "da"; softc->disk->d_drv1 = periph; if (cpi.maxio == 0) softc->maxio = DFLTPHYS; /* traditional default */ else if (cpi.maxio > MAXPHYS) softc->maxio = MAXPHYS; /* for safety */ else softc->maxio = cpi.maxio; softc->disk->d_maxsize = softc->maxio; softc->disk->d_unit = periph->unit_number; softc->disk->d_flags = DISKFLAG_DIRECT_COMPLETION; if ((softc->quirks & DA_Q_NO_SYNC_CACHE) == 0) softc->disk->d_flags |= DISKFLAG_CANFLUSHCACHE; if ((cpi.hba_misc & PIM_UNMAPPED) != 0) softc->disk->d_flags |= DISKFLAG_UNMAPPED_BIO; cam_strvis(softc->disk->d_descr, cgd->inq_data.vendor, sizeof(cgd->inq_data.vendor), sizeof(softc->disk->d_descr)); strlcat(softc->disk->d_descr, " ", sizeof(softc->disk->d_descr)); cam_strvis(&softc->disk->d_descr[strlen(softc->disk->d_descr)], cgd->inq_data.product, sizeof(cgd->inq_data.product), sizeof(softc->disk->d_descr) - strlen(softc->disk->d_descr)); softc->disk->d_hba_vendor = cpi.hba_vendor; softc->disk->d_hba_device = cpi.hba_device; softc->disk->d_hba_subvendor = cpi.hba_subvendor; softc->disk->d_hba_subdevice = cpi.hba_subdevice; /* * Acquire a reference to the periph before we register with GEOM. * We'll release this reference once GEOM calls us back (via * dadiskgonecb()) telling us that our provider has been freed. */ if (cam_periph_acquire(periph) != CAM_REQ_CMP) { xpt_print(periph->path, "%s: lost periph during " "registration!\n", __func__); cam_periph_lock(periph); return (CAM_REQ_CMP_ERR); } disk_create(softc->disk, DISK_VERSION); cam_periph_lock(periph); /* * Add async callbacks for events of interest. * I don't bother checking if this fails as, * in most cases, the system will function just * fine without them and the only alternative * would be to not attach the device on failure. */ xpt_register_async(AC_SENT_BDR | AC_BUS_RESET | AC_LOST_DEVICE | AC_ADVINFO_CHANGED | AC_SCSI_AEN | AC_UNIT_ATTENTION, daasync, periph, periph->path); /* * Emit an attribute changed notification just in case * physical path information arrived before our async * event handler was registered, but after anyone attaching * to our disk device polled it. */ disk_attr_changed(softc->disk, "GEOM::physpath", M_NOWAIT); /* * Schedule a periodic media polling events. */ callout_init_mtx(&softc->mediapoll_c, cam_periph_mtx(periph), 0); if ((softc->flags & DA_FLAG_PACK_REMOVABLE) && (cgd->inq_flags & SID_AEN) == 0 && da_poll_period != 0) callout_reset(&softc->mediapoll_c, da_poll_period * hz, damediapoll, periph); xpt_schedule(periph, CAM_PRIORITY_DEV); return(CAM_REQ_CMP); } static void dastart(struct cam_periph *periph, union ccb *start_ccb) { struct da_softc *softc; softc = (struct da_softc *)periph->softc; CAM_DEBUG(periph->path, CAM_DEBUG_TRACE, ("dastart\n")); skipstate: switch (softc->state) { case DA_STATE_NORMAL: { struct bio *bp; uint8_t tag_code; /* Run BIO_DELETE if not running yet. */ if (!softc->delete_running && (bp = bioq_first(&softc->delete_queue)) != NULL) { if (softc->delete_func != NULL) { softc->delete_func(periph, start_ccb, bp); goto out; } else { bioq_flush(&softc->delete_queue, NULL, 0); /* FALLTHROUGH */ } } /* Run regular command. */ bp = bioq_takefirst(&softc->bio_queue); if (bp == NULL) { if (softc->tur) { softc->tur = 0; scsi_test_unit_ready(&start_ccb->csio, /*retries*/ da_retry_count, dadone, MSG_SIMPLE_Q_TAG, SSD_FULL_SIZE, da_default_timeout * 1000); start_ccb->ccb_h.ccb_bp = NULL; start_ccb->ccb_h.ccb_state = DA_CCB_TUR; xpt_action(start_ccb); } else xpt_release_ccb(start_ccb); break; } if (softc->tur) { softc->tur = 0; cam_periph_release_locked(periph); } if ((bp->bio_flags & BIO_ORDERED) != 0 || (softc->flags & DA_FLAG_NEED_OTAG) != 0) { softc->flags &= ~DA_FLAG_NEED_OTAG; softc->flags |= DA_FLAG_WAS_OTAG; tag_code = MSG_ORDERED_Q_TAG; } else { tag_code = MSG_SIMPLE_Q_TAG; } switch (bp->bio_cmd) { case BIO_WRITE: softc->flags |= DA_FLAG_DIRTY; /* FALLTHROUGH */ case BIO_READ: scsi_read_write(&start_ccb->csio, /*retries*/da_retry_count, /*cbfcnp*/dadone, /*tag_action*/tag_code, /*read_op*/(bp->bio_cmd == BIO_READ ? SCSI_RW_READ : SCSI_RW_WRITE) | ((bp->bio_flags & BIO_UNMAPPED) != 0 ? SCSI_RW_BIO : 0), /*byte2*/0, softc->minimum_cmd_size, /*lba*/bp->bio_pblkno, /*block_count*/bp->bio_bcount / softc->params.secsize, /*data_ptr*/ (bp->bio_flags & BIO_UNMAPPED) != 0 ? (void *)bp : bp->bio_data, /*dxfer_len*/ bp->bio_bcount, /*sense_len*/SSD_FULL_SIZE, da_default_timeout * 1000); break; case BIO_FLUSH: /* * BIO_FLUSH doesn't currently communicate * range data, so we synchronize the cache * over the whole disk. We also force * ordered tag semantics the flush applies * to all previously queued I/O. */ scsi_synchronize_cache(&start_ccb->csio, /*retries*/1, /*cbfcnp*/dadone, MSG_ORDERED_Q_TAG, /*begin_lba*/0, /*lb_count*/0, SSD_FULL_SIZE, da_default_timeout*1000); break; } start_ccb->ccb_h.ccb_state = DA_CCB_BUFFER_IO; start_ccb->ccb_h.flags |= CAM_UNLOCKED; out: LIST_INSERT_HEAD(&softc->pending_ccbs, &start_ccb->ccb_h, periph_links.le); /* We expect a unit attention from this device */ if ((softc->flags & DA_FLAG_RETRY_UA) != 0) { start_ccb->ccb_h.ccb_state |= DA_CCB_RETRY_UA; softc->flags &= ~DA_FLAG_RETRY_UA; } start_ccb->ccb_h.ccb_bp = bp; softc->refcount++; cam_periph_unlock(periph); xpt_action(start_ccb); cam_periph_lock(periph); softc->refcount--; /* May have more work to do, so ensure we stay scheduled */ daschedule(periph); break; } case DA_STATE_PROBE_RC: { struct scsi_read_capacity_data *rcap; rcap = (struct scsi_read_capacity_data *) malloc(sizeof(*rcap), M_SCSIDA, M_NOWAIT|M_ZERO); if (rcap == NULL) { printf("dastart: Couldn't malloc read_capacity data\n"); /* da_free_periph??? */ break; } scsi_read_capacity(&start_ccb->csio, /*retries*/da_retry_count, dadone, MSG_SIMPLE_Q_TAG, rcap, SSD_FULL_SIZE, /*timeout*/5000); start_ccb->ccb_h.ccb_bp = NULL; start_ccb->ccb_h.ccb_state = DA_CCB_PROBE_RC; xpt_action(start_ccb); break; } case DA_STATE_PROBE_RC16: { struct scsi_read_capacity_data_long *rcaplong; rcaplong = (struct scsi_read_capacity_data_long *) malloc(sizeof(*rcaplong), M_SCSIDA, M_NOWAIT|M_ZERO); if (rcaplong == NULL) { printf("dastart: Couldn't malloc read_capacity data\n"); /* da_free_periph??? */ break; } scsi_read_capacity_16(&start_ccb->csio, /*retries*/ da_retry_count, /*cbfcnp*/ dadone, /*tag_action*/ MSG_SIMPLE_Q_TAG, /*lba*/ 0, /*reladr*/ 0, /*pmi*/ 0, /*rcap_buf*/ (uint8_t *)rcaplong, /*rcap_buf_len*/ sizeof(*rcaplong), /*sense_len*/ SSD_FULL_SIZE, /*timeout*/ da_default_timeout * 1000); start_ccb->ccb_h.ccb_bp = NULL; start_ccb->ccb_h.ccb_state = DA_CCB_PROBE_RC16; xpt_action(start_ccb); break; } case DA_STATE_PROBE_LBP: { struct scsi_vpd_logical_block_prov *lbp; if (!scsi_vpd_supported_page(periph, SVPD_LBP)) { /* * If we get here we don't support any SBC-3 delete * methods with UNMAP as the Logical Block Provisioning * VPD page support is required for devices which * support it according to T10/1799-D Revision 31 * however older revisions of the spec don't mandate * this so we currently don't remove these methods * from the available set. */ softc->state = DA_STATE_PROBE_BLK_LIMITS; goto skipstate; } lbp = (struct scsi_vpd_logical_block_prov *) malloc(sizeof(*lbp), M_SCSIDA, M_NOWAIT|M_ZERO); if (lbp == NULL) { printf("dastart: Couldn't malloc lbp data\n"); /* da_free_periph??? */ break; } scsi_inquiry(&start_ccb->csio, /*retries*/da_retry_count, /*cbfcnp*/dadone, /*tag_action*/MSG_SIMPLE_Q_TAG, /*inq_buf*/(u_int8_t *)lbp, /*inq_len*/sizeof(*lbp), /*evpd*/TRUE, /*page_code*/SVPD_LBP, /*sense_len*/SSD_MIN_SIZE, /*timeout*/da_default_timeout * 1000); start_ccb->ccb_h.ccb_bp = NULL; start_ccb->ccb_h.ccb_state = DA_CCB_PROBE_LBP; xpt_action(start_ccb); break; } case DA_STATE_PROBE_BLK_LIMITS: { struct scsi_vpd_block_limits *block_limits; if (!scsi_vpd_supported_page(periph, SVPD_BLOCK_LIMITS)) { /* Not supported skip to next probe */ softc->state = DA_STATE_PROBE_BDC; goto skipstate; } block_limits = (struct scsi_vpd_block_limits *) malloc(sizeof(*block_limits), M_SCSIDA, M_NOWAIT|M_ZERO); if (block_limits == NULL) { printf("dastart: Couldn't malloc block_limits data\n"); /* da_free_periph??? */ break; } scsi_inquiry(&start_ccb->csio, /*retries*/da_retry_count, /*cbfcnp*/dadone, /*tag_action*/MSG_SIMPLE_Q_TAG, /*inq_buf*/(u_int8_t *)block_limits, /*inq_len*/sizeof(*block_limits), /*evpd*/TRUE, /*page_code*/SVPD_BLOCK_LIMITS, /*sense_len*/SSD_MIN_SIZE, /*timeout*/da_default_timeout * 1000); start_ccb->ccb_h.ccb_bp = NULL; start_ccb->ccb_h.ccb_state = DA_CCB_PROBE_BLK_LIMITS; xpt_action(start_ccb); break; } case DA_STATE_PROBE_BDC: { struct scsi_vpd_block_characteristics *bdc; if (!scsi_vpd_supported_page(periph, SVPD_BDC)) { softc->state = DA_STATE_PROBE_ATA; goto skipstate; } bdc = (struct scsi_vpd_block_characteristics *) malloc(sizeof(*bdc), M_SCSIDA, M_NOWAIT|M_ZERO); if (bdc == NULL) { printf("dastart: Couldn't malloc bdc data\n"); /* da_free_periph??? */ break; } scsi_inquiry(&start_ccb->csio, /*retries*/da_retry_count, /*cbfcnp*/dadone, /*tag_action*/MSG_SIMPLE_Q_TAG, /*inq_buf*/(u_int8_t *)bdc, /*inq_len*/sizeof(*bdc), /*evpd*/TRUE, /*page_code*/SVPD_BDC, /*sense_len*/SSD_MIN_SIZE, /*timeout*/da_default_timeout * 1000); start_ccb->ccb_h.ccb_bp = NULL; start_ccb->ccb_h.ccb_state = DA_CCB_PROBE_BDC; xpt_action(start_ccb); break; } case DA_STATE_PROBE_ATA: { struct ata_params *ata_params; if (!scsi_vpd_supported_page(periph, SVPD_ATA_INFORMATION)) { daprobedone(periph, start_ccb); break; } ata_params = (struct ata_params*) malloc(sizeof(*ata_params), M_SCSIDA, M_NOWAIT|M_ZERO); if (ata_params == NULL) { printf("dastart: Couldn't malloc ata_params data\n"); /* da_free_periph??? */ break; } scsi_ata_identify(&start_ccb->csio, /*retries*/da_retry_count, /*cbfcnp*/dadone, /*tag_action*/MSG_SIMPLE_Q_TAG, /*data_ptr*/(u_int8_t *)ata_params, /*dxfer_len*/sizeof(*ata_params), /*sense_len*/SSD_FULL_SIZE, /*timeout*/da_default_timeout * 1000); start_ccb->ccb_h.ccb_bp = NULL; start_ccb->ccb_h.ccb_state = DA_CCB_PROBE_ATA; xpt_action(start_ccb); break; } } } /* * In each of the methods below, while its the caller's * responsibility to ensure the request will fit into a * single device request, we might have changed the delete * method due to the device incorrectly advertising either * its supported methods or limits. * * To prevent this causing further issues we validate the * against the methods limits, and warn which would * otherwise be unnecessary. */ static void da_delete_unmap(struct cam_periph *periph, union ccb *ccb, struct bio *bp) { struct da_softc *softc = (struct da_softc *)periph->softc;; struct bio *bp1; uint8_t *buf = softc->unmap_buf; uint64_t lba, lastlba = (uint64_t)-1; uint64_t totalcount = 0; uint64_t count; uint32_t lastcount = 0, c; uint32_t off, ranges = 0; /* * Currently this doesn't take the UNMAP * Granularity and Granularity Alignment * fields into account. * * This could result in both unoptimal unmap * requests as as well as UNMAP calls unmapping * fewer LBA's than requested. */ softc->delete_running = 1; bzero(softc->unmap_buf, sizeof(softc->unmap_buf)); bp1 = bp; do { bioq_remove(&softc->delete_queue, bp1); if (bp1 != bp) bioq_insert_tail(&softc->delete_run_queue, bp1); lba = bp1->bio_pblkno; count = bp1->bio_bcount / softc->params.secsize; /* Try to extend the previous range. */ if (lba == lastlba) { c = omin(count, UNMAP_RANGE_MAX - lastcount); lastcount += c; off = ((ranges - 1) * UNMAP_RANGE_SIZE) + UNMAP_HEAD_SIZE; scsi_ulto4b(lastcount, &buf[off + 8]); count -= c; lba +=c; totalcount += c; } while (count > 0) { c = omin(count, UNMAP_RANGE_MAX); if (totalcount + c > softc->unmap_max_lba || ranges >= softc->unmap_max_ranges) { xpt_print(periph->path, "%s issuing short delete %ld > %ld" "|| %d >= %d", da_delete_method_desc[softc->delete_method], totalcount + c, softc->unmap_max_lba, ranges, softc->unmap_max_ranges); break; } off = (ranges * UNMAP_RANGE_SIZE) + UNMAP_HEAD_SIZE; scsi_u64to8b(lba, &buf[off + 0]); scsi_ulto4b(c, &buf[off + 8]); lba += c; totalcount += c; ranges++; count -= c; lastcount = c; } lastlba = lba; bp1 = bioq_first(&softc->delete_queue); if (bp1 == NULL || ranges >= softc->unmap_max_ranges || totalcount + bp1->bio_bcount / softc->params.secsize > softc->unmap_max_lba) break; } while (1); scsi_ulto2b(ranges * 16 + 6, &buf[0]); scsi_ulto2b(ranges * 16, &buf[2]); scsi_unmap(&ccb->csio, /*retries*/da_retry_count, /*cbfcnp*/dadone, /*tag_action*/MSG_SIMPLE_Q_TAG, /*byte2*/0, /*data_ptr*/ buf, /*dxfer_len*/ ranges * 16 + 8, /*sense_len*/SSD_FULL_SIZE, da_default_timeout * 1000); ccb->ccb_h.ccb_state = DA_CCB_DELETE; ccb->ccb_h.flags |= CAM_UNLOCKED; } static void da_delete_trim(struct cam_periph *periph, union ccb *ccb, struct bio *bp) { struct da_softc *softc = (struct da_softc *)periph->softc; struct bio *bp1; uint8_t *buf = softc->unmap_buf; uint64_t lastlba = (uint64_t)-1; uint64_t count; uint64_t lba; uint32_t lastcount = 0, c, requestcount; int ranges = 0, off, block_count; softc->delete_running = 1; bzero(softc->unmap_buf, sizeof(softc->unmap_buf)); bp1 = bp; do { bioq_remove(&softc->delete_queue, bp1); if (bp1 != bp) bioq_insert_tail(&softc->delete_run_queue, bp1); lba = bp1->bio_pblkno; count = bp1->bio_bcount / softc->params.secsize; requestcount = count; /* Try to extend the previous range. */ if (lba == lastlba) { c = omin(count, ATA_DSM_RANGE_MAX - lastcount); lastcount += c; off = (ranges - 1) * 8; buf[off + 6] = lastcount & 0xff; buf[off + 7] = (lastcount >> 8) & 0xff; count -= c; lba += c; } while (count > 0) { c = omin(count, ATA_DSM_RANGE_MAX); off = ranges * 8; buf[off + 0] = lba & 0xff; buf[off + 1] = (lba >> 8) & 0xff; buf[off + 2] = (lba >> 16) & 0xff; buf[off + 3] = (lba >> 24) & 0xff; buf[off + 4] = (lba >> 32) & 0xff; buf[off + 5] = (lba >> 40) & 0xff; buf[off + 6] = c & 0xff; buf[off + 7] = (c >> 8) & 0xff; lba += c; ranges++; count -= c; lastcount = c; if (count != 0 && ranges == softc->trim_max_ranges) { xpt_print(periph->path, "%s issuing short delete %ld > %ld\n", da_delete_method_desc[softc->delete_method], requestcount, (softc->trim_max_ranges - ranges) * ATA_DSM_RANGE_MAX); break; } } lastlba = lba; bp1 = bioq_first(&softc->delete_queue); if (bp1 == NULL || bp1->bio_bcount / softc->params.secsize > (softc->trim_max_ranges - ranges) * ATA_DSM_RANGE_MAX) break; } while (1); block_count = (ranges + ATA_DSM_BLK_RANGES - 1) / ATA_DSM_BLK_RANGES; scsi_ata_trim(&ccb->csio, /*retries*/da_retry_count, /*cbfcnp*/dadone, /*tag_action*/MSG_SIMPLE_Q_TAG, block_count, /*data_ptr*/buf, /*dxfer_len*/block_count * ATA_DSM_BLK_SIZE, /*sense_len*/SSD_FULL_SIZE, da_default_timeout * 1000); ccb->ccb_h.ccb_state = DA_CCB_DELETE; ccb->ccb_h.flags |= CAM_UNLOCKED; } /* * We calculate ws_max_blks here based off d_delmaxsize instead * of using softc->ws_max_blks as it is absolute max for the * device not the protocol max which may well be lower. */ static void da_delete_ws(struct cam_periph *periph, union ccb *ccb, struct bio *bp) { struct da_softc *softc; struct bio *bp1; uint64_t ws_max_blks; uint64_t lba; uint64_t count; /* forward compat with WS32 */ softc = (struct da_softc *)periph->softc; ws_max_blks = softc->disk->d_delmaxsize / softc->params.secsize; softc->delete_running = 1; lba = bp->bio_pblkno; count = 0; bp1 = bp; do { bioq_remove(&softc->delete_queue, bp1); if (bp1 != bp) bioq_insert_tail(&softc->delete_run_queue, bp1); count += bp1->bio_bcount / softc->params.secsize; if (count > ws_max_blks) { xpt_print(periph->path, "%s issuing short delete %ld > %ld\n", da_delete_method_desc[softc->delete_method], count, ws_max_blks); count = omin(count, ws_max_blks); break; } bp1 = bioq_first(&softc->delete_queue); if (bp1 == NULL || lba + count != bp1->bio_pblkno || count + bp1->bio_bcount / softc->params.secsize > ws_max_blks) break; } while (1); scsi_write_same(&ccb->csio, /*retries*/da_retry_count, /*cbfcnp*/dadone, /*tag_action*/MSG_SIMPLE_Q_TAG, /*byte2*/softc->delete_method == DA_DELETE_ZERO ? 0 : SWS_UNMAP, softc->delete_method == DA_DELETE_WS16 ? 16 : 10, /*lba*/lba, /*block_count*/count, /*data_ptr*/ __DECONST(void *, zero_region), /*dxfer_len*/ softc->params.secsize, /*sense_len*/SSD_FULL_SIZE, da_default_timeout * 1000); ccb->ccb_h.ccb_state = DA_CCB_DELETE; ccb->ccb_h.flags |= CAM_UNLOCKED; } static int cmd6workaround(union ccb *ccb) { struct scsi_rw_6 cmd6; struct scsi_rw_10 *cmd10; struct da_softc *softc; u_int8_t *cdb; struct bio *bp; int frozen; cdb = ccb->csio.cdb_io.cdb_bytes; softc = (struct da_softc *)xpt_path_periph(ccb->ccb_h.path)->softc; if (ccb->ccb_h.ccb_state == DA_CCB_DELETE) { da_delete_methods old_method = softc->delete_method; /* * Typically there are two reasons for failure here * 1. Delete method was detected as supported but isn't * 2. Delete failed due to invalid params e.g. too big * * While we will attempt to choose an alternative delete method * this may result in short deletes if the existing delete * requests from geom are big for the new method choosen. * * This method assumes that the error which triggered this * will not retry the io otherwise a panic will occur */ dadeleteflag(softc, old_method, 0); dadeletemethodchoose(softc, DA_DELETE_DISABLE); if (softc->delete_method == DA_DELETE_DISABLE) xpt_print(ccb->ccb_h.path, "%s failed, disabling BIO_DELETE\n", da_delete_method_desc[old_method]); else xpt_print(ccb->ccb_h.path, "%s failed, switching to %s BIO_DELETE\n", da_delete_method_desc[old_method], da_delete_method_desc[softc->delete_method]); while ((bp = bioq_takefirst(&softc->delete_run_queue)) != NULL) bioq_disksort(&softc->delete_queue, bp); bioq_disksort(&softc->delete_queue, (struct bio *)ccb->ccb_h.ccb_bp); ccb->ccb_h.ccb_bp = NULL; return (0); } /* Detect unsupported PREVENT ALLOW MEDIUM REMOVAL. */ if ((ccb->ccb_h.flags & CAM_CDB_POINTER) == 0 && (*cdb == PREVENT_ALLOW) && (softc->quirks & DA_Q_NO_PREVENT) == 0) { if (bootverbose) xpt_print(ccb->ccb_h.path, "PREVENT ALLOW MEDIUM REMOVAL not supported.\n"); softc->quirks |= DA_Q_NO_PREVENT; return (0); } /* Detect unsupported SYNCHRONIZE CACHE(10). */ if ((ccb->ccb_h.flags & CAM_CDB_POINTER) == 0 && (*cdb == SYNCHRONIZE_CACHE) && (softc->quirks & DA_Q_NO_SYNC_CACHE) == 0) { if (bootverbose) xpt_print(ccb->ccb_h.path, "SYNCHRONIZE CACHE(10) not supported.\n"); softc->quirks |= DA_Q_NO_SYNC_CACHE; softc->disk->d_flags &= ~DISKFLAG_CANFLUSHCACHE; return (0); } /* Translation only possible if CDB is an array and cmd is R/W6 */ if ((ccb->ccb_h.flags & CAM_CDB_POINTER) != 0 || (*cdb != READ_6 && *cdb != WRITE_6)) return 0; xpt_print(ccb->ccb_h.path, "READ(6)/WRITE(6) not supported, " "increasing minimum_cmd_size to 10.\n"); softc->minimum_cmd_size = 10; bcopy(cdb, &cmd6, sizeof(struct scsi_rw_6)); cmd10 = (struct scsi_rw_10 *)cdb; cmd10->opcode = (cmd6.opcode == READ_6) ? READ_10 : WRITE_10; cmd10->byte2 = 0; scsi_ulto4b(scsi_3btoul(cmd6.addr), cmd10->addr); cmd10->reserved = 0; scsi_ulto2b(cmd6.length, cmd10->length); cmd10->control = cmd6.control; ccb->csio.cdb_len = sizeof(*cmd10); /* Requeue request, unfreezing queue if necessary */ frozen = (ccb->ccb_h.status & CAM_DEV_QFRZN) != 0; ccb->ccb_h.status = CAM_REQUEUE_REQ; xpt_action(ccb); if (frozen) { cam_release_devq(ccb->ccb_h.path, /*relsim_flags*/0, /*reduction*/0, /*timeout*/0, /*getcount_only*/0); } return (ERESTART); } static void dadone(struct cam_periph *periph, union ccb *done_ccb) { struct da_softc *softc; struct ccb_scsiio *csio; u_int32_t priority; da_ccb_state state; softc = (struct da_softc *)periph->softc; priority = done_ccb->ccb_h.pinfo.priority; CAM_DEBUG(periph->path, CAM_DEBUG_TRACE, ("dadone\n")); csio = &done_ccb->csio; state = csio->ccb_h.ccb_state & DA_CCB_TYPE_MASK; switch (state) { case DA_CCB_BUFFER_IO: case DA_CCB_DELETE: { struct bio *bp, *bp1; cam_periph_lock(periph); bp = (struct bio *)done_ccb->ccb_h.ccb_bp; if ((done_ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) { int error; int sf; if ((csio->ccb_h.ccb_state & DA_CCB_RETRY_UA) != 0) sf = SF_RETRY_UA; else sf = 0; error = daerror(done_ccb, CAM_RETRY_SELTO, sf); if (error == ERESTART) { /* * A retry was scheduled, so * just return. */ cam_periph_unlock(periph); return; } bp = (struct bio *)done_ccb->ccb_h.ccb_bp; if (error != 0) { int queued_error; /* * return all queued I/O with EIO, so that * the client can retry these I/Os in the * proper order should it attempt to recover. */ queued_error = EIO; if (error == ENXIO && (softc->flags & DA_FLAG_PACK_INVALID)== 0) { /* * Catastrophic error. Mark our pack as * invalid. */ /* * XXX See if this is really a media * XXX change first? */ xpt_print(periph->path, "Invalidating pack\n"); softc->flags |= DA_FLAG_PACK_INVALID; queued_error = ENXIO; } bioq_flush(&softc->bio_queue, NULL, queued_error); if (bp != NULL) { bp->bio_error = error; bp->bio_resid = bp->bio_bcount; bp->bio_flags |= BIO_ERROR; } } else if (bp != NULL) { if (state == DA_CCB_DELETE) bp->bio_resid = 0; else bp->bio_resid = csio->resid; bp->bio_error = 0; if (bp->bio_resid != 0) bp->bio_flags |= BIO_ERROR; } if ((done_ccb->ccb_h.status & CAM_DEV_QFRZN) != 0) cam_release_devq(done_ccb->ccb_h.path, /*relsim_flags*/0, /*reduction*/0, /*timeout*/0, /*getcount_only*/0); } else if (bp != NULL) { if ((done_ccb->ccb_h.status & CAM_DEV_QFRZN) != 0) panic("REQ_CMP with QFRZN"); if (state == DA_CCB_DELETE) bp->bio_resid = 0; else bp->bio_resid = csio->resid; if (csio->resid > 0) bp->bio_flags |= BIO_ERROR; if (softc->error_inject != 0) { bp->bio_error = softc->error_inject; bp->bio_resid = bp->bio_bcount; bp->bio_flags |= BIO_ERROR; softc->error_inject = 0; } } LIST_REMOVE(&done_ccb->ccb_h, periph_links.le); if (LIST_EMPTY(&softc->pending_ccbs)) softc->flags |= DA_FLAG_WAS_OTAG; xpt_release_ccb(done_ccb); if (state == DA_CCB_DELETE) { TAILQ_HEAD(, bio) queue; TAILQ_INIT(&queue); TAILQ_CONCAT(&queue, &softc->delete_run_queue.queue, bio_queue); softc->delete_run_queue.insert_point = NULL; /* * Normally, the xpt_release_ccb() above would make sure * that when we have more work to do, that work would * get kicked off. However, we specifically keep * delete_running set to 0 before the call above to * allow other I/O to progress when many BIO_DELETE * requests are pushed down. We set delete_running to 0 * and call daschedule again so that we don't stall if * there are no other I/Os pending apart from BIO_DELETEs. */ softc->delete_running = 0; daschedule(periph); cam_periph_unlock(periph); while ((bp1 = TAILQ_FIRST(&queue)) != NULL) { TAILQ_REMOVE(&queue, bp1, bio_queue); bp1->bio_error = bp->bio_error; if (bp->bio_flags & BIO_ERROR) { bp1->bio_flags |= BIO_ERROR; bp1->bio_resid = bp1->bio_bcount; } else bp1->bio_resid = 0; biodone(bp1); } } else cam_periph_unlock(periph); if (bp != NULL) biodone(bp); return; } case DA_CCB_PROBE_RC: case DA_CCB_PROBE_RC16: { struct scsi_read_capacity_data *rdcap; struct scsi_read_capacity_data_long *rcaplong; char announce_buf[80]; int lbp; lbp = 0; rdcap = NULL; rcaplong = NULL; if (state == DA_CCB_PROBE_RC) rdcap =(struct scsi_read_capacity_data *)csio->data_ptr; else rcaplong = (struct scsi_read_capacity_data_long *) csio->data_ptr; if ((csio->ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_CMP) { struct disk_params *dp; uint32_t block_size; uint64_t maxsector; u_int lalba; /* Lowest aligned LBA. */ if (state == DA_CCB_PROBE_RC) { block_size = scsi_4btoul(rdcap->length); maxsector = scsi_4btoul(rdcap->addr); lalba = 0; /* * According to SBC-2, if the standard 10 * byte READ CAPACITY command returns 2^32, * we should issue the 16 byte version of * the command, since the device in question * has more sectors than can be represented * with the short version of the command. */ if (maxsector == 0xffffffff) { free(rdcap, M_SCSIDA); xpt_release_ccb(done_ccb); softc->state = DA_STATE_PROBE_RC16; xpt_schedule(periph, priority); return; } } else { block_size = scsi_4btoul(rcaplong->length); maxsector = scsi_8btou64(rcaplong->addr); lalba = scsi_2btoul(rcaplong->lalba_lbp); } /* * Because GEOM code just will panic us if we * give them an 'illegal' value we'll avoid that * here. */ if (block_size == 0) { block_size = 512; if (maxsector == 0) maxsector = -1; } if (block_size >= MAXPHYS) { xpt_print(periph->path, "unsupportable block size %ju\n", (uintmax_t) block_size); announce_buf[0] = '\0'; cam_periph_invalidate(periph); } else { /* * We pass rcaplong into dasetgeom(), * because it will only use it if it is * non-NULL. */ dasetgeom(periph, block_size, maxsector, rcaplong, sizeof(*rcaplong)); lbp = (lalba & SRC16_LBPME_A); dp = &softc->params; snprintf(announce_buf, sizeof(announce_buf), "%juMB (%ju %u byte sectors: %dH %dS/T " "%dC)", (uintmax_t) (((uintmax_t)dp->secsize * dp->sectors) / (1024*1024)), (uintmax_t)dp->sectors, dp->secsize, dp->heads, dp->secs_per_track, dp->cylinders); } } else { int error; announce_buf[0] = '\0'; /* * Retry any UNIT ATTENTION type errors. They * are expected at boot. */ error = daerror(done_ccb, CAM_RETRY_SELTO, SF_RETRY_UA|SF_NO_PRINT); if (error == ERESTART) { /* * A retry was scheuled, so * just return. */ return; } else if (error != 0) { int asc, ascq; int sense_key, error_code; int have_sense; cam_status status; struct ccb_getdev cgd; /* Don't wedge this device's queue */ status = done_ccb->ccb_h.status; if ((status & CAM_DEV_QFRZN) != 0) cam_release_devq(done_ccb->ccb_h.path, /*relsim_flags*/0, /*reduction*/0, /*timeout*/0, /*getcount_only*/0); xpt_setup_ccb(&cgd.ccb_h, done_ccb->ccb_h.path, CAM_PRIORITY_NORMAL); cgd.ccb_h.func_code = XPT_GDEV_TYPE; xpt_action((union ccb *)&cgd); if (scsi_extract_sense_ccb(done_ccb, &error_code, &sense_key, &asc, &ascq)) have_sense = TRUE; else have_sense = FALSE; /* * If we tried READ CAPACITY(16) and failed, * fallback to READ CAPACITY(10). */ if ((state == DA_CCB_PROBE_RC16) && (softc->flags & DA_FLAG_CAN_RC16) && (((csio->ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_INVALID) || ((have_sense) && (error_code == SSD_CURRENT_ERROR) && (sense_key == SSD_KEY_ILLEGAL_REQUEST)))) { softc->flags &= ~DA_FLAG_CAN_RC16; free(rdcap, M_SCSIDA); xpt_release_ccb(done_ccb); softc->state = DA_STATE_PROBE_RC; xpt_schedule(periph, priority); return; } else /* * Attach to anything that claims to be a * direct access or optical disk device, * as long as it doesn't return a "Logical * unit not supported" (0x25) error. */ if ((have_sense) && (asc != 0x25) && (error_code == SSD_CURRENT_ERROR)) { const char *sense_key_desc; const char *asc_desc; dasetgeom(periph, 512, -1, NULL, 0); scsi_sense_desc(sense_key, asc, ascq, &cgd.inq_data, &sense_key_desc, &asc_desc); snprintf(announce_buf, sizeof(announce_buf), "Attempt to query device " "size failed: %s, %s", sense_key_desc, asc_desc); } else { if (have_sense) scsi_sense_print( &done_ccb->csio); else { xpt_print(periph->path, "got CAM status %#x\n", done_ccb->ccb_h.status); } xpt_print(periph->path, "fatal error, " "failed to attach to device\n"); /* * Free up resources. */ cam_periph_invalidate(periph); } } } free(csio->data_ptr, M_SCSIDA); if (announce_buf[0] != '\0' && ((softc->flags & DA_FLAG_ANNOUNCED) == 0)) { /* * Create our sysctl variables, now that we know * we have successfully attached. */ /* increase the refcount */ if (cam_periph_acquire(periph) == CAM_REQ_CMP) { taskqueue_enqueue(taskqueue_thread, &softc->sysctl_task); xpt_announce_periph(periph, announce_buf); xpt_announce_quirks(periph, softc->quirks, DA_Q_BIT_STRING); } else { xpt_print(periph->path, "fatal error, " "could not acquire reference count\n"); } } /* We already probed the device. */ if (softc->flags & DA_FLAG_PROBED) { daprobedone(periph, done_ccb); return; } /* Ensure re-probe doesn't see old delete. */ softc->delete_available = 0; if (lbp && (softc->quirks & DA_Q_NO_UNMAP) == 0) { /* * Based on older SBC-3 spec revisions * any of the UNMAP methods "may" be * available via LBP given this flag so * we flag all of them as availble and * then remove those which further * probes confirm aren't available * later. * * We could also check readcap(16) p_type * flag to exclude one or more invalid * write same (X) types here */ dadeleteflag(softc, DA_DELETE_WS16, 1); dadeleteflag(softc, DA_DELETE_WS10, 1); dadeleteflag(softc, DA_DELETE_ZERO, 1); dadeleteflag(softc, DA_DELETE_UNMAP, 1); xpt_release_ccb(done_ccb); softc->state = DA_STATE_PROBE_LBP; xpt_schedule(periph, priority); return; } xpt_release_ccb(done_ccb); softc->state = DA_STATE_PROBE_BDC; xpt_schedule(periph, priority); return; } case DA_CCB_PROBE_LBP: { struct scsi_vpd_logical_block_prov *lbp; lbp = (struct scsi_vpd_logical_block_prov *)csio->data_ptr; if ((csio->ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_CMP) { /* * T10/1799-D Revision 31 states at least one of these * must be supported but we don't currently enforce this. */ dadeleteflag(softc, DA_DELETE_WS16, (lbp->flags & SVPD_LBP_WS16)); dadeleteflag(softc, DA_DELETE_WS10, (lbp->flags & SVPD_LBP_WS10)); dadeleteflag(softc, DA_DELETE_ZERO, (lbp->flags & SVPD_LBP_WS10)); dadeleteflag(softc, DA_DELETE_UNMAP, (lbp->flags & SVPD_LBP_UNMAP)); } else { int error; error = daerror(done_ccb, CAM_RETRY_SELTO, SF_RETRY_UA|SF_NO_PRINT); if (error == ERESTART) return; else if (error != 0) { if ((done_ccb->ccb_h.status & CAM_DEV_QFRZN) != 0) { /* Don't wedge this device's queue */ cam_release_devq(done_ccb->ccb_h.path, /*relsim_flags*/0, /*reduction*/0, /*timeout*/0, /*getcount_only*/0); } /* * Failure indicates we don't support any SBC-3 * delete methods with UNMAP */ } } free(lbp, M_SCSIDA); xpt_release_ccb(done_ccb); softc->state = DA_STATE_PROBE_BLK_LIMITS; xpt_schedule(periph, priority); return; } case DA_CCB_PROBE_BLK_LIMITS: { struct scsi_vpd_block_limits *block_limits; block_limits = (struct scsi_vpd_block_limits *)csio->data_ptr; if ((csio->ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_CMP) { uint32_t max_txfer_len = scsi_4btoul( block_limits->max_txfer_len); uint32_t max_unmap_lba_cnt = scsi_4btoul( block_limits->max_unmap_lba_cnt); uint32_t max_unmap_blk_cnt = scsi_4btoul( block_limits->max_unmap_blk_cnt); uint64_t ws_max_blks = scsi_8btou64( block_limits->max_write_same_length); if (max_txfer_len != 0) { softc->disk->d_maxsize = MIN(softc->maxio, (off_t)max_txfer_len * softc->params.secsize); } /* * We should already support UNMAP but we check lba * and block count to be sure */ if (max_unmap_lba_cnt != 0x00L && max_unmap_blk_cnt != 0x00L) { softc->unmap_max_lba = max_unmap_lba_cnt; softc->unmap_max_ranges = min(max_unmap_blk_cnt, UNMAP_MAX_RANGES); } else { /* * Unexpected UNMAP limits which means the * device doesn't actually support UNMAP */ dadeleteflag(softc, DA_DELETE_UNMAP, 0); } if (ws_max_blks != 0x00L) softc->ws_max_blks = ws_max_blks; } else { int error; error = daerror(done_ccb, CAM_RETRY_SELTO, SF_RETRY_UA|SF_NO_PRINT); if (error == ERESTART) return; else if (error != 0) { if ((done_ccb->ccb_h.status & CAM_DEV_QFRZN) != 0) { /* Don't wedge this device's queue */ cam_release_devq(done_ccb->ccb_h.path, /*relsim_flags*/0, /*reduction*/0, /*timeout*/0, /*getcount_only*/0); } /* * Failure here doesn't mean UNMAP is not * supported as this is an optional page. */ softc->unmap_max_lba = 1; softc->unmap_max_ranges = 1; } } free(block_limits, M_SCSIDA); xpt_release_ccb(done_ccb); softc->state = DA_STATE_PROBE_BDC; xpt_schedule(periph, priority); return; } case DA_CCB_PROBE_BDC: { struct scsi_vpd_block_characteristics *bdc; bdc = (struct scsi_vpd_block_characteristics *)csio->data_ptr; if ((csio->ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_CMP) { /* * Disable queue sorting for non-rotational media * by default. */ u_int16_t old_rate = softc->disk->d_rotation_rate; softc->disk->d_rotation_rate = scsi_2btoul(bdc->medium_rotation_rate); if (softc->disk->d_rotation_rate == SVPD_BDC_RATE_NON_ROTATING) { softc->sort_io_queue = 0; } if (softc->disk->d_rotation_rate != old_rate) { disk_attr_changed(softc->disk, "GEOM::rotation_rate", M_NOWAIT); } } else { int error; error = daerror(done_ccb, CAM_RETRY_SELTO, SF_RETRY_UA|SF_NO_PRINT); if (error == ERESTART) return; else if (error != 0) { if ((done_ccb->ccb_h.status & CAM_DEV_QFRZN) != 0) { /* Don't wedge this device's queue */ cam_release_devq(done_ccb->ccb_h.path, /*relsim_flags*/0, /*reduction*/0, /*timeout*/0, /*getcount_only*/0); } } } free(bdc, M_SCSIDA); xpt_release_ccb(done_ccb); softc->state = DA_STATE_PROBE_ATA; xpt_schedule(periph, priority); return; } case DA_CCB_PROBE_ATA: { int i; struct ata_params *ata_params; int16_t *ptr; ata_params = (struct ata_params *)csio->data_ptr; ptr = (uint16_t *)ata_params; if ((csio->ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_CMP) { uint16_t old_rate; for (i = 0; i < sizeof(*ata_params) / 2; i++) ptr[i] = le16toh(ptr[i]); if (ata_params->support_dsm & ATA_SUPPORT_DSM_TRIM && (softc->quirks & DA_Q_NO_UNMAP) == 0) { dadeleteflag(softc, DA_DELETE_ATA_TRIM, 1); if (ata_params->max_dsm_blocks != 0) softc->trim_max_ranges = min( softc->trim_max_ranges, ata_params->max_dsm_blocks * ATA_DSM_BLK_RANGES); } /* * Disable queue sorting for non-rotational media * by default. */ old_rate = softc->disk->d_rotation_rate; softc->disk->d_rotation_rate = ata_params->media_rotation_rate; if (softc->disk->d_rotation_rate == ATA_RATE_NON_ROTATING) { softc->sort_io_queue = 0; } if (softc->disk->d_rotation_rate != old_rate) { disk_attr_changed(softc->disk, "GEOM::rotation_rate", M_NOWAIT); } } else { int error; error = daerror(done_ccb, CAM_RETRY_SELTO, SF_RETRY_UA|SF_NO_PRINT); if (error == ERESTART) return; else if (error != 0) { if ((done_ccb->ccb_h.status & CAM_DEV_QFRZN) != 0) { /* Don't wedge this device's queue */ cam_release_devq(done_ccb->ccb_h.path, /*relsim_flags*/0, /*reduction*/0, /*timeout*/0, /*getcount_only*/0); } } } free(ata_params, M_SCSIDA); daprobedone(periph, done_ccb); return; } case DA_CCB_DUMP: /* No-op. We're polling */ return; case DA_CCB_TUR: { if ((done_ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) { if (daerror(done_ccb, CAM_RETRY_SELTO, SF_RETRY_UA | SF_NO_RECOVERY | SF_NO_PRINT) == ERESTART) return; if ((done_ccb->ccb_h.status & CAM_DEV_QFRZN) != 0) cam_release_devq(done_ccb->ccb_h.path, /*relsim_flags*/0, /*reduction*/0, /*timeout*/0, /*getcount_only*/0); } xpt_release_ccb(done_ccb); cam_periph_release_locked(periph); return; } default: break; } xpt_release_ccb(done_ccb); } static void dareprobe(struct cam_periph *periph) { struct da_softc *softc; cam_status status; softc = (struct da_softc *)periph->softc; /* Probe in progress; don't interfere. */ if (softc->state != DA_STATE_NORMAL) return; status = cam_periph_acquire(periph); KASSERT(status == CAM_REQ_CMP, ("dareprobe: cam_periph_acquire failed")); if (softc->flags & DA_FLAG_CAN_RC16) softc->state = DA_STATE_PROBE_RC16; else softc->state = DA_STATE_PROBE_RC; xpt_schedule(periph, CAM_PRIORITY_DEV); } static int daerror(union ccb *ccb, u_int32_t cam_flags, u_int32_t sense_flags) { struct da_softc *softc; struct cam_periph *periph; int error, error_code, sense_key, asc, ascq; periph = xpt_path_periph(ccb->ccb_h.path); softc = (struct da_softc *)periph->softc; /* * Automatically detect devices that do not support * READ(6)/WRITE(6) and upgrade to using 10 byte cdbs. */ error = 0; if ((ccb->ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_INVALID) { error = cmd6workaround(ccb); } else if (scsi_extract_sense_ccb(ccb, &error_code, &sense_key, &asc, &ascq)) { if (sense_key == SSD_KEY_ILLEGAL_REQUEST) error = cmd6workaround(ccb); /* * If the target replied with CAPACITY DATA HAS CHANGED UA, * query the capacity and notify upper layers. */ else if (sense_key == SSD_KEY_UNIT_ATTENTION && asc == 0x2A && ascq == 0x09) { xpt_print(periph->path, "Capacity data has changed\n"); softc->flags &= ~DA_FLAG_PROBED; dareprobe(periph); sense_flags |= SF_NO_PRINT; } else if (sense_key == SSD_KEY_UNIT_ATTENTION && asc == 0x28 && ascq == 0x00) { softc->flags &= ~DA_FLAG_PROBED; disk_media_changed(softc->disk, M_NOWAIT); } else if (sense_key == SSD_KEY_UNIT_ATTENTION && asc == 0x3F && ascq == 0x03) { xpt_print(periph->path, "INQUIRY data has changed\n"); softc->flags &= ~DA_FLAG_PROBED; dareprobe(periph); sense_flags |= SF_NO_PRINT; } else if (sense_key == SSD_KEY_NOT_READY && asc == 0x3a && (softc->flags & DA_FLAG_PACK_INVALID) == 0) { softc->flags |= DA_FLAG_PACK_INVALID; disk_media_gone(softc->disk, M_NOWAIT); } } if (error == ERESTART) return (ERESTART); /* * XXX * Until we have a better way of doing pack validation, * don't treat UAs as errors. */ sense_flags |= SF_RETRY_UA; if (softc->quirks & DA_Q_RETRY_BUSY) sense_flags |= SF_RETRY_BUSY; return(cam_periph_error(ccb, cam_flags, sense_flags, &softc->saved_ccb)); } static void damediapoll(void *arg) { struct cam_periph *periph = arg; struct da_softc *softc = periph->softc; if (!softc->tur && LIST_EMPTY(&softc->pending_ccbs)) { if (cam_periph_acquire(periph) == CAM_REQ_CMP) { softc->tur = 1; daschedule(periph); } } /* Queue us up again */ if (da_poll_period != 0) callout_schedule(&softc->mediapoll_c, da_poll_period * hz); } static void daprevent(struct cam_periph *periph, int action) { struct da_softc *softc; union ccb *ccb; int error; softc = (struct da_softc *)periph->softc; if (((action == PR_ALLOW) && (softc->flags & DA_FLAG_PACK_LOCKED) == 0) || ((action == PR_PREVENT) && (softc->flags & DA_FLAG_PACK_LOCKED) != 0)) { return; } ccb = cam_periph_getccb(periph, CAM_PRIORITY_NORMAL); scsi_prevent(&ccb->csio, /*retries*/1, /*cbcfp*/dadone, MSG_SIMPLE_Q_TAG, action, SSD_FULL_SIZE, 5000); error = cam_periph_runccb(ccb, daerror, CAM_RETRY_SELTO, SF_RETRY_UA | SF_NO_PRINT, softc->disk->d_devstat); if (error == 0) { if (action == PR_ALLOW) softc->flags &= ~DA_FLAG_PACK_LOCKED; else softc->flags |= DA_FLAG_PACK_LOCKED; } xpt_release_ccb(ccb); } static void dasetgeom(struct cam_periph *periph, uint32_t block_len, uint64_t maxsector, struct scsi_read_capacity_data_long *rcaplong, size_t rcap_len) { struct ccb_calc_geometry ccg; struct da_softc *softc; struct disk_params *dp; u_int lbppbe, lalba; int error; softc = (struct da_softc *)periph->softc; dp = &softc->params; dp->secsize = block_len; dp->sectors = maxsector + 1; if (rcaplong != NULL) { lbppbe = rcaplong->prot_lbppbe & SRC16_LBPPBE; lalba = scsi_2btoul(rcaplong->lalba_lbp); lalba &= SRC16_LALBA_A; } else { lbppbe = 0; lalba = 0; } if (lbppbe > 0) { dp->stripesize = block_len << lbppbe; dp->stripeoffset = (dp->stripesize - block_len * lalba) % dp->stripesize; } else if (softc->quirks & DA_Q_4K) { dp->stripesize = 4096; dp->stripeoffset = 0; } else { dp->stripesize = 0; dp->stripeoffset = 0; } /* * Have the controller provide us with a geometry * for this disk. The only time the geometry * matters is when we boot and the controller * is the only one knowledgeable enough to come * up with something that will make this a bootable * device. */ xpt_setup_ccb(&ccg.ccb_h, periph->path, CAM_PRIORITY_NORMAL); ccg.ccb_h.func_code = XPT_CALC_GEOMETRY; ccg.block_size = dp->secsize; ccg.volume_size = dp->sectors; ccg.heads = 0; ccg.secs_per_track = 0; ccg.cylinders = 0; xpt_action((union ccb*)&ccg); if ((ccg.ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) { /* * We don't know what went wrong here- but just pick * a geometry so we don't have nasty things like divide * by zero. */ dp->heads = 255; dp->secs_per_track = 255; dp->cylinders = dp->sectors / (255 * 255); if (dp->cylinders == 0) { dp->cylinders = 1; } } else { dp->heads = ccg.heads; dp->secs_per_track = ccg.secs_per_track; dp->cylinders = ccg.cylinders; } /* * If the user supplied a read capacity buffer, and if it is * different than the previous buffer, update the data in the EDT. * If it's the same, we don't bother. This avoids sending an * update every time someone opens this device. */ if ((rcaplong != NULL) && (bcmp(rcaplong, &softc->rcaplong, min(sizeof(softc->rcaplong), rcap_len)) != 0)) { struct ccb_dev_advinfo cdai; xpt_setup_ccb(&cdai.ccb_h, periph->path, CAM_PRIORITY_NORMAL); cdai.ccb_h.func_code = XPT_DEV_ADVINFO; cdai.buftype = CDAI_TYPE_RCAPLONG; cdai.flags = CDAI_FLAG_STORE; cdai.bufsiz = rcap_len; cdai.buf = (uint8_t *)rcaplong; xpt_action((union ccb *)&cdai); if ((cdai.ccb_h.status & CAM_DEV_QFRZN) != 0) cam_release_devq(cdai.ccb_h.path, 0, 0, 0, FALSE); if (cdai.ccb_h.status != CAM_REQ_CMP) { xpt_print(periph->path, "%s: failed to set read " "capacity advinfo\n", __func__); /* Use cam_error_print() to decode the status */ cam_error_print((union ccb *)&cdai, CAM_ESF_CAM_STATUS, CAM_EPF_ALL); } else { bcopy(rcaplong, &softc->rcaplong, min(sizeof(softc->rcaplong), rcap_len)); } } softc->disk->d_sectorsize = softc->params.secsize; softc->disk->d_mediasize = softc->params.secsize * (off_t)softc->params.sectors; softc->disk->d_stripesize = softc->params.stripesize; softc->disk->d_stripeoffset = softc->params.stripeoffset; /* XXX: these are not actually "firmware" values, so they may be wrong */ softc->disk->d_fwsectors = softc->params.secs_per_track; softc->disk->d_fwheads = softc->params.heads; softc->disk->d_devstat->block_size = softc->params.secsize; softc->disk->d_devstat->flags &= ~DEVSTAT_BS_UNAVAILABLE; error = disk_resize(softc->disk, M_NOWAIT); if (error != 0) xpt_print(periph->path, "disk_resize(9) failed, error = %d\n", error); } static void dasendorderedtag(void *arg) { struct da_softc *softc = arg; if (da_send_ordered) { if (!LIST_EMPTY(&softc->pending_ccbs)) { if ((softc->flags & DA_FLAG_WAS_OTAG) == 0) softc->flags |= DA_FLAG_NEED_OTAG; softc->flags &= ~DA_FLAG_WAS_OTAG; } } /* Queue us up again */ callout_reset(&softc->sendordered_c, (da_default_timeout * hz) / DA_ORDEREDTAG_INTERVAL, dasendorderedtag, softc); } /* * Step through all DA peripheral drivers, and if the device is still open, * sync the disk cache to physical media. */ static void dashutdown(void * arg, int howto) { struct cam_periph *periph; struct da_softc *softc; union ccb *ccb; int error; CAM_PERIPH_FOREACH(periph, &dadriver) { softc = (struct da_softc *)periph->softc; if (SCHEDULER_STOPPED()) { /* If we paniced with the lock held, do not recurse. */ if (!cam_periph_owned(periph) && (softc->flags & DA_FLAG_OPEN)) { dadump(softc->disk, NULL, 0, 0, 0); } continue; } cam_periph_lock(periph); /* * We only sync the cache if the drive is still open, and * if the drive is capable of it.. */ if (((softc->flags & DA_FLAG_OPEN) == 0) || (softc->quirks & DA_Q_NO_SYNC_CACHE)) { cam_periph_unlock(periph); continue; } ccb = cam_periph_getccb(periph, CAM_PRIORITY_NORMAL); scsi_synchronize_cache(&ccb->csio, /*retries*/0, /*cbfcnp*/dadone, MSG_SIMPLE_Q_TAG, /*begin_lba*/0, /* whole disk */ /*lb_count*/0, SSD_FULL_SIZE, 60 * 60 * 1000); error = cam_periph_runccb(ccb, daerror, /*cam_flags*/0, /*sense_flags*/ SF_NO_RECOVERY | SF_NO_RETRY | SF_QUIET_IR, softc->disk->d_devstat); if (error != 0) xpt_print(periph->path, "Synchronize cache failed\n"); xpt_release_ccb(ccb); cam_periph_unlock(periph); } } #else /* !_KERNEL */ /* * XXX These are only left out of the kernel build to silence warnings. If, * for some reason these functions are used in the kernel, the ifdefs should * be moved so they are included both in the kernel and userland. */ void scsi_format_unit(struct ccb_scsiio *csio, u_int32_t retries, void (*cbfcnp)(struct cam_periph *, union ccb *), u_int8_t tag_action, u_int8_t byte2, u_int16_t ileave, u_int8_t *data_ptr, u_int32_t dxfer_len, u_int8_t sense_len, u_int32_t timeout) { struct scsi_format_unit *scsi_cmd; scsi_cmd = (struct scsi_format_unit *)&csio->cdb_io.cdb_bytes; scsi_cmd->opcode = FORMAT_UNIT; scsi_cmd->byte2 = byte2; scsi_ulto2b(ileave, scsi_cmd->interleave); cam_fill_csio(csio, retries, cbfcnp, /*flags*/ (dxfer_len > 0) ? CAM_DIR_OUT : CAM_DIR_NONE, tag_action, data_ptr, dxfer_len, sense_len, sizeof(*scsi_cmd), timeout); } void scsi_read_defects(struct ccb_scsiio *csio, uint32_t retries, void (*cbfcnp)(struct cam_periph *, union ccb *), uint8_t tag_action, uint8_t list_format, uint32_t addr_desc_index, uint8_t *data_ptr, uint32_t dxfer_len, int minimum_cmd_size, uint8_t sense_len, uint32_t timeout) { uint8_t cdb_len; /* * These conditions allow using the 10 byte command. Otherwise we * need to use the 12 byte command. */ if ((minimum_cmd_size <= 10) && (addr_desc_index == 0) && (dxfer_len <= SRDD10_MAX_LENGTH)) { struct scsi_read_defect_data_10 *cdb10; cdb10 = (struct scsi_read_defect_data_10 *) &csio->cdb_io.cdb_bytes; cdb_len = sizeof(*cdb10); bzero(cdb10, cdb_len); cdb10->opcode = READ_DEFECT_DATA_10; cdb10->format = list_format; scsi_ulto2b(dxfer_len, cdb10->alloc_length); } else { struct scsi_read_defect_data_12 *cdb12; cdb12 = (struct scsi_read_defect_data_12 *) &csio->cdb_io.cdb_bytes; cdb_len = sizeof(*cdb12); bzero(cdb12, cdb_len); cdb12->opcode = READ_DEFECT_DATA_12; cdb12->format = list_format; scsi_ulto4b(dxfer_len, cdb12->alloc_length); scsi_ulto4b(addr_desc_index, cdb12->address_descriptor_index); } cam_fill_csio(csio, retries, cbfcnp, /*flags*/ CAM_DIR_IN, tag_action, data_ptr, dxfer_len, sense_len, cdb_len, timeout); } void scsi_sanitize(struct ccb_scsiio *csio, u_int32_t retries, void (*cbfcnp)(struct cam_periph *, union ccb *), u_int8_t tag_action, u_int8_t byte2, u_int16_t control, u_int8_t *data_ptr, u_int32_t dxfer_len, u_int8_t sense_len, u_int32_t timeout) { struct scsi_sanitize *scsi_cmd; scsi_cmd = (struct scsi_sanitize *)&csio->cdb_io.cdb_bytes; scsi_cmd->opcode = SANITIZE; scsi_cmd->byte2 = byte2; scsi_cmd->control = control; scsi_ulto2b(dxfer_len, scsi_cmd->length); cam_fill_csio(csio, retries, cbfcnp, /*flags*/ (dxfer_len > 0) ? CAM_DIR_OUT : CAM_DIR_NONE, tag_action, data_ptr, dxfer_len, sense_len, sizeof(*scsi_cmd), timeout); } #endif /* _KERNEL */ diff --git a/sys/cam/scsi/scsi_pt.c b/sys/cam/scsi/scsi_pt.c index f34748c3cd54..15240da32784 100644 --- a/sys/cam/scsi/scsi_pt.c +++ b/sys/cam/scsi/scsi_pt.c @@ -1,631 +1,632 @@ /*- * Implementation of SCSI Processor Target Peripheral driver for CAM. * * Copyright (c) 1998 Justin T. Gibbs. * 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, * without modification, immediately at the beginning of the file. * 2. 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 AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "opt_pt.h" typedef enum { PT_STATE_PROBE, PT_STATE_NORMAL } pt_state; typedef enum { PT_FLAG_NONE = 0x00, PT_FLAG_OPEN = 0x01, PT_FLAG_DEVICE_INVALID = 0x02, PT_FLAG_RETRY_UA = 0x04 } pt_flags; typedef enum { PT_CCB_BUFFER_IO = 0x01, PT_CCB_RETRY_UA = 0x04, PT_CCB_BUFFER_IO_UA = PT_CCB_BUFFER_IO|PT_CCB_RETRY_UA } pt_ccb_state; /* Offsets into our private area for storing information */ #define ccb_state ppriv_field0 #define ccb_bp ppriv_ptr1 struct pt_softc { struct bio_queue_head bio_queue; struct devstat *device_stats; LIST_HEAD(, ccb_hdr) pending_ccbs; pt_state state; pt_flags flags; union ccb saved_ccb; int io_timeout; struct cdev *dev; }; static d_open_t ptopen; static d_close_t ptclose; static d_strategy_t ptstrategy; static periph_init_t ptinit; static void ptasync(void *callback_arg, u_int32_t code, struct cam_path *path, void *arg); static periph_ctor_t ptctor; static periph_oninv_t ptoninvalidate; static periph_dtor_t ptdtor; static periph_start_t ptstart; static void ptdone(struct cam_periph *periph, union ccb *done_ccb); static d_ioctl_t ptioctl; static int pterror(union ccb *ccb, u_int32_t cam_flags, u_int32_t sense_flags); void scsi_send_receive(struct ccb_scsiio *csio, u_int32_t retries, void (*cbfcnp)(struct cam_periph *, union ccb *), u_int tag_action, int readop, u_int byte2, u_int32_t xfer_len, u_int8_t *data_ptr, u_int8_t sense_len, u_int32_t timeout); static struct periph_driver ptdriver = { ptinit, "pt", TAILQ_HEAD_INITIALIZER(ptdriver.units), /* generation */ 0 }; PERIPHDRIVER_DECLARE(pt, ptdriver); static struct cdevsw pt_cdevsw = { .d_version = D_VERSION, .d_flags = 0, .d_open = ptopen, .d_close = ptclose, .d_read = physread, .d_write = physwrite, .d_ioctl = ptioctl, .d_strategy = ptstrategy, .d_name = "pt", }; #ifndef SCSI_PT_DEFAULT_TIMEOUT #define SCSI_PT_DEFAULT_TIMEOUT 60 #endif static int ptopen(struct cdev *dev, int flags, int fmt, struct thread *td) { struct cam_periph *periph; struct pt_softc *softc; int error = 0; periph = (struct cam_periph *)dev->si_drv1; if (cam_periph_acquire(periph) != CAM_REQ_CMP) return (ENXIO); softc = (struct pt_softc *)periph->softc; cam_periph_lock(periph); if (softc->flags & PT_FLAG_DEVICE_INVALID) { cam_periph_release_locked(periph); cam_periph_unlock(periph); return(ENXIO); } if ((softc->flags & PT_FLAG_OPEN) == 0) softc->flags |= PT_FLAG_OPEN; else { error = EBUSY; cam_periph_release(periph); } CAM_DEBUG(periph->path, CAM_DEBUG_TRACE, ("ptopen: dev=%s\n", devtoname(dev))); cam_periph_unlock(periph); return (error); } static int ptclose(struct cdev *dev, int flag, int fmt, struct thread *td) { struct cam_periph *periph; struct pt_softc *softc; periph = (struct cam_periph *)dev->si_drv1; if (periph == NULL) return (ENXIO); softc = (struct pt_softc *)periph->softc; cam_periph_lock(periph); softc->flags &= ~PT_FLAG_OPEN; cam_periph_release_locked(periph); cam_periph_unlock(periph); return (0); } /* * Actually translate the requested transfer into one the physical driver * can understand. The transfer is described by a buf and will include * only one physical transfer. */ static void ptstrategy(struct bio *bp) { struct cam_periph *periph; struct pt_softc *softc; periph = (struct cam_periph *)bp->bio_dev->si_drv1; bp->bio_resid = bp->bio_bcount; if (periph == NULL) { biofinish(bp, NULL, ENXIO); return; } cam_periph_lock(periph); softc = (struct pt_softc *)periph->softc; /* * If the device has been made invalid, error out */ if ((softc->flags & PT_FLAG_DEVICE_INVALID)) { cam_periph_unlock(periph); biofinish(bp, NULL, ENXIO); return; } /* * Place it in the queue of disk activities for this disk */ bioq_insert_tail(&softc->bio_queue, bp); /* * Schedule ourselves for performing the work. */ xpt_schedule(periph, CAM_PRIORITY_NORMAL); cam_periph_unlock(periph); return; } static void ptinit(void) { cam_status status; /* * Install a global async callback. This callback will * receive async callbacks like "new device found". */ status = xpt_register_async(AC_FOUND_DEVICE, ptasync, NULL, NULL); if (status != CAM_REQ_CMP) { printf("pt: Failed to attach master async callback " "due to status 0x%x!\n", status); } } static cam_status ptctor(struct cam_periph *periph, void *arg) { struct pt_softc *softc; struct ccb_getdev *cgd; struct ccb_pathinq cpi; cgd = (struct ccb_getdev *)arg; if (cgd == NULL) { printf("ptregister: no getdev CCB, can't register device\n"); return(CAM_REQ_CMP_ERR); } softc = (struct pt_softc *)malloc(sizeof(*softc),M_DEVBUF,M_NOWAIT); if (softc == NULL) { printf("daregister: Unable to probe new device. " "Unable to allocate softc\n"); return(CAM_REQ_CMP_ERR); } bzero(softc, sizeof(*softc)); LIST_INIT(&softc->pending_ccbs); softc->state = PT_STATE_NORMAL; bioq_init(&softc->bio_queue); softc->io_timeout = SCSI_PT_DEFAULT_TIMEOUT * 1000; periph->softc = softc; bzero(&cpi, sizeof(cpi)); xpt_setup_ccb(&cpi.ccb_h, periph->path, CAM_PRIORITY_NORMAL); cpi.ccb_h.func_code = XPT_PATH_INQ; xpt_action((union ccb *)&cpi); cam_periph_unlock(periph); softc->device_stats = devstat_new_entry("pt", periph->unit_number, 0, DEVSTAT_NO_BLOCKSIZE, SID_TYPE(&cgd->inq_data) | XPORT_DEVSTAT_TYPE(cpi.transport), DEVSTAT_PRIORITY_OTHER); softc->dev = make_dev(&pt_cdevsw, periph->unit_number, UID_ROOT, GID_OPERATOR, 0600, "%s%d", periph->periph_name, periph->unit_number); cam_periph_lock(periph); softc->dev->si_drv1 = periph; /* * Add async callbacks for bus reset and * bus device reset calls. I don't bother * checking if this fails as, in most cases, * the system will function just fine without * them and the only alternative would be to * not attach the device on failure. */ xpt_register_async(AC_SENT_BDR | AC_BUS_RESET | AC_LOST_DEVICE, ptasync, periph, periph->path); /* Tell the user we've attached to the device */ xpt_announce_periph(periph, NULL); return(CAM_REQ_CMP); } static void ptoninvalidate(struct cam_periph *periph) { struct pt_softc *softc; softc = (struct pt_softc *)periph->softc; /* * De-register any async callbacks. */ xpt_register_async(0, ptasync, periph, periph->path); softc->flags |= PT_FLAG_DEVICE_INVALID; /* * Return all queued I/O with ENXIO. * XXX Handle any transactions queued to the card * with XPT_ABORT_CCB. */ bioq_flush(&softc->bio_queue, NULL, ENXIO); } static void ptdtor(struct cam_periph *periph) { struct pt_softc *softc; softc = (struct pt_softc *)periph->softc; devstat_remove_entry(softc->device_stats); cam_periph_unlock(periph); destroy_dev(softc->dev); cam_periph_lock(periph); free(softc, M_DEVBUF); } static void ptasync(void *callback_arg, u_int32_t code, struct cam_path *path, void *arg) { struct cam_periph *periph; periph = (struct cam_periph *)callback_arg; switch (code) { case AC_FOUND_DEVICE: { struct ccb_getdev *cgd; cam_status status; cgd = (struct ccb_getdev *)arg; if (cgd == NULL) break; if (cgd->protocol != PROTO_SCSI) break; - + if (SID_QUAL(&cgd->inq_data) != SID_QUAL_LU_CONNECTED) + break; if (SID_TYPE(&cgd->inq_data) != T_PROCESSOR) break; /* * Allocate a peripheral instance for * this device and start the probe * process. */ status = cam_periph_alloc(ptctor, ptoninvalidate, ptdtor, ptstart, "pt", CAM_PERIPH_BIO, path, ptasync, AC_FOUND_DEVICE, cgd); if (status != CAM_REQ_CMP && status != CAM_REQ_INPROG) printf("ptasync: Unable to attach to new device " "due to status 0x%x\n", status); break; } case AC_SENT_BDR: case AC_BUS_RESET: { struct pt_softc *softc; struct ccb_hdr *ccbh; softc = (struct pt_softc *)periph->softc; /* * Don't fail on the expected unit attention * that will occur. */ softc->flags |= PT_FLAG_RETRY_UA; LIST_FOREACH(ccbh, &softc->pending_ccbs, periph_links.le) ccbh->ccb_state |= PT_CCB_RETRY_UA; } /* FALLTHROUGH */ default: cam_periph_async(periph, code, path, arg); break; } } static void ptstart(struct cam_periph *periph, union ccb *start_ccb) { struct pt_softc *softc; struct bio *bp; softc = (struct pt_softc *)periph->softc; CAM_DEBUG(periph->path, CAM_DEBUG_TRACE, ("ptstart\n")); /* * See if there is a buf with work for us to do.. */ bp = bioq_first(&softc->bio_queue); if (bp == NULL) { xpt_release_ccb(start_ccb); } else { bioq_remove(&softc->bio_queue, bp); devstat_start_transaction_bio(softc->device_stats, bp); scsi_send_receive(&start_ccb->csio, /*retries*/4, ptdone, MSG_SIMPLE_Q_TAG, bp->bio_cmd == BIO_READ, /*byte2*/0, bp->bio_bcount, bp->bio_data, /*sense_len*/SSD_FULL_SIZE, /*timeout*/softc->io_timeout); start_ccb->ccb_h.ccb_state = PT_CCB_BUFFER_IO_UA; /* * Block out any asynchronous callbacks * while we touch the pending ccb list. */ LIST_INSERT_HEAD(&softc->pending_ccbs, &start_ccb->ccb_h, periph_links.le); start_ccb->ccb_h.ccb_bp = bp; bp = bioq_first(&softc->bio_queue); xpt_action(start_ccb); if (bp != NULL) { /* Have more work to do, so ensure we stay scheduled */ xpt_schedule(periph, CAM_PRIORITY_NORMAL); } } } static void ptdone(struct cam_periph *periph, union ccb *done_ccb) { struct pt_softc *softc; struct ccb_scsiio *csio; softc = (struct pt_softc *)periph->softc; CAM_DEBUG(periph->path, CAM_DEBUG_TRACE, ("ptdone\n")); csio = &done_ccb->csio; switch (csio->ccb_h.ccb_state) { case PT_CCB_BUFFER_IO: case PT_CCB_BUFFER_IO_UA: { struct bio *bp; bp = (struct bio *)done_ccb->ccb_h.ccb_bp; if ((done_ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) { int error; int sf; if ((csio->ccb_h.ccb_state & PT_CCB_RETRY_UA) != 0) sf = SF_RETRY_UA; else sf = 0; error = pterror(done_ccb, CAM_RETRY_SELTO, sf); if (error == ERESTART) { /* * A retry was scheuled, so * just return. */ return; } if (error != 0) { if (error == ENXIO) { /* * Catastrophic error. Mark our device * as invalid. */ xpt_print(periph->path, "Invalidating device\n"); softc->flags |= PT_FLAG_DEVICE_INVALID; } /* * return all queued I/O with EIO, so that * the client can retry these I/Os in the * proper order should it attempt to recover. */ bioq_flush(&softc->bio_queue, NULL, EIO); bp->bio_error = error; bp->bio_resid = bp->bio_bcount; bp->bio_flags |= BIO_ERROR; } else { bp->bio_resid = csio->resid; bp->bio_error = 0; if (bp->bio_resid != 0) { /* Short transfer ??? */ bp->bio_flags |= BIO_ERROR; } } if ((done_ccb->ccb_h.status & CAM_DEV_QFRZN) != 0) cam_release_devq(done_ccb->ccb_h.path, /*relsim_flags*/0, /*reduction*/0, /*timeout*/0, /*getcount_only*/0); } else { bp->bio_resid = csio->resid; if (bp->bio_resid != 0) bp->bio_flags |= BIO_ERROR; } /* * Block out any asynchronous callbacks * while we touch the pending ccb list. */ LIST_REMOVE(&done_ccb->ccb_h, periph_links.le); biofinish(bp, softc->device_stats, 0); break; } } xpt_release_ccb(done_ccb); } static int pterror(union ccb *ccb, u_int32_t cam_flags, u_int32_t sense_flags) { struct pt_softc *softc; struct cam_periph *periph; periph = xpt_path_periph(ccb->ccb_h.path); softc = (struct pt_softc *)periph->softc; return(cam_periph_error(ccb, cam_flags, sense_flags, &softc->saved_ccb)); } static int ptioctl(struct cdev *dev, u_long cmd, caddr_t addr, int flag, struct thread *td) { struct cam_periph *periph; struct pt_softc *softc; int error = 0; periph = (struct cam_periph *)dev->si_drv1; if (periph == NULL) return(ENXIO); softc = (struct pt_softc *)periph->softc; cam_periph_lock(periph); switch(cmd) { case PTIOCGETTIMEOUT: if (softc->io_timeout >= 1000) *(int *)addr = softc->io_timeout / 1000; else *(int *)addr = 0; break; case PTIOCSETTIMEOUT: if (*(int *)addr < 1) { error = EINVAL; break; } softc->io_timeout = *(int *)addr * 1000; break; default: error = cam_periph_ioctl(periph, cmd, addr, pterror); break; } cam_periph_unlock(periph); return(error); } void scsi_send_receive(struct ccb_scsiio *csio, u_int32_t retries, void (*cbfcnp)(struct cam_periph *, union ccb *), u_int tag_action, int readop, u_int byte2, u_int32_t xfer_len, u_int8_t *data_ptr, u_int8_t sense_len, u_int32_t timeout) { struct scsi_send_receive *scsi_cmd; scsi_cmd = (struct scsi_send_receive *)&csio->cdb_io.cdb_bytes; scsi_cmd->opcode = readop ? RECEIVE : SEND; scsi_cmd->byte2 = byte2; scsi_ulto3b(xfer_len, scsi_cmd->xfer_len); scsi_cmd->control = 0; cam_fill_csio(csio, retries, cbfcnp, /*flags*/readop ? CAM_DIR_IN : CAM_DIR_OUT, tag_action, data_ptr, xfer_len, sense_len, sizeof(*scsi_cmd), timeout); } diff --git a/sys/cam/scsi/scsi_sa.c b/sys/cam/scsi/scsi_sa.c index 5826caa132ce..6110b3d7adc3 100644 --- a/sys/cam/scsi/scsi_sa.c +++ b/sys/cam/scsi/scsi_sa.c @@ -1,5869 +1,5870 @@ /*- * Implementation of SCSI Sequential Access Peripheral driver for CAM. * * Copyright (c) 1999, 2000 Matthew Jacob * Copyright (c) 2013, 2014, 2015 Spectra Logic Corporation * 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, * without modification, immediately at the beginning of the file. * 2. 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 AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include #include #ifdef _KERNEL #include #include #endif #include #include #include #include #include #include #ifdef _KERNEL #include #include #include #include #endif #include #include #ifndef _KERNEL #include #include #endif #include #include #include #include #include #include #include #include #ifdef _KERNEL #include #ifndef SA_IO_TIMEOUT #define SA_IO_TIMEOUT 32 #endif #ifndef SA_SPACE_TIMEOUT #define SA_SPACE_TIMEOUT 1 * 60 #endif #ifndef SA_REWIND_TIMEOUT #define SA_REWIND_TIMEOUT 2 * 60 #endif #ifndef SA_ERASE_TIMEOUT #define SA_ERASE_TIMEOUT 4 * 60 #endif #ifndef SA_REP_DENSITY_TIMEOUT #define SA_REP_DENSITY_TIMEOUT 90 #endif #define SCSIOP_TIMEOUT (60 * 1000) /* not an option */ #define IO_TIMEOUT (SA_IO_TIMEOUT * 60 * 1000) #define REWIND_TIMEOUT (SA_REWIND_TIMEOUT * 60 * 1000) #define ERASE_TIMEOUT (SA_ERASE_TIMEOUT * 60 * 1000) #define SPACE_TIMEOUT (SA_SPACE_TIMEOUT * 60 * 1000) #define REP_DENSITY_TIMEOUT (SA_REP_DENSITY_TIMEOUT * 60 * 1000) /* * Additional options that can be set for config: SA_1FM_AT_EOT */ #ifndef UNUSED_PARAMETER #define UNUSED_PARAMETER(x) x = x #endif #define QFRLS(ccb) \ if (((ccb)->ccb_h.status & CAM_DEV_QFRZN) != 0) \ cam_release_devq((ccb)->ccb_h.path, 0, 0, 0, FALSE) /* * Driver states */ static MALLOC_DEFINE(M_SCSISA, "SCSI sa", "SCSI sequential access buffers"); typedef enum { SA_STATE_NORMAL, SA_STATE_ABNORMAL } sa_state; #define ccb_pflags ppriv_field0 #define ccb_bp ppriv_ptr1 /* bits in ccb_pflags */ #define SA_POSITION_UPDATED 0x1 typedef enum { SA_FLAG_OPEN = 0x0001, SA_FLAG_FIXED = 0x0002, SA_FLAG_TAPE_LOCKED = 0x0004, SA_FLAG_TAPE_MOUNTED = 0x0008, SA_FLAG_TAPE_WP = 0x0010, SA_FLAG_TAPE_WRITTEN = 0x0020, SA_FLAG_EOM_PENDING = 0x0040, SA_FLAG_EIO_PENDING = 0x0080, SA_FLAG_EOF_PENDING = 0x0100, SA_FLAG_ERR_PENDING = (SA_FLAG_EOM_PENDING|SA_FLAG_EIO_PENDING| SA_FLAG_EOF_PENDING), SA_FLAG_INVALID = 0x0200, SA_FLAG_COMP_ENABLED = 0x0400, SA_FLAG_COMP_SUPP = 0x0800, SA_FLAG_COMP_UNSUPP = 0x1000, SA_FLAG_TAPE_FROZEN = 0x2000, SA_FLAG_PROTECT_SUPP = 0x4000, SA_FLAG_COMPRESSION = (SA_FLAG_COMP_SUPP|SA_FLAG_COMP_ENABLED| SA_FLAG_COMP_UNSUPP), SA_FLAG_SCTX_INIT = 0x8000 } sa_flags; typedef enum { SA_MODE_REWIND = 0x00, SA_MODE_NOREWIND = 0x01, SA_MODE_OFFLINE = 0x02 } sa_mode; typedef enum { SA_PARAM_NONE = 0x000, SA_PARAM_BLOCKSIZE = 0x001, SA_PARAM_DENSITY = 0x002, SA_PARAM_COMPRESSION = 0x004, SA_PARAM_BUFF_MODE = 0x008, SA_PARAM_NUMBLOCKS = 0x010, SA_PARAM_WP = 0x020, SA_PARAM_SPEED = 0x040, SA_PARAM_DENSITY_EXT = 0x080, SA_PARAM_LBP = 0x100, SA_PARAM_ALL = 0x1ff } sa_params; typedef enum { SA_QUIRK_NONE = 0x000, SA_QUIRK_NOCOMP = 0x001, /* Can't deal with compression at all*/ SA_QUIRK_FIXED = 0x002, /* Force fixed mode */ SA_QUIRK_VARIABLE = 0x004, /* Force variable mode */ SA_QUIRK_2FM = 0x008, /* Needs Two File Marks at EOD */ SA_QUIRK_1FM = 0x010, /* No more than 1 File Mark at EOD */ SA_QUIRK_NODREAD = 0x020, /* Don't try and dummy read density */ SA_QUIRK_NO_MODESEL = 0x040, /* Don't do mode select at all */ SA_QUIRK_NO_CPAGE = 0x080, /* Don't use DEVICE COMPRESSION page */ SA_QUIRK_NO_LONG_POS = 0x100 /* No long position information */ } sa_quirks; #define SA_QUIRK_BIT_STRING \ "\020" \ "\001NOCOMP" \ "\002FIXED" \ "\003VARIABLE" \ "\0042FM" \ "\0051FM" \ "\006NODREAD" \ "\007NO_MODESEL" \ "\010NO_CPAGE" \ "\011NO_LONG_POS" #define SAMODE(z) (dev2unit(z) & 0x3) #define SA_IS_CTRL(z) (dev2unit(z) & (1 << 4)) #define SA_NOT_CTLDEV 0 #define SA_CTLDEV 1 #define SA_ATYPE_R 0 #define SA_ATYPE_NR 1 #define SA_ATYPE_ER 2 #define SA_NUM_ATYPES 3 #define SAMINOR(ctl, access) \ ((ctl << 4) | (access & 0x3)) struct sa_devs { struct cdev *ctl_dev; struct cdev *r_dev; struct cdev *nr_dev; struct cdev *er_dev; }; #define SASBADDBASE(sb, indent, data, xfmt, name, type, xsize, desc) \ sbuf_printf(sb, "%*s<%s type=\"%s\" size=\"%zd\" " \ "fmt=\"%s\" desc=\"%s\">" #xfmt "\n", indent, "", \ #name, #type, xsize, #xfmt, desc ? desc : "", data, #name); #define SASBADDINT(sb, indent, data, fmt, name) \ SASBADDBASE(sb, indent, data, fmt, name, int, sizeof(data), \ NULL) #define SASBADDINTDESC(sb, indent, data, fmt, name, desc) \ SASBADDBASE(sb, indent, data, fmt, name, int, sizeof(data), \ desc) #define SASBADDUINT(sb, indent, data, fmt, name) \ SASBADDBASE(sb, indent, data, fmt, name, uint, sizeof(data), \ NULL) #define SASBADDUINTDESC(sb, indent, data, fmt, name, desc) \ SASBADDBASE(sb, indent, data, fmt, name, uint, sizeof(data), \ desc) #define SASBADDFIXEDSTR(sb, indent, data, fmt, name) \ SASBADDBASE(sb, indent, data, fmt, name, str, sizeof(data), \ NULL) #define SASBADDFIXEDSTRDESC(sb, indent, data, fmt, name, desc) \ SASBADDBASE(sb, indent, data, fmt, name, str, sizeof(data), \ desc) #define SASBADDVARSTR(sb, indent, data, fmt, name, maxlen) \ SASBADDBASE(sb, indent, data, fmt, name, str, maxlen, NULL) #define SASBADDVARSTRDESC(sb, indent, data, fmt, name, maxlen, desc) \ SASBADDBASE(sb, indent, data, fmt, name, str, maxlen, desc) #define SASBADDNODE(sb, indent, name) { \ sbuf_printf(sb, "%*s<%s type=\"%s\">\n", indent, "", #name, \ "node"); \ indent += 2; \ } #define SASBADDNODENUM(sb, indent, name, num) { \ sbuf_printf(sb, "%*s<%s type=\"%s\" num=\"%d\">\n", indent, "", \ #name, "node", num); \ indent += 2; \ } #define SASBENDNODE(sb, indent, name) { \ indent -= 2; \ sbuf_printf(sb, "%*s\n", indent, "", #name); \ } #define SA_DENSITY_TYPES 4 struct sa_prot_state { int initialized; uint32_t prot_method; uint32_t pi_length; uint32_t lbp_w; uint32_t lbp_r; uint32_t rbdp; }; struct sa_prot_info { struct sa_prot_state cur_prot_state; struct sa_prot_state pending_prot_state; }; /* * A table mapping protection parameters to their types and values. */ struct sa_prot_map { char *name; mt_param_set_type param_type; off_t offset; uint32_t min_val; uint32_t max_val; uint32_t *value; } sa_prot_table[] = { { "prot_method", MT_PARAM_SET_UNSIGNED, __offsetof(struct sa_prot_state, prot_method), /*min_val*/ 0, /*max_val*/ 255, NULL }, { "pi_length", MT_PARAM_SET_UNSIGNED, __offsetof(struct sa_prot_state, pi_length), /*min_val*/ 0, /*max_val*/ SA_CTRL_DP_PI_LENGTH_MASK, NULL }, { "lbp_w", MT_PARAM_SET_UNSIGNED, __offsetof(struct sa_prot_state, lbp_w), /*min_val*/ 0, /*max_val*/ 1, NULL }, { "lbp_r", MT_PARAM_SET_UNSIGNED, __offsetof(struct sa_prot_state, lbp_r), /*min_val*/ 0, /*max_val*/ 1, NULL }, { "rbdp", MT_PARAM_SET_UNSIGNED, __offsetof(struct sa_prot_state, rbdp), /*min_val*/ 0, /*max_val*/ 1, NULL } }; #define SA_NUM_PROT_ENTS sizeof(sa_prot_table)/sizeof(sa_prot_table[0]) #define SA_PROT_ENABLED(softc) ((softc->flags & SA_FLAG_PROTECT_SUPP) \ && (softc->prot_info.cur_prot_state.initialized != 0) \ && (softc->prot_info.cur_prot_state.prot_method != 0)) #define SA_PROT_LEN(softc) softc->prot_info.cur_prot_state.pi_length struct sa_softc { sa_state state; sa_flags flags; sa_quirks quirks; u_int si_flags; struct cam_periph *periph; struct bio_queue_head bio_queue; int queue_count; struct devstat *device_stats; struct sa_devs devs; int open_count; int num_devs_to_destroy; int blk_gran; int blk_mask; int blk_shift; u_int32_t max_blk; u_int32_t min_blk; u_int32_t maxio; u_int32_t cpi_maxio; int allow_io_split; u_int32_t comp_algorithm; u_int32_t saved_comp_algorithm; u_int32_t media_blksize; u_int32_t last_media_blksize; u_int32_t media_numblks; u_int8_t media_density; u_int8_t speed; u_int8_t scsi_rev; u_int8_t dsreg; /* mtio mt_dsreg, redux */ int buffer_mode; int filemarks; union ccb saved_ccb; int last_resid_was_io; uint8_t density_type_bits[SA_DENSITY_TYPES]; int density_info_valid[SA_DENSITY_TYPES]; uint8_t density_info[SA_DENSITY_TYPES][SRDS_MAX_LENGTH]; struct sa_prot_info prot_info; int sili; int eot_warn; /* * Current position information. -1 means that the given value is * unknown. fileno and blkno are always calculated. blkno is * relative to the previous file mark. rep_fileno and rep_blkno * are as reported by the drive, if it supports the long form * report for the READ POSITION command. rep_blkno is relative to * the beginning of the partition. * * bop means that the drive is at the beginning of the partition. * eop means that the drive is between early warning and end of * partition, inside the current partition. * bpew means that the position is in a PEWZ (Programmable Early * Warning Zone) */ daddr_t partition; /* Absolute from BOT */ daddr_t fileno; /* Relative to beginning of partition */ daddr_t blkno; /* Relative to last file mark */ daddr_t rep_blkno; /* Relative to beginning of partition */ daddr_t rep_fileno; /* Relative to beginning of partition */ int bop; /* Beginning of Partition */ int eop; /* End of Partition */ int bpew; /* Beyond Programmable Early Warning */ /* * Latched Error Info */ struct { struct scsi_sense_data _last_io_sense; u_int64_t _last_io_resid; u_int8_t _last_io_cdb[CAM_MAX_CDBLEN]; struct scsi_sense_data _last_ctl_sense; u_int64_t _last_ctl_resid; u_int8_t _last_ctl_cdb[CAM_MAX_CDBLEN]; #define last_io_sense errinfo._last_io_sense #define last_io_resid errinfo._last_io_resid #define last_io_cdb errinfo._last_io_cdb #define last_ctl_sense errinfo._last_ctl_sense #define last_ctl_resid errinfo._last_ctl_resid #define last_ctl_cdb errinfo._last_ctl_cdb } errinfo; /* * Misc other flags/state */ u_int32_t : 29, open_rdonly : 1, /* open read-only */ open_pending_mount : 1, /* open pending mount */ ctrl_mode : 1; /* control device open */ struct task sysctl_task; struct sysctl_ctx_list sysctl_ctx; struct sysctl_oid *sysctl_tree; }; struct sa_quirk_entry { struct scsi_inquiry_pattern inq_pat; /* matching pattern */ sa_quirks quirks; /* specific quirk type */ u_int32_t prefblk; /* preferred blocksize when in fixed mode */ }; static struct sa_quirk_entry sa_quirk_table[] = { { { T_SEQUENTIAL, SIP_MEDIA_REMOVABLE, "OnStream", "ADR*", "*"}, SA_QUIRK_FIXED|SA_QUIRK_NODREAD | SA_QUIRK_1FM|SA_QUIRK_NO_MODESEL, 32768 }, { { T_SEQUENTIAL, SIP_MEDIA_REMOVABLE, "ARCHIVE", "Python 06408*", "*"}, SA_QUIRK_NODREAD, 0 }, { { T_SEQUENTIAL, SIP_MEDIA_REMOVABLE, "ARCHIVE", "Python 25601*", "*"}, SA_QUIRK_NOCOMP|SA_QUIRK_NODREAD, 0 }, { { T_SEQUENTIAL, SIP_MEDIA_REMOVABLE, "ARCHIVE", "Python*", "*"}, SA_QUIRK_NODREAD, 0 }, { { T_SEQUENTIAL, SIP_MEDIA_REMOVABLE, "ARCHIVE", "VIPER 150*", "*"}, SA_QUIRK_FIXED|SA_QUIRK_1FM, 512 }, { { T_SEQUENTIAL, SIP_MEDIA_REMOVABLE, "ARCHIVE", "VIPER 2525 25462", "-011"}, SA_QUIRK_NOCOMP|SA_QUIRK_1FM|SA_QUIRK_NODREAD, 0 }, { { T_SEQUENTIAL, SIP_MEDIA_REMOVABLE, "ARCHIVE", "VIPER 2525*", "*"}, SA_QUIRK_FIXED|SA_QUIRK_1FM, 1024 }, #if 0 { { T_SEQUENTIAL, SIP_MEDIA_REMOVABLE, "HP", "C15*", "*"}, SA_QUIRK_VARIABLE|SA_QUIRK_NO_CPAGE, 0, }, #endif { { T_SEQUENTIAL, SIP_MEDIA_REMOVABLE, "HP", "C56*", "*"}, SA_QUIRK_VARIABLE|SA_QUIRK_2FM, 0 }, { { T_SEQUENTIAL, SIP_MEDIA_REMOVABLE, "HP", "T20*", "*"}, SA_QUIRK_FIXED|SA_QUIRK_1FM, 512 }, { { T_SEQUENTIAL, SIP_MEDIA_REMOVABLE, "HP", "T4000*", "*"}, SA_QUIRK_FIXED|SA_QUIRK_1FM, 512 }, { { T_SEQUENTIAL, SIP_MEDIA_REMOVABLE, "HP", "HP-88780*", "*"}, SA_QUIRK_VARIABLE|SA_QUIRK_2FM, 0 }, { { T_SEQUENTIAL, SIP_MEDIA_REMOVABLE, "KENNEDY", "*", "*"}, SA_QUIRK_VARIABLE|SA_QUIRK_2FM, 0 }, { { T_SEQUENTIAL, SIP_MEDIA_REMOVABLE, "M4 DATA", "123107 SCSI*", "*"}, SA_QUIRK_VARIABLE|SA_QUIRK_2FM, 0 }, { /* jreynold@primenet.com */ { T_SEQUENTIAL, SIP_MEDIA_REMOVABLE, "Seagate", "STT8000N*", "*"}, SA_QUIRK_1FM, 0 }, { /* mike@sentex.net */ { T_SEQUENTIAL, SIP_MEDIA_REMOVABLE, "Seagate", "STT20000*", "*"}, SA_QUIRK_1FM, 0 }, { { T_SEQUENTIAL, SIP_MEDIA_REMOVABLE, "SEAGATE", "DAT 06241-XXX", "*"}, SA_QUIRK_VARIABLE|SA_QUIRK_2FM, 0 }, { { T_SEQUENTIAL, SIP_MEDIA_REMOVABLE, "TANDBERG", " TDC 3600", "U07:"}, SA_QUIRK_NOCOMP|SA_QUIRK_1FM, 512 }, { { T_SEQUENTIAL, SIP_MEDIA_REMOVABLE, "TANDBERG", " TDC 3800", "*"}, SA_QUIRK_NOCOMP|SA_QUIRK_1FM, 512 }, { { T_SEQUENTIAL, SIP_MEDIA_REMOVABLE, "TANDBERG", " TDC 4100", "*"}, SA_QUIRK_NOCOMP|SA_QUIRK_1FM, 512 }, { { T_SEQUENTIAL, SIP_MEDIA_REMOVABLE, "TANDBERG", " TDC 4200", "*"}, SA_QUIRK_NOCOMP|SA_QUIRK_1FM, 512 }, { { T_SEQUENTIAL, SIP_MEDIA_REMOVABLE, "TANDBERG", " SLR*", "*"}, SA_QUIRK_1FM, 0 }, { { T_SEQUENTIAL, SIP_MEDIA_REMOVABLE, "WANGTEK", "5525ES*", "*"}, SA_QUIRK_FIXED|SA_QUIRK_1FM, 512 }, { { T_SEQUENTIAL, SIP_MEDIA_REMOVABLE, "WANGTEK", "51000*", "*"}, SA_QUIRK_FIXED|SA_QUIRK_1FM, 1024 } }; static d_open_t saopen; static d_close_t saclose; static d_strategy_t sastrategy; static d_ioctl_t saioctl; static periph_init_t sainit; static periph_ctor_t saregister; static periph_oninv_t saoninvalidate; static periph_dtor_t sacleanup; static periph_start_t sastart; static void saasync(void *callback_arg, u_int32_t code, struct cam_path *path, void *arg); static void sadone(struct cam_periph *periph, union ccb *start_ccb); static int saerror(union ccb *ccb, u_int32_t cam_flags, u_int32_t sense_flags); static int samarkswanted(struct cam_periph *); static int sacheckeod(struct cam_periph *periph); static int sagetparams(struct cam_periph *periph, sa_params params_to_get, u_int32_t *blocksize, u_int8_t *density, u_int32_t *numblocks, int *buff_mode, u_int8_t *write_protect, u_int8_t *speed, int *comp_supported, int *comp_enabled, u_int32_t *comp_algorithm, sa_comp_t *comp_page, struct scsi_control_data_prot_subpage *prot_page, int dp_size, int prot_changeable); static int sasetprot(struct cam_periph *periph, struct sa_prot_state *new_prot); static int sasetparams(struct cam_periph *periph, sa_params params_to_set, u_int32_t blocksize, u_int8_t density, u_int32_t comp_algorithm, u_int32_t sense_flags); static int sasetsili(struct cam_periph *periph, struct mtparamset *ps, int num_params); static int saseteotwarn(struct cam_periph *periph, struct mtparamset *ps, int num_params); static void safillprot(struct sa_softc *softc, int *indent, struct sbuf *sb); static void sapopulateprots(struct sa_prot_state *cur_state, struct sa_prot_map *new_table, int table_ents); static struct sa_prot_map *safindprotent(char *name, struct sa_prot_map *table, int table_ents); static int sasetprotents(struct cam_periph *periph, struct mtparamset *ps, int num_params); static struct sa_param_ent *safindparament(struct mtparamset *ps); static int saparamsetlist(struct cam_periph *periph, struct mtsetlist *list, int need_copy); static int saextget(struct cdev *dev, struct cam_periph *periph, struct sbuf *sb, struct mtextget *g); static int saparamget(struct sa_softc *softc, struct sbuf *sb); static void saprevent(struct cam_periph *periph, int action); static int sarewind(struct cam_periph *periph); static int saspace(struct cam_periph *periph, int count, scsi_space_code code); static void sadevgonecb(void *arg); static void sasetupdev(struct sa_softc *softc, struct cdev *dev); static int samount(struct cam_periph *, int, struct cdev *); static int saretension(struct cam_periph *periph); static int sareservereleaseunit(struct cam_periph *periph, int reserve); static int saloadunload(struct cam_periph *periph, int load); static int saerase(struct cam_periph *periph, int longerase); static int sawritefilemarks(struct cam_periph *periph, int nmarks, int setmarks, int immed); static int sagetpos(struct cam_periph *periph); static int sardpos(struct cam_periph *periph, int, u_int32_t *); static int sasetpos(struct cam_periph *periph, int, struct mtlocate *); static void safilldenstypesb(struct sbuf *sb, int *indent, uint8_t *buf, int buf_len, int is_density); static void safilldensitysb(struct sa_softc *softc, int *indent, struct sbuf *sb); #ifndef SA_DEFAULT_IO_SPLIT #define SA_DEFAULT_IO_SPLIT 0 #endif static int sa_allow_io_split = SA_DEFAULT_IO_SPLIT; /* * Tunable to allow the user to set a global allow_io_split value. Note * that this WILL GO AWAY in FreeBSD 11.0. Silently splitting the I/O up * is bad behavior, because it hides the true tape block size from the * application. */ static SYSCTL_NODE(_kern_cam, OID_AUTO, sa, CTLFLAG_RD, 0, "CAM Sequential Access Tape Driver"); SYSCTL_INT(_kern_cam_sa, OID_AUTO, allow_io_split, CTLFLAG_RDTUN, &sa_allow_io_split, 0, "Default I/O split value"); static struct periph_driver sadriver = { sainit, "sa", TAILQ_HEAD_INITIALIZER(sadriver.units), /* generation */ 0 }; PERIPHDRIVER_DECLARE(sa, sadriver); /* For 2.2-stable support */ #ifndef D_TAPE #define D_TAPE 0 #endif static struct cdevsw sa_cdevsw = { .d_version = D_VERSION, .d_open = saopen, .d_close = saclose, .d_read = physread, .d_write = physwrite, .d_ioctl = saioctl, .d_strategy = sastrategy, .d_name = "sa", .d_flags = D_TAPE | D_TRACKCLOSE, }; static int saopen(struct cdev *dev, int flags, int fmt, struct thread *td) { struct cam_periph *periph; struct sa_softc *softc; int error; periph = (struct cam_periph *)dev->si_drv1; if (cam_periph_acquire(periph) != CAM_REQ_CMP) { return (ENXIO); } cam_periph_lock(periph); softc = (struct sa_softc *)periph->softc; CAM_DEBUG(periph->path, CAM_DEBUG_TRACE|CAM_DEBUG_INFO, ("saopen(%s): softc=0x%x\n", devtoname(dev), softc->flags)); if (SA_IS_CTRL(dev)) { softc->ctrl_mode = 1; softc->open_count++; cam_periph_unlock(periph); return (0); } if ((error = cam_periph_hold(periph, PRIBIO|PCATCH)) != 0) { cam_periph_unlock(periph); cam_periph_release(periph); return (error); } if (softc->flags & SA_FLAG_OPEN) { error = EBUSY; } else if (softc->flags & SA_FLAG_INVALID) { error = ENXIO; } else { /* * Preserve whether this is a read_only open. */ softc->open_rdonly = (flags & O_RDWR) == O_RDONLY; /* * The function samount ensures media is loaded and ready. * It also does a device RESERVE if the tape isn't yet mounted. * * If the mount fails and this was a non-blocking open, * make this a 'open_pending_mount' action. */ error = samount(periph, flags, dev); if (error && (flags & O_NONBLOCK)) { softc->flags |= SA_FLAG_OPEN; softc->open_pending_mount = 1; softc->open_count++; cam_periph_unhold(periph); cam_periph_unlock(periph); return (0); } } if (error) { cam_periph_unhold(periph); cam_periph_unlock(periph); cam_periph_release(periph); return (error); } saprevent(periph, PR_PREVENT); softc->flags |= SA_FLAG_OPEN; softc->open_count++; cam_periph_unhold(periph); cam_periph_unlock(periph); return (error); } static int saclose(struct cdev *dev, int flag, int fmt, struct thread *td) { struct cam_periph *periph; struct sa_softc *softc; int mode, error, writing, tmp, i; int closedbits = SA_FLAG_OPEN; mode = SAMODE(dev); periph = (struct cam_periph *)dev->si_drv1; if (periph == NULL) return (ENXIO); cam_periph_lock(periph); softc = (struct sa_softc *)periph->softc; CAM_DEBUG(periph->path, CAM_DEBUG_TRACE|CAM_DEBUG_INFO, ("saclose(%s): softc=0x%x\n", devtoname(dev), softc->flags)); softc->open_rdonly = 0; if (SA_IS_CTRL(dev)) { softc->ctrl_mode = 0; softc->open_count--; cam_periph_unlock(periph); cam_periph_release(periph); return (0); } if (softc->open_pending_mount) { softc->flags &= ~SA_FLAG_OPEN; softc->open_pending_mount = 0; softc->open_count--; cam_periph_unlock(periph); cam_periph_release(periph); return (0); } if ((error = cam_periph_hold(periph, PRIBIO)) != 0) { cam_periph_unlock(periph); return (error); } /* * Were we writing the tape? */ writing = (softc->flags & SA_FLAG_TAPE_WRITTEN) != 0; /* * See whether or not we need to write filemarks. If this * fails, we probably have to assume we've lost tape * position. */ error = sacheckeod(periph); if (error) { xpt_print(periph->path, "failed to write terminating filemark(s)\n"); softc->flags |= SA_FLAG_TAPE_FROZEN; } /* * Whatever we end up doing, allow users to eject tapes from here on. */ saprevent(periph, PR_ALLOW); /* * Decide how to end... */ if ((softc->flags & SA_FLAG_TAPE_MOUNTED) == 0) { closedbits |= SA_FLAG_TAPE_FROZEN; } else switch (mode) { case SA_MODE_OFFLINE: /* * An 'offline' close is an unconditional release of * frozen && mount conditions, irrespective of whether * these operations succeeded. The reason for this is * to allow at least some kind of programmatic way * around our state getting all fouled up. If somebody * issues an 'offline' command, that will be allowed * to clear state. */ (void) sarewind(periph); (void) saloadunload(periph, FALSE); closedbits |= SA_FLAG_TAPE_MOUNTED|SA_FLAG_TAPE_FROZEN; break; case SA_MODE_REWIND: /* * If the rewind fails, return an error- if anyone cares, * but not overwriting any previous error. * * We don't clear the notion of mounted here, but we do * clear the notion of frozen if we successfully rewound. */ tmp = sarewind(periph); if (tmp) { if (error != 0) error = tmp; } else { closedbits |= SA_FLAG_TAPE_FROZEN; } break; case SA_MODE_NOREWIND: /* * If we're not rewinding/unloading the tape, find out * whether we need to back up over one of two filemarks * we wrote (if we wrote two filemarks) so that appends * from this point on will be sane. */ if (error == 0 && writing && (softc->quirks & SA_QUIRK_2FM)) { tmp = saspace(periph, -1, SS_FILEMARKS); if (tmp) { xpt_print(periph->path, "unable to backspace " "over one of double filemarks at end of " "tape\n"); xpt_print(periph->path, "it is possible that " "this device needs a SA_QUIRK_1FM quirk set" "for it\n"); softc->flags |= SA_FLAG_TAPE_FROZEN; } } break; default: xpt_print(periph->path, "unknown mode 0x%x in saclose\n", mode); /* NOTREACHED */ break; } /* * We wish to note here that there are no more filemarks to be written. */ softc->filemarks = 0; softc->flags &= ~SA_FLAG_TAPE_WRITTEN; /* * And we are no longer open for business. */ softc->flags &= ~closedbits; softc->open_count--; /* * Invalidate any density information that depends on having tape * media in the drive. */ for (i = 0; i < SA_DENSITY_TYPES; i++) { if (softc->density_type_bits[i] & SRDS_MEDIA) softc->density_info_valid[i] = 0; } /* * Inform users if tape state if frozen.... */ if (softc->flags & SA_FLAG_TAPE_FROZEN) { xpt_print(periph->path, "tape is now frozen- use an OFFLINE, " "REWIND or MTEOM command to clear this state.\n"); } /* release the device if it is no longer mounted */ if ((softc->flags & SA_FLAG_TAPE_MOUNTED) == 0) sareservereleaseunit(periph, FALSE); cam_periph_unhold(periph); cam_periph_unlock(periph); cam_periph_release(periph); return (error); } /* * Actually translate the requested transfer into one the physical driver * can understand. The transfer is described by a buf and will include * only one physical transfer. */ static void sastrategy(struct bio *bp) { struct cam_periph *periph; struct sa_softc *softc; bp->bio_resid = bp->bio_bcount; if (SA_IS_CTRL(bp->bio_dev)) { biofinish(bp, NULL, EINVAL); return; } periph = (struct cam_periph *)bp->bio_dev->si_drv1; if (periph == NULL) { biofinish(bp, NULL, ENXIO); return; } cam_periph_lock(periph); softc = (struct sa_softc *)periph->softc; if (softc->flags & SA_FLAG_INVALID) { cam_periph_unlock(periph); biofinish(bp, NULL, ENXIO); return; } if (softc->flags & SA_FLAG_TAPE_FROZEN) { cam_periph_unlock(periph); biofinish(bp, NULL, EPERM); return; } /* * This should actually never occur as the write(2) * system call traps attempts to write to a read-only * file descriptor. */ if (bp->bio_cmd == BIO_WRITE && softc->open_rdonly) { cam_periph_unlock(periph); biofinish(bp, NULL, EBADF); return; } if (softc->open_pending_mount) { int error = samount(periph, 0, bp->bio_dev); if (error) { cam_periph_unlock(periph); biofinish(bp, NULL, ENXIO); return; } saprevent(periph, PR_PREVENT); softc->open_pending_mount = 0; } /* * If it's a null transfer, return immediately */ if (bp->bio_bcount == 0) { cam_periph_unlock(periph); biodone(bp); return; } /* valid request? */ if (softc->flags & SA_FLAG_FIXED) { /* * Fixed block device. The byte count must * be a multiple of our block size. */ if (((softc->blk_mask != ~0) && ((bp->bio_bcount & softc->blk_mask) != 0)) || ((softc->blk_mask == ~0) && ((bp->bio_bcount % softc->min_blk) != 0))) { xpt_print(periph->path, "Invalid request. Fixed block " "device requests must be a multiple of %d bytes\n", softc->min_blk); cam_periph_unlock(periph); biofinish(bp, NULL, EINVAL); return; } } else if ((bp->bio_bcount > softc->max_blk) || (bp->bio_bcount < softc->min_blk) || (bp->bio_bcount & softc->blk_mask) != 0) { xpt_print_path(periph->path); printf("Invalid request. Variable block " "device requests must be "); if (softc->blk_mask != 0) { printf("a multiple of %d ", (0x1 << softc->blk_gran)); } printf("between %d and %d bytes\n", softc->min_blk, softc->max_blk); cam_periph_unlock(periph); biofinish(bp, NULL, EINVAL); return; } /* * Place it at the end of the queue. */ bioq_insert_tail(&softc->bio_queue, bp); softc->queue_count++; #if 0 CAM_DEBUG(periph->path, CAM_DEBUG_INFO, ("sastrategy: queuing a %ld %s byte %s\n", bp->bio_bcount, (softc->flags & SA_FLAG_FIXED)? "fixed" : "variable", (bp->bio_cmd == BIO_READ)? "read" : "write")); #endif if (softc->queue_count > 1) { CAM_DEBUG(periph->path, CAM_DEBUG_INFO, ("sastrategy: queue count now %d\n", softc->queue_count)); } /* * Schedule ourselves for performing the work. */ xpt_schedule(periph, CAM_PRIORITY_NORMAL); cam_periph_unlock(periph); return; } static int sasetsili(struct cam_periph *periph, struct mtparamset *ps, int num_params) { uint32_t sili_blocksize; struct sa_softc *softc; int error; error = 0; softc = (struct sa_softc *)periph->softc; if (ps->value_type != MT_PARAM_SET_SIGNED) { snprintf(ps->error_str, sizeof(ps->error_str), "sili is a signed parameter"); goto bailout; } if ((ps->value.value_signed < 0) || (ps->value.value_signed > 1)) { snprintf(ps->error_str, sizeof(ps->error_str), "invalid sili value %jd", (intmax_t)ps->value.value_signed); goto bailout_error; } /* * We only set the SILI flag in variable block * mode. You'll get a check condition in fixed * block mode if things don't line up in any case. */ if (softc->flags & SA_FLAG_FIXED) { snprintf(ps->error_str, sizeof(ps->error_str), "can't set sili bit in fixed block mode"); goto bailout_error; } if (softc->sili == ps->value.value_signed) goto bailout; if (ps->value.value_signed == 1) sili_blocksize = 4; else sili_blocksize = 0; error = sasetparams(periph, SA_PARAM_BLOCKSIZE, sili_blocksize, 0, 0, SF_QUIET_IR); if (error != 0) { snprintf(ps->error_str, sizeof(ps->error_str), "sasetparams() returned error %d", error); goto bailout_error; } softc->sili = ps->value.value_signed; bailout: ps->status = MT_PARAM_STATUS_OK; return (error); bailout_error: ps->status = MT_PARAM_STATUS_ERROR; if (error == 0) error = EINVAL; return (error); } static int saseteotwarn(struct cam_periph *periph, struct mtparamset *ps, int num_params) { struct sa_softc *softc; int error; error = 0; softc = (struct sa_softc *)periph->softc; if (ps->value_type != MT_PARAM_SET_SIGNED) { snprintf(ps->error_str, sizeof(ps->error_str), "eot_warn is a signed parameter"); ps->status = MT_PARAM_STATUS_ERROR; goto bailout; } if ((ps->value.value_signed < 0) || (ps->value.value_signed > 1)) { snprintf(ps->error_str, sizeof(ps->error_str), "invalid eot_warn value %jd\n", (intmax_t)ps->value.value_signed); ps->status = MT_PARAM_STATUS_ERROR; goto bailout; } softc->eot_warn = ps->value.value_signed; ps->status = MT_PARAM_STATUS_OK; bailout: if (ps->status != MT_PARAM_STATUS_OK) error = EINVAL; return (error); } static void safillprot(struct sa_softc *softc, int *indent, struct sbuf *sb) { int tmpint; SASBADDNODE(sb, *indent, protection); if (softc->flags & SA_FLAG_PROTECT_SUPP) tmpint = 1; else tmpint = 0; SASBADDINTDESC(sb, *indent, tmpint, %d, protection_supported, "Set to 1 if protection information is supported"); if ((tmpint != 0) && (softc->prot_info.cur_prot_state.initialized != 0)) { struct sa_prot_state *prot; prot = &softc->prot_info.cur_prot_state; SASBADDUINTDESC(sb, *indent, prot->prot_method, %u, prot_method, "Current Protection Method"); SASBADDUINTDESC(sb, *indent, prot->pi_length, %u, pi_length, "Length of Protection Information"); SASBADDUINTDESC(sb, *indent, prot->lbp_w, %u, lbp_w, "Check Protection on Writes"); SASBADDUINTDESC(sb, *indent, prot->lbp_r, %u, lbp_r, "Check and Include Protection on Reads"); SASBADDUINTDESC(sb, *indent, prot->rbdp, %u, rbdp, "Transfer Protection Information for RECOVER " "BUFFERED DATA command"); } SASBENDNODE(sb, *indent, protection); } static void sapopulateprots(struct sa_prot_state *cur_state, struct sa_prot_map *new_table, int table_ents) { int i; bcopy(sa_prot_table, new_table, min(table_ents * sizeof(*new_table), sizeof(sa_prot_table))); table_ents = min(table_ents, SA_NUM_PROT_ENTS); for (i = 0; i < table_ents; i++) new_table[i].value = (uint32_t *)((uint8_t *)cur_state + new_table[i].offset); return; } static struct sa_prot_map * safindprotent(char *name, struct sa_prot_map *table, int table_ents) { char *prot_name = "protection."; int i, prot_len; prot_len = strlen(prot_name); /* * This shouldn't happen, but we check just in case. */ if (strncmp(name, prot_name, prot_len) != 0) goto bailout; for (i = 0; i < table_ents; i++) { if (strcmp(&name[prot_len], table[i].name) != 0) continue; return (&table[i]); } bailout: return (NULL); } static int sasetprotents(struct cam_periph *periph, struct mtparamset *ps, int num_params) { struct sa_softc *softc; struct sa_prot_map prot_ents[SA_NUM_PROT_ENTS]; struct sa_prot_state new_state; int error; int i; softc = (struct sa_softc *)periph->softc; error = 0; /* * Make sure that this tape drive supports protection information. * Otherwise we can't set anything. */ if ((softc->flags & SA_FLAG_PROTECT_SUPP) == 0) { snprintf(ps[0].error_str, sizeof(ps[0].error_str), "Protection information is not supported for this device"); ps[0].status = MT_PARAM_STATUS_ERROR; goto bailout; } /* * We can't operate with physio(9) splitting enabled, because there * is no way to insure (especially in variable block mode) that * what the user writes (with a checksum block at the end) will * make it into the sa(4) driver intact. */ if ((softc->si_flags & SI_NOSPLIT) == 0) { snprintf(ps[0].error_str, sizeof(ps[0].error_str), "Protection information cannot be enabled with I/O " "splitting"); ps[0].status = MT_PARAM_STATUS_ERROR; goto bailout; } /* * Take the current cached protection state and use that as the * basis for our new entries. */ bcopy(&softc->prot_info.cur_prot_state, &new_state, sizeof(new_state)); /* * Populate the table mapping property names to pointers into the * state structure. */ sapopulateprots(&new_state, prot_ents, SA_NUM_PROT_ENTS); /* * For each parameter the user passed in, make sure the name, type * and value are valid. */ for (i = 0; i < num_params; i++) { struct sa_prot_map *ent; ent = safindprotent(ps[i].value_name, prot_ents, SA_NUM_PROT_ENTS); if (ent == NULL) { ps[i].status = MT_PARAM_STATUS_ERROR; snprintf(ps[i].error_str, sizeof(ps[i].error_str), "Invalid protection entry name %s", ps[i].value_name); error = EINVAL; goto bailout; } if (ent->param_type != ps[i].value_type) { ps[i].status = MT_PARAM_STATUS_ERROR; snprintf(ps[i].error_str, sizeof(ps[i].error_str), "Supplied type %d does not match actual type %d", ps[i].value_type, ent->param_type); error = EINVAL; goto bailout; } if ((ps[i].value.value_unsigned < ent->min_val) || (ps[i].value.value_unsigned > ent->max_val)) { ps[i].status = MT_PARAM_STATUS_ERROR; snprintf(ps[i].error_str, sizeof(ps[i].error_str), "Value %ju is outside valid range %u - %u", (uintmax_t)ps[i].value.value_unsigned, ent->min_val, ent->max_val); error = EINVAL; goto bailout; } *(ent->value) = ps[i].value.value_unsigned; } /* * Actually send the protection settings to the drive. */ error = sasetprot(periph, &new_state); if (error != 0) { for (i = 0; i < num_params; i++) { ps[i].status = MT_PARAM_STATUS_ERROR; snprintf(ps[i].error_str, sizeof(ps[i].error_str), "Unable to set parameter, see dmesg(8)"); } goto bailout; } /* * Let the user know that his settings were stored successfully. */ for (i = 0; i < num_params; i++) ps[i].status = MT_PARAM_STATUS_OK; bailout: return (error); } /* * Entry handlers generally only handle a single entry. Node handlers will * handle a contiguous range of parameters to set in a single call. */ typedef enum { SA_PARAM_TYPE_ENTRY, SA_PARAM_TYPE_NODE } sa_param_type; struct sa_param_ent { char *name; sa_param_type param_type; int (*set_func)(struct cam_periph *periph, struct mtparamset *ps, int num_params); } sa_param_table[] = { {"sili", SA_PARAM_TYPE_ENTRY, sasetsili }, {"eot_warn", SA_PARAM_TYPE_ENTRY, saseteotwarn }, {"protection.", SA_PARAM_TYPE_NODE, sasetprotents } }; static struct sa_param_ent * safindparament(struct mtparamset *ps) { unsigned int i; for (i = 0; i < sizeof(sa_param_table) /sizeof(sa_param_table[0]); i++){ /* * For entries, we compare all of the characters. For * nodes, we only compare the first N characters. The node * handler will decode the rest. */ if (sa_param_table[i].param_type == SA_PARAM_TYPE_ENTRY) { if (strcmp(ps->value_name, sa_param_table[i].name) != 0) continue; } else { if (strncmp(ps->value_name, sa_param_table[i].name, strlen(sa_param_table[i].name)) != 0) continue; } return (&sa_param_table[i]); } return (NULL); } /* * Go through a list of parameters, coalescing contiguous parameters with * the same parent node into a single call to a set_func. */ static int saparamsetlist(struct cam_periph *periph, struct mtsetlist *list, int need_copy) { int i, contig_ents; int error; struct mtparamset *params, *first; struct sa_param_ent *first_ent; error = 0; params = NULL; if (list->num_params == 0) /* Nothing to do */ goto bailout; /* * Verify that the user has the correct structure size. */ if ((list->num_params * sizeof(struct mtparamset)) != list->param_len) { xpt_print(periph->path, "%s: length of params %d != " "sizeof(struct mtparamset) %zd * num_params %d\n", __func__, list->param_len, sizeof(struct mtparamset), list->num_params); error = EINVAL; goto bailout; } if (need_copy != 0) { /* * XXX KDM will dropping the lock cause an issue here? */ cam_periph_unlock(periph); params = malloc(list->param_len, M_SCSISA, M_WAITOK | M_ZERO); error = copyin(list->params, params, list->param_len); cam_periph_lock(periph); if (error != 0) goto bailout; } else { params = list->params; } contig_ents = 0; first = NULL; first_ent = NULL; for (i = 0; i < list->num_params; i++) { struct sa_param_ent *ent; ent = safindparament(¶ms[i]); if (ent == NULL) { snprintf(params[i].error_str, sizeof(params[i].error_str), "%s: cannot find parameter %s", __func__, params[i].value_name); params[i].status = MT_PARAM_STATUS_ERROR; break; } if (first != NULL) { if (first_ent == ent) { /* * We're still in a contiguous list of * parameters that can be handled by one * node handler. */ contig_ents++; continue; } else { error = first_ent->set_func(periph, first, contig_ents); first = NULL; first_ent = NULL; contig_ents = 0; if (error != 0) { error = 0; break; } } } if (ent->param_type == SA_PARAM_TYPE_NODE) { first = ¶ms[i]; first_ent = ent; contig_ents = 1; } else { error = ent->set_func(periph, ¶ms[i], 1); if (error != 0) { error = 0; break; } } } if (first != NULL) first_ent->set_func(periph, first, contig_ents); bailout: if (need_copy != 0) { if (error != EFAULT) { cam_periph_unlock(periph); copyout(params, list->params, list->param_len); cam_periph_lock(periph); } free(params, M_SCSISA); } return (error); } static int sagetparams_common(struct cdev *dev, struct cam_periph *periph) { struct sa_softc *softc; u_int8_t write_protect; int comp_enabled, comp_supported, error; softc = (struct sa_softc *)periph->softc; if (softc->open_pending_mount) return (0); /* The control device may issue getparams() if there are no opens. */ if (SA_IS_CTRL(dev) && (softc->flags & SA_FLAG_OPEN) != 0) return (0); error = sagetparams(periph, SA_PARAM_ALL, &softc->media_blksize, &softc->media_density, &softc->media_numblks, &softc->buffer_mode, &write_protect, &softc->speed, &comp_supported, &comp_enabled, &softc->comp_algorithm, NULL, NULL, 0, 0); if (error) return (error); if (write_protect) softc->flags |= SA_FLAG_TAPE_WP; else softc->flags &= ~SA_FLAG_TAPE_WP; softc->flags &= ~SA_FLAG_COMPRESSION; if (comp_supported) { if (softc->saved_comp_algorithm == 0) softc->saved_comp_algorithm = softc->comp_algorithm; softc->flags |= SA_FLAG_COMP_SUPP; if (comp_enabled) softc->flags |= SA_FLAG_COMP_ENABLED; } else softc->flags |= SA_FLAG_COMP_UNSUPP; return (0); } #define PENDING_MOUNT_CHECK(softc, periph, dev) \ if (softc->open_pending_mount) { \ error = samount(periph, 0, dev); \ if (error) { \ break; \ } \ saprevent(periph, PR_PREVENT); \ softc->open_pending_mount = 0; \ } static int saioctl(struct cdev *dev, u_long cmd, caddr_t arg, int flag, struct thread *td) { struct cam_periph *periph; struct sa_softc *softc; scsi_space_code spaceop; int didlockperiph = 0; int mode; int error = 0; mode = SAMODE(dev); error = 0; /* shut up gcc */ spaceop = 0; /* shut up gcc */ periph = (struct cam_periph *)dev->si_drv1; if (periph == NULL) return (ENXIO); cam_periph_lock(periph); softc = (struct sa_softc *)periph->softc; /* * Check for control mode accesses. We allow MTIOCGET and * MTIOCERRSTAT (but need to be the only one open in order * to clear latched status), and MTSETBSIZE, MTSETDNSTY * and MTCOMP (but need to be the only one accessing this * device to run those). */ if (SA_IS_CTRL(dev)) { switch (cmd) { case MTIOCGETEOTMODEL: case MTIOCGET: case MTIOCEXTGET: case MTIOCPARAMGET: case MTIOCRBLIM: break; case MTIOCERRSTAT: /* * If the periph isn't already locked, lock it * so our MTIOCERRSTAT can reset latched error stats. * * If the periph is already locked, skip it because * we're just getting status and it'll be up to the * other thread that has this device open to do * an MTIOCERRSTAT that would clear latched status. */ if ((periph->flags & CAM_PERIPH_LOCKED) == 0) { error = cam_periph_hold(periph, PRIBIO|PCATCH); if (error != 0) { cam_periph_unlock(periph); return (error); } didlockperiph = 1; } break; case MTIOCTOP: { struct mtop *mt = (struct mtop *) arg; /* * Check to make sure it's an OP we can perform * with no media inserted. */ switch (mt->mt_op) { case MTSETBSIZ: case MTSETDNSTY: case MTCOMP: mt = NULL; /* FALLTHROUGH */ default: break; } if (mt != NULL) { break; } /* FALLTHROUGH */ } case MTIOCSETEOTMODEL: /* * We need to acquire the peripheral here rather * than at open time because we are sharing writable * access to data structures. */ error = cam_periph_hold(periph, PRIBIO|PCATCH); if (error != 0) { cam_periph_unlock(periph); return (error); } didlockperiph = 1; break; default: cam_periph_unlock(periph); return (EINVAL); } } /* * Find the device that the user is talking about */ switch (cmd) { case MTIOCGET: { struct mtget *g = (struct mtget *)arg; error = sagetparams_common(dev, periph); if (error) break; bzero(g, sizeof(struct mtget)); g->mt_type = MT_ISAR; if (softc->flags & SA_FLAG_COMP_UNSUPP) { g->mt_comp = MT_COMP_UNSUPP; g->mt_comp0 = MT_COMP_UNSUPP; g->mt_comp1 = MT_COMP_UNSUPP; g->mt_comp2 = MT_COMP_UNSUPP; g->mt_comp3 = MT_COMP_UNSUPP; } else { if ((softc->flags & SA_FLAG_COMP_ENABLED) == 0) { g->mt_comp = MT_COMP_DISABLED; } else { g->mt_comp = softc->comp_algorithm; } g->mt_comp0 = softc->comp_algorithm; g->mt_comp1 = softc->comp_algorithm; g->mt_comp2 = softc->comp_algorithm; g->mt_comp3 = softc->comp_algorithm; } g->mt_density = softc->media_density; g->mt_density0 = softc->media_density; g->mt_density1 = softc->media_density; g->mt_density2 = softc->media_density; g->mt_density3 = softc->media_density; g->mt_blksiz = softc->media_blksize; g->mt_blksiz0 = softc->media_blksize; g->mt_blksiz1 = softc->media_blksize; g->mt_blksiz2 = softc->media_blksize; g->mt_blksiz3 = softc->media_blksize; g->mt_fileno = softc->fileno; g->mt_blkno = softc->blkno; g->mt_dsreg = (short) softc->dsreg; /* * Yes, we know that this is likely to overflow */ if (softc->last_resid_was_io) { if ((g->mt_resid = (short) softc->last_io_resid) != 0) { if (SA_IS_CTRL(dev) == 0 || didlockperiph) { softc->last_io_resid = 0; } } } else { if ((g->mt_resid = (short)softc->last_ctl_resid) != 0) { if (SA_IS_CTRL(dev) == 0 || didlockperiph) { softc->last_ctl_resid = 0; } } } error = 0; break; } case MTIOCEXTGET: case MTIOCPARAMGET: { struct mtextget *g = (struct mtextget *)arg; char *tmpstr2; struct sbuf *sb; /* * Report drive status using an XML format. */ /* * XXX KDM will dropping the lock cause any problems here? */ cam_periph_unlock(periph); sb = sbuf_new(NULL, NULL, g->alloc_len, SBUF_FIXEDLEN); if (sb == NULL) { g->status = MT_EXT_GET_ERROR; snprintf(g->error_str, sizeof(g->error_str), "Unable to allocate %d bytes for status info", g->alloc_len); cam_periph_lock(periph); goto extget_bailout; } cam_periph_lock(periph); if (cmd == MTIOCEXTGET) error = saextget(dev, periph, sb, g); else error = saparamget(softc, sb); if (error != 0) goto extget_bailout; error = sbuf_finish(sb); if (error == ENOMEM) { g->status = MT_EXT_GET_NEED_MORE_SPACE; error = 0; } else if (error != 0) { g->status = MT_EXT_GET_ERROR; snprintf(g->error_str, sizeof(g->error_str), "Error %d returned from sbuf_finish()", error); } else g->status = MT_EXT_GET_OK; error = 0; tmpstr2 = sbuf_data(sb); g->fill_len = strlen(tmpstr2) + 1; cam_periph_unlock(periph); error = copyout(tmpstr2, g->status_xml, g->fill_len); cam_periph_lock(periph); extget_bailout: sbuf_delete(sb); break; } case MTIOCPARAMSET: { struct mtsetlist list; struct mtparamset *ps = (struct mtparamset *)arg; bzero(&list, sizeof(list)); list.num_params = 1; list.param_len = sizeof(*ps); list.params = ps; error = saparamsetlist(periph, &list, /*need_copy*/ 0); break; } case MTIOCSETLIST: { struct mtsetlist *list = (struct mtsetlist *)arg; error = saparamsetlist(periph, list, /*need_copy*/ 1); break; } case MTIOCERRSTAT: { struct scsi_tape_errors *sep = &((union mterrstat *)arg)->scsi_errstat; CAM_DEBUG(periph->path, CAM_DEBUG_TRACE, ("saioctl: MTIOCERRSTAT\n")); bzero(sep, sizeof(*sep)); sep->io_resid = softc->last_io_resid; bcopy((caddr_t) &softc->last_io_sense, sep->io_sense, sizeof (sep->io_sense)); bcopy((caddr_t) &softc->last_io_cdb, sep->io_cdb, sizeof (sep->io_cdb)); sep->ctl_resid = softc->last_ctl_resid; bcopy((caddr_t) &softc->last_ctl_sense, sep->ctl_sense, sizeof (sep->ctl_sense)); bcopy((caddr_t) &softc->last_ctl_cdb, sep->ctl_cdb, sizeof (sep->ctl_cdb)); if ((SA_IS_CTRL(dev) == 0 && !softc->open_pending_mount) || didlockperiph) bzero((caddr_t) &softc->errinfo, sizeof (softc->errinfo)); error = 0; break; } case MTIOCTOP: { struct mtop *mt; int count; PENDING_MOUNT_CHECK(softc, periph, dev); mt = (struct mtop *)arg; CAM_DEBUG(periph->path, CAM_DEBUG_TRACE, ("saioctl: op=0x%x count=0x%x\n", mt->mt_op, mt->mt_count)); count = mt->mt_count; switch (mt->mt_op) { case MTWEOF: /* write an end-of-file marker */ /* * We don't need to clear the SA_FLAG_TAPE_WRITTEN * flag because by keeping track of filemarks * we have last written we know whether or not * we need to write more when we close the device. */ error = sawritefilemarks(periph, count, FALSE, FALSE); break; case MTWEOFI: /* write an end-of-file marker without waiting */ error = sawritefilemarks(periph, count, FALSE, TRUE); break; case MTWSS: /* write a setmark */ error = sawritefilemarks(periph, count, TRUE, FALSE); break; case MTBSR: /* backward space record */ case MTFSR: /* forward space record */ case MTBSF: /* backward space file */ case MTFSF: /* forward space file */ case MTBSS: /* backward space setmark */ case MTFSS: /* forward space setmark */ case MTEOD: /* space to end of recorded medium */ { int nmarks; spaceop = SS_FILEMARKS; nmarks = softc->filemarks; error = sacheckeod(periph); if (error) { xpt_print(periph->path, "EOD check prior to spacing failed\n"); softc->flags |= SA_FLAG_EIO_PENDING; break; } nmarks -= softc->filemarks; switch(mt->mt_op) { case MTBSR: count = -count; /* FALLTHROUGH */ case MTFSR: spaceop = SS_BLOCKS; break; case MTBSF: count = -count; /* FALLTHROUGH */ case MTFSF: break; case MTBSS: count = -count; /* FALLTHROUGH */ case MTFSS: spaceop = SS_SETMARKS; break; case MTEOD: spaceop = SS_EOD; count = 0; nmarks = 0; break; default: error = EINVAL; break; } if (error) break; nmarks = softc->filemarks; /* * XXX: Why are we checking again? */ error = sacheckeod(periph); if (error) break; nmarks -= softc->filemarks; error = saspace(periph, count - nmarks, spaceop); /* * At this point, clear that we've written the tape * and that we've written any filemarks. We really * don't know what the applications wishes to do next- * the sacheckeod's will make sure we terminated the * tape correctly if we'd been writing, but the next * action the user application takes will set again * whether we need to write filemarks. */ softc->flags &= ~(SA_FLAG_TAPE_WRITTEN|SA_FLAG_TAPE_FROZEN); softc->filemarks = 0; break; } case MTREW: /* rewind */ PENDING_MOUNT_CHECK(softc, periph, dev); (void) sacheckeod(periph); error = sarewind(periph); /* see above */ softc->flags &= ~(SA_FLAG_TAPE_WRITTEN|SA_FLAG_TAPE_FROZEN); softc->flags &= ~SA_FLAG_ERR_PENDING; softc->filemarks = 0; break; case MTERASE: /* erase */ PENDING_MOUNT_CHECK(softc, periph, dev); error = saerase(periph, count); softc->flags &= ~(SA_FLAG_TAPE_WRITTEN|SA_FLAG_TAPE_FROZEN); softc->flags &= ~SA_FLAG_ERR_PENDING; break; case MTRETENS: /* re-tension tape */ PENDING_MOUNT_CHECK(softc, periph, dev); error = saretension(periph); softc->flags &= ~(SA_FLAG_TAPE_WRITTEN|SA_FLAG_TAPE_FROZEN); softc->flags &= ~SA_FLAG_ERR_PENDING; break; case MTOFFL: /* rewind and put the drive offline */ PENDING_MOUNT_CHECK(softc, periph, dev); (void) sacheckeod(periph); /* see above */ softc->flags &= ~SA_FLAG_TAPE_WRITTEN; softc->filemarks = 0; error = sarewind(periph); /* clear the frozen flag anyway */ softc->flags &= ~SA_FLAG_TAPE_FROZEN; /* * Be sure to allow media removal before ejecting. */ saprevent(periph, PR_ALLOW); if (error == 0) { error = saloadunload(periph, FALSE); if (error == 0) { softc->flags &= ~SA_FLAG_TAPE_MOUNTED; } } break; case MTLOAD: error = saloadunload(periph, TRUE); break; case MTNOP: /* no operation, sets status only */ case MTCACHE: /* enable controller cache */ case MTNOCACHE: /* disable controller cache */ error = 0; break; case MTSETBSIZ: /* Set block size for device */ PENDING_MOUNT_CHECK(softc, periph, dev); if ((softc->sili != 0) && (count != 0)) { xpt_print(periph->path, "Can't enter fixed " "block mode with SILI enabled\n"); error = EINVAL; break; } error = sasetparams(periph, SA_PARAM_BLOCKSIZE, count, 0, 0, 0); if (error == 0) { softc->last_media_blksize = softc->media_blksize; softc->media_blksize = count; if (count) { softc->flags |= SA_FLAG_FIXED; if (powerof2(count)) { softc->blk_shift = ffs(count) - 1; softc->blk_mask = count - 1; } else { softc->blk_mask = ~0; softc->blk_shift = 0; } /* * Make the user's desire 'persistent'. */ softc->quirks &= ~SA_QUIRK_VARIABLE; softc->quirks |= SA_QUIRK_FIXED; } else { softc->flags &= ~SA_FLAG_FIXED; if (softc->max_blk == 0) { softc->max_blk = ~0; } softc->blk_shift = 0; if (softc->blk_gran != 0) { softc->blk_mask = softc->blk_gran - 1; } else { softc->blk_mask = 0; } /* * Make the user's desire 'persistent'. */ softc->quirks |= SA_QUIRK_VARIABLE; softc->quirks &= ~SA_QUIRK_FIXED; } } break; case MTSETDNSTY: /* Set density for device and mode */ PENDING_MOUNT_CHECK(softc, periph, dev); if (count > UCHAR_MAX) { error = EINVAL; break; } else { error = sasetparams(periph, SA_PARAM_DENSITY, 0, count, 0, 0); } break; case MTCOMP: /* enable compression */ PENDING_MOUNT_CHECK(softc, periph, dev); /* * Some devices don't support compression, and * don't like it if you ask them for the * compression page. */ if ((softc->quirks & SA_QUIRK_NOCOMP) || (softc->flags & SA_FLAG_COMP_UNSUPP)) { error = ENODEV; break; } error = sasetparams(periph, SA_PARAM_COMPRESSION, 0, 0, count, SF_NO_PRINT); break; default: error = EINVAL; } break; } case MTIOCIEOT: case MTIOCEEOT: error = 0; break; case MTIOCRDSPOS: PENDING_MOUNT_CHECK(softc, periph, dev); error = sardpos(periph, 0, (u_int32_t *) arg); break; case MTIOCRDHPOS: PENDING_MOUNT_CHECK(softc, periph, dev); error = sardpos(periph, 1, (u_int32_t *) arg); break; case MTIOCSLOCATE: case MTIOCHLOCATE: { struct mtlocate locate_info; int hard; bzero(&locate_info, sizeof(locate_info)); locate_info.logical_id = *((uint32_t *)arg); if (cmd == MTIOCSLOCATE) hard = 0; else hard = 1; PENDING_MOUNT_CHECK(softc, periph, dev); error = sasetpos(periph, hard, &locate_info); break; } case MTIOCEXTLOCATE: PENDING_MOUNT_CHECK(softc, periph, dev); error = sasetpos(periph, /*hard*/ 0, (struct mtlocate *)arg); softc->flags &= ~(SA_FLAG_TAPE_WRITTEN|SA_FLAG_TAPE_FROZEN); softc->flags &= ~SA_FLAG_ERR_PENDING; softc->filemarks = 0; break; case MTIOCGETEOTMODEL: error = 0; if (softc->quirks & SA_QUIRK_1FM) mode = 1; else mode = 2; *((u_int32_t *) arg) = mode; break; case MTIOCSETEOTMODEL: error = 0; switch (*((u_int32_t *) arg)) { case 1: softc->quirks &= ~SA_QUIRK_2FM; softc->quirks |= SA_QUIRK_1FM; break; case 2: softc->quirks &= ~SA_QUIRK_1FM; softc->quirks |= SA_QUIRK_2FM; break; default: error = EINVAL; break; } break; case MTIOCRBLIM: { struct mtrblim *rblim; rblim = (struct mtrblim *)arg; rblim->granularity = softc->blk_gran; rblim->min_block_length = softc->min_blk; rblim->max_block_length = softc->max_blk; break; } default: error = cam_periph_ioctl(periph, cmd, arg, saerror); break; } /* * Check to see if we cleared a frozen state */ if (error == 0 && (softc->flags & SA_FLAG_TAPE_FROZEN)) { switch(cmd) { case MTIOCRDSPOS: case MTIOCRDHPOS: case MTIOCSLOCATE: case MTIOCHLOCATE: /* * XXX KDM look at this. */ softc->fileno = (daddr_t) -1; softc->blkno = (daddr_t) -1; softc->rep_blkno = (daddr_t) -1; softc->rep_fileno = (daddr_t) -1; softc->partition = (daddr_t) -1; softc->flags &= ~SA_FLAG_TAPE_FROZEN; xpt_print(periph->path, "tape state now unfrozen.\n"); break; default: break; } } if (didlockperiph) { cam_periph_unhold(periph); } cam_periph_unlock(periph); return (error); } static void sainit(void) { cam_status status; /* * Install a global async callback. */ status = xpt_register_async(AC_FOUND_DEVICE, saasync, NULL, NULL); if (status != CAM_REQ_CMP) { printf("sa: Failed to attach master async callback " "due to status 0x%x!\n", status); } } static void sadevgonecb(void *arg) { struct cam_periph *periph; struct mtx *mtx; struct sa_softc *softc; periph = (struct cam_periph *)arg; softc = (struct sa_softc *)periph->softc; mtx = cam_periph_mtx(periph); mtx_lock(mtx); softc->num_devs_to_destroy--; if (softc->num_devs_to_destroy == 0) { int i; /* * When we have gotten all of our callbacks, we will get * no more close calls from devfs. So if we have any * dangling opens, we need to release the reference held * for that particular context. */ for (i = 0; i < softc->open_count; i++) cam_periph_release_locked(periph); softc->open_count = 0; /* * Release the reference held for devfs, all of our * instances are gone now. */ cam_periph_release_locked(periph); } /* * We reference the lock directly here, instead of using * cam_periph_unlock(). The reason is that the final call to * cam_periph_release_locked() above could result in the periph * getting freed. If that is the case, dereferencing the periph * with a cam_periph_unlock() call would cause a page fault. */ mtx_unlock(mtx); } static void saoninvalidate(struct cam_periph *periph) { struct sa_softc *softc; softc = (struct sa_softc *)periph->softc; /* * De-register any async callbacks. */ xpt_register_async(0, saasync, periph, periph->path); softc->flags |= SA_FLAG_INVALID; /* * Return all queued I/O with ENXIO. * XXX Handle any transactions queued to the card * with XPT_ABORT_CCB. */ bioq_flush(&softc->bio_queue, NULL, ENXIO); softc->queue_count = 0; /* * Tell devfs that all of our devices have gone away, and ask for a * callback when it has cleaned up its state. */ destroy_dev_sched_cb(softc->devs.ctl_dev, sadevgonecb, periph); destroy_dev_sched_cb(softc->devs.r_dev, sadevgonecb, periph); destroy_dev_sched_cb(softc->devs.nr_dev, sadevgonecb, periph); destroy_dev_sched_cb(softc->devs.er_dev, sadevgonecb, periph); } static void sacleanup(struct cam_periph *periph) { struct sa_softc *softc; softc = (struct sa_softc *)periph->softc; cam_periph_unlock(periph); if ((softc->flags & SA_FLAG_SCTX_INIT) != 0 && sysctl_ctx_free(&softc->sysctl_ctx) != 0) xpt_print(periph->path, "can't remove sysctl context\n"); cam_periph_lock(periph); devstat_remove_entry(softc->device_stats); free(softc, M_SCSISA); } static void saasync(void *callback_arg, u_int32_t code, struct cam_path *path, void *arg) { struct cam_periph *periph; periph = (struct cam_periph *)callback_arg; switch (code) { case AC_FOUND_DEVICE: { struct ccb_getdev *cgd; cam_status status; cgd = (struct ccb_getdev *)arg; if (cgd == NULL) break; if (cgd->protocol != PROTO_SCSI) break; - + if (SID_QUAL(&cgd->inq_data) != SID_QUAL_LU_CONNECTED) + break; if (SID_TYPE(&cgd->inq_data) != T_SEQUENTIAL) break; /* * Allocate a peripheral instance for * this device and start the probe * process. */ status = cam_periph_alloc(saregister, saoninvalidate, sacleanup, sastart, "sa", CAM_PERIPH_BIO, path, saasync, AC_FOUND_DEVICE, cgd); if (status != CAM_REQ_CMP && status != CAM_REQ_INPROG) printf("saasync: Unable to probe new device " "due to status 0x%x\n", status); break; } default: cam_periph_async(periph, code, path, arg); break; } } static void sasetupdev(struct sa_softc *softc, struct cdev *dev) { dev->si_drv1 = softc->periph; dev->si_iosize_max = softc->maxio; dev->si_flags |= softc->si_flags; /* * Keep a count of how many non-alias devices we have created, * so we can make sure we clean them all up on shutdown. Aliases * are cleaned up when we destroy the device they're an alias for. */ if ((dev->si_flags & SI_ALIAS) == 0) softc->num_devs_to_destroy++; } static void sasysctlinit(void *context, int pending) { struct cam_periph *periph; struct sa_softc *softc; char tmpstr[80], tmpstr2[80]; periph = (struct cam_periph *)context; /* * If the periph is invalid, no need to setup the sysctls. */ if (periph->flags & CAM_PERIPH_INVALID) goto bailout; softc = (struct sa_softc *)periph->softc; snprintf(tmpstr, sizeof(tmpstr), "CAM SA unit %d", periph->unit_number); snprintf(tmpstr2, sizeof(tmpstr2), "%u", periph->unit_number); sysctl_ctx_init(&softc->sysctl_ctx); softc->flags |= SA_FLAG_SCTX_INIT; softc->sysctl_tree = SYSCTL_ADD_NODE(&softc->sysctl_ctx, SYSCTL_STATIC_CHILDREN(_kern_cam_sa), OID_AUTO, tmpstr2, CTLFLAG_RD, 0, tmpstr); if (softc->sysctl_tree == NULL) goto bailout; SYSCTL_ADD_INT(&softc->sysctl_ctx, SYSCTL_CHILDREN(softc->sysctl_tree), OID_AUTO, "allow_io_split", CTLFLAG_RDTUN | CTLFLAG_NOFETCH, &softc->allow_io_split, 0, "Allow Splitting I/O"); SYSCTL_ADD_INT(&softc->sysctl_ctx, SYSCTL_CHILDREN(softc->sysctl_tree), OID_AUTO, "maxio", CTLFLAG_RD, &softc->maxio, 0, "Maximum I/O size"); SYSCTL_ADD_INT(&softc->sysctl_ctx, SYSCTL_CHILDREN(softc->sysctl_tree), OID_AUTO, "cpi_maxio", CTLFLAG_RD, &softc->cpi_maxio, 0, "Maximum Controller I/O size"); bailout: /* * Release the reference that was held when this task was enqueued. */ cam_periph_release(periph); } static cam_status saregister(struct cam_periph *periph, void *arg) { struct sa_softc *softc; struct ccb_getdev *cgd; struct ccb_pathinq cpi; caddr_t match; char tmpstr[80]; cgd = (struct ccb_getdev *)arg; if (cgd == NULL) { printf("saregister: no getdev CCB, can't register device\n"); return (CAM_REQ_CMP_ERR); } softc = (struct sa_softc *) malloc(sizeof (*softc), M_SCSISA, M_NOWAIT | M_ZERO); if (softc == NULL) { printf("saregister: Unable to probe new device. " "Unable to allocate softc\n"); return (CAM_REQ_CMP_ERR); } softc->scsi_rev = SID_ANSI_REV(&cgd->inq_data); softc->state = SA_STATE_NORMAL; softc->fileno = (daddr_t) -1; softc->blkno = (daddr_t) -1; softc->rep_fileno = (daddr_t) -1; softc->rep_blkno = (daddr_t) -1; softc->partition = (daddr_t) -1; softc->bop = -1; softc->eop = -1; softc->bpew = -1; bioq_init(&softc->bio_queue); softc->periph = periph; periph->softc = softc; /* * See if this device has any quirks. */ match = cam_quirkmatch((caddr_t)&cgd->inq_data, (caddr_t)sa_quirk_table, sizeof(sa_quirk_table)/sizeof(*sa_quirk_table), sizeof(*sa_quirk_table), scsi_inquiry_match); if (match != NULL) { softc->quirks = ((struct sa_quirk_entry *)match)->quirks; softc->last_media_blksize = ((struct sa_quirk_entry *)match)->prefblk; } else softc->quirks = SA_QUIRK_NONE; /* * Long format data for READ POSITION was introduced in SSC, which * was after SCSI-2. (Roughly equivalent to SCSI-3.) If the drive * reports that it is SCSI-2 or older, it is unlikely to support * long position data, but it might. Some drives from that era * claim to be SCSI-2, but do support long position information. * So, instead of immediately disabling long position information * for SCSI-2 devices, we'll try one pass through sagetpos(), and * then disable long position information if we get an error. */ if (cgd->inq_data.version <= SCSI_REV_CCS) softc->quirks |= SA_QUIRK_NO_LONG_POS; if (cgd->inq_data.spc3_flags & SPC3_SID_PROTECT) { struct ccb_dev_advinfo cdai; struct scsi_vpd_extended_inquiry_data ext_inq; bzero(&ext_inq, sizeof(ext_inq)); xpt_setup_ccb(&cdai.ccb_h, periph->path, CAM_PRIORITY_NORMAL); cdai.ccb_h.func_code = XPT_DEV_ADVINFO; cdai.flags = CDAI_FLAG_NONE; cdai.buftype = CDAI_TYPE_EXT_INQ; cdai.bufsiz = sizeof(ext_inq); cdai.buf = (uint8_t *)&ext_inq; xpt_action((union ccb *)&cdai); if ((cdai.ccb_h.status & CAM_DEV_QFRZN) != 0) cam_release_devq(cdai.ccb_h.path, 0, 0, 0, FALSE); if ((cdai.ccb_h.status == CAM_REQ_CMP) && (ext_inq.flags1 & SVPD_EID_SA_SPT_LBP)) softc->flags |= SA_FLAG_PROTECT_SUPP; } bzero(&cpi, sizeof(cpi)); xpt_setup_ccb(&cpi.ccb_h, periph->path, CAM_PRIORITY_NORMAL); cpi.ccb_h.func_code = XPT_PATH_INQ; xpt_action((union ccb *)&cpi); /* * The SA driver supports a blocksize, but we don't know the * blocksize until we media is inserted. So, set a flag to * indicate that the blocksize is unavailable right now. */ cam_periph_unlock(periph); softc->device_stats = devstat_new_entry("sa", periph->unit_number, 0, DEVSTAT_BS_UNAVAILABLE, SID_TYPE(&cgd->inq_data) | XPORT_DEVSTAT_TYPE(cpi.transport), DEVSTAT_PRIORITY_TAPE); /* * Load the default value that is either compiled in, or loaded * in the global kern.cam.sa.allow_io_split tunable. */ softc->allow_io_split = sa_allow_io_split; /* * Load a per-instance tunable, if it exists. NOTE that this * tunable WILL GO AWAY in FreeBSD 11.0. */ snprintf(tmpstr, sizeof(tmpstr), "kern.cam.sa.%u.allow_io_split", periph->unit_number); TUNABLE_INT_FETCH(tmpstr, &softc->allow_io_split); /* * If maxio isn't set, we fall back to DFLTPHYS. Otherwise we take * the smaller of cpi.maxio or MAXPHYS. */ if (cpi.maxio == 0) softc->maxio = DFLTPHYS; else if (cpi.maxio > MAXPHYS) softc->maxio = MAXPHYS; else softc->maxio = cpi.maxio; /* * Record the controller's maximum I/O size so we can report it to * the user later. */ softc->cpi_maxio = cpi.maxio; /* * By default we tell physio that we do not want our I/O split. * The user needs to have a 1:1 mapping between the size of his * write to a tape character device and the size of the write * that actually goes down to the drive. */ if (softc->allow_io_split == 0) softc->si_flags = SI_NOSPLIT; else softc->si_flags = 0; TASK_INIT(&softc->sysctl_task, 0, sasysctlinit, periph); /* * If the SIM supports unmapped I/O, let physio know that we can * handle unmapped buffers. */ if (cpi.hba_misc & PIM_UNMAPPED) softc->si_flags |= SI_UNMAPPED; /* * Acquire a reference to the periph before we create the devfs * instances for it. We'll release this reference once the devfs * instances have been freed. */ if (cam_periph_acquire(periph) != CAM_REQ_CMP) { xpt_print(periph->path, "%s: lost periph during " "registration!\n", __func__); cam_periph_lock(periph); return (CAM_REQ_CMP_ERR); } softc->devs.ctl_dev = make_dev(&sa_cdevsw, SAMINOR(SA_CTLDEV, SA_ATYPE_R), UID_ROOT, GID_OPERATOR, 0660, "%s%d.ctl", periph->periph_name, periph->unit_number); sasetupdev(softc, softc->devs.ctl_dev); softc->devs.r_dev = make_dev(&sa_cdevsw, SAMINOR(SA_NOT_CTLDEV, SA_ATYPE_R), UID_ROOT, GID_OPERATOR, 0660, "%s%d", periph->periph_name, periph->unit_number); sasetupdev(softc, softc->devs.r_dev); softc->devs.nr_dev = make_dev(&sa_cdevsw, SAMINOR(SA_NOT_CTLDEV, SA_ATYPE_NR), UID_ROOT, GID_OPERATOR, 0660, "n%s%d", periph->periph_name, periph->unit_number); sasetupdev(softc, softc->devs.nr_dev); softc->devs.er_dev = make_dev(&sa_cdevsw, SAMINOR(SA_NOT_CTLDEV, SA_ATYPE_ER), UID_ROOT, GID_OPERATOR, 0660, "e%s%d", periph->periph_name, periph->unit_number); sasetupdev(softc, softc->devs.er_dev); cam_periph_lock(periph); softc->density_type_bits[0] = 0; softc->density_type_bits[1] = SRDS_MEDIA; softc->density_type_bits[2] = SRDS_MEDIUM_TYPE; softc->density_type_bits[3] = SRDS_MEDIUM_TYPE | SRDS_MEDIA; /* * Bump the peripheral refcount for the sysctl thread, in case we * get invalidated before the thread has a chance to run. */ cam_periph_acquire(periph); taskqueue_enqueue(taskqueue_thread, &softc->sysctl_task); /* * Add an async callback so that we get * notified if this device goes away. */ xpt_register_async(AC_LOST_DEVICE, saasync, periph, periph->path); xpt_announce_periph(periph, NULL); xpt_announce_quirks(periph, softc->quirks, SA_QUIRK_BIT_STRING); return (CAM_REQ_CMP); } static void sastart(struct cam_periph *periph, union ccb *start_ccb) { struct sa_softc *softc; softc = (struct sa_softc *)periph->softc; CAM_DEBUG(periph->path, CAM_DEBUG_TRACE, ("sastart\n")); switch (softc->state) { case SA_STATE_NORMAL: { /* Pull a buffer from the queue and get going on it */ struct bio *bp; /* * See if there is a buf with work for us to do.. */ bp = bioq_first(&softc->bio_queue); if (bp == NULL) { xpt_release_ccb(start_ccb); } else if ((softc->flags & SA_FLAG_ERR_PENDING) != 0) { struct bio *done_bp; again: softc->queue_count--; bioq_remove(&softc->bio_queue, bp); bp->bio_resid = bp->bio_bcount; done_bp = bp; if ((softc->flags & SA_FLAG_EOM_PENDING) != 0) { /* * We have two different behaviors for * writes when we hit either Early Warning * or the PEWZ (Programmable Early Warning * Zone). The default behavior is that * for all writes that are currently * queued after the write where we saw the * early warning, we will return the write * with the residual equal to the count. * i.e. tell the application that 0 bytes * were written. * * The alternate behavior, which is enabled * when eot_warn is set, is that in * addition to setting the residual equal * to the count, we will set the error * to ENOSPC. * * In either case, once queued writes are * cleared out, we clear the error flag * (see below) and the application is free to * attempt to write more. */ if (softc->eot_warn != 0) { bp->bio_flags |= BIO_ERROR; bp->bio_error = ENOSPC; } else bp->bio_error = 0; } else if ((softc->flags & SA_FLAG_EOF_PENDING) != 0) { /* * This can only happen if we're reading * in fixed length mode. In this case, * we dump the rest of the list the * same way. */ bp->bio_error = 0; if (bioq_first(&softc->bio_queue) != NULL) { biodone(done_bp); goto again; } } else if ((softc->flags & SA_FLAG_EIO_PENDING) != 0) { bp->bio_error = EIO; bp->bio_flags |= BIO_ERROR; } bp = bioq_first(&softc->bio_queue); /* * Only if we have no other buffers queued up * do we clear the pending error flag. */ if (bp == NULL) softc->flags &= ~SA_FLAG_ERR_PENDING; CAM_DEBUG(periph->path, CAM_DEBUG_INFO, ("sastart- ERR_PENDING now 0x%x, bp is %sNULL, " "%d more buffers queued up\n", (softc->flags & SA_FLAG_ERR_PENDING), (bp != NULL)? "not " : " ", softc->queue_count)); xpt_release_ccb(start_ccb); biodone(done_bp); } else { u_int32_t length; bioq_remove(&softc->bio_queue, bp); softc->queue_count--; length = bp->bio_bcount; if ((softc->flags & SA_FLAG_FIXED) != 0) { if (softc->blk_shift != 0) { length = length >> softc->blk_shift; } else if (softc->media_blksize != 0) { length = length / softc->media_blksize; } else { bp->bio_error = EIO; xpt_print(periph->path, "zero blocksize" " for FIXED length writes?\n"); biodone(bp); break; } #if 0 CAM_DEBUG(start_ccb->ccb_h.path, CAM_DEBUG_INFO, ("issuing a %d fixed record %s\n", length, (bp->bio_cmd == BIO_READ)? "read" : "write")); #endif } else { #if 0 CAM_DEBUG(start_ccb->ccb_h.path, CAM_DEBUG_INFO, ("issuing a %d variable byte %s\n", length, (bp->bio_cmd == BIO_READ)? "read" : "write")); #endif } devstat_start_transaction_bio(softc->device_stats, bp); /* * Some people have theorized that we should * suppress illegal length indication if we are * running in variable block mode so that we don't * have to request sense every time our requested * block size is larger than the written block. * The residual information from the ccb allows * us to identify this situation anyway. The only * problem with this is that we will not get * information about blocks that are larger than * our read buffer unless we set the block size * in the mode page to something other than 0. * * I believe that this is a non-issue. If user apps * don't adjust their read size to match our record * size, that's just life. Anyway, the typical usage * would be to issue, e.g., 64KB reads and occasionally * have to do deal with 512 byte or 1KB intermediate * records. * * That said, though, we now support setting the * SILI bit on reads, and we set the blocksize to 4 * bytes when we do that. This gives us * compatibility with software that wants this, * although the only real difference between that * and not setting the SILI bit on reads is that we * won't get a check condition on reads where our * request size is larger than the block on tape. * That probably only makes a real difference in * non-packetized SCSI, where you have to go back * to the drive to request sense and thus incur * more latency. */ softc->dsreg = (bp->bio_cmd == BIO_READ)? MTIO_DSREG_RD : MTIO_DSREG_WR; scsi_sa_read_write(&start_ccb->csio, 0, sadone, MSG_SIMPLE_Q_TAG, (bp->bio_cmd == BIO_READ ? SCSI_RW_READ : SCSI_RW_WRITE) | ((bp->bio_flags & BIO_UNMAPPED) != 0 ? SCSI_RW_BIO : 0), softc->sili, (softc->flags & SA_FLAG_FIXED) != 0, length, (bp->bio_flags & BIO_UNMAPPED) != 0 ? (void *)bp : bp->bio_data, bp->bio_bcount, SSD_FULL_SIZE, IO_TIMEOUT); start_ccb->ccb_h.ccb_pflags &= ~SA_POSITION_UPDATED; start_ccb->ccb_h.ccb_bp = bp; bp = bioq_first(&softc->bio_queue); xpt_action(start_ccb); } if (bp != NULL) { /* Have more work to do, so ensure we stay scheduled */ xpt_schedule(periph, CAM_PRIORITY_NORMAL); } break; } case SA_STATE_ABNORMAL: default: panic("state 0x%x in sastart", softc->state); break; } } static void sadone(struct cam_periph *periph, union ccb *done_ccb) { struct sa_softc *softc; struct ccb_scsiio *csio; struct bio *bp; int error; softc = (struct sa_softc *)periph->softc; csio = &done_ccb->csio; softc->dsreg = MTIO_DSREG_REST; bp = (struct bio *)done_ccb->ccb_h.ccb_bp; error = 0; if ((done_ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) { if ((error = saerror(done_ccb, 0, 0)) == ERESTART) { /* * A retry was scheduled, so just return. */ return; } } if (error == EIO) { /* * Catastrophic error. Mark the tape as frozen * (we no longer know tape position). * * Return all queued I/O with EIO, and unfreeze * our queue so that future transactions that * attempt to fix this problem can get to the * device. * */ softc->flags |= SA_FLAG_TAPE_FROZEN; bioq_flush(&softc->bio_queue, NULL, EIO); } if (error != 0) { bp->bio_resid = bp->bio_bcount; bp->bio_error = error; bp->bio_flags |= BIO_ERROR; /* * In the error case, position is updated in saerror. */ } else { bp->bio_resid = csio->resid; bp->bio_error = 0; if (csio->resid != 0) { bp->bio_flags |= BIO_ERROR; } if (bp->bio_cmd == BIO_WRITE) { softc->flags |= SA_FLAG_TAPE_WRITTEN; softc->filemarks = 0; } if (!(csio->ccb_h.ccb_pflags & SA_POSITION_UPDATED) && (softc->blkno != (daddr_t) -1)) { if ((softc->flags & SA_FLAG_FIXED) != 0) { u_int32_t l; if (softc->blk_shift != 0) { l = bp->bio_bcount >> softc->blk_shift; } else { l = bp->bio_bcount / softc->media_blksize; } softc->blkno += (daddr_t) l; } else { softc->blkno++; } } } /* * If we had an error (immediate or pending), * release the device queue now. */ if (error || (softc->flags & SA_FLAG_ERR_PENDING)) cam_release_devq(done_ccb->ccb_h.path, 0, 0, 0, 0); if (error || bp->bio_resid) { CAM_DEBUG(periph->path, CAM_DEBUG_INFO, ("error %d resid %ld count %ld\n", error, bp->bio_resid, bp->bio_bcount)); } biofinish(bp, softc->device_stats, 0); xpt_release_ccb(done_ccb); } /* * Mount the tape (make sure it's ready for I/O). */ static int samount(struct cam_periph *periph, int oflags, struct cdev *dev) { struct sa_softc *softc; union ccb *ccb; int error; /* * oflags can be checked for 'kind' of open (read-only check) - later * dev can be checked for a control-mode or compression open - later */ UNUSED_PARAMETER(oflags); UNUSED_PARAMETER(dev); softc = (struct sa_softc *)periph->softc; /* * This should determine if something has happend since the last * open/mount that would invalidate the mount. We do *not* want * to retry this command- we just want the status. But we only * do this if we're mounted already- if we're not mounted, * we don't care about the unit read state and can instead use * this opportunity to attempt to reserve the tape unit. */ if (softc->flags & SA_FLAG_TAPE_MOUNTED) { ccb = cam_periph_getccb(periph, 1); scsi_test_unit_ready(&ccb->csio, 0, sadone, MSG_SIMPLE_Q_TAG, SSD_FULL_SIZE, IO_TIMEOUT); error = cam_periph_runccb(ccb, saerror, 0, SF_NO_PRINT, softc->device_stats); if (error == ENXIO) { softc->flags &= ~SA_FLAG_TAPE_MOUNTED; scsi_test_unit_ready(&ccb->csio, 0, sadone, MSG_SIMPLE_Q_TAG, SSD_FULL_SIZE, IO_TIMEOUT); error = cam_periph_runccb(ccb, saerror, 0, SF_NO_PRINT, softc->device_stats); } else if (error) { /* * We don't need to freeze the tape because we * will now attempt to rewind/load it. */ softc->flags &= ~SA_FLAG_TAPE_MOUNTED; if (CAM_DEBUGGED(periph->path, CAM_DEBUG_INFO)) { xpt_print(periph->path, "error %d on TUR in samount\n", error); } } } else { error = sareservereleaseunit(periph, TRUE); if (error) { return (error); } ccb = cam_periph_getccb(periph, 1); scsi_test_unit_ready(&ccb->csio, 0, sadone, MSG_SIMPLE_Q_TAG, SSD_FULL_SIZE, IO_TIMEOUT); error = cam_periph_runccb(ccb, saerror, 0, SF_NO_PRINT, softc->device_stats); } if ((softc->flags & SA_FLAG_TAPE_MOUNTED) == 0) { struct scsi_read_block_limits_data *rblim = NULL; int comp_enabled, comp_supported; u_int8_t write_protect, guessing = 0; /* * Clear out old state. */ softc->flags &= ~(SA_FLAG_TAPE_WP|SA_FLAG_TAPE_WRITTEN| SA_FLAG_ERR_PENDING|SA_FLAG_COMPRESSION); softc->filemarks = 0; /* * *Very* first off, make sure we're loaded to BOT. */ scsi_load_unload(&ccb->csio, 2, sadone, MSG_SIMPLE_Q_TAG, FALSE, FALSE, FALSE, 1, SSD_FULL_SIZE, REWIND_TIMEOUT); error = cam_periph_runccb(ccb, saerror, 0, SF_NO_PRINT, softc->device_stats); /* * In case this doesn't work, do a REWIND instead */ if (error) { scsi_rewind(&ccb->csio, 2, sadone, MSG_SIMPLE_Q_TAG, FALSE, SSD_FULL_SIZE, REWIND_TIMEOUT); error = cam_periph_runccb(ccb, saerror, 0, SF_NO_PRINT, softc->device_stats); } if (error) { xpt_release_ccb(ccb); goto exit; } /* * Do a dummy test read to force access to the * media so that the drive will really know what's * there. We actually don't really care what the * blocksize on tape is and don't expect to really * read a full record. */ rblim = (struct scsi_read_block_limits_data *) malloc(8192, M_SCSISA, M_NOWAIT); if (rblim == NULL) { xpt_print(periph->path, "no memory for test read\n"); xpt_release_ccb(ccb); error = ENOMEM; goto exit; } if ((softc->quirks & SA_QUIRK_NODREAD) == 0) { scsi_sa_read_write(&ccb->csio, 0, sadone, MSG_SIMPLE_Q_TAG, 1, FALSE, 0, 8192, (void *) rblim, 8192, SSD_FULL_SIZE, IO_TIMEOUT); (void) cam_periph_runccb(ccb, saerror, 0, SF_NO_PRINT, softc->device_stats); scsi_rewind(&ccb->csio, 1, sadone, MSG_SIMPLE_Q_TAG, FALSE, SSD_FULL_SIZE, REWIND_TIMEOUT); error = cam_periph_runccb(ccb, saerror, CAM_RETRY_SELTO, SF_NO_PRINT | SF_RETRY_UA, softc->device_stats); if (error) { xpt_print(periph->path, "unable to rewind after test read\n"); xpt_release_ccb(ccb); goto exit; } } /* * Next off, determine block limits. */ scsi_read_block_limits(&ccb->csio, 5, sadone, MSG_SIMPLE_Q_TAG, rblim, SSD_FULL_SIZE, SCSIOP_TIMEOUT); error = cam_periph_runccb(ccb, saerror, CAM_RETRY_SELTO, SF_NO_PRINT | SF_RETRY_UA, softc->device_stats); xpt_release_ccb(ccb); if (error != 0) { /* * If it's less than SCSI-2, READ BLOCK LIMITS is not * a MANDATORY command. Anyway- it doesn't matter- * we can proceed anyway. */ softc->blk_gran = 0; softc->max_blk = ~0; softc->min_blk = 0; } else { if (softc->scsi_rev >= SCSI_REV_SPC) { softc->blk_gran = RBL_GRAN(rblim); } else { softc->blk_gran = 0; } /* * We take max_blk == min_blk to mean a default to * fixed mode- but note that whatever we get out of * sagetparams below will actually determine whether * we are actually *in* fixed mode. */ softc->max_blk = scsi_3btoul(rblim->maximum); softc->min_blk = scsi_2btoul(rblim->minimum); } /* * Next, perform a mode sense to determine * current density, blocksize, compression etc. */ error = sagetparams(periph, SA_PARAM_ALL, &softc->media_blksize, &softc->media_density, &softc->media_numblks, &softc->buffer_mode, &write_protect, &softc->speed, &comp_supported, &comp_enabled, &softc->comp_algorithm, NULL, NULL, 0, 0); if (error != 0) { /* * We could work a little harder here. We could * adjust our attempts to get information. It * might be an ancient tape drive. If someone * nudges us, we'll do that. */ goto exit; } /* * If no quirk has determined that this is a device that is * preferred to be in fixed or variable mode, now is the time * to find out. */ if ((softc->quirks & (SA_QUIRK_FIXED|SA_QUIRK_VARIABLE)) == 0) { guessing = 1; /* * This could be expensive to find out. Luckily we * only need to do this once. If we start out in * 'default' mode, try and set ourselves to one * of the densities that would determine a wad * of other stuff. Go from highest to lowest. */ if (softc->media_density == SCSI_DEFAULT_DENSITY) { int i; static u_int8_t ctry[] = { SCSI_DENSITY_HALFINCH_PE, SCSI_DENSITY_HALFINCH_6250C, SCSI_DENSITY_HALFINCH_6250, SCSI_DENSITY_HALFINCH_1600, SCSI_DENSITY_HALFINCH_800, SCSI_DENSITY_QIC_4GB, SCSI_DENSITY_QIC_2GB, SCSI_DENSITY_QIC_525_320, SCSI_DENSITY_QIC_150, SCSI_DENSITY_QIC_120, SCSI_DENSITY_QIC_24, SCSI_DENSITY_QIC_11_9TRK, SCSI_DENSITY_QIC_11_4TRK, SCSI_DENSITY_QIC_1320, SCSI_DENSITY_QIC_3080, 0 }; for (i = 0; ctry[i]; i++) { error = sasetparams(periph, SA_PARAM_DENSITY, 0, ctry[i], 0, SF_NO_PRINT); if (error == 0) { softc->media_density = ctry[i]; break; } } } switch (softc->media_density) { case SCSI_DENSITY_QIC_11_4TRK: case SCSI_DENSITY_QIC_11_9TRK: case SCSI_DENSITY_QIC_24: case SCSI_DENSITY_QIC_120: case SCSI_DENSITY_QIC_150: case SCSI_DENSITY_QIC_525_320: case SCSI_DENSITY_QIC_1320: case SCSI_DENSITY_QIC_3080: softc->quirks &= ~SA_QUIRK_2FM; softc->quirks |= SA_QUIRK_FIXED|SA_QUIRK_1FM; softc->last_media_blksize = 512; break; case SCSI_DENSITY_QIC_4GB: case SCSI_DENSITY_QIC_2GB: softc->quirks &= ~SA_QUIRK_2FM; softc->quirks |= SA_QUIRK_FIXED|SA_QUIRK_1FM; softc->last_media_blksize = 1024; break; default: softc->last_media_blksize = softc->media_blksize; softc->quirks |= SA_QUIRK_VARIABLE; break; } } /* * If no quirk has determined that this is a device that needs * to have 2 Filemarks at EOD, now is the time to find out. */ if ((softc->quirks & SA_QUIRK_2FM) == 0) { switch (softc->media_density) { case SCSI_DENSITY_HALFINCH_800: case SCSI_DENSITY_HALFINCH_1600: case SCSI_DENSITY_HALFINCH_6250: case SCSI_DENSITY_HALFINCH_6250C: case SCSI_DENSITY_HALFINCH_PE: softc->quirks &= ~SA_QUIRK_1FM; softc->quirks |= SA_QUIRK_2FM; break; default: break; } } /* * Now validate that some info we got makes sense. */ if ((softc->max_blk < softc->media_blksize) || (softc->min_blk > softc->media_blksize && softc->media_blksize)) { xpt_print(periph->path, "BLOCK LIMITS (%d..%d) could not match current " "block settings (%d)- adjusting\n", softc->min_blk, softc->max_blk, softc->media_blksize); softc->max_blk = softc->min_blk = softc->media_blksize; } /* * Now put ourselves into the right frame of mind based * upon quirks... */ tryagain: /* * If we want to be in FIXED mode and our current blocksize * is not equal to our last blocksize (if nonzero), try and * set ourselves to this last blocksize (as the 'preferred' * block size). The initial quirkmatch at registry sets the * initial 'last' blocksize. If, for whatever reason, this * 'last' blocksize is zero, set the blocksize to 512, * or min_blk if that's larger. */ if ((softc->quirks & SA_QUIRK_FIXED) && (softc->quirks & SA_QUIRK_NO_MODESEL) == 0 && (softc->media_blksize != softc->last_media_blksize)) { softc->media_blksize = softc->last_media_blksize; if (softc->media_blksize == 0) { softc->media_blksize = 512; if (softc->media_blksize < softc->min_blk) { softc->media_blksize = softc->min_blk; } } error = sasetparams(periph, SA_PARAM_BLOCKSIZE, softc->media_blksize, 0, 0, SF_NO_PRINT); if (error) { xpt_print(periph->path, "unable to set fixed blocksize to %d\n", softc->media_blksize); goto exit; } } if ((softc->quirks & SA_QUIRK_VARIABLE) && (softc->media_blksize != 0)) { softc->last_media_blksize = softc->media_blksize; softc->media_blksize = 0; error = sasetparams(periph, SA_PARAM_BLOCKSIZE, 0, 0, 0, SF_NO_PRINT); if (error) { /* * If this fails and we were guessing, just * assume that we got it wrong and go try * fixed block mode. Don't even check against * density code at this point. */ if (guessing) { softc->quirks &= ~SA_QUIRK_VARIABLE; softc->quirks |= SA_QUIRK_FIXED; if (softc->last_media_blksize == 0) softc->last_media_blksize = 512; goto tryagain; } xpt_print(periph->path, "unable to set variable blocksize\n"); goto exit; } } /* * Now that we have the current block size, * set up some parameters for sastart's usage. */ if (softc->media_blksize) { softc->flags |= SA_FLAG_FIXED; if (powerof2(softc->media_blksize)) { softc->blk_shift = ffs(softc->media_blksize) - 1; softc->blk_mask = softc->media_blksize - 1; } else { softc->blk_mask = ~0; softc->blk_shift = 0; } } else { /* * The SCSI-3 spec allows 0 to mean "unspecified". * The SCSI-1 spec allows 0 to mean 'infinite'. * * Either works here. */ if (softc->max_blk == 0) { softc->max_blk = ~0; } softc->blk_shift = 0; if (softc->blk_gran != 0) { softc->blk_mask = softc->blk_gran - 1; } else { softc->blk_mask = 0; } } if (write_protect) softc->flags |= SA_FLAG_TAPE_WP; if (comp_supported) { if (softc->saved_comp_algorithm == 0) softc->saved_comp_algorithm = softc->comp_algorithm; softc->flags |= SA_FLAG_COMP_SUPP; if (comp_enabled) softc->flags |= SA_FLAG_COMP_ENABLED; } else softc->flags |= SA_FLAG_COMP_UNSUPP; if ((softc->buffer_mode == SMH_SA_BUF_MODE_NOBUF) && (softc->quirks & SA_QUIRK_NO_MODESEL) == 0) { error = sasetparams(periph, SA_PARAM_BUFF_MODE, 0, 0, 0, SF_NO_PRINT); if (error == 0) { softc->buffer_mode = SMH_SA_BUF_MODE_SIBUF; } else { xpt_print(periph->path, "unable to set buffered mode\n"); } error = 0; /* not an error */ } if (error == 0) { softc->flags |= SA_FLAG_TAPE_MOUNTED; } exit: if (rblim != NULL) free(rblim, M_SCSISA); if (error != 0) { softc->dsreg = MTIO_DSREG_NIL; } else { softc->fileno = softc->blkno = 0; softc->rep_fileno = softc->rep_blkno = -1; softc->partition = 0; softc->dsreg = MTIO_DSREG_REST; } #ifdef SA_1FM_AT_EOD if ((softc->quirks & SA_QUIRK_2FM) == 0) softc->quirks |= SA_QUIRK_1FM; #else if ((softc->quirks & SA_QUIRK_1FM) == 0) softc->quirks |= SA_QUIRK_2FM; #endif } else xpt_release_ccb(ccb); /* * If we return an error, we're not mounted any more, * so release any device reservation. */ if (error != 0) { (void) sareservereleaseunit(periph, FALSE); } else { /* * Clear I/O residual. */ softc->last_io_resid = 0; softc->last_ctl_resid = 0; } return (error); } /* * How many filemarks do we need to write if we were to terminate the * tape session right now? Note that this can be a negative number */ static int samarkswanted(struct cam_periph *periph) { int markswanted; struct sa_softc *softc; softc = (struct sa_softc *)periph->softc; markswanted = 0; if ((softc->flags & SA_FLAG_TAPE_WRITTEN) != 0) { markswanted++; if (softc->quirks & SA_QUIRK_2FM) markswanted++; } markswanted -= softc->filemarks; return (markswanted); } static int sacheckeod(struct cam_periph *periph) { int error; int markswanted; markswanted = samarkswanted(periph); if (markswanted > 0) { error = sawritefilemarks(periph, markswanted, FALSE, FALSE); } else { error = 0; } return (error); } static int saerror(union ccb *ccb, u_int32_t cflgs, u_int32_t sflgs) { static const char *toobig = "%d-byte tape record bigger than supplied buffer\n"; struct cam_periph *periph; struct sa_softc *softc; struct ccb_scsiio *csio; struct scsi_sense_data *sense; uint64_t resid = 0; int64_t info = 0; cam_status status; int error_code, sense_key, asc, ascq, error, aqvalid, stream_valid; int sense_len; uint8_t stream_bits; periph = xpt_path_periph(ccb->ccb_h.path); softc = (struct sa_softc *)periph->softc; csio = &ccb->csio; sense = &csio->sense_data; sense_len = csio->sense_len - csio->sense_resid; scsi_extract_sense_len(sense, sense_len, &error_code, &sense_key, &asc, &ascq, /*show_errors*/ 1); if (asc != -1 && ascq != -1) aqvalid = 1; else aqvalid = 0; if (scsi_get_stream_info(sense, sense_len, NULL, &stream_bits) == 0) stream_valid = 1; else stream_valid = 0; error = 0; status = csio->ccb_h.status & CAM_STATUS_MASK; /* * Calculate/latch up, any residuals... We do this in a funny 2-step * so we can print stuff here if we have CAM_DEBUG enabled for this * unit. */ if (status == CAM_SCSI_STATUS_ERROR) { if (scsi_get_sense_info(sense, sense_len, SSD_DESC_INFO, &resid, &info) == 0) { if ((softc->flags & SA_FLAG_FIXED) != 0) resid *= softc->media_blksize; } else { resid = csio->dxfer_len; info = resid; if ((softc->flags & SA_FLAG_FIXED) != 0) { if (softc->media_blksize) info /= softc->media_blksize; } } if (csio->cdb_io.cdb_bytes[0] == SA_READ || csio->cdb_io.cdb_bytes[0] == SA_WRITE) { bcopy((caddr_t) sense, (caddr_t) &softc->last_io_sense, sizeof (struct scsi_sense_data)); bcopy(csio->cdb_io.cdb_bytes, softc->last_io_cdb, (int) csio->cdb_len); softc->last_io_resid = resid; softc->last_resid_was_io = 1; } else { bcopy((caddr_t) sense, (caddr_t) &softc->last_ctl_sense, sizeof (struct scsi_sense_data)); bcopy(csio->cdb_io.cdb_bytes, softc->last_ctl_cdb, (int) csio->cdb_len); softc->last_ctl_resid = resid; softc->last_resid_was_io = 0; } CAM_DEBUG(periph->path, CAM_DEBUG_INFO, ("CDB[0]=0x%x Key 0x%x " "ASC/ASCQ 0x%x/0x%x CAM STATUS 0x%x flags 0x%x resid %jd " "dxfer_len %d\n", csio->cdb_io.cdb_bytes[0] & 0xff, sense_key, asc, ascq, status, (stream_valid) ? stream_bits : 0, (intmax_t)resid, csio->dxfer_len)); } else { CAM_DEBUG(periph->path, CAM_DEBUG_INFO, ("Cam Status 0x%x\n", status)); } switch (status) { case CAM_REQ_CMP: return (0); case CAM_SCSI_STATUS_ERROR: /* * If a read/write command, we handle it here. */ if (csio->cdb_io.cdb_bytes[0] == SA_READ || csio->cdb_io.cdb_bytes[0] == SA_WRITE) { break; } /* * If this was just EOM/EOP, Filemark, Setmark or ILI detected * on a non read/write command, we assume it's not an error * and propagate the residule and return. */ if ((aqvalid && asc == 0 && ascq > 0 && ascq <= 5) || (aqvalid == 0 && sense_key == SSD_KEY_NO_SENSE)) { csio->resid = resid; QFRLS(ccb); return (0); } /* * Otherwise, we let the common code handle this. */ return (cam_periph_error(ccb, cflgs, sflgs, &softc->saved_ccb)); /* * XXX: To Be Fixed * We cannot depend upon CAM honoring retry counts for these. */ case CAM_SCSI_BUS_RESET: case CAM_BDR_SENT: if (ccb->ccb_h.retry_count <= 0) { return (EIO); } /* FALLTHROUGH */ default: return (cam_periph_error(ccb, cflgs, sflgs, &softc->saved_ccb)); } /* * Handle filemark, end of tape, mismatched record sizes.... * From this point out, we're only handling read/write cases. * Handle writes && reads differently. */ if (csio->cdb_io.cdb_bytes[0] == SA_WRITE) { if (sense_key == SSD_KEY_VOLUME_OVERFLOW) { csio->resid = resid; error = ENOSPC; } else if ((stream_valid != 0) && (stream_bits & SSD_EOM)) { softc->flags |= SA_FLAG_EOM_PENDING; /* * Grotesque as it seems, the few times * I've actually seen a non-zero resid, * the tape drive actually lied and had * written all the data!. */ csio->resid = 0; } } else { csio->resid = resid; if (sense_key == SSD_KEY_BLANK_CHECK) { if (softc->quirks & SA_QUIRK_1FM) { error = 0; softc->flags |= SA_FLAG_EOM_PENDING; } else { error = EIO; } } else if ((stream_valid != 0) && (stream_bits & SSD_FILEMARK)){ if (softc->flags & SA_FLAG_FIXED) { error = -1; softc->flags |= SA_FLAG_EOF_PENDING; } /* * Unconditionally, if we detected a filemark on a read, * mark that we've run moved a file ahead. */ if (softc->fileno != (daddr_t) -1) { softc->fileno++; softc->blkno = 0; csio->ccb_h.ccb_pflags |= SA_POSITION_UPDATED; } } } /* * Incorrect Length usually applies to read, but can apply to writes. */ if (error == 0 && (stream_valid != 0) && (stream_bits & SSD_ILI)) { if (info < 0) { xpt_print(csio->ccb_h.path, toobig, csio->dxfer_len - info); csio->resid = csio->dxfer_len; error = EIO; } else { csio->resid = resid; if (softc->flags & SA_FLAG_FIXED) { softc->flags |= SA_FLAG_EIO_PENDING; } /* * Bump the block number if we hadn't seen a filemark. * Do this independent of errors (we've moved anyway). */ if ((stream_valid == 0) || (stream_bits & SSD_FILEMARK) == 0) { if (softc->blkno != (daddr_t) -1) { softc->blkno++; csio->ccb_h.ccb_pflags |= SA_POSITION_UPDATED; } } } } if (error <= 0) { /* * Unfreeze the queue if frozen as we're not returning anything * to our waiters that would indicate an I/O error has occurred * (yet). */ QFRLS(ccb); error = 0; } return (error); } static int sagetparams(struct cam_periph *periph, sa_params params_to_get, u_int32_t *blocksize, u_int8_t *density, u_int32_t *numblocks, int *buff_mode, u_int8_t *write_protect, u_int8_t *speed, int *comp_supported, int *comp_enabled, u_int32_t *comp_algorithm, sa_comp_t *tcs, struct scsi_control_data_prot_subpage *prot_page, int dp_size, int prot_changeable) { union ccb *ccb; void *mode_buffer; struct scsi_mode_header_6 *mode_hdr; struct scsi_mode_blk_desc *mode_blk; int mode_buffer_len; struct sa_softc *softc; u_int8_t cpage; int error; cam_status status; softc = (struct sa_softc *)periph->softc; ccb = cam_periph_getccb(periph, 1); if (softc->quirks & SA_QUIRK_NO_CPAGE) cpage = SA_DEVICE_CONFIGURATION_PAGE; else cpage = SA_DATA_COMPRESSION_PAGE; retry: mode_buffer_len = sizeof(*mode_hdr) + sizeof(*mode_blk); if (params_to_get & SA_PARAM_COMPRESSION) { if (softc->quirks & SA_QUIRK_NOCOMP) { *comp_supported = FALSE; params_to_get &= ~SA_PARAM_COMPRESSION; } else mode_buffer_len += sizeof (sa_comp_t); } /* XXX Fix M_NOWAIT */ mode_buffer = malloc(mode_buffer_len, M_SCSISA, M_NOWAIT | M_ZERO); if (mode_buffer == NULL) { xpt_release_ccb(ccb); return (ENOMEM); } mode_hdr = (struct scsi_mode_header_6 *)mode_buffer; mode_blk = (struct scsi_mode_blk_desc *)&mode_hdr[1]; /* it is safe to retry this */ scsi_mode_sense(&ccb->csio, 5, sadone, MSG_SIMPLE_Q_TAG, FALSE, SMS_PAGE_CTRL_CURRENT, (params_to_get & SA_PARAM_COMPRESSION) ? cpage : SMS_VENDOR_SPECIFIC_PAGE, mode_buffer, mode_buffer_len, SSD_FULL_SIZE, SCSIOP_TIMEOUT); error = cam_periph_runccb(ccb, saerror, 0, SF_NO_PRINT, softc->device_stats); status = ccb->ccb_h.status & CAM_STATUS_MASK; if (error == EINVAL && (params_to_get & SA_PARAM_COMPRESSION) != 0) { /* * Hmm. Let's see if we can try another page... * If we've already done that, give up on compression * for this device and remember this for the future * and attempt the request without asking for compression * info. */ if (cpage == SA_DATA_COMPRESSION_PAGE) { cpage = SA_DEVICE_CONFIGURATION_PAGE; goto retry; } softc->quirks |= SA_QUIRK_NOCOMP; free(mode_buffer, M_SCSISA); goto retry; } else if (status == CAM_SCSI_STATUS_ERROR) { /* Tell the user about the fatal error. */ scsi_sense_print(&ccb->csio); goto sagetparamsexit; } /* * If the user only wants the compression information, and * the device doesn't send back the block descriptor, it's * no big deal. If the user wants more than just * compression, though, and the device doesn't pass back the * block descriptor, we need to send another mode sense to * get the block descriptor. */ if ((mode_hdr->blk_desc_len == 0) && (params_to_get & SA_PARAM_COMPRESSION) && (params_to_get & ~(SA_PARAM_COMPRESSION))) { /* * Decrease the mode buffer length by the size of * the compression page, to make sure the data * there doesn't get overwritten. */ mode_buffer_len -= sizeof (sa_comp_t); /* * Now move the compression page that we presumably * got back down the memory chunk a little bit so * it doesn't get spammed. */ bcopy(&mode_hdr[0], &mode_hdr[1], sizeof (sa_comp_t)); bzero(&mode_hdr[0], sizeof (mode_hdr[0])); /* * Now, we issue another mode sense and just ask * for the block descriptor, etc. */ scsi_mode_sense(&ccb->csio, 2, sadone, MSG_SIMPLE_Q_TAG, FALSE, SMS_PAGE_CTRL_CURRENT, SMS_VENDOR_SPECIFIC_PAGE, mode_buffer, mode_buffer_len, SSD_FULL_SIZE, SCSIOP_TIMEOUT); error = cam_periph_runccb(ccb, saerror, 0, SF_NO_PRINT, softc->device_stats); if (error != 0) goto sagetparamsexit; } if (params_to_get & SA_PARAM_BLOCKSIZE) *blocksize = scsi_3btoul(mode_blk->blklen); if (params_to_get & SA_PARAM_NUMBLOCKS) *numblocks = scsi_3btoul(mode_blk->nblocks); if (params_to_get & SA_PARAM_BUFF_MODE) *buff_mode = mode_hdr->dev_spec & SMH_SA_BUF_MODE_MASK; if (params_to_get & SA_PARAM_DENSITY) *density = mode_blk->density; if (params_to_get & SA_PARAM_WP) *write_protect = (mode_hdr->dev_spec & SMH_SA_WP)? TRUE : FALSE; if (params_to_get & SA_PARAM_SPEED) *speed = mode_hdr->dev_spec & SMH_SA_SPEED_MASK; if (params_to_get & SA_PARAM_COMPRESSION) { sa_comp_t *ntcs = (sa_comp_t *) &mode_blk[1]; if (cpage == SA_DATA_COMPRESSION_PAGE) { struct scsi_data_compression_page *cp = &ntcs->dcomp; *comp_supported = (cp->dce_and_dcc & SA_DCP_DCC)? TRUE : FALSE; *comp_enabled = (cp->dce_and_dcc & SA_DCP_DCE)? TRUE : FALSE; *comp_algorithm = scsi_4btoul(cp->comp_algorithm); } else { struct scsi_dev_conf_page *cp = &ntcs->dconf; /* * We don't really know whether this device supports * Data Compression if the algorithm field is * zero. Just say we do. */ *comp_supported = TRUE; *comp_enabled = (cp->sel_comp_alg != SA_COMP_NONE)? TRUE : FALSE; *comp_algorithm = cp->sel_comp_alg; } if (tcs != NULL) bcopy(ntcs, tcs, sizeof (sa_comp_t)); } if ((params_to_get & SA_PARAM_DENSITY_EXT) && (softc->scsi_rev >= SCSI_REV_SPC)) { int i; for (i = 0; i < SA_DENSITY_TYPES; i++) { scsi_report_density_support(&ccb->csio, /*retries*/ 1, /*cbfcnp*/ sadone, /*tag_action*/ MSG_SIMPLE_Q_TAG, /*media*/ softc->density_type_bits[i] & SRDS_MEDIA, /*medium_type*/ softc->density_type_bits[i] & SRDS_MEDIUM_TYPE, /*data_ptr*/ softc->density_info[i], /*length*/ sizeof(softc->density_info[i]), /*sense_len*/ SSD_FULL_SIZE, /*timeout*/ REP_DENSITY_TIMEOUT); error = cam_periph_runccb(ccb, saerror, 0, SF_NO_PRINT, softc->device_stats); status = ccb->ccb_h.status & CAM_STATUS_MASK; /* * Some tape drives won't support this command at * all, but hopefully we'll minimize that with the * check for SPC or greater support above. If they * don't support the default report (neither the * MEDIA or MEDIUM_TYPE bits set), then there is * really no point in continuing on to look for * other reports. */ if ((error != 0) || (status != CAM_REQ_CMP)) { error = 0; softc->density_info_valid[i] = 0; if (softc->density_type_bits[i] == 0) break; else continue; } softc->density_info_valid[i] = ccb->csio.dxfer_len - ccb->csio.resid; } } /* * Get logical block protection parameters if the drive supports it. */ if ((params_to_get & SA_PARAM_LBP) && (softc->flags & SA_FLAG_PROTECT_SUPP)) { struct scsi_mode_header_10 *mode10_hdr; struct scsi_control_data_prot_subpage *dp_page; struct scsi_mode_sense_10 *cdb; struct sa_prot_state *prot; int dp_len, returned_len; if (dp_size == 0) dp_size = sizeof(*dp_page); dp_len = sizeof(*mode10_hdr) + dp_size; mode10_hdr = malloc(dp_len, M_SCSISA, M_NOWAIT | M_ZERO); if (mode10_hdr == NULL) { error = ENOMEM; goto sagetparamsexit; } scsi_mode_sense_len(&ccb->csio, /*retries*/ 5, /*cbfcnp*/ sadone, /*tag_action*/ MSG_SIMPLE_Q_TAG, /*dbd*/ TRUE, /*page_code*/ (prot_changeable == 0) ? SMS_PAGE_CTRL_CURRENT : SMS_PAGE_CTRL_CHANGEABLE, /*page*/ SMS_CONTROL_MODE_PAGE, /*param_buf*/ (uint8_t *)mode10_hdr, /*param_len*/ dp_len, /*minimum_cmd_size*/ 10, /*sense_len*/ SSD_FULL_SIZE, /*timeout*/ SCSIOP_TIMEOUT); /* * XXX KDM we need to be able to set the subpage in the * fill function. */ cdb = (struct scsi_mode_sense_10 *)ccb->csio.cdb_io.cdb_bytes; cdb->subpage = SA_CTRL_DP_SUBPAGE_CODE; error = cam_periph_runccb(ccb, saerror, 0, SF_NO_PRINT, softc->device_stats); if (error != 0) { free(mode10_hdr, M_SCSISA); goto sagetparamsexit; } status = ccb->ccb_h.status & CAM_STATUS_MASK; if (status != CAM_REQ_CMP) { error = EINVAL; free(mode10_hdr, M_SCSISA); goto sagetparamsexit; } /* * The returned data length at least has to be long enough * for us to look at length in the mode page header. */ returned_len = ccb->csio.dxfer_len - ccb->csio.resid; if (returned_len < sizeof(mode10_hdr->data_length)) { error = EINVAL; free(mode10_hdr, M_SCSISA); goto sagetparamsexit; } returned_len = min(returned_len, sizeof(mode10_hdr->data_length) + scsi_2btoul(mode10_hdr->data_length)); dp_page = (struct scsi_control_data_prot_subpage *) &mode10_hdr[1]; /* * We also have to have enough data to include the prot_bits * in the subpage. */ if (returned_len < (sizeof(*mode10_hdr) + __offsetof(struct scsi_control_data_prot_subpage, prot_bits) + sizeof(dp_page->prot_bits))) { error = EINVAL; free(mode10_hdr, M_SCSISA); goto sagetparamsexit; } prot = &softc->prot_info.cur_prot_state; prot->prot_method = dp_page->prot_method; prot->pi_length = dp_page->pi_length & SA_CTRL_DP_PI_LENGTH_MASK; prot->lbp_w = (dp_page->prot_bits & SA_CTRL_DP_LBP_W) ? 1 :0; prot->lbp_r = (dp_page->prot_bits & SA_CTRL_DP_LBP_R) ? 1 :0; prot->rbdp = (dp_page->prot_bits & SA_CTRL_DP_RBDP) ? 1 :0; prot->initialized = 1; if (prot_page != NULL) bcopy(dp_page, prot_page, min(sizeof(*prot_page), sizeof(*dp_page))); free(mode10_hdr, M_SCSISA); } if (CAM_DEBUGGED(periph->path, CAM_DEBUG_INFO)) { int idx; char *xyz = mode_buffer; xpt_print_path(periph->path); printf("Mode Sense Data="); for (idx = 0; idx < mode_buffer_len; idx++) printf(" 0x%02x", xyz[idx] & 0xff); printf("\n"); } sagetparamsexit: xpt_release_ccb(ccb); free(mode_buffer, M_SCSISA); return (error); } /* * Set protection information to the pending protection information stored * in the softc. */ static int sasetprot(struct cam_periph *periph, struct sa_prot_state *new_prot) { struct sa_softc *softc; struct scsi_control_data_prot_subpage *dp_page, *dp_changeable; struct scsi_mode_header_10 *mode10_hdr, *mode10_changeable; union ccb *ccb; uint8_t current_speed; size_t dp_size, dp_page_length; int dp_len, buff_mode; int error; softc = (struct sa_softc *)periph->softc; mode10_hdr = NULL; mode10_changeable = NULL; ccb = NULL; /* * Start off with the size set to the actual length of the page * that we have defined. */ dp_size = sizeof(*dp_changeable); dp_page_length = dp_size - __offsetof(struct scsi_control_data_prot_subpage, prot_method); retry_length: dp_len = sizeof(*mode10_changeable) + dp_size; mode10_changeable = malloc(dp_len, M_SCSISA, M_NOWAIT | M_ZERO); if (mode10_changeable == NULL) { error = ENOMEM; goto bailout; } dp_changeable = (struct scsi_control_data_prot_subpage *)&mode10_changeable[1]; /* * First get the data protection page changeable parameters mask. * We need to know which parameters the drive supports changing. * We also need to know what the drive claims that its page length * is. The reason is that IBM drives in particular are very picky * about the page length. They want it (the length set in the * page structure itself) to be 28 bytes, and they want the * parameter list length specified in the mode select header to be * 40 bytes. So, to work with IBM drives as well as any other tape * drive, find out what the drive claims the page length is, and * make sure that we match that. */ error = sagetparams(periph, SA_PARAM_SPEED | SA_PARAM_LBP, NULL, NULL, NULL, &buff_mode, NULL, ¤t_speed, NULL, NULL, NULL, NULL, dp_changeable, dp_size, /*prot_changeable*/ 1); if (error != 0) goto bailout; if (scsi_2btoul(dp_changeable->length) > dp_page_length) { dp_page_length = scsi_2btoul(dp_changeable->length); dp_size = dp_page_length + __offsetof(struct scsi_control_data_prot_subpage, prot_method); free(mode10_changeable, M_SCSISA); mode10_changeable = NULL; goto retry_length; } mode10_hdr = malloc(dp_len, M_SCSISA, M_NOWAIT | M_ZERO); if (mode10_hdr == NULL) { error = ENOMEM; goto bailout; } dp_page = (struct scsi_control_data_prot_subpage *)&mode10_hdr[1]; /* * Now grab the actual current settings in the page. */ error = sagetparams(periph, SA_PARAM_SPEED | SA_PARAM_LBP, NULL, NULL, NULL, &buff_mode, NULL, ¤t_speed, NULL, NULL, NULL, NULL, dp_page, dp_size, /*prot_changeable*/ 0); if (error != 0) goto bailout; /* These two fields need to be 0 for MODE SELECT */ scsi_ulto2b(0, mode10_hdr->data_length); mode10_hdr->medium_type = 0; /* We are not including a block descriptor */ scsi_ulto2b(0, mode10_hdr->blk_desc_len); mode10_hdr->dev_spec = current_speed; /* if set, set single-initiator buffering mode */ if (softc->buffer_mode == SMH_SA_BUF_MODE_SIBUF) { mode10_hdr->dev_spec |= SMH_SA_BUF_MODE_SIBUF; } /* * For each field, make sure that the drive allows changing it * before bringing in the user's setting. */ if (dp_changeable->prot_method != 0) dp_page->prot_method = new_prot->prot_method; if (dp_changeable->pi_length & SA_CTRL_DP_PI_LENGTH_MASK) { dp_page->pi_length &= ~SA_CTRL_DP_PI_LENGTH_MASK; dp_page->pi_length |= (new_prot->pi_length & SA_CTRL_DP_PI_LENGTH_MASK); } if (dp_changeable->prot_bits & SA_CTRL_DP_LBP_W) { if (new_prot->lbp_w) dp_page->prot_bits |= SA_CTRL_DP_LBP_W; else dp_page->prot_bits &= ~SA_CTRL_DP_LBP_W; } if (dp_changeable->prot_bits & SA_CTRL_DP_LBP_R) { if (new_prot->lbp_r) dp_page->prot_bits |= SA_CTRL_DP_LBP_R; else dp_page->prot_bits &= ~SA_CTRL_DP_LBP_R; } if (dp_changeable->prot_bits & SA_CTRL_DP_RBDP) { if (new_prot->rbdp) dp_page->prot_bits |= SA_CTRL_DP_RBDP; else dp_page->prot_bits &= ~SA_CTRL_DP_RBDP; } ccb = cam_periph_getccb(periph, 1); scsi_mode_select_len(&ccb->csio, /*retries*/ 5, /*cbfcnp*/ sadone, /*tag_action*/ MSG_SIMPLE_Q_TAG, /*scsi_page_fmt*/ TRUE, /*save_pages*/ FALSE, /*param_buf*/ (uint8_t *)mode10_hdr, /*param_len*/ dp_len, /*minimum_cmd_size*/ 10, /*sense_len*/ SSD_FULL_SIZE, /*timeout*/ SCSIOP_TIMEOUT); error = cam_periph_runccb(ccb, saerror, 0, 0, softc->device_stats); if (error != 0) goto bailout; if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) { error = EINVAL; goto bailout; } /* * The operation was successful. We could just copy the settings * the user requested, but just in case the drive ignored some of * our settings, let's ask for status again. */ error = sagetparams(periph, SA_PARAM_SPEED | SA_PARAM_LBP, NULL, NULL, NULL, &buff_mode, NULL, ¤t_speed, NULL, NULL, NULL, NULL, dp_page, dp_size, 0); bailout: if (ccb != NULL) xpt_release_ccb(ccb); free(mode10_hdr, M_SCSISA); free(mode10_changeable, M_SCSISA); return (error); } /* * The purpose of this function is to set one of four different parameters * for a tape drive: * - blocksize * - density * - compression / compression algorithm * - buffering mode * * The assumption is that this will be called from saioctl(), and therefore * from a process context. Thus the waiting malloc calls below. If that * assumption ever changes, the malloc calls should be changed to be * NOWAIT mallocs. * * Any or all of the four parameters may be set when this function is * called. It should handle setting more than one parameter at once. */ static int sasetparams(struct cam_periph *periph, sa_params params_to_set, u_int32_t blocksize, u_int8_t density, u_int32_t calg, u_int32_t sense_flags) { struct sa_softc *softc; u_int32_t current_blocksize; u_int32_t current_calg; u_int8_t current_density; u_int8_t current_speed; int comp_enabled, comp_supported; void *mode_buffer; int mode_buffer_len; struct scsi_mode_header_6 *mode_hdr; struct scsi_mode_blk_desc *mode_blk; sa_comp_t *ccomp, *cpage; int buff_mode; union ccb *ccb = NULL; int error; softc = (struct sa_softc *)periph->softc; ccomp = malloc(sizeof (sa_comp_t), M_SCSISA, M_NOWAIT); if (ccomp == NULL) return (ENOMEM); /* * Since it doesn't make sense to set the number of blocks, or * write protection, we won't try to get the current value. We * always want to get the blocksize, so we can set it back to the * proper value. */ error = sagetparams(periph, params_to_set | SA_PARAM_BLOCKSIZE | SA_PARAM_SPEED, ¤t_blocksize, ¤t_density, NULL, &buff_mode, NULL, ¤t_speed, &comp_supported, &comp_enabled, ¤t_calg, ccomp, NULL, 0, 0); if (error != 0) { free(ccomp, M_SCSISA); return (error); } mode_buffer_len = sizeof(*mode_hdr) + sizeof(*mode_blk); if (params_to_set & SA_PARAM_COMPRESSION) mode_buffer_len += sizeof (sa_comp_t); mode_buffer = malloc(mode_buffer_len, M_SCSISA, M_NOWAIT | M_ZERO); if (mode_buffer == NULL) { free(ccomp, M_SCSISA); return (ENOMEM); } mode_hdr = (struct scsi_mode_header_6 *)mode_buffer; mode_blk = (struct scsi_mode_blk_desc *)&mode_hdr[1]; ccb = cam_periph_getccb(periph, 1); retry: if (params_to_set & SA_PARAM_COMPRESSION) { if (mode_blk) { cpage = (sa_comp_t *)&mode_blk[1]; } else { cpage = (sa_comp_t *)&mode_hdr[1]; } bcopy(ccomp, cpage, sizeof (sa_comp_t)); cpage->hdr.pagecode &= ~0x80; } else cpage = NULL; /* * If the caller wants us to set the blocksize, use the one they * pass in. Otherwise, use the blocksize we got back from the * mode select above. */ if (mode_blk) { if (params_to_set & SA_PARAM_BLOCKSIZE) scsi_ulto3b(blocksize, mode_blk->blklen); else scsi_ulto3b(current_blocksize, mode_blk->blklen); /* * Set density if requested, else preserve old density. * SCSI_SAME_DENSITY only applies to SCSI-2 or better * devices, else density we've latched up in our softc. */ if (params_to_set & SA_PARAM_DENSITY) { mode_blk->density = density; } else if (softc->scsi_rev > SCSI_REV_CCS) { mode_blk->density = SCSI_SAME_DENSITY; } else { mode_blk->density = softc->media_density; } } /* * For mode selects, these two fields must be zero. */ mode_hdr->data_length = 0; mode_hdr->medium_type = 0; /* set the speed to the current value */ mode_hdr->dev_spec = current_speed; /* if set, set single-initiator buffering mode */ if (softc->buffer_mode == SMH_SA_BUF_MODE_SIBUF) { mode_hdr->dev_spec |= SMH_SA_BUF_MODE_SIBUF; } if (mode_blk) mode_hdr->blk_desc_len = sizeof(struct scsi_mode_blk_desc); else mode_hdr->blk_desc_len = 0; /* * First, if the user wants us to set the compression algorithm or * just turn compression on, check to make sure that this drive * supports compression. */ if (params_to_set & SA_PARAM_COMPRESSION) { /* * If the compression algorithm is 0, disable compression. * If the compression algorithm is non-zero, enable * compression and set the compression type to the * specified compression algorithm, unless the algorithm is * MT_COMP_ENABLE. In that case, we look at the * compression algorithm that is currently set and if it is * non-zero, we leave it as-is. If it is zero, and we have * saved a compression algorithm from a time when * compression was enabled before, set the compression to * the saved value. */ switch (ccomp->hdr.pagecode & ~0x80) { case SA_DEVICE_CONFIGURATION_PAGE: { struct scsi_dev_conf_page *dcp = &cpage->dconf; if (calg == 0) { dcp->sel_comp_alg = SA_COMP_NONE; break; } if (calg != MT_COMP_ENABLE) { dcp->sel_comp_alg = calg; } else if (dcp->sel_comp_alg == SA_COMP_NONE && softc->saved_comp_algorithm != 0) { dcp->sel_comp_alg = softc->saved_comp_algorithm; } break; } case SA_DATA_COMPRESSION_PAGE: if (ccomp->dcomp.dce_and_dcc & SA_DCP_DCC) { struct scsi_data_compression_page *dcp = &cpage->dcomp; if (calg == 0) { /* * Disable compression, but leave the * decompression and the capability bit * alone. */ dcp->dce_and_dcc = SA_DCP_DCC; dcp->dde_and_red |= SA_DCP_DDE; break; } /* enable compression && decompression */ dcp->dce_and_dcc = SA_DCP_DCE | SA_DCP_DCC; dcp->dde_and_red |= SA_DCP_DDE; /* * If there, use compression algorithm from caller. * Otherwise, if there's a saved compression algorithm * and there is no current algorithm, use the saved * algorithm. Else parrot back what we got and hope * for the best. */ if (calg != MT_COMP_ENABLE) { scsi_ulto4b(calg, dcp->comp_algorithm); scsi_ulto4b(calg, dcp->decomp_algorithm); } else if (scsi_4btoul(dcp->comp_algorithm) == 0 && softc->saved_comp_algorithm != 0) { scsi_ulto4b(softc->saved_comp_algorithm, dcp->comp_algorithm); scsi_ulto4b(softc->saved_comp_algorithm, dcp->decomp_algorithm); } break; } /* * Compression does not appear to be supported- * at least via the DATA COMPRESSION page. It * would be too much to ask us to believe that * the page itself is supported, but incorrectly * reports an ability to manipulate data compression, * so we'll assume that this device doesn't support * compression. We can just fall through for that. */ /* FALLTHROUGH */ default: /* * The drive doesn't seem to support compression, * so turn off the set compression bit. */ params_to_set &= ~SA_PARAM_COMPRESSION; xpt_print(periph->path, "device does not seem to support compression\n"); /* * If that was the only thing the user wanted us to set, * clean up allocated resources and return with * 'operation not supported'. */ if (params_to_set == SA_PARAM_NONE) { free(mode_buffer, M_SCSISA); xpt_release_ccb(ccb); return (ENODEV); } /* * That wasn't the only thing the user wanted us to set. * So, decrease the stated mode buffer length by the * size of the compression mode page. */ mode_buffer_len -= sizeof(sa_comp_t); } } /* It is safe to retry this operation */ scsi_mode_select(&ccb->csio, 5, sadone, MSG_SIMPLE_Q_TAG, (params_to_set & SA_PARAM_COMPRESSION)? TRUE : FALSE, FALSE, mode_buffer, mode_buffer_len, SSD_FULL_SIZE, SCSIOP_TIMEOUT); error = cam_periph_runccb(ccb, saerror, 0, sense_flags, softc->device_stats); if (CAM_DEBUGGED(periph->path, CAM_DEBUG_INFO)) { int idx; char *xyz = mode_buffer; xpt_print_path(periph->path); printf("Err%d, Mode Select Data=", error); for (idx = 0; idx < mode_buffer_len; idx++) printf(" 0x%02x", xyz[idx] & 0xff); printf("\n"); } if (error) { /* * If we can, try without setting density/blocksize. */ if (mode_blk) { if ((params_to_set & (SA_PARAM_DENSITY|SA_PARAM_BLOCKSIZE)) == 0) { mode_blk = NULL; goto retry; } } else { mode_blk = (struct scsi_mode_blk_desc *)&mode_hdr[1]; cpage = (sa_comp_t *)&mode_blk[1]; } /* * If we were setting the blocksize, and that failed, we * want to set it to its original value. If we weren't * setting the blocksize, we don't want to change it. */ scsi_ulto3b(current_blocksize, mode_blk->blklen); /* * Set density if requested, else preserve old density. * SCSI_SAME_DENSITY only applies to SCSI-2 or better * devices, else density we've latched up in our softc. */ if (params_to_set & SA_PARAM_DENSITY) { mode_blk->density = current_density; } else if (softc->scsi_rev > SCSI_REV_CCS) { mode_blk->density = SCSI_SAME_DENSITY; } else { mode_blk->density = softc->media_density; } if (params_to_set & SA_PARAM_COMPRESSION) bcopy(ccomp, cpage, sizeof (sa_comp_t)); /* * The retry count is the only CCB field that might have been * changed that we care about, so reset it back to 1. */ ccb->ccb_h.retry_count = 1; cam_periph_runccb(ccb, saerror, 0, sense_flags, softc->device_stats); } xpt_release_ccb(ccb); if (ccomp != NULL) free(ccomp, M_SCSISA); if (params_to_set & SA_PARAM_COMPRESSION) { if (error) { softc->flags &= ~SA_FLAG_COMP_ENABLED; /* * Even if we get an error setting compression, * do not say that we don't support it. We could * have been wrong, or it may be media specific. * softc->flags &= ~SA_FLAG_COMP_SUPP; */ softc->saved_comp_algorithm = softc->comp_algorithm; softc->comp_algorithm = 0; } else { softc->flags |= SA_FLAG_COMP_ENABLED; softc->comp_algorithm = calg; } } free(mode_buffer, M_SCSISA); return (error); } static int saextget(struct cdev *dev, struct cam_periph *periph, struct sbuf *sb, struct mtextget *g) { int indent, error; char tmpstr[80]; struct sa_softc *softc; int tmpint; uint32_t maxio_tmp; struct ccb_getdev cgd; softc = (struct sa_softc *)periph->softc; error = 0; error = sagetparams_common(dev, periph); if (error) goto extget_bailout; if (!SA_IS_CTRL(dev) && !softc->open_pending_mount) sagetpos(periph); indent = 0; SASBADDNODE(sb, indent, mtextget); /* * Basic CAM peripheral information. */ SASBADDVARSTR(sb, indent, periph->periph_name, %s, periph_name, strlen(periph->periph_name) + 1); SASBADDUINT(sb, indent, periph->unit_number, %u, unit_number); xpt_setup_ccb(&cgd.ccb_h, periph->path, CAM_PRIORITY_NORMAL); cgd.ccb_h.func_code = XPT_GDEV_TYPE; xpt_action((union ccb *)&cgd); if ((cgd.ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) { g->status = MT_EXT_GET_ERROR; snprintf(g->error_str, sizeof(g->error_str), "Error %#x returned for XPT_GDEV_TYPE CCB", cgd.ccb_h.status); goto extget_bailout; } cam_strvis(tmpstr, cgd.inq_data.vendor, sizeof(cgd.inq_data.vendor), sizeof(tmpstr)); SASBADDVARSTRDESC(sb, indent, tmpstr, %s, vendor, sizeof(cgd.inq_data.vendor) + 1, "SCSI Vendor ID"); cam_strvis(tmpstr, cgd.inq_data.product, sizeof(cgd.inq_data.product), sizeof(tmpstr)); SASBADDVARSTRDESC(sb, indent, tmpstr, %s, product, sizeof(cgd.inq_data.product) + 1, "SCSI Product ID"); cam_strvis(tmpstr, cgd.inq_data.revision, sizeof(cgd.inq_data.revision), sizeof(tmpstr)); SASBADDVARSTRDESC(sb, indent, tmpstr, %s, revision, sizeof(cgd.inq_data.revision) + 1, "SCSI Revision"); if (cgd.serial_num_len > 0) { char *tmpstr2; size_t ts2_len; int ts2_malloc; ts2_len = 0; if (cgd.serial_num_len > sizeof(tmpstr)) { ts2_len = cgd.serial_num_len + 1; ts2_malloc = 1; tmpstr2 = malloc(ts2_len, M_SCSISA, M_WAITOK | M_ZERO); } else { ts2_len = sizeof(tmpstr); ts2_malloc = 0; tmpstr2 = tmpstr; } cam_strvis(tmpstr2, cgd.serial_num, cgd.serial_num_len, ts2_len); SASBADDVARSTRDESC(sb, indent, tmpstr2, %s, serial_num, (ssize_t)cgd.serial_num_len + 1, "Serial Number"); if (ts2_malloc != 0) free(tmpstr2, M_SCSISA); } else { /* * We return a serial_num element in any case, but it will * be empty if the device has no serial number. */ tmpstr[0] = '\0'; SASBADDVARSTRDESC(sb, indent, tmpstr, %s, serial_num, (ssize_t)0, "Serial Number"); } SASBADDUINTDESC(sb, indent, softc->maxio, %u, maxio, "Maximum I/O size allowed by driver and controller"); SASBADDUINTDESC(sb, indent, softc->cpi_maxio, %u, cpi_maxio, "Maximum I/O size reported by controller"); SASBADDUINTDESC(sb, indent, softc->max_blk, %u, max_blk, "Maximum block size supported by tape drive and media"); SASBADDUINTDESC(sb, indent, softc->min_blk, %u, min_blk, "Minimum block size supported by tape drive and media"); SASBADDUINTDESC(sb, indent, softc->blk_gran, %u, blk_gran, "Block granularity supported by tape drive and media"); maxio_tmp = min(softc->max_blk, softc->maxio); SASBADDUINTDESC(sb, indent, maxio_tmp, %u, max_effective_iosize, "Maximum possible I/O size"); SASBADDINTDESC(sb, indent, softc->flags & SA_FLAG_FIXED ? 1 : 0, %d, fixed_mode, "Set to 1 for fixed block mode, 0 for variable block"); /* * XXX KDM include SIM, bus, target, LUN? */ if (softc->flags & SA_FLAG_COMP_UNSUPP) tmpint = 0; else tmpint = 1; SASBADDINTDESC(sb, indent, tmpint, %d, compression_supported, "Set to 1 if compression is supported, 0 if not"); if (softc->flags & SA_FLAG_COMP_ENABLED) tmpint = 1; else tmpint = 0; SASBADDINTDESC(sb, indent, tmpint, %d, compression_enabled, "Set to 1 if compression is enabled, 0 if not"); SASBADDUINTDESC(sb, indent, softc->comp_algorithm, %u, compression_algorithm, "Numeric compression algorithm"); safillprot(softc, &indent, sb); SASBADDUINTDESC(sb, indent, softc->media_blksize, %u, media_blocksize, "Block size reported by drive or set by user"); SASBADDINTDESC(sb, indent, (intmax_t)softc->fileno, %jd, calculated_fileno, "Calculated file number, -1 if unknown"); SASBADDINTDESC(sb, indent, (intmax_t)softc->blkno, %jd, calculated_rel_blkno, "Calculated block number relative to file, " "set to -1 if unknown"); SASBADDINTDESC(sb, indent, (intmax_t)softc->rep_fileno, %jd, reported_fileno, "File number reported by drive, -1 if unknown"); SASBADDINTDESC(sb, indent, (intmax_t)softc->rep_blkno, %jd, reported_blkno, "Block number relative to BOP/BOT reported by " "drive, -1 if unknown"); SASBADDINTDESC(sb, indent, (intmax_t)softc->partition, %jd, partition, "Current partition number, 0 is the default"); SASBADDINTDESC(sb, indent, softc->bop, %d, bop, "Set to 1 if drive is at the beginning of partition/tape, 0 if " "not, -1 if unknown"); SASBADDINTDESC(sb, indent, softc->eop, %d, eop, "Set to 1 if drive is past early warning, 0 if not, -1 if unknown"); SASBADDINTDESC(sb, indent, softc->bpew, %d, bpew, "Set to 1 if drive is past programmable early warning, 0 if not, " "-1 if unknown"); SASBADDINTDESC(sb, indent, (intmax_t)softc->last_io_resid, %jd, residual, "Residual for the last I/O"); /* * XXX KDM should we send a string with the current driver * status already decoded instead of a numeric value? */ SASBADDINTDESC(sb, indent, softc->dsreg, %d, dsreg, "Current state of the driver"); safilldensitysb(softc, &indent, sb); SASBENDNODE(sb, indent, mtextget); extget_bailout: return (error); } static int saparamget(struct sa_softc *softc, struct sbuf *sb) { int indent; indent = 0; SASBADDNODE(sb, indent, mtparamget); SASBADDINTDESC(sb, indent, softc->sili, %d, sili, "Suppress an error on underlength variable reads"); SASBADDINTDESC(sb, indent, softc->eot_warn, %d, eot_warn, "Return an error to warn that end of tape is approaching"); safillprot(softc, &indent, sb); SASBENDNODE(sb, indent, mtparamget); return (0); } static void saprevent(struct cam_periph *periph, int action) { struct sa_softc *softc; union ccb *ccb; int error, sf; softc = (struct sa_softc *)periph->softc; if ((action == PR_ALLOW) && (softc->flags & SA_FLAG_TAPE_LOCKED) == 0) return; if ((action == PR_PREVENT) && (softc->flags & SA_FLAG_TAPE_LOCKED) != 0) return; /* * We can be quiet about illegal requests. */ if (CAM_DEBUGGED(periph->path, CAM_DEBUG_INFO)) { sf = 0; } else sf = SF_QUIET_IR; ccb = cam_periph_getccb(periph, 1); /* It is safe to retry this operation */ scsi_prevent(&ccb->csio, 5, sadone, MSG_SIMPLE_Q_TAG, action, SSD_FULL_SIZE, SCSIOP_TIMEOUT); error = cam_periph_runccb(ccb, saerror, 0, sf, softc->device_stats); if (error == 0) { if (action == PR_ALLOW) softc->flags &= ~SA_FLAG_TAPE_LOCKED; else softc->flags |= SA_FLAG_TAPE_LOCKED; } xpt_release_ccb(ccb); } static int sarewind(struct cam_periph *periph) { union ccb *ccb; struct sa_softc *softc; int error; softc = (struct sa_softc *)periph->softc; ccb = cam_periph_getccb(periph, 1); /* It is safe to retry this operation */ scsi_rewind(&ccb->csio, 2, sadone, MSG_SIMPLE_Q_TAG, FALSE, SSD_FULL_SIZE, REWIND_TIMEOUT); softc->dsreg = MTIO_DSREG_REW; error = cam_periph_runccb(ccb, saerror, 0, 0, softc->device_stats); softc->dsreg = MTIO_DSREG_REST; xpt_release_ccb(ccb); if (error == 0) { softc->partition = softc->fileno = softc->blkno = (daddr_t) 0; softc->rep_fileno = softc->rep_blkno = (daddr_t) 0; } else { softc->fileno = softc->blkno = (daddr_t) -1; softc->partition = (daddr_t) -1; softc->rep_fileno = softc->rep_blkno = (daddr_t) -1; } return (error); } static int saspace(struct cam_periph *periph, int count, scsi_space_code code) { union ccb *ccb; struct sa_softc *softc; int error; softc = (struct sa_softc *)periph->softc; ccb = cam_periph_getccb(periph, 1); /* This cannot be retried */ scsi_space(&ccb->csio, 0, sadone, MSG_SIMPLE_Q_TAG, code, count, SSD_FULL_SIZE, SPACE_TIMEOUT); /* * Clear residual because we will be using it. */ softc->last_ctl_resid = 0; softc->dsreg = (count < 0)? MTIO_DSREG_REV : MTIO_DSREG_FWD; error = cam_periph_runccb(ccb, saerror, 0, 0, softc->device_stats); softc->dsreg = MTIO_DSREG_REST; xpt_release_ccb(ccb); /* * If a spacing operation has failed, we need to invalidate * this mount. * * If the spacing operation was setmarks or to end of recorded data, * we no longer know our relative position. * * If the spacing operations was spacing files in reverse, we * take account of the residual, but still check against less * than zero- if we've gone negative, we must have hit BOT. * * If the spacing operations was spacing records in reverse and * we have a residual, we've either hit BOT or hit a filemark. * In the former case, we know our new record number (0). In * the latter case, we have absolutely no idea what the real * record number is- we've stopped between the end of the last * record in the previous file and the filemark that stopped * our spacing backwards. */ if (error) { softc->fileno = softc->blkno = (daddr_t) -1; softc->rep_blkno = softc->partition = (daddr_t) -1; softc->rep_fileno = (daddr_t) -1; } else if (code == SS_SETMARKS || code == SS_EOD) { softc->fileno = softc->blkno = (daddr_t) -1; } else if (code == SS_FILEMARKS && softc->fileno != (daddr_t) -1) { softc->fileno += (count - softc->last_ctl_resid); if (softc->fileno < 0) /* we must of hit BOT */ softc->fileno = 0; softc->blkno = 0; } else if (code == SS_BLOCKS && softc->blkno != (daddr_t) -1) { softc->blkno += (count - softc->last_ctl_resid); if (count < 0) { if (softc->last_ctl_resid || softc->blkno < 0) { if (softc->fileno == 0) { softc->blkno = 0; } else { softc->blkno = (daddr_t) -1; } } } } if (error == 0) sagetpos(periph); return (error); } static int sawritefilemarks(struct cam_periph *periph, int nmarks, int setmarks, int immed) { union ccb *ccb; struct sa_softc *softc; int error, nwm = 0; softc = (struct sa_softc *)periph->softc; if (softc->open_rdonly) return (EBADF); ccb = cam_periph_getccb(periph, 1); /* * Clear residual because we will be using it. */ softc->last_ctl_resid = 0; softc->dsreg = MTIO_DSREG_FMK; /* this *must* not be retried */ scsi_write_filemarks(&ccb->csio, 0, sadone, MSG_SIMPLE_Q_TAG, immed, setmarks, nmarks, SSD_FULL_SIZE, IO_TIMEOUT); softc->dsreg = MTIO_DSREG_REST; error = cam_periph_runccb(ccb, saerror, 0, 0, softc->device_stats); if (error == 0 && nmarks) { struct sa_softc *softc = (struct sa_softc *)periph->softc; nwm = nmarks - softc->last_ctl_resid; softc->filemarks += nwm; } xpt_release_ccb(ccb); /* * Update relative positions (if we're doing that). */ if (error) { softc->fileno = softc->blkno = softc->partition = (daddr_t) -1; } else if (softc->fileno != (daddr_t) -1) { softc->fileno += nwm; softc->blkno = 0; } /* * Ask the tape drive for position information. */ sagetpos(periph); /* * If we got valid position information, since we just wrote a file * mark, we know we're at the file mark and block 0 after that * filemark. */ if (softc->rep_fileno != (daddr_t) -1) { softc->fileno = softc->rep_fileno; softc->blkno = 0; } return (error); } static int sagetpos(struct cam_periph *periph) { union ccb *ccb; struct scsi_tape_position_long_data long_pos; struct sa_softc *softc = (struct sa_softc *)periph->softc; int error; if (softc->quirks & SA_QUIRK_NO_LONG_POS) { softc->rep_fileno = (daddr_t) -1; softc->rep_blkno = (daddr_t) -1; softc->bop = softc->eop = softc->bpew = -1; return (EOPNOTSUPP); } bzero(&long_pos, sizeof(long_pos)); ccb = cam_periph_getccb(periph, CAM_PRIORITY_NORMAL); scsi_read_position_10(&ccb->csio, /*retries*/ 1, /*cbfcnp*/ sadone, /*tag_action*/ MSG_SIMPLE_Q_TAG, /*service_action*/ SA_RPOS_LONG_FORM, /*data_ptr*/ (uint8_t *)&long_pos, /*length*/ sizeof(long_pos), /*sense_len*/ SSD_FULL_SIZE, /*timeout*/ SCSIOP_TIMEOUT); softc->dsreg = MTIO_DSREG_RBSY; error = cam_periph_runccb(ccb, saerror, 0, SF_QUIET_IR, softc->device_stats); softc->dsreg = MTIO_DSREG_REST; if (error == 0) { if (long_pos.flags & SA_RPOS_LONG_MPU) { /* * If the drive doesn't know what file mark it is * on, our calculated filemark isn't going to be * accurate either. */ softc->fileno = (daddr_t) -1; softc->rep_fileno = (daddr_t) -1; } else { softc->fileno = softc->rep_fileno = scsi_8btou64(long_pos.logical_file_num); } if (long_pos.flags & SA_RPOS_LONG_LONU) { softc->partition = (daddr_t) -1; softc->rep_blkno = (daddr_t) -1; /* * If the tape drive doesn't know its block * position, we can't claim to know it either. */ softc->blkno = (daddr_t) -1; } else { softc->partition = scsi_4btoul(long_pos.partition); softc->rep_blkno = scsi_8btou64(long_pos.logical_object_num); } if (long_pos.flags & SA_RPOS_LONG_BOP) softc->bop = 1; else softc->bop = 0; if (long_pos.flags & SA_RPOS_LONG_EOP) softc->eop = 1; else softc->eop = 0; if (long_pos.flags & SA_RPOS_LONG_BPEW) softc->bpew = 1; else softc->bpew = 0; } else if (error == EINVAL) { /* * If this drive returned an invalid-request type error, * then it likely doesn't support the long form report. */ softc->quirks |= SA_QUIRK_NO_LONG_POS; } if (error != 0) { softc->rep_fileno = softc->rep_blkno = (daddr_t) -1; softc->partition = (daddr_t) -1; softc->bop = softc->eop = softc->bpew = -1; } xpt_release_ccb(ccb); return (error); } static int sardpos(struct cam_periph *periph, int hard, u_int32_t *blkptr) { struct scsi_tape_position_data loc; union ccb *ccb; struct sa_softc *softc = (struct sa_softc *)periph->softc; int error; /* * We try and flush any buffered writes here if we were writing * and we're trying to get hardware block position. It eats * up performance substantially, but I'm wary of drive firmware. * * I think that *logical* block position is probably okay- * but hardware block position might have to wait for data * to hit media to be valid. Caveat Emptor. */ if (hard && (softc->flags & SA_FLAG_TAPE_WRITTEN)) { error = sawritefilemarks(periph, 0, 0, 0); if (error && error != EACCES) return (error); } ccb = cam_periph_getccb(periph, 1); scsi_read_position(&ccb->csio, 1, sadone, MSG_SIMPLE_Q_TAG, hard, &loc, SSD_FULL_SIZE, SCSIOP_TIMEOUT); softc->dsreg = MTIO_DSREG_RBSY; error = cam_periph_runccb(ccb, saerror, 0, 0, softc->device_stats); softc->dsreg = MTIO_DSREG_REST; if (error == 0) { if (loc.flags & SA_RPOS_UNCERTAIN) { error = EINVAL; /* nothing is certain */ } else { *blkptr = scsi_4btoul(loc.firstblk); } } xpt_release_ccb(ccb); return (error); } static int sasetpos(struct cam_periph *periph, int hard, struct mtlocate *locate_info) { union ccb *ccb; struct sa_softc *softc; int locate16; int immed, cp; int error; /* * We used to try and flush any buffered writes here. * Now we push this onto user applications to either * flush the pending writes themselves (via a zero count * WRITE FILEMARKS command) or they can trust their tape * drive to do this correctly for them. */ softc = (struct sa_softc *)periph->softc; ccb = cam_periph_getccb(periph, 1); cp = locate_info->flags & MT_LOCATE_FLAG_CHANGE_PART ? 1 : 0; immed = locate_info->flags & MT_LOCATE_FLAG_IMMED ? 1 : 0; /* * Determine whether we have to use LOCATE or LOCATE16. The hard * bit is only possible with LOCATE, but the new ioctls do not * allow setting that bit. So we can't get into the situation of * having the hard bit set with a block address that is larger than * 32-bits. */ if (hard != 0) locate16 = 0; else if ((locate_info->dest_type != MT_LOCATE_DEST_OBJECT) || (locate_info->block_address_mode != MT_LOCATE_BAM_IMPLICIT) || (locate_info->logical_id > SA_SPOS_MAX_BLK)) locate16 = 1; else locate16 = 0; if (locate16 != 0) { scsi_locate_16(&ccb->csio, /*retries*/ 1, /*cbfcnp*/ sadone, /*tag_action*/ MSG_SIMPLE_Q_TAG, /*immed*/ immed, /*cp*/ cp, /*dest_type*/ locate_info->dest_type, /*bam*/ locate_info->block_address_mode, /*partition*/ locate_info->partition, /*logical_id*/ locate_info->logical_id, /*sense_len*/ SSD_FULL_SIZE, /*timeout*/ SPACE_TIMEOUT); } else { uint32_t blk_pointer; blk_pointer = locate_info->logical_id; scsi_locate_10(&ccb->csio, /*retries*/ 1, /*cbfcnp*/ sadone, /*tag_action*/ MSG_SIMPLE_Q_TAG, /*immed*/ immed, /*cp*/ cp, /*hard*/ hard, /*partition*/ locate_info->partition, /*block_address*/ locate_info->logical_id, /*sense_len*/ SSD_FULL_SIZE, /*timeout*/ SPACE_TIMEOUT); } softc->dsreg = MTIO_DSREG_POS; error = cam_periph_runccb(ccb, saerror, 0, 0, softc->device_stats); softc->dsreg = MTIO_DSREG_REST; xpt_release_ccb(ccb); /* * We assume the calculated file and block numbers are unknown * unless we have enough information to populate them. */ softc->fileno = softc->blkno = (daddr_t) -1; /* * If the user requested changing the partition and the request * succeeded, note the partition. */ if ((error == 0) && (cp != 0)) softc->partition = locate_info->partition; else softc->partition = (daddr_t) -1; if (error == 0) { switch (locate_info->dest_type) { case MT_LOCATE_DEST_FILE: /* * This is the only case where we can reliably * calculate the file and block numbers. */ softc->fileno = locate_info->logical_id; softc->blkno = 0; break; case MT_LOCATE_DEST_OBJECT: case MT_LOCATE_DEST_SET: case MT_LOCATE_DEST_EOD: default: break; } } /* * Ask the drive for current position information. */ sagetpos(periph); return (error); } static int saretension(struct cam_periph *periph) { union ccb *ccb; struct sa_softc *softc; int error; softc = (struct sa_softc *)periph->softc; ccb = cam_periph_getccb(periph, 1); /* It is safe to retry this operation */ scsi_load_unload(&ccb->csio, 5, sadone, MSG_SIMPLE_Q_TAG, FALSE, FALSE, TRUE, TRUE, SSD_FULL_SIZE, ERASE_TIMEOUT); softc->dsreg = MTIO_DSREG_TEN; error = cam_periph_runccb(ccb, saerror, 0, 0, softc->device_stats); softc->dsreg = MTIO_DSREG_REST; xpt_release_ccb(ccb); if (error == 0) { softc->partition = softc->fileno = softc->blkno = (daddr_t) 0; sagetpos(periph); } else softc->partition = softc->fileno = softc->blkno = (daddr_t) -1; return (error); } static int sareservereleaseunit(struct cam_periph *periph, int reserve) { union ccb *ccb; struct sa_softc *softc; int error; softc = (struct sa_softc *)periph->softc; ccb = cam_periph_getccb(periph, 1); /* It is safe to retry this operation */ scsi_reserve_release_unit(&ccb->csio, 2, sadone, MSG_SIMPLE_Q_TAG, FALSE, 0, SSD_FULL_SIZE, SCSIOP_TIMEOUT, reserve); softc->dsreg = MTIO_DSREG_RBSY; error = cam_periph_runccb(ccb, saerror, 0, SF_RETRY_UA | SF_NO_PRINT, softc->device_stats); softc->dsreg = MTIO_DSREG_REST; xpt_release_ccb(ccb); /* * If the error was Illegal Request, then the device doesn't support * RESERVE/RELEASE. This is not an error. */ if (error == EINVAL) { error = 0; } return (error); } static int saloadunload(struct cam_periph *periph, int load) { union ccb *ccb; struct sa_softc *softc; int error; softc = (struct sa_softc *)periph->softc; ccb = cam_periph_getccb(periph, 1); /* It is safe to retry this operation */ scsi_load_unload(&ccb->csio, 5, sadone, MSG_SIMPLE_Q_TAG, FALSE, FALSE, FALSE, load, SSD_FULL_SIZE, REWIND_TIMEOUT); softc->dsreg = (load)? MTIO_DSREG_LD : MTIO_DSREG_UNL; error = cam_periph_runccb(ccb, saerror, 0, 0, softc->device_stats); softc->dsreg = MTIO_DSREG_REST; xpt_release_ccb(ccb); if (error || load == 0) { softc->partition = softc->fileno = softc->blkno = (daddr_t) -1; softc->rep_fileno = softc->rep_blkno = (daddr_t) -1; } else if (error == 0) { softc->partition = softc->fileno = softc->blkno = (daddr_t) 0; sagetpos(periph); } return (error); } static int saerase(struct cam_periph *periph, int longerase) { union ccb *ccb; struct sa_softc *softc; int error; softc = (struct sa_softc *)periph->softc; if (softc->open_rdonly) return (EBADF); ccb = cam_periph_getccb(periph, 1); scsi_erase(&ccb->csio, 1, sadone, MSG_SIMPLE_Q_TAG, FALSE, longerase, SSD_FULL_SIZE, ERASE_TIMEOUT); softc->dsreg = MTIO_DSREG_ZER; error = cam_periph_runccb(ccb, saerror, 0, 0, softc->device_stats); softc->dsreg = MTIO_DSREG_REST; xpt_release_ccb(ccb); return (error); } /* * Fill an sbuf with density data in XML format. This particular macro * works for multi-byte integer fields. * * Note that 1 byte fields aren't supported here. The reason is that the * compiler does not evaluate the sizeof(), and assumes that any of the * sizes are possible for a given field. So passing in a multi-byte * field will result in a warning that the assignment makes an integer * from a pointer without a cast, if there is an assignment in the 1 byte * case. */ #define SAFILLDENSSB(dens_data, sb, indent, field, desc_remain, \ len_to_go, cur_offset, desc){ \ size_t cur_field_len; \ \ cur_field_len = sizeof(dens_data->field); \ if (desc_remain < cur_field_len) { \ len_to_go -= desc_remain; \ cur_offset += desc_remain; \ continue; \ } \ len_to_go -= cur_field_len; \ cur_offset += cur_field_len; \ desc_remain -= cur_field_len; \ \ switch (sizeof(dens_data->field)) { \ case 1: \ KASSERT(1 == 0, ("Programmer error, invalid 1 byte " \ "field width for SAFILLDENSFIELD")); \ break; \ case 2: \ SASBADDUINTDESC(sb, indent, \ scsi_2btoul(dens_data->field), %u, field, desc); \ break; \ case 3: \ SASBADDUINTDESC(sb, indent, \ scsi_3btoul(dens_data->field), %u, field, desc); \ break; \ case 4: \ SASBADDUINTDESC(sb, indent, \ scsi_4btoul(dens_data->field), %u, field, desc); \ break; \ case 8: \ SASBADDUINTDESC(sb, indent, \ (uintmax_t)scsi_8btou64(dens_data->field), %ju, \ field, desc); \ break; \ default: \ break; \ } \ }; /* * Fill an sbuf with density data in XML format. This particular macro * works for strings. */ #define SAFILLDENSSBSTR(dens_data, sb, indent, field, desc_remain, \ len_to_go, cur_offset, desc){ \ size_t cur_field_len; \ char tmpstr[32]; \ \ cur_field_len = sizeof(dens_data->field); \ if (desc_remain < cur_field_len) { \ len_to_go -= desc_remain; \ cur_offset += desc_remain; \ continue; \ } \ len_to_go -= cur_field_len; \ cur_offset += cur_field_len; \ desc_remain -= cur_field_len; \ \ cam_strvis(tmpstr, dens_data->field, \ sizeof(dens_data->field), sizeof(tmpstr)); \ SASBADDVARSTRDESC(sb, indent, tmpstr, %s, field, \ strlen(tmpstr) + 1, desc); \ }; /* * Fill an sbuf with density data descriptors. */ static void safilldenstypesb(struct sbuf *sb, int *indent, uint8_t *buf, int buf_len, int is_density) { struct scsi_density_hdr *hdr; uint32_t hdr_len; int len_to_go, cur_offset; int length_offset; int num_reports, need_close; /* * We need at least the header length. Note that this isn't an * error, not all tape drives will have every data type. */ if (buf_len < sizeof(*hdr)) goto bailout; hdr = (struct scsi_density_hdr *)buf; hdr_len = scsi_2btoul(hdr->length); len_to_go = min(buf_len - sizeof(*hdr), hdr_len); if (is_density) { length_offset = __offsetof(struct scsi_density_data, bits_per_mm); } else { length_offset = __offsetof(struct scsi_medium_type_data, num_density_codes); } cur_offset = sizeof(*hdr); num_reports = 0; need_close = 0; while (len_to_go > length_offset) { struct scsi_density_data *dens_data; struct scsi_medium_type_data *type_data; int desc_remain; size_t cur_field_len; dens_data = NULL; type_data = NULL; if (is_density) { dens_data =(struct scsi_density_data *)&buf[cur_offset]; if (dens_data->byte2 & SDD_DLV) desc_remain = scsi_2btoul(dens_data->length); else desc_remain = SDD_DEFAULT_LENGTH - length_offset; } else { type_data = (struct scsi_medium_type_data *) &buf[cur_offset]; desc_remain = scsi_2btoul(type_data->length); } len_to_go -= length_offset; desc_remain = min(desc_remain, len_to_go); cur_offset += length_offset; if (need_close != 0) { SASBENDNODE(sb, *indent, density_entry); } SASBADDNODENUM(sb, *indent, density_entry, num_reports); num_reports++; need_close = 1; if (is_density) { SASBADDUINTDESC(sb, *indent, dens_data->primary_density_code, %u, primary_density_code, "Primary Density Code"); SASBADDUINTDESC(sb, *indent, dens_data->secondary_density_code, %u, secondary_density_code, "Secondary Density Code"); SASBADDUINTDESC(sb, *indent, dens_data->byte2 & ~SDD_DLV, %#x, density_flags, "Density Flags"); SAFILLDENSSB(dens_data, sb, *indent, bits_per_mm, desc_remain, len_to_go, cur_offset, "Bits per mm"); SAFILLDENSSB(dens_data, sb, *indent, media_width, desc_remain, len_to_go, cur_offset, "Media width"); SAFILLDENSSB(dens_data, sb, *indent, tracks, desc_remain, len_to_go, cur_offset, "Number of Tracks"); SAFILLDENSSB(dens_data, sb, *indent, capacity, desc_remain, len_to_go, cur_offset, "Capacity"); SAFILLDENSSBSTR(dens_data, sb, *indent, assigning_org, desc_remain, len_to_go, cur_offset, "Assigning Organization"); SAFILLDENSSBSTR(dens_data, sb, *indent, density_name, desc_remain, len_to_go, cur_offset, "Density Name"); SAFILLDENSSBSTR(dens_data, sb, *indent, description, desc_remain, len_to_go, cur_offset, "Description"); } else { int i; SASBADDUINTDESC(sb, *indent, type_data->medium_type, %u, medium_type, "Medium Type"); cur_field_len = __offsetof(struct scsi_medium_type_data, media_width) - __offsetof(struct scsi_medium_type_data, num_density_codes); if (desc_remain < cur_field_len) { len_to_go -= desc_remain; cur_offset += desc_remain; continue; } len_to_go -= cur_field_len; cur_offset += cur_field_len; desc_remain -= cur_field_len; SASBADDINTDESC(sb, *indent, type_data->num_density_codes, %d, num_density_codes, "Number of Density Codes"); SASBADDNODE(sb, *indent, density_code_list); for (i = 0; i < type_data->num_density_codes; i++) { SASBADDUINTDESC(sb, *indent, type_data->primary_density_codes[i], %u, density_code, "Density Code"); } SASBENDNODE(sb, *indent, density_code_list); SAFILLDENSSB(type_data, sb, *indent, media_width, desc_remain, len_to_go, cur_offset, "Media width"); SAFILLDENSSB(type_data, sb, *indent, medium_length, desc_remain, len_to_go, cur_offset, "Medium length"); /* * Account for the two reserved bytes. */ cur_field_len = sizeof(type_data->reserved2); if (desc_remain < cur_field_len) { len_to_go -= desc_remain; cur_offset += desc_remain; continue; } len_to_go -= cur_field_len; cur_offset += cur_field_len; desc_remain -= cur_field_len; SAFILLDENSSBSTR(type_data, sb, *indent, assigning_org, desc_remain, len_to_go, cur_offset, "Assigning Organization"); SAFILLDENSSBSTR(type_data, sb, *indent, medium_type_name, desc_remain, len_to_go, cur_offset, "Medium type name"); SAFILLDENSSBSTR(type_data, sb, *indent, description, desc_remain, len_to_go, cur_offset, "Description"); } } if (need_close != 0) { SASBENDNODE(sb, *indent, density_entry); } bailout: return; } /* * Fill an sbuf with density data information */ static void safilldensitysb(struct sa_softc *softc, int *indent, struct sbuf *sb) { int i, is_density; SASBADDNODE(sb, *indent, mtdensity); SASBADDUINTDESC(sb, *indent, softc->media_density, %u, media_density, "Current Medium Density"); is_density = 0; for (i = 0; i < SA_DENSITY_TYPES; i++) { int tmpint; if (softc->density_info_valid[i] == 0) continue; SASBADDNODE(sb, *indent, density_report); if (softc->density_type_bits[i] & SRDS_MEDIUM_TYPE) { tmpint = 1; is_density = 0; } else { tmpint = 0; is_density = 1; } SASBADDINTDESC(sb, *indent, tmpint, %d, medium_type_report, "Medium type report"); if (softc->density_type_bits[i] & SRDS_MEDIA) tmpint = 1; else tmpint = 0; SASBADDINTDESC(sb, *indent, tmpint, %d, media_report, "Media report"); safilldenstypesb(sb, indent, softc->density_info[i], softc->density_info_valid[i], is_density); SASBENDNODE(sb, *indent, density_report); } SASBENDNODE(sb, *indent, mtdensity); } #endif /* _KERNEL */ /* * Read tape block limits command. */ void scsi_read_block_limits(struct ccb_scsiio *csio, u_int32_t retries, void (*cbfcnp)(struct cam_periph *, union ccb *), u_int8_t tag_action, struct scsi_read_block_limits_data *rlimit_buf, u_int8_t sense_len, u_int32_t timeout) { struct scsi_read_block_limits *scsi_cmd; cam_fill_csio(csio, retries, cbfcnp, CAM_DIR_IN, tag_action, (u_int8_t *)rlimit_buf, sizeof(*rlimit_buf), sense_len, sizeof(*scsi_cmd), timeout); scsi_cmd = (struct scsi_read_block_limits *)&csio->cdb_io.cdb_bytes; bzero(scsi_cmd, sizeof(*scsi_cmd)); scsi_cmd->opcode = READ_BLOCK_LIMITS; } void scsi_sa_read_write(struct ccb_scsiio *csio, u_int32_t retries, void (*cbfcnp)(struct cam_periph *, union ccb *), u_int8_t tag_action, int readop, int sli, int fixed, u_int32_t length, u_int8_t *data_ptr, u_int32_t dxfer_len, u_int8_t sense_len, u_int32_t timeout) { struct scsi_sa_rw *scsi_cmd; int read; read = (readop & SCSI_RW_DIRMASK) == SCSI_RW_READ; scsi_cmd = (struct scsi_sa_rw *)&csio->cdb_io.cdb_bytes; scsi_cmd->opcode = read ? SA_READ : SA_WRITE; scsi_cmd->sli_fixed = 0; if (sli && read) scsi_cmd->sli_fixed |= SAR_SLI; if (fixed) scsi_cmd->sli_fixed |= SARW_FIXED; scsi_ulto3b(length, scsi_cmd->length); scsi_cmd->control = 0; cam_fill_csio(csio, retries, cbfcnp, (read ? CAM_DIR_IN : CAM_DIR_OUT) | ((readop & SCSI_RW_BIO) != 0 ? CAM_DATA_BIO : 0), tag_action, data_ptr, dxfer_len, sense_len, sizeof(*scsi_cmd), timeout); } void scsi_load_unload(struct ccb_scsiio *csio, u_int32_t retries, void (*cbfcnp)(struct cam_periph *, union ccb *), u_int8_t tag_action, int immediate, int eot, int reten, int load, u_int8_t sense_len, u_int32_t timeout) { struct scsi_load_unload *scsi_cmd; scsi_cmd = (struct scsi_load_unload *)&csio->cdb_io.cdb_bytes; bzero(scsi_cmd, sizeof(*scsi_cmd)); scsi_cmd->opcode = LOAD_UNLOAD; if (immediate) scsi_cmd->immediate = SLU_IMMED; if (eot) scsi_cmd->eot_reten_load |= SLU_EOT; if (reten) scsi_cmd->eot_reten_load |= SLU_RETEN; if (load) scsi_cmd->eot_reten_load |= SLU_LOAD; cam_fill_csio(csio, retries, cbfcnp, CAM_DIR_NONE, tag_action, NULL, 0, sense_len, sizeof(*scsi_cmd), timeout); } void scsi_rewind(struct ccb_scsiio *csio, u_int32_t retries, void (*cbfcnp)(struct cam_periph *, union ccb *), u_int8_t tag_action, int immediate, u_int8_t sense_len, u_int32_t timeout) { struct scsi_rewind *scsi_cmd; scsi_cmd = (struct scsi_rewind *)&csio->cdb_io.cdb_bytes; bzero(scsi_cmd, sizeof(*scsi_cmd)); scsi_cmd->opcode = REWIND; if (immediate) scsi_cmd->immediate = SREW_IMMED; cam_fill_csio(csio, retries, cbfcnp, CAM_DIR_NONE, tag_action, NULL, 0, sense_len, sizeof(*scsi_cmd), timeout); } void scsi_space(struct ccb_scsiio *csio, u_int32_t retries, void (*cbfcnp)(struct cam_periph *, union ccb *), u_int8_t tag_action, scsi_space_code code, u_int32_t count, u_int8_t sense_len, u_int32_t timeout) { struct scsi_space *scsi_cmd; scsi_cmd = (struct scsi_space *)&csio->cdb_io.cdb_bytes; scsi_cmd->opcode = SPACE; scsi_cmd->code = code; scsi_ulto3b(count, scsi_cmd->count); scsi_cmd->control = 0; cam_fill_csio(csio, retries, cbfcnp, CAM_DIR_NONE, tag_action, NULL, 0, sense_len, sizeof(*scsi_cmd), timeout); } void scsi_write_filemarks(struct ccb_scsiio *csio, u_int32_t retries, void (*cbfcnp)(struct cam_periph *, union ccb *), u_int8_t tag_action, int immediate, int setmark, u_int32_t num_marks, u_int8_t sense_len, u_int32_t timeout) { struct scsi_write_filemarks *scsi_cmd; scsi_cmd = (struct scsi_write_filemarks *)&csio->cdb_io.cdb_bytes; bzero(scsi_cmd, sizeof(*scsi_cmd)); scsi_cmd->opcode = WRITE_FILEMARKS; if (immediate) scsi_cmd->byte2 |= SWFMRK_IMMED; if (setmark) scsi_cmd->byte2 |= SWFMRK_WSMK; scsi_ulto3b(num_marks, scsi_cmd->num_marks); cam_fill_csio(csio, retries, cbfcnp, CAM_DIR_NONE, tag_action, NULL, 0, sense_len, sizeof(*scsi_cmd), timeout); } /* * The reserve and release unit commands differ only by their opcodes. */ void scsi_reserve_release_unit(struct ccb_scsiio *csio, u_int32_t retries, void (*cbfcnp)(struct cam_periph *, union ccb *), u_int8_t tag_action, int third_party, int third_party_id, u_int8_t sense_len, u_int32_t timeout, int reserve) { struct scsi_reserve_release_unit *scsi_cmd; scsi_cmd = (struct scsi_reserve_release_unit *)&csio->cdb_io.cdb_bytes; bzero(scsi_cmd, sizeof(*scsi_cmd)); if (reserve) scsi_cmd->opcode = RESERVE_UNIT; else scsi_cmd->opcode = RELEASE_UNIT; if (third_party) { scsi_cmd->lun_thirdparty |= SRRU_3RD_PARTY; scsi_cmd->lun_thirdparty |= ((third_party_id << SRRU_3RD_SHAMT) & SRRU_3RD_MASK); } cam_fill_csio(csio, retries, cbfcnp, CAM_DIR_NONE, tag_action, NULL, 0, sense_len, sizeof(*scsi_cmd), timeout); } void scsi_erase(struct ccb_scsiio *csio, u_int32_t retries, void (*cbfcnp)(struct cam_periph *, union ccb *), u_int8_t tag_action, int immediate, int long_erase, u_int8_t sense_len, u_int32_t timeout) { struct scsi_erase *scsi_cmd; scsi_cmd = (struct scsi_erase *)&csio->cdb_io.cdb_bytes; bzero(scsi_cmd, sizeof(*scsi_cmd)); scsi_cmd->opcode = ERASE; if (immediate) scsi_cmd->lun_imm_long |= SE_IMMED; if (long_erase) scsi_cmd->lun_imm_long |= SE_LONG; cam_fill_csio(csio, retries, cbfcnp, CAM_DIR_NONE, tag_action, NULL, 0, sense_len, sizeof(*scsi_cmd), timeout); } /* * Read Tape Position command. */ void scsi_read_position(struct ccb_scsiio *csio, u_int32_t retries, void (*cbfcnp)(struct cam_periph *, union ccb *), u_int8_t tag_action, int hardsoft, struct scsi_tape_position_data *sbp, u_int8_t sense_len, u_int32_t timeout) { struct scsi_tape_read_position *scmd; cam_fill_csio(csio, retries, cbfcnp, CAM_DIR_IN, tag_action, (u_int8_t *)sbp, sizeof (*sbp), sense_len, sizeof(*scmd), timeout); scmd = (struct scsi_tape_read_position *)&csio->cdb_io.cdb_bytes; bzero(scmd, sizeof(*scmd)); scmd->opcode = READ_POSITION; scmd->byte1 = hardsoft; } /* * Read Tape Position command. */ void scsi_read_position_10(struct ccb_scsiio *csio, u_int32_t retries, void (*cbfcnp)(struct cam_periph *, union ccb *), u_int8_t tag_action, int service_action, u_int8_t *data_ptr, u_int32_t length, u_int32_t sense_len, u_int32_t timeout) { struct scsi_tape_read_position *scmd; cam_fill_csio(csio, retries, cbfcnp, /*flags*/CAM_DIR_IN, tag_action, /*data_ptr*/data_ptr, /*dxfer_len*/length, sense_len, sizeof(*scmd), timeout); scmd = (struct scsi_tape_read_position *)&csio->cdb_io.cdb_bytes; bzero(scmd, sizeof(*scmd)); scmd->opcode = READ_POSITION; scmd->byte1 = service_action; /* * The length is only currently set (as of SSC4r03) if the extended * form is specified. The other forms have fixed lengths. */ if (service_action == SA_RPOS_EXTENDED_FORM) scsi_ulto2b(length, scmd->length); } /* * Set Tape Position command. */ void scsi_set_position(struct ccb_scsiio *csio, u_int32_t retries, void (*cbfcnp)(struct cam_periph *, union ccb *), u_int8_t tag_action, int hardsoft, u_int32_t blkno, u_int8_t sense_len, u_int32_t timeout) { struct scsi_tape_locate *scmd; cam_fill_csio(csio, retries, cbfcnp, CAM_DIR_NONE, tag_action, (u_int8_t *)NULL, 0, sense_len, sizeof(*scmd), timeout); scmd = (struct scsi_tape_locate *)&csio->cdb_io.cdb_bytes; bzero(scmd, sizeof(*scmd)); scmd->opcode = LOCATE; if (hardsoft) scmd->byte1 |= SA_SPOS_BT; scsi_ulto4b(blkno, scmd->blkaddr); } /* * XXX KDM figure out how to make a compatibility function. */ void scsi_locate_10(struct ccb_scsiio *csio, u_int32_t retries, void (*cbfcnp)(struct cam_periph *, union ccb *), u_int8_t tag_action, int immed, int cp, int hard, int64_t partition, u_int32_t block_address, int sense_len, u_int32_t timeout) { struct scsi_tape_locate *scmd; cam_fill_csio(csio, retries, cbfcnp, CAM_DIR_NONE, tag_action, /*data_ptr*/ NULL, /*dxfer_len*/ 0, sense_len, sizeof(*scmd), timeout); scmd = (struct scsi_tape_locate *)&csio->cdb_io.cdb_bytes; bzero(scmd, sizeof(*scmd)); scmd->opcode = LOCATE; if (immed) scmd->byte1 |= SA_SPOS_IMMED; if (cp) scmd->byte1 |= SA_SPOS_CP; if (hard) scmd->byte1 |= SA_SPOS_BT; scsi_ulto4b(block_address, scmd->blkaddr); scmd->partition = partition; } void scsi_locate_16(struct ccb_scsiio *csio, u_int32_t retries, void (*cbfcnp)(struct cam_periph *, union ccb *), u_int8_t tag_action, int immed, int cp, u_int8_t dest_type, int bam, int64_t partition, u_int64_t logical_id, int sense_len, u_int32_t timeout) { struct scsi_locate_16 *scsi_cmd; cam_fill_csio(csio, retries, cbfcnp, /*flags*/CAM_DIR_NONE, tag_action, /*data_ptr*/NULL, /*dxfer_len*/0, sense_len, sizeof(*scsi_cmd), timeout); scsi_cmd = (struct scsi_locate_16 *)&csio->cdb_io.cdb_bytes; bzero(scsi_cmd, sizeof(*scsi_cmd)); scsi_cmd->opcode = LOCATE_16; if (immed) scsi_cmd->byte1 |= SA_LC_IMMEDIATE; if (cp) scsi_cmd->byte1 |= SA_LC_CP; scsi_cmd->byte1 |= (dest_type << SA_LC_DEST_TYPE_SHIFT); scsi_cmd->byte2 |= bam; scsi_cmd->partition = partition; scsi_u64to8b(logical_id, scsi_cmd->logical_id); } void scsi_report_density_support(struct ccb_scsiio *csio, u_int32_t retries, void (*cbfcnp)(struct cam_periph *, union ccb *), u_int8_t tag_action, int media, int medium_type, u_int8_t *data_ptr, u_int32_t length, u_int32_t sense_len, u_int32_t timeout) { struct scsi_report_density_support *scsi_cmd; scsi_cmd =(struct scsi_report_density_support *)&csio->cdb_io.cdb_bytes; bzero(scsi_cmd, sizeof(*scsi_cmd)); scsi_cmd->opcode = REPORT_DENSITY_SUPPORT; if (media != 0) scsi_cmd->byte1 |= SRDS_MEDIA; if (medium_type != 0) scsi_cmd->byte1 |= SRDS_MEDIUM_TYPE; scsi_ulto2b(length, scsi_cmd->length); cam_fill_csio(csio, retries, cbfcnp, /*flags*/CAM_DIR_IN, tag_action, /*data_ptr*/data_ptr, /*dxfer_len*/length, sense_len, sizeof(*scsi_cmd), timeout); } void scsi_set_capacity(struct ccb_scsiio *csio, u_int32_t retries, void (*cbfcnp)(struct cam_periph *, union ccb *), u_int8_t tag_action, int byte1, u_int32_t proportion, u_int32_t sense_len, u_int32_t timeout) { struct scsi_set_capacity *scsi_cmd; scsi_cmd = (struct scsi_set_capacity *)&csio->cdb_io.cdb_bytes; bzero(scsi_cmd, sizeof(*scsi_cmd)); scsi_cmd->opcode = SET_CAPACITY; scsi_cmd->byte1 = byte1; scsi_ulto2b(proportion, scsi_cmd->cap_proportion); cam_fill_csio(csio, retries, cbfcnp, /*flags*/CAM_DIR_NONE, tag_action, /*data_ptr*/NULL, /*dxfer_len*/0, sense_len, sizeof(*scsi_cmd), timeout); } void scsi_format_medium(struct ccb_scsiio *csio, u_int32_t retries, void (*cbfcnp)(struct cam_periph *, union ccb *), u_int8_t tag_action, int byte1, int byte2, u_int8_t *data_ptr, u_int32_t dxfer_len, u_int32_t sense_len, u_int32_t timeout) { struct scsi_format_medium *scsi_cmd; scsi_cmd = (struct scsi_format_medium*)&csio->cdb_io.cdb_bytes; bzero(scsi_cmd, sizeof(*scsi_cmd)); scsi_cmd->opcode = FORMAT_MEDIUM; scsi_cmd->byte1 = byte1; scsi_cmd->byte2 = byte2; scsi_ulto2b(dxfer_len, scsi_cmd->length); cam_fill_csio(csio, retries, cbfcnp, /*flags*/(dxfer_len > 0) ? CAM_DIR_OUT : CAM_DIR_NONE, tag_action, /*data_ptr*/ data_ptr, /*dxfer_len*/ dxfer_len, sense_len, sizeof(*scsi_cmd), timeout); } void scsi_allow_overwrite(struct ccb_scsiio *csio, u_int32_t retries, void (*cbfcnp)(struct cam_periph *, union ccb *), u_int8_t tag_action, int allow_overwrite, int partition, u_int64_t logical_id, u_int32_t sense_len, u_int32_t timeout) { struct scsi_allow_overwrite *scsi_cmd; scsi_cmd = (struct scsi_allow_overwrite *)&csio->cdb_io.cdb_bytes; bzero(scsi_cmd, sizeof(*scsi_cmd)); scsi_cmd->opcode = ALLOW_OVERWRITE; scsi_cmd->allow_overwrite = allow_overwrite; scsi_cmd->partition = partition; scsi_u64to8b(logical_id, scsi_cmd->logical_id); cam_fill_csio(csio, retries, cbfcnp, CAM_DIR_NONE, tag_action, /*data_ptr*/ NULL, /*dxfer_len*/ 0, sense_len, sizeof(*scsi_cmd), timeout); } diff --git a/sys/cam/scsi/scsi_xpt.c b/sys/cam/scsi/scsi_xpt.c index 0f44cf55a473..7cffd67955f5 100644 --- a/sys/cam/scsi/scsi_xpt.c +++ b/sys/cam/scsi/scsi_xpt.c @@ -1,3085 +1,3086 @@ /*- * Implementation of the SCSI Transport * * Copyright (c) 1997, 1998, 1999 Justin T. Gibbs. * Copyright (c) 1997, 1998, 1999 Kenneth D. Merry. * 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, * without modification, immediately at the beginning of the file. * 2. 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 AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* for xpt_print below */ #include "opt_cam.h" struct scsi_quirk_entry { struct scsi_inquiry_pattern inq_pat; u_int8_t quirks; #define CAM_QUIRK_NOLUNS 0x01 #define CAM_QUIRK_NOVPDS 0x02 #define CAM_QUIRK_HILUNS 0x04 #define CAM_QUIRK_NOHILUNS 0x08 #define CAM_QUIRK_NORPTLUNS 0x10 u_int mintags; u_int maxtags; }; #define SCSI_QUIRK(dev) ((struct scsi_quirk_entry *)((dev)->quirk)) static int cam_srch_hi = 0; static int sysctl_cam_search_luns(SYSCTL_HANDLER_ARGS); SYSCTL_PROC(_kern_cam, OID_AUTO, cam_srch_hi, CTLTYPE_INT | CTLFLAG_RWTUN, 0, 0, sysctl_cam_search_luns, "I", "allow search above LUN 7 for SCSI3 and greater devices"); #define CAM_SCSI2_MAXLUN 8 #define CAM_CAN_GET_SIMPLE_LUN(x, i) \ ((((x)->luns[i].lundata[0] & RPL_LUNDATA_ATYP_MASK) == \ RPL_LUNDATA_ATYP_PERIPH) || \ (((x)->luns[i].lundata[0] & RPL_LUNDATA_ATYP_MASK) == \ RPL_LUNDATA_ATYP_FLAT)) #define CAM_GET_SIMPLE_LUN(lp, i, lval) \ if (((lp)->luns[(i)].lundata[0] & RPL_LUNDATA_ATYP_MASK) == \ RPL_LUNDATA_ATYP_PERIPH) { \ (lval) = (lp)->luns[(i)].lundata[1]; \ } else { \ (lval) = (lp)->luns[(i)].lundata[0]; \ (lval) &= RPL_LUNDATA_FLAT_LUN_MASK; \ (lval) <<= 8; \ (lval) |= (lp)->luns[(i)].lundata[1]; \ } #define CAM_GET_LUN(lp, i, lval) \ (lval) = scsi_8btou64((lp)->luns[(i)].lundata); \ (lval) = CAM_EXTLUN_BYTE_SWIZZLE(lval); /* * If we're not quirked to search <= the first 8 luns * and we are either quirked to search above lun 8, * or we're > SCSI-2 and we've enabled hilun searching, * or we're > SCSI-2 and the last lun was a success, * we can look for luns above lun 8. */ #define CAN_SRCH_HI_SPARSE(dv) \ (((SCSI_QUIRK(dv)->quirks & CAM_QUIRK_NOHILUNS) == 0) \ && ((SCSI_QUIRK(dv)->quirks & CAM_QUIRK_HILUNS) \ || (SID_ANSI_REV(&dv->inq_data) > SCSI_REV_2 && cam_srch_hi))) #define CAN_SRCH_HI_DENSE(dv) \ (((SCSI_QUIRK(dv)->quirks & CAM_QUIRK_NOHILUNS) == 0) \ && ((SCSI_QUIRK(dv)->quirks & CAM_QUIRK_HILUNS) \ || (SID_ANSI_REV(&dv->inq_data) > SCSI_REV_2))) static periph_init_t probe_periph_init; static struct periph_driver probe_driver = { probe_periph_init, "probe", TAILQ_HEAD_INITIALIZER(probe_driver.units), /* generation */ 0, CAM_PERIPH_DRV_EARLY }; PERIPHDRIVER_DECLARE(probe, probe_driver); typedef enum { PROBE_TUR, PROBE_INQUIRY, /* this counts as DV0 for Basic Domain Validation */ PROBE_FULL_INQUIRY, PROBE_REPORT_LUNS, PROBE_MODE_SENSE, PROBE_SUPPORTED_VPD_LIST, PROBE_DEVICE_ID, PROBE_EXTENDED_INQUIRY, PROBE_SERIAL_NUM, PROBE_TUR_FOR_NEGOTIATION, PROBE_INQUIRY_BASIC_DV1, PROBE_INQUIRY_BASIC_DV2, PROBE_DV_EXIT, PROBE_DONE, PROBE_INVALID } probe_action; static char *probe_action_text[] = { "PROBE_TUR", "PROBE_INQUIRY", "PROBE_FULL_INQUIRY", "PROBE_REPORT_LUNS", "PROBE_MODE_SENSE", "PROBE_SUPPORTED_VPD_LIST", "PROBE_DEVICE_ID", "PROBE_EXTENDED_INQUIRY", "PROBE_SERIAL_NUM", "PROBE_TUR_FOR_NEGOTIATION", "PROBE_INQUIRY_BASIC_DV1", "PROBE_INQUIRY_BASIC_DV2", "PROBE_DV_EXIT", "PROBE_DONE", "PROBE_INVALID" }; #define PROBE_SET_ACTION(softc, newaction) \ do { \ char **text; \ text = probe_action_text; \ CAM_DEBUG((softc)->periph->path, CAM_DEBUG_PROBE, \ ("Probe %s to %s\n", text[(softc)->action], \ text[(newaction)])); \ (softc)->action = (newaction); \ } while(0) typedef enum { PROBE_INQUIRY_CKSUM = 0x01, PROBE_SERIAL_CKSUM = 0x02, PROBE_NO_ANNOUNCE = 0x04, PROBE_EXTLUN = 0x08 } probe_flags; typedef struct { TAILQ_HEAD(, ccb_hdr) request_ccbs; probe_action action; union ccb saved_ccb; probe_flags flags; MD5_CTX context; u_int8_t digest[16]; struct cam_periph *periph; } probe_softc; static const char quantum[] = "QUANTUM"; static const char sony[] = "SONY"; static const char west_digital[] = "WDIGTL"; static const char samsung[] = "SAMSUNG"; static const char seagate[] = "SEAGATE"; static const char microp[] = "MICROP"; static struct scsi_quirk_entry scsi_quirk_table[] = { { /* Reports QUEUE FULL for temporary resource shortages */ { T_DIRECT, SIP_MEDIA_FIXED, quantum, "XP39100*", "*" }, /*quirks*/0, /*mintags*/24, /*maxtags*/32 }, { /* Reports QUEUE FULL for temporary resource shortages */ { T_DIRECT, SIP_MEDIA_FIXED, quantum, "XP34550*", "*" }, /*quirks*/0, /*mintags*/24, /*maxtags*/32 }, { /* Reports QUEUE FULL for temporary resource shortages */ { T_DIRECT, SIP_MEDIA_FIXED, quantum, "XP32275*", "*" }, /*quirks*/0, /*mintags*/24, /*maxtags*/32 }, { /* Broken tagged queuing drive */ { T_DIRECT, SIP_MEDIA_FIXED, microp, "4421-07*", "*" }, /*quirks*/0, /*mintags*/0, /*maxtags*/0 }, { /* Broken tagged queuing drive */ { T_DIRECT, SIP_MEDIA_FIXED, "HP", "C372*", "*" }, /*quirks*/0, /*mintags*/0, /*maxtags*/0 }, { /* Broken tagged queuing drive */ { T_DIRECT, SIP_MEDIA_FIXED, microp, "3391*", "x43h" }, /*quirks*/0, /*mintags*/0, /*maxtags*/0 }, { /* * Unfortunately, the Quantum Atlas III has the same * problem as the Atlas II drives above. * Reported by: "Johan Granlund" * * For future reference, the drive with the problem was: * QUANTUM QM39100TD-SW N1B0 * * It's possible that Quantum will fix the problem in later * firmware revisions. If that happens, the quirk entry * will need to be made specific to the firmware revisions * with the problem. * */ /* Reports QUEUE FULL for temporary resource shortages */ { T_DIRECT, SIP_MEDIA_FIXED, quantum, "QM39100*", "*" }, /*quirks*/0, /*mintags*/24, /*maxtags*/32 }, { /* * 18 Gig Atlas III, same problem as the 9G version. * Reported by: Andre Albsmeier * * * For future reference, the drive with the problem was: * QUANTUM QM318000TD-S N491 */ /* Reports QUEUE FULL for temporary resource shortages */ { T_DIRECT, SIP_MEDIA_FIXED, quantum, "QM318000*", "*" }, /*quirks*/0, /*mintags*/24, /*maxtags*/32 }, { /* * Broken tagged queuing drive * Reported by: Bret Ford * and: Martin Renters */ { T_DIRECT, SIP_MEDIA_FIXED, seagate, "ST410800*", "71*" }, /*quirks*/0, /*mintags*/0, /*maxtags*/0 }, /* * The Seagate Medalist Pro drives have very poor write * performance with anything more than 2 tags. * * Reported by: Paul van der Zwan * Drive: * * Reported by: Jeremy Lea * Drive: * * No one has actually reported that the 9G version * (ST39140*) of the Medalist Pro has the same problem, but * we're assuming that it does because the 4G and 6.5G * versions of the drive are broken. */ { { T_DIRECT, SIP_MEDIA_FIXED, seagate, "ST34520*", "*"}, /*quirks*/0, /*mintags*/2, /*maxtags*/2 }, { { T_DIRECT, SIP_MEDIA_FIXED, seagate, "ST36530*", "*"}, /*quirks*/0, /*mintags*/2, /*maxtags*/2 }, { { T_DIRECT, SIP_MEDIA_FIXED, seagate, "ST39140*", "*"}, /*quirks*/0, /*mintags*/2, /*maxtags*/2 }, { /* * Experiences command timeouts under load with a * tag count higher than 55. */ { T_DIRECT, SIP_MEDIA_FIXED, seagate, "ST3146855LW", "*"}, /*quirks*/0, /*mintags*/2, /*maxtags*/55 }, { /* * Slow when tagged queueing is enabled. Write performance * steadily drops off with more and more concurrent * transactions. Best sequential write performance with * tagged queueing turned off and write caching turned on. * * PR: kern/10398 * Submitted by: Hideaki Okada * Drive: DCAS-34330 w/ "S65A" firmware. * * The drive with the problem had the "S65A" firmware * revision, and has also been reported (by Stephen J. * Roznowski ) for a drive with the "S61A" * firmware revision. * * Although no one has reported problems with the 2 gig * version of the DCAS drive, the assumption is that it * has the same problems as the 4 gig version. Therefore * this quirk entries disables tagged queueing for all * DCAS drives. */ { T_DIRECT, SIP_MEDIA_FIXED, "IBM", "DCAS*", "*" }, /*quirks*/0, /*mintags*/0, /*maxtags*/0 }, { /* Broken tagged queuing drive */ { T_DIRECT, SIP_MEDIA_REMOVABLE, "iomega", "jaz*", "*" }, /*quirks*/0, /*mintags*/0, /*maxtags*/0 }, { /* Broken tagged queuing drive */ { T_DIRECT, SIP_MEDIA_FIXED, "CONNER", "CFP2107*", "*" }, /*quirks*/0, /*mintags*/0, /*maxtags*/0 }, { /* This does not support other than LUN 0 */ { T_DIRECT, SIP_MEDIA_FIXED, "VMware*", "*", "*" }, CAM_QUIRK_NOLUNS, /*mintags*/2, /*maxtags*/255 }, { /* * Broken tagged queuing drive. * Submitted by: * NAKAJI Hiroyuki * in PR kern/9535 */ { T_DIRECT, SIP_MEDIA_FIXED, samsung, "WN34324U*", "*" }, /*quirks*/0, /*mintags*/0, /*maxtags*/0 }, { /* * Slow when tagged queueing is enabled. (1.5MB/sec versus * 8MB/sec.) * Submitted by: Andrew Gallatin * Best performance with these drives is achieved with * tagged queueing turned off, and write caching turned on. */ { T_DIRECT, SIP_MEDIA_FIXED, west_digital, "WDE*", "*" }, /*quirks*/0, /*mintags*/0, /*maxtags*/0 }, { /* * Slow when tagged queueing is enabled. (1.5MB/sec versus * 8MB/sec.) * Submitted by: Andrew Gallatin * Best performance with these drives is achieved with * tagged queueing turned off, and write caching turned on. */ { T_DIRECT, SIP_MEDIA_FIXED, west_digital, "ENTERPRISE", "*" }, /*quirks*/0, /*mintags*/0, /*maxtags*/0 }, { /* * Doesn't handle queue full condition correctly, * so we need to limit maxtags to what the device * can handle instead of determining this automatically. */ { T_DIRECT, SIP_MEDIA_FIXED, samsung, "WN321010S*", "*" }, /*quirks*/0, /*mintags*/2, /*maxtags*/32 }, { /* Really only one LUN */ { T_ENCLOSURE, SIP_MEDIA_FIXED, "SUN", "SENA", "*" }, CAM_QUIRK_NOLUNS, /*mintags*/0, /*maxtags*/0 }, { /* I can't believe we need a quirk for DPT volumes. */ { T_ANY, SIP_MEDIA_FIXED|SIP_MEDIA_REMOVABLE, "DPT", "*", "*" }, CAM_QUIRK_NOLUNS, /*mintags*/0, /*maxtags*/255 }, { /* * Many Sony CDROM drives don't like multi-LUN probing. */ { T_CDROM, SIP_MEDIA_REMOVABLE, sony, "CD-ROM CDU*", "*" }, CAM_QUIRK_NOLUNS, /*mintags*/0, /*maxtags*/0 }, { /* * This drive doesn't like multiple LUN probing. * Submitted by: Parag Patel */ { T_WORM, SIP_MEDIA_REMOVABLE, sony, "CD-R CDU9*", "*" }, CAM_QUIRK_NOLUNS, /*mintags*/0, /*maxtags*/0 }, { { T_WORM, SIP_MEDIA_REMOVABLE, "YAMAHA", "CDR100*", "*" }, CAM_QUIRK_NOLUNS, /*mintags*/0, /*maxtags*/0 }, { /* * The 8200 doesn't like multi-lun probing, and probably * don't like serial number requests either. */ { T_SEQUENTIAL, SIP_MEDIA_REMOVABLE, "EXABYTE", "EXB-8200*", "*" }, CAM_QUIRK_NOLUNS, /*mintags*/0, /*maxtags*/0 }, { /* * Let's try the same as above, but for a drive that says * it's an IPL-6860 but is actually an EXB 8200. */ { T_SEQUENTIAL, SIP_MEDIA_REMOVABLE, "EXABYTE", "IPL-6860*", "*" }, CAM_QUIRK_NOLUNS, /*mintags*/0, /*maxtags*/0 }, { /* * These Hitachi drives don't like multi-lun probing. * The PR submitter has a DK319H, but says that the Linux * kernel has a similar work-around for the DK312 and DK314, * so all DK31* drives are quirked here. * PR: misc/18793 * Submitted by: Paul Haddad */ { T_DIRECT, SIP_MEDIA_FIXED, "HITACHI", "DK31*", "*" }, CAM_QUIRK_NOLUNS, /*mintags*/2, /*maxtags*/255 }, { /* * The Hitachi CJ series with J8A8 firmware apparantly has * problems with tagged commands. * PR: 23536 * Reported by: amagai@nue.org */ { T_DIRECT, SIP_MEDIA_FIXED, "HITACHI", "DK32CJ*", "J8A8" }, CAM_QUIRK_NOLUNS, /*mintags*/0, /*maxtags*/0 }, { /* * These are the large storage arrays. * Submitted by: William Carrel */ { T_DIRECT, SIP_MEDIA_FIXED, "HITACHI", "OPEN*", "*" }, CAM_QUIRK_HILUNS, 2, 1024 }, { /* * This old revision of the TDC3600 is also SCSI-1, and * hangs upon serial number probing. */ { T_SEQUENTIAL, SIP_MEDIA_REMOVABLE, "TANDBERG", " TDC 3600", "U07:" }, CAM_QUIRK_NOVPDS, /*mintags*/0, /*maxtags*/0 }, { /* * Would repond to all LUNs if asked for. */ { T_SEQUENTIAL, SIP_MEDIA_REMOVABLE, "CALIPER", "CP150", "*" }, CAM_QUIRK_NOLUNS, /*mintags*/0, /*maxtags*/0 }, { /* * Would repond to all LUNs if asked for. */ { T_SEQUENTIAL, SIP_MEDIA_REMOVABLE, "KENNEDY", "96X2*", "*" }, CAM_QUIRK_NOLUNS, /*mintags*/0, /*maxtags*/0 }, { /* Submitted by: Matthew Dodd */ { T_PROCESSOR, SIP_MEDIA_FIXED, "Cabletrn", "EA41*", "*" }, CAM_QUIRK_NOLUNS, /*mintags*/0, /*maxtags*/0 }, { /* Submitted by: Matthew Dodd */ { T_PROCESSOR, SIP_MEDIA_FIXED, "CABLETRN", "EA41*", "*" }, CAM_QUIRK_NOLUNS, /*mintags*/0, /*maxtags*/0 }, { /* TeraSolutions special settings for TRC-22 RAID */ { T_DIRECT, SIP_MEDIA_FIXED, "TERASOLU", "TRC-22", "*" }, /*quirks*/0, /*mintags*/55, /*maxtags*/255 }, { /* Veritas Storage Appliance */ { T_DIRECT, SIP_MEDIA_FIXED, "VERITAS", "*", "*" }, CAM_QUIRK_HILUNS, /*mintags*/2, /*maxtags*/1024 }, { /* * Would respond to all LUNs. Device type and removable * flag are jumper-selectable. */ { T_ANY, SIP_MEDIA_REMOVABLE|SIP_MEDIA_FIXED, "MaxOptix", "Tahiti 1", "*" }, CAM_QUIRK_NOLUNS, /*mintags*/0, /*maxtags*/0 }, { /* EasyRAID E5A aka. areca ARC-6010 */ { T_DIRECT, SIP_MEDIA_FIXED, "easyRAID", "*", "*" }, CAM_QUIRK_NOHILUNS, /*mintags*/2, /*maxtags*/255 }, { { T_ENCLOSURE, SIP_MEDIA_FIXED, "DP", "BACKPLANE", "*" }, CAM_QUIRK_NOLUNS, /*mintags*/0, /*maxtags*/0 }, { { T_DIRECT, SIP_MEDIA_REMOVABLE, "Garmin", "*", "*" }, CAM_QUIRK_NORPTLUNS, /*mintags*/2, /*maxtags*/255 }, { /* Default tagged queuing parameters for all devices */ { T_ANY, SIP_MEDIA_REMOVABLE|SIP_MEDIA_FIXED, /*vendor*/"*", /*product*/"*", /*revision*/"*" }, /*quirks*/0, /*mintags*/2, /*maxtags*/255 }, }; static const int scsi_quirk_table_size = sizeof(scsi_quirk_table) / sizeof(*scsi_quirk_table); static cam_status proberegister(struct cam_periph *periph, void *arg); static void probeschedule(struct cam_periph *probe_periph); static void probestart(struct cam_periph *periph, union ccb *start_ccb); static void proberequestdefaultnegotiation(struct cam_periph *periph); static int proberequestbackoff(struct cam_periph *periph, struct cam_ed *device); static void probedone(struct cam_periph *periph, union ccb *done_ccb); static void probe_purge_old(struct cam_path *path, struct scsi_report_luns_data *new, probe_flags flags); static void probecleanup(struct cam_periph *periph); static void scsi_find_quirk(struct cam_ed *device); static void scsi_scan_bus(struct cam_periph *periph, union ccb *ccb); static void scsi_scan_lun(struct cam_periph *periph, struct cam_path *path, cam_flags flags, union ccb *ccb); static void xptscandone(struct cam_periph *periph, union ccb *done_ccb); static struct cam_ed * scsi_alloc_device(struct cam_eb *bus, struct cam_et *target, lun_id_t lun_id); static void scsi_devise_transport(struct cam_path *path); static void scsi_set_transfer_settings(struct ccb_trans_settings *cts, struct cam_path *path, int async_update); static void scsi_toggle_tags(struct cam_path *path); static void scsi_dev_async(u_int32_t async_code, struct cam_eb *bus, struct cam_et *target, struct cam_ed *device, void *async_arg); static void scsi_action(union ccb *start_ccb); static void scsi_announce_periph(struct cam_periph *periph); static struct xpt_xport scsi_xport = { .alloc_device = scsi_alloc_device, .action = scsi_action, .async = scsi_dev_async, .announce = scsi_announce_periph, }; struct xpt_xport * scsi_get_xport(void) { return (&scsi_xport); } static void probe_periph_init() { } static cam_status proberegister(struct cam_periph *periph, void *arg) { union ccb *request_ccb; /* CCB representing the probe request */ cam_status status; probe_softc *softc; request_ccb = (union ccb *)arg; if (request_ccb == NULL) { printf("proberegister: no probe CCB, " "can't register device\n"); return(CAM_REQ_CMP_ERR); } softc = (probe_softc *)malloc(sizeof(*softc), M_CAMXPT, M_NOWAIT); if (softc == NULL) { printf("proberegister: Unable to probe new device. " "Unable to allocate softc\n"); return(CAM_REQ_CMP_ERR); } TAILQ_INIT(&softc->request_ccbs); TAILQ_INSERT_TAIL(&softc->request_ccbs, &request_ccb->ccb_h, periph_links.tqe); softc->flags = 0; periph->softc = softc; softc->periph = periph; softc->action = PROBE_INVALID; status = cam_periph_acquire(periph); if (status != CAM_REQ_CMP) { return (status); } CAM_DEBUG(periph->path, CAM_DEBUG_PROBE, ("Probe started\n")); scsi_devise_transport(periph->path); /* * Ensure we've waited at least a bus settle * delay before attempting to probe the device. * For HBAs that don't do bus resets, this won't make a difference. */ cam_periph_freeze_after_event(periph, &periph->path->bus->last_reset, scsi_delay); probeschedule(periph); return(CAM_REQ_CMP); } static void probeschedule(struct cam_periph *periph) { struct ccb_pathinq cpi; union ccb *ccb; probe_softc *softc; softc = (probe_softc *)periph->softc; ccb = (union ccb *)TAILQ_FIRST(&softc->request_ccbs); xpt_setup_ccb(&cpi.ccb_h, periph->path, CAM_PRIORITY_NONE); cpi.ccb_h.func_code = XPT_PATH_INQ; xpt_action((union ccb *)&cpi); /* * If a device has gone away and another device, or the same one, * is back in the same place, it should have a unit attention * condition pending. It will not report the unit attention in * response to an inquiry, which may leave invalid transfer * negotiations in effect. The TUR will reveal the unit attention * condition. Only send the TUR for lun 0, since some devices * will get confused by commands other than inquiry to non-existent * luns. If you think a device has gone away start your scan from * lun 0. This will insure that any bogus transfer settings are * invalidated. * * If we haven't seen the device before and the controller supports * some kind of transfer negotiation, negotiate with the first * sent command if no bus reset was performed at startup. This * ensures that the device is not confused by transfer negotiation * settings left over by loader or BIOS action. */ if (((ccb->ccb_h.path->device->flags & CAM_DEV_UNCONFIGURED) == 0) && (ccb->ccb_h.target_lun == 0)) { PROBE_SET_ACTION(softc, PROBE_TUR); } else if ((cpi.hba_inquiry & (PI_WIDE_32|PI_WIDE_16|PI_SDTR_ABLE)) != 0 && (cpi.hba_misc & PIM_NOBUSRESET) != 0) { proberequestdefaultnegotiation(periph); PROBE_SET_ACTION(softc, PROBE_INQUIRY); } else { PROBE_SET_ACTION(softc, PROBE_INQUIRY); } if (ccb->crcn.flags & CAM_EXPECT_INQ_CHANGE) softc->flags |= PROBE_NO_ANNOUNCE; else softc->flags &= ~PROBE_NO_ANNOUNCE; if (cpi.hba_misc & PIM_EXTLUNS) softc->flags |= PROBE_EXTLUN; else softc->flags &= ~PROBE_EXTLUN; xpt_schedule(periph, CAM_PRIORITY_XPT); } static void probestart(struct cam_periph *periph, union ccb *start_ccb) { /* Probe the device that our peripheral driver points to */ struct ccb_scsiio *csio; probe_softc *softc; CAM_DEBUG(start_ccb->ccb_h.path, CAM_DEBUG_TRACE, ("probestart\n")); softc = (probe_softc *)periph->softc; csio = &start_ccb->csio; again: switch (softc->action) { case PROBE_TUR: case PROBE_TUR_FOR_NEGOTIATION: case PROBE_DV_EXIT: { scsi_test_unit_ready(csio, /*retries*/4, probedone, MSG_SIMPLE_Q_TAG, SSD_FULL_SIZE, /*timeout*/60000); break; } case PROBE_INQUIRY: case PROBE_FULL_INQUIRY: case PROBE_INQUIRY_BASIC_DV1: case PROBE_INQUIRY_BASIC_DV2: { u_int inquiry_len; struct scsi_inquiry_data *inq_buf; inq_buf = &periph->path->device->inq_data; /* * If the device is currently configured, we calculate an * MD5 checksum of the inquiry data, and if the serial number * length is greater than 0, add the serial number data * into the checksum as well. Once the inquiry and the * serial number check finish, we attempt to figure out * whether we still have the same device. */ if (((periph->path->device->flags & CAM_DEV_UNCONFIGURED) == 0) && ((softc->flags & PROBE_INQUIRY_CKSUM) == 0)) { MD5Init(&softc->context); MD5Update(&softc->context, (unsigned char *)inq_buf, sizeof(struct scsi_inquiry_data)); softc->flags |= PROBE_INQUIRY_CKSUM; if (periph->path->device->serial_num_len > 0) { MD5Update(&softc->context, periph->path->device->serial_num, periph->path->device->serial_num_len); softc->flags |= PROBE_SERIAL_CKSUM; } MD5Final(softc->digest, &softc->context); } if (softc->action == PROBE_INQUIRY) inquiry_len = SHORT_INQUIRY_LENGTH; else inquiry_len = SID_ADDITIONAL_LENGTH(inq_buf); /* * Some parallel SCSI devices fail to send an * ignore wide residue message when dealing with * odd length inquiry requests. Round up to be * safe. */ inquiry_len = roundup2(inquiry_len, 2); if (softc->action == PROBE_INQUIRY_BASIC_DV1 || softc->action == PROBE_INQUIRY_BASIC_DV2) { inq_buf = malloc(inquiry_len, M_CAMXPT, M_NOWAIT); } if (inq_buf == NULL) { xpt_print(periph->path, "malloc failure- skipping Basic" "Domain Validation\n"); PROBE_SET_ACTION(softc, PROBE_DV_EXIT); scsi_test_unit_ready(csio, /*retries*/4, probedone, MSG_SIMPLE_Q_TAG, SSD_FULL_SIZE, /*timeout*/60000); break; } scsi_inquiry(csio, /*retries*/4, probedone, MSG_SIMPLE_Q_TAG, (u_int8_t *)inq_buf, inquiry_len, /*evpd*/FALSE, /*page_code*/0, SSD_MIN_SIZE, /*timeout*/60 * 1000); break; } case PROBE_REPORT_LUNS: { void *rp; rp = malloc(periph->path->target->rpl_size, M_CAMXPT, M_NOWAIT | M_ZERO); if (rp == NULL) { struct scsi_inquiry_data *inq_buf; inq_buf = &periph->path->device->inq_data; xpt_print(periph->path, "Unable to alloc report luns storage\n"); if (INQ_DATA_TQ_ENABLED(inq_buf)) PROBE_SET_ACTION(softc, PROBE_MODE_SENSE); else PROBE_SET_ACTION(softc, PROBE_SUPPORTED_VPD_LIST); goto again; } scsi_report_luns(csio, 5, probedone, MSG_SIMPLE_Q_TAG, RPL_REPORT_DEFAULT, rp, periph->path->target->rpl_size, SSD_FULL_SIZE, 60000); break; break; } case PROBE_MODE_SENSE: { void *mode_buf; int mode_buf_len; mode_buf_len = sizeof(struct scsi_mode_header_6) + sizeof(struct scsi_mode_blk_desc) + sizeof(struct scsi_control_page); mode_buf = malloc(mode_buf_len, M_CAMXPT, M_NOWAIT); if (mode_buf != NULL) { scsi_mode_sense(csio, /*retries*/4, probedone, MSG_SIMPLE_Q_TAG, /*dbd*/FALSE, SMS_PAGE_CTRL_CURRENT, SMS_CONTROL_MODE_PAGE, mode_buf, mode_buf_len, SSD_FULL_SIZE, /*timeout*/60000); break; } xpt_print(periph->path, "Unable to mode sense control page - " "malloc failure\n"); PROBE_SET_ACTION(softc, PROBE_SUPPORTED_VPD_LIST); } /* FALLTHROUGH */ case PROBE_SUPPORTED_VPD_LIST: { struct scsi_vpd_supported_page_list *vpd_list; struct cam_ed *device; vpd_list = NULL; device = periph->path->device; if ((SCSI_QUIRK(device)->quirks & CAM_QUIRK_NOVPDS) == 0) vpd_list = malloc(sizeof(*vpd_list), M_CAMXPT, M_NOWAIT | M_ZERO); if (vpd_list != NULL) { scsi_inquiry(csio, /*retries*/4, probedone, MSG_SIMPLE_Q_TAG, (u_int8_t *)vpd_list, sizeof(*vpd_list), /*evpd*/TRUE, SVPD_SUPPORTED_PAGE_LIST, SSD_MIN_SIZE, /*timeout*/60 * 1000); break; } done: /* * We'll have to do without, let our probedone * routine finish up for us. */ start_ccb->csio.data_ptr = NULL; cam_freeze_devq(periph->path); cam_periph_doacquire(periph); probedone(periph, start_ccb); return; } case PROBE_DEVICE_ID: { struct scsi_vpd_device_id *devid; devid = NULL; if (scsi_vpd_supported_page(periph, SVPD_DEVICE_ID)) devid = malloc(SVPD_DEVICE_ID_MAX_SIZE, M_CAMXPT, M_NOWAIT | M_ZERO); if (devid != NULL) { scsi_inquiry(csio, /*retries*/4, probedone, MSG_SIMPLE_Q_TAG, (uint8_t *)devid, SVPD_DEVICE_ID_MAX_SIZE, /*evpd*/TRUE, SVPD_DEVICE_ID, SSD_MIN_SIZE, /*timeout*/60 * 1000); break; } goto done; } case PROBE_EXTENDED_INQUIRY: { struct scsi_vpd_extended_inquiry_data *ext_inq; ext_inq = NULL; if (scsi_vpd_supported_page(periph, SVPD_EXTENDED_INQUIRY_DATA)) ext_inq = malloc(sizeof(*ext_inq), M_CAMXPT, M_NOWAIT | M_ZERO); if (ext_inq != NULL) { scsi_inquiry(csio, /*retries*/4, probedone, MSG_SIMPLE_Q_TAG, (uint8_t *)ext_inq, sizeof(*ext_inq), /*evpd*/TRUE, SVPD_EXTENDED_INQUIRY_DATA, SSD_MIN_SIZE, /*timeout*/60 * 1000); break; } /* * We'll have to do without, let our probedone * routine finish up for us. */ goto done; } case PROBE_SERIAL_NUM: { struct scsi_vpd_unit_serial_number *serial_buf; struct cam_ed* device; serial_buf = NULL; device = periph->path->device; if (device->serial_num != NULL) { free(device->serial_num, M_CAMXPT); device->serial_num = NULL; device->serial_num_len = 0; } if (scsi_vpd_supported_page(periph, SVPD_UNIT_SERIAL_NUMBER)) serial_buf = (struct scsi_vpd_unit_serial_number *) malloc(sizeof(*serial_buf), M_CAMXPT, M_NOWAIT|M_ZERO); if (serial_buf != NULL) { scsi_inquiry(csio, /*retries*/4, probedone, MSG_SIMPLE_Q_TAG, (u_int8_t *)serial_buf, sizeof(*serial_buf), /*evpd*/TRUE, SVPD_UNIT_SERIAL_NUMBER, SSD_MIN_SIZE, /*timeout*/60 * 1000); break; } goto done; } default: panic("probestart: invalid action state 0x%x\n", softc->action); } start_ccb->ccb_h.flags |= CAM_DEV_QFREEZE; cam_periph_doacquire(periph); xpt_action(start_ccb); } static void proberequestdefaultnegotiation(struct cam_periph *periph) { struct ccb_trans_settings cts; xpt_setup_ccb(&cts.ccb_h, periph->path, CAM_PRIORITY_NONE); cts.ccb_h.func_code = XPT_GET_TRAN_SETTINGS; cts.type = CTS_TYPE_USER_SETTINGS; xpt_action((union ccb *)&cts); if (cam_ccb_status((union ccb *)&cts) != CAM_REQ_CMP) { return; } cts.ccb_h.func_code = XPT_SET_TRAN_SETTINGS; cts.type = CTS_TYPE_CURRENT_SETTINGS; xpt_action((union ccb *)&cts); } /* * Backoff Negotiation Code- only pertinent for SPI devices. */ static int proberequestbackoff(struct cam_periph *periph, struct cam_ed *device) { struct ccb_trans_settings cts; struct ccb_trans_settings_spi *spi; memset(&cts, 0, sizeof (cts)); xpt_setup_ccb(&cts.ccb_h, periph->path, CAM_PRIORITY_NONE); cts.ccb_h.func_code = XPT_GET_TRAN_SETTINGS; cts.type = CTS_TYPE_CURRENT_SETTINGS; xpt_action((union ccb *)&cts); if (cam_ccb_status((union ccb *)&cts) != CAM_REQ_CMP) { if (bootverbose) { xpt_print(periph->path, "failed to get current device settings\n"); } return (0); } if (cts.transport != XPORT_SPI) { if (bootverbose) { xpt_print(periph->path, "not SPI transport\n"); } return (0); } spi = &cts.xport_specific.spi; /* * We cannot renegotiate sync rate if we don't have one. */ if ((spi->valid & CTS_SPI_VALID_SYNC_RATE) == 0) { if (bootverbose) { xpt_print(periph->path, "no sync rate known\n"); } return (0); } /* * We'll assert that we don't have to touch PPR options- the * SIM will see what we do with period and offset and adjust * the PPR options as appropriate. */ /* * A sync rate with unknown or zero offset is nonsensical. * A sync period of zero means Async. */ if ((spi->valid & CTS_SPI_VALID_SYNC_OFFSET) == 0 || spi->sync_offset == 0 || spi->sync_period == 0) { if (bootverbose) { xpt_print(periph->path, "no sync rate available\n"); } return (0); } if (device->flags & CAM_DEV_DV_HIT_BOTTOM) { CAM_DEBUG(periph->path, CAM_DEBUG_PROBE, ("hit async: giving up on DV\n")); return (0); } /* * Jump sync_period up by one, but stop at 5MHz and fall back to Async. * We don't try to remember 'last' settings to see if the SIM actually * gets into the speed we want to set. We check on the SIM telling * us that a requested speed is bad, but otherwise don't try and * check the speed due to the asynchronous and handshake nature * of speed setting. */ spi->valid = CTS_SPI_VALID_SYNC_RATE | CTS_SPI_VALID_SYNC_OFFSET; for (;;) { spi->sync_period++; if (spi->sync_period >= 0xf) { spi->sync_period = 0; spi->sync_offset = 0; CAM_DEBUG(periph->path, CAM_DEBUG_PROBE, ("setting to async for DV\n")); /* * Once we hit async, we don't want to try * any more settings. */ device->flags |= CAM_DEV_DV_HIT_BOTTOM; } else if (bootverbose) { CAM_DEBUG(periph->path, CAM_DEBUG_PROBE, ("DV: period 0x%x\n", spi->sync_period)); printf("setting period to 0x%x\n", spi->sync_period); } cts.ccb_h.func_code = XPT_SET_TRAN_SETTINGS; cts.type = CTS_TYPE_CURRENT_SETTINGS; xpt_action((union ccb *)&cts); if (cam_ccb_status((union ccb *)&cts) != CAM_REQ_CMP) { break; } CAM_DEBUG(periph->path, CAM_DEBUG_PROBE, ("DV: failed to set period 0x%x\n", spi->sync_period)); if (spi->sync_period == 0) { return (0); } } return (1); } #define CCB_COMPLETED_OK(ccb) (((ccb).status & CAM_STATUS_MASK) == CAM_REQ_CMP) static void probedone(struct cam_periph *periph, union ccb *done_ccb) { probe_softc *softc; struct cam_path *path; + struct scsi_inquiry_data *inq_buf; u_int32_t priority; CAM_DEBUG(done_ccb->ccb_h.path, CAM_DEBUG_TRACE, ("probedone\n")); softc = (probe_softc *)periph->softc; path = done_ccb->ccb_h.path; priority = done_ccb->ccb_h.pinfo.priority; switch (softc->action) { case PROBE_TUR: { if (cam_ccb_status(done_ccb) != CAM_REQ_CMP) { if (cam_periph_error(done_ccb, 0, SF_NO_PRINT, NULL) == ERESTART) { outr: /* Drop freeze taken due to CAM_DEV_QFREEZE */ cam_release_devq(path, 0, 0, 0, FALSE); return; } else if ((done_ccb->ccb_h.status & CAM_DEV_QFRZN) != 0) /* Don't wedge the queue */ xpt_release_devq(done_ccb->ccb_h.path, /*count*/1, /*run_queue*/TRUE); } PROBE_SET_ACTION(softc, PROBE_INQUIRY); xpt_release_ccb(done_ccb); xpt_schedule(periph, priority); out: /* Drop freeze taken due to CAM_DEV_QFREEZE and release. */ cam_release_devq(path, 0, 0, 0, FALSE); cam_periph_release_locked(periph); return; } case PROBE_INQUIRY: case PROBE_FULL_INQUIRY: { if (cam_ccb_status(done_ccb) == CAM_REQ_CMP) { - struct scsi_inquiry_data *inq_buf; u_int8_t periph_qual; path->device->flags |= CAM_DEV_INQUIRY_DATA_VALID; scsi_find_quirk(path->device); inq_buf = &path->device->inq_data; periph_qual = SID_QUAL(inq_buf); - if (periph_qual == SID_QUAL_LU_CONNECTED) { + if (periph_qual == SID_QUAL_LU_CONNECTED || + periph_qual == SID_QUAL_LU_OFFLINE) { u_int8_t len; /* * We conservatively request only * SHORT_INQUIRY_LEN bytes of inquiry * information during our first try * at sending an INQUIRY. If the device * has more information to give, * perform a second request specifying * the amount of information the device * is willing to give. */ len = inq_buf->additional_length + offsetof(struct scsi_inquiry_data, additional_length) + 1; if (softc->action == PROBE_INQUIRY && len > SHORT_INQUIRY_LENGTH) { PROBE_SET_ACTION(softc, PROBE_FULL_INQUIRY); xpt_release_ccb(done_ccb); xpt_schedule(periph, priority); goto out; } scsi_devise_transport(path); if (path->device->lun_id == 0 && SID_ANSI_REV(inq_buf) > SCSI_REV_SPC2 && (SCSI_QUIRK(path->device)->quirks & CAM_QUIRK_NORPTLUNS) == 0) { PROBE_SET_ACTION(softc, PROBE_REPORT_LUNS); /* * Start with room for *one* lun. */ periph->path->target->rpl_size = 16; } else if (INQ_DATA_TQ_ENABLED(inq_buf)) PROBE_SET_ACTION(softc, PROBE_MODE_SENSE); else PROBE_SET_ACTION(softc, PROBE_SUPPORTED_VPD_LIST); if (path->device->flags & CAM_DEV_UNCONFIGURED) { path->device->flags &= ~CAM_DEV_UNCONFIGURED; xpt_acquire_device(path->device); } xpt_release_ccb(done_ccb); xpt_schedule(periph, priority); goto out; } else if (path->device->lun_id == 0 && SID_ANSI_REV(inq_buf) >= SCSI_REV_SPC2 && (SCSI_QUIRK(path->device)->quirks & CAM_QUIRK_NORPTLUNS) == 0) { PROBE_SET_ACTION(softc, PROBE_REPORT_LUNS); periph->path->target->rpl_size = 16; xpt_release_ccb(done_ccb); xpt_schedule(periph, priority); goto out; } } else if (cam_periph_error(done_ccb, 0, done_ccb->ccb_h.target_lun > 0 ? SF_RETRY_UA|SF_QUIET_IR : SF_RETRY_UA, &softc->saved_ccb) == ERESTART) { goto outr; } else { if ((done_ccb->ccb_h.status & CAM_DEV_QFRZN) != 0) { /* Don't wedge the queue */ xpt_release_devq(done_ccb->ccb_h.path, /*count*/1, /*run_queue*/TRUE); } path->device->flags &= ~CAM_DEV_INQUIRY_DATA_VALID; } /* * If we get to this point, we got an error status back * from the inquiry and the error status doesn't require * automatically retrying the command. Therefore, the * inquiry failed. If we had inquiry information before * for this device, but this latest inquiry command failed, * the device has probably gone away. If this device isn't * already marked unconfigured, notify the peripheral * drivers that this device is no more. */ if ((path->device->flags & CAM_DEV_UNCONFIGURED) == 0) /* Send the async notification. */ xpt_async(AC_LOST_DEVICE, path, NULL); PROBE_SET_ACTION(softc, PROBE_INVALID); xpt_release_ccb(done_ccb); break; } case PROBE_REPORT_LUNS: { struct ccb_scsiio *csio; struct scsi_report_luns_data *lp; u_int nlun, maxlun; csio = &done_ccb->csio; lp = (struct scsi_report_luns_data *)csio->data_ptr; nlun = scsi_4btoul(lp->length) / 8; maxlun = (csio->dxfer_len / 8) - 1; if (cam_ccb_status(done_ccb) != CAM_REQ_CMP) { if (cam_periph_error(done_ccb, 0, done_ccb->ccb_h.target_lun > 0 ? SF_RETRY_UA|SF_QUIET_IR : SF_RETRY_UA, &softc->saved_ccb) == ERESTART) { goto outr; } if ((done_ccb->ccb_h.status & CAM_DEV_QFRZN) != 0) { xpt_release_devq(done_ccb->ccb_h.path, 1, TRUE); } free(lp, M_CAMXPT); lp = NULL; } else if (nlun > maxlun) { /* * Reallocate and retry to cover all luns */ CAM_DEBUG(path, CAM_DEBUG_PROBE, ("Probe: reallocating REPORT_LUNS for %u luns\n", nlun)); free(lp, M_CAMXPT); path->target->rpl_size = (nlun << 3) + 8; xpt_release_ccb(done_ccb); xpt_schedule(periph, priority); goto out; } else if (nlun == 0) { /* * If there don't appear to be any luns, bail. */ free(lp, M_CAMXPT); lp = NULL; } else { lun_id_t lun; int idx; CAM_DEBUG(path, CAM_DEBUG_PROBE, ("Probe: %u lun(s) reported\n", nlun)); CAM_GET_LUN(lp, 0, lun); /* * If the first lun is not lun 0, then either there * is no lun 0 in the list, or the list is unsorted. */ if (lun != 0) { for (idx = 0; idx < nlun; idx++) { CAM_GET_LUN(lp, idx, lun); if (lun == 0) { break; } } if (idx != nlun) { uint8_t tlun[8]; memcpy(tlun, lp->luns[0].lundata, 8); memcpy(lp->luns[0].lundata, lp->luns[idx].lundata, 8); memcpy(lp->luns[idx].lundata, tlun, 8); CAM_DEBUG(path, CAM_DEBUG_PROBE, ("lun 0 in position %u\n", idx)); } } /* * If we have an old lun list, We can either * retest luns that appear to have been dropped, * or just nuke them. We'll opt for the latter. * This function will also install the new list * in the target structure. */ probe_purge_old(path, lp, softc->flags); lp = NULL; } + inq_buf = &path->device->inq_data; if (path->device->flags & CAM_DEV_INQUIRY_DATA_VALID && - SID_QUAL(&path->device->inq_data) == SID_QUAL_LU_CONNECTED) { - struct scsi_inquiry_data *inq_buf; - inq_buf = &path->device->inq_data; + (SID_QUAL(inq_buf) == SID_QUAL_LU_CONNECTED || + SID_QUAL(inq_buf) == SID_QUAL_LU_OFFLINE)) { if (INQ_DATA_TQ_ENABLED(inq_buf)) PROBE_SET_ACTION(softc, PROBE_MODE_SENSE); else PROBE_SET_ACTION(softc, PROBE_SUPPORTED_VPD_LIST); xpt_release_ccb(done_ccb); xpt_schedule(periph, priority); goto out; } if (lp) { free(lp, M_CAMXPT); } PROBE_SET_ACTION(softc, PROBE_INVALID); xpt_release_ccb(done_ccb); break; } case PROBE_MODE_SENSE: { struct ccb_scsiio *csio; struct scsi_mode_header_6 *mode_hdr; csio = &done_ccb->csio; mode_hdr = (struct scsi_mode_header_6 *)csio->data_ptr; if (cam_ccb_status(done_ccb) == CAM_REQ_CMP) { struct scsi_control_page *page; u_int8_t *offset; offset = ((u_int8_t *)&mode_hdr[1]) + mode_hdr->blk_desc_len; page = (struct scsi_control_page *)offset; path->device->queue_flags = page->queue_flags; } else if (cam_periph_error(done_ccb, 0, SF_RETRY_UA|SF_NO_PRINT, &softc->saved_ccb) == ERESTART) { goto outr; } else if ((done_ccb->ccb_h.status & CAM_DEV_QFRZN) != 0) { /* Don't wedge the queue */ xpt_release_devq(done_ccb->ccb_h.path, /*count*/1, /*run_queue*/TRUE); } xpt_release_ccb(done_ccb); free(mode_hdr, M_CAMXPT); PROBE_SET_ACTION(softc, PROBE_SUPPORTED_VPD_LIST); xpt_schedule(periph, priority); goto out; } case PROBE_SUPPORTED_VPD_LIST: { struct ccb_scsiio *csio; struct scsi_vpd_supported_page_list *page_list; csio = &done_ccb->csio; page_list = (struct scsi_vpd_supported_page_list *)csio->data_ptr; if (path->device->supported_vpds != NULL) { free(path->device->supported_vpds, M_CAMXPT); path->device->supported_vpds = NULL; path->device->supported_vpds_len = 0; } if (page_list == NULL) { /* * Don't process the command as it was never sent */ } else if (CCB_COMPLETED_OK(csio->ccb_h)) { /* Got vpd list */ path->device->supported_vpds_len = page_list->length + SVPD_SUPPORTED_PAGES_HDR_LEN; path->device->supported_vpds = (uint8_t *)page_list; xpt_release_ccb(done_ccb); PROBE_SET_ACTION(softc, PROBE_DEVICE_ID); xpt_schedule(periph, priority); goto out; } else if (cam_periph_error(done_ccb, 0, SF_RETRY_UA|SF_NO_PRINT, &softc->saved_ccb) == ERESTART) { goto outr; } else if ((done_ccb->ccb_h.status & CAM_DEV_QFRZN) != 0) { /* Don't wedge the queue */ xpt_release_devq(done_ccb->ccb_h.path, /*count*/1, /*run_queue*/TRUE); } if (page_list) free(page_list, M_CAMXPT); /* No VPDs available, skip to device check. */ csio->data_ptr = NULL; goto probe_device_check; } case PROBE_DEVICE_ID: { struct scsi_vpd_device_id *devid; struct ccb_scsiio *csio; uint32_t length = 0; csio = &done_ccb->csio; devid = (struct scsi_vpd_device_id *)csio->data_ptr; /* Clean up from previous instance of this device */ if (path->device->device_id != NULL) { path->device->device_id_len = 0; free(path->device->device_id, M_CAMXPT); path->device->device_id = NULL; } if (devid == NULL) { /* Don't process the command as it was never sent */ } else if (CCB_COMPLETED_OK(csio->ccb_h)) { length = scsi_2btoul(devid->length); if (length != 0) { /* * NB: device_id_len is actual response * size, not buffer size. */ path->device->device_id_len = length + SVPD_DEVICE_ID_HDR_LEN; path->device->device_id = (uint8_t *)devid; } } else if (cam_periph_error(done_ccb, 0, SF_RETRY_UA, &softc->saved_ccb) == ERESTART) { goto outr; } else if ((done_ccb->ccb_h.status & CAM_DEV_QFRZN) != 0) { /* Don't wedge the queue */ xpt_release_devq(done_ccb->ccb_h.path, /*count*/1, /*run_queue*/TRUE); } /* Free the device id space if we don't use it */ if (devid && length == 0) free(devid, M_CAMXPT); xpt_release_ccb(done_ccb); PROBE_SET_ACTION(softc, PROBE_EXTENDED_INQUIRY); xpt_schedule(periph, priority); goto out; } case PROBE_EXTENDED_INQUIRY: { struct scsi_vpd_extended_inquiry_data *ext_inq; struct ccb_scsiio *csio; int32_t length = 0; csio = &done_ccb->csio; ext_inq = (struct scsi_vpd_extended_inquiry_data *) csio->data_ptr; if (path->device->ext_inq != NULL) { path->device->ext_inq_len = 0; free(path->device->ext_inq, M_CAMXPT); path->device->ext_inq = NULL; } if (ext_inq == NULL) { /* Don't process the command as it was never sent */ } else if (CCB_COMPLETED_OK(csio->ccb_h)) { length = scsi_2btoul(ext_inq->page_length) + __offsetof(struct scsi_vpd_extended_inquiry_data, flags1); length = min(length, sizeof(*ext_inq)); length -= csio->resid; if (length > 0) { path->device->ext_inq_len = length; path->device->ext_inq = (uint8_t *)ext_inq; } } else if (cam_periph_error(done_ccb, 0, SF_RETRY_UA, &softc->saved_ccb) == ERESTART) { return; } else if ((done_ccb->ccb_h.status & CAM_DEV_QFRZN) != 0) { /* Don't wedge the queue */ xpt_release_devq(done_ccb->ccb_h.path, /*count*/1, /*run_queue*/TRUE); } /* Free the device id space if we don't use it */ if (ext_inq && length <= 0) free(ext_inq, M_CAMXPT); xpt_release_ccb(done_ccb); PROBE_SET_ACTION(softc, PROBE_SERIAL_NUM); xpt_schedule(periph, priority); goto out; } probe_device_check: case PROBE_SERIAL_NUM: { struct ccb_scsiio *csio; struct scsi_vpd_unit_serial_number *serial_buf; u_int32_t priority; int changed; int have_serialnum; changed = 1; have_serialnum = 0; csio = &done_ccb->csio; priority = done_ccb->ccb_h.pinfo.priority; serial_buf = (struct scsi_vpd_unit_serial_number *)csio->data_ptr; if (serial_buf == NULL) { /* * Don't process the command as it was never sent */ } else if (cam_ccb_status(done_ccb) == CAM_REQ_CMP && (serial_buf->length > 0)) { have_serialnum = 1; path->device->serial_num = (u_int8_t *)malloc((serial_buf->length + 1), M_CAMXPT, M_NOWAIT); if (path->device->serial_num != NULL) { memcpy(path->device->serial_num, serial_buf->serial_num, serial_buf->length); path->device->serial_num_len = serial_buf->length; path->device->serial_num[serial_buf->length] = '\0'; } } else if (cam_periph_error(done_ccb, 0, SF_RETRY_UA|SF_NO_PRINT, &softc->saved_ccb) == ERESTART) { goto outr; } else if ((done_ccb->ccb_h.status & CAM_DEV_QFRZN) != 0) { /* Don't wedge the queue */ xpt_release_devq(done_ccb->ccb_h.path, /*count*/1, /*run_queue*/TRUE); } /* * Let's see if we have seen this device before. */ if ((softc->flags & PROBE_INQUIRY_CKSUM) != 0) { MD5_CTX context; u_int8_t digest[16]; MD5Init(&context); MD5Update(&context, (unsigned char *)&path->device->inq_data, sizeof(struct scsi_inquiry_data)); if (have_serialnum) MD5Update(&context, serial_buf->serial_num, serial_buf->length); MD5Final(digest, &context); if (bcmp(softc->digest, digest, 16) == 0) changed = 0; /* * XXX Do we need to do a TUR in order to ensure * that the device really hasn't changed??? */ if ((changed != 0) && ((softc->flags & PROBE_NO_ANNOUNCE) == 0)) xpt_async(AC_LOST_DEVICE, path, NULL); } if (serial_buf != NULL) free(serial_buf, M_CAMXPT); if (changed != 0) { /* * Now that we have all the necessary * information to safely perform transfer * negotiations... Controllers don't perform * any negotiation or tagged queuing until * after the first XPT_SET_TRAN_SETTINGS ccb is * received. So, on a new device, just retrieve * the user settings, and set them as the current * settings to set the device up. */ proberequestdefaultnegotiation(periph); xpt_release_ccb(done_ccb); /* * Perform a TUR to allow the controller to * perform any necessary transfer negotiation. */ PROBE_SET_ACTION(softc, PROBE_TUR_FOR_NEGOTIATION); xpt_schedule(periph, priority); goto out; } xpt_release_ccb(done_ccb); break; } case PROBE_TUR_FOR_NEGOTIATION: case PROBE_DV_EXIT: if (cam_ccb_status(done_ccb) != CAM_REQ_CMP) { cam_periph_error(done_ccb, 0, SF_NO_PRINT | SF_NO_RECOVERY | SF_NO_RETRY, NULL); } if ((done_ccb->ccb_h.status & CAM_DEV_QFRZN) != 0) { /* Don't wedge the queue */ xpt_release_devq(done_ccb->ccb_h.path, /*count*/1, /*run_queue*/TRUE); } /* * Do Domain Validation for lun 0 on devices that claim * to support Synchronous Transfer modes. */ if (softc->action == PROBE_TUR_FOR_NEGOTIATION && done_ccb->ccb_h.target_lun == 0 && (path->device->inq_data.flags & SID_Sync) != 0 && (path->device->flags & CAM_DEV_IN_DV) == 0) { CAM_DEBUG(periph->path, CAM_DEBUG_PROBE, ("Begin Domain Validation\n")); path->device->flags |= CAM_DEV_IN_DV; xpt_release_ccb(done_ccb); PROBE_SET_ACTION(softc, PROBE_INQUIRY_BASIC_DV1); xpt_schedule(periph, priority); goto out; } if (softc->action == PROBE_DV_EXIT) { CAM_DEBUG(periph->path, CAM_DEBUG_PROBE, ("Leave Domain Validation\n")); } if (path->device->flags & CAM_DEV_UNCONFIGURED) { path->device->flags &= ~CAM_DEV_UNCONFIGURED; xpt_acquire_device(path->device); } path->device->flags &= ~(CAM_DEV_IN_DV|CAM_DEV_DV_HIT_BOTTOM); if ((softc->flags & PROBE_NO_ANNOUNCE) == 0) { /* Inform the XPT that a new device has been found */ done_ccb->ccb_h.func_code = XPT_GDEV_TYPE; xpt_action(done_ccb); xpt_async(AC_FOUND_DEVICE, done_ccb->ccb_h.path, done_ccb); } PROBE_SET_ACTION(softc, PROBE_DONE); xpt_release_ccb(done_ccb); break; case PROBE_INQUIRY_BASIC_DV1: case PROBE_INQUIRY_BASIC_DV2: { struct scsi_inquiry_data *nbuf; struct ccb_scsiio *csio; if (cam_ccb_status(done_ccb) != CAM_REQ_CMP) { cam_periph_error(done_ccb, 0, SF_NO_PRINT | SF_NO_RECOVERY | SF_NO_RETRY, NULL); } if ((done_ccb->ccb_h.status & CAM_DEV_QFRZN) != 0) { /* Don't wedge the queue */ xpt_release_devq(done_ccb->ccb_h.path, /*count*/1, /*run_queue*/TRUE); } csio = &done_ccb->csio; nbuf = (struct scsi_inquiry_data *)csio->data_ptr; if (bcmp(nbuf, &path->device->inq_data, SHORT_INQUIRY_LENGTH)) { xpt_print(path, "inquiry data fails comparison at DV%d step\n", softc->action == PROBE_INQUIRY_BASIC_DV1 ? 1 : 2); if (proberequestbackoff(periph, path->device)) { path->device->flags &= ~CAM_DEV_IN_DV; PROBE_SET_ACTION(softc, PROBE_TUR_FOR_NEGOTIATION); } else { /* give up */ PROBE_SET_ACTION(softc, PROBE_DV_EXIT); } free(nbuf, M_CAMXPT); xpt_release_ccb(done_ccb); xpt_schedule(periph, priority); goto out; } free(nbuf, M_CAMXPT); if (softc->action == PROBE_INQUIRY_BASIC_DV1) { PROBE_SET_ACTION(softc, PROBE_INQUIRY_BASIC_DV2); xpt_release_ccb(done_ccb); xpt_schedule(periph, priority); goto out; } if (softc->action == PROBE_INQUIRY_BASIC_DV2) { CAM_DEBUG(periph->path, CAM_DEBUG_PROBE, ("Leave Domain Validation Successfully\n")); } if (path->device->flags & CAM_DEV_UNCONFIGURED) { path->device->flags &= ~CAM_DEV_UNCONFIGURED; xpt_acquire_device(path->device); } path->device->flags &= ~(CAM_DEV_IN_DV|CAM_DEV_DV_HIT_BOTTOM); if ((softc->flags & PROBE_NO_ANNOUNCE) == 0) { /* Inform the XPT that a new device has been found */ done_ccb->ccb_h.func_code = XPT_GDEV_TYPE; xpt_action(done_ccb); xpt_async(AC_FOUND_DEVICE, done_ccb->ccb_h.path, done_ccb); } PROBE_SET_ACTION(softc, PROBE_DONE); xpt_release_ccb(done_ccb); break; } default: panic("probedone: invalid action state 0x%x\n", softc->action); } done_ccb = (union ccb *)TAILQ_FIRST(&softc->request_ccbs); TAILQ_REMOVE(&softc->request_ccbs, &done_ccb->ccb_h, periph_links.tqe); done_ccb->ccb_h.status = CAM_REQ_CMP; xpt_done(done_ccb); if (TAILQ_FIRST(&softc->request_ccbs) == NULL) { CAM_DEBUG(periph->path, CAM_DEBUG_PROBE, ("Probe completed\n")); /* Drop freeze taken due to CAM_DEV_QFREEZE flag set. */ cam_release_devq(path, 0, 0, 0, FALSE); cam_periph_release_locked(periph); cam_periph_invalidate(periph); cam_periph_release_locked(periph); } else { probeschedule(periph); goto out; } } static void probe_purge_old(struct cam_path *path, struct scsi_report_luns_data *new, probe_flags flags) { struct cam_path *tp; struct scsi_report_luns_data *old; u_int idx1, idx2, nlun_old, nlun_new; lun_id_t this_lun; u_int8_t *ol, *nl; if (path->target == NULL) { return; } mtx_lock(&path->target->luns_mtx); old = path->target->luns; path->target->luns = new; mtx_unlock(&path->target->luns_mtx); if (old == NULL) return; nlun_old = scsi_4btoul(old->length) / 8; nlun_new = scsi_4btoul(new->length) / 8; /* * We are not going to assume sorted lists. Deal. */ for (idx1 = 0; idx1 < nlun_old; idx1++) { ol = old->luns[idx1].lundata; for (idx2 = 0; idx2 < nlun_new; idx2++) { nl = new->luns[idx2].lundata; if (memcmp(nl, ol, 8) == 0) { break; } } if (idx2 < nlun_new) { continue; } /* * An 'old' item not in the 'new' list. * Nuke it. Except that if it is lun 0, * that would be what the probe state * machine is currently working on, * so we won't do that. */ CAM_GET_LUN(old, idx1, this_lun); if (this_lun == 0) { continue; } /* * We also cannot nuke it if it is * not in a lun format we understand * and replace the LUN with a "simple" LUN * if that is all the HBA supports. */ if (!(flags & PROBE_EXTLUN)) { if (!CAM_CAN_GET_SIMPLE_LUN(old, idx1)) continue; CAM_GET_SIMPLE_LUN(old, idx1, this_lun); } if (xpt_create_path(&tp, NULL, xpt_path_path_id(path), xpt_path_target_id(path), this_lun) == CAM_REQ_CMP) { xpt_async(AC_LOST_DEVICE, tp, NULL); xpt_free_path(tp); } } free(old, M_CAMXPT); } static void probecleanup(struct cam_periph *periph) { free(periph->softc, M_CAMXPT); } static void scsi_find_quirk(struct cam_ed *device) { struct scsi_quirk_entry *quirk; caddr_t match; match = cam_quirkmatch((caddr_t)&device->inq_data, (caddr_t)scsi_quirk_table, sizeof(scsi_quirk_table) / sizeof(*scsi_quirk_table), sizeof(*scsi_quirk_table), scsi_inquiry_match); if (match == NULL) panic("xpt_find_quirk: device didn't match wildcard entry!!"); quirk = (struct scsi_quirk_entry *)match; device->quirk = quirk; device->mintags = quirk->mintags; device->maxtags = quirk->maxtags; } static int sysctl_cam_search_luns(SYSCTL_HANDLER_ARGS) { int error, val; val = cam_srch_hi; error = sysctl_handle_int(oidp, &val, 0, req); if (error != 0 || req->newptr == NULL) return (error); if (val == 0 || val == 1) { cam_srch_hi = val; return (0); } else { return (EINVAL); } } typedef struct { union ccb *request_ccb; struct ccb_pathinq *cpi; int counter; int lunindex[0]; } scsi_scan_bus_info; /* * To start a scan, request_ccb is an XPT_SCAN_BUS ccb. * As the scan progresses, scsi_scan_bus is used as the * callback on completion function. */ static void scsi_scan_bus(struct cam_periph *periph, union ccb *request_ccb) { struct mtx *mtx; CAM_DEBUG(request_ccb->ccb_h.path, CAM_DEBUG_TRACE, ("scsi_scan_bus\n")); switch (request_ccb->ccb_h.func_code) { case XPT_SCAN_BUS: case XPT_SCAN_TGT: { scsi_scan_bus_info *scan_info; union ccb *work_ccb, *reset_ccb; struct cam_path *path; u_int i; u_int low_target, max_target; u_int initiator_id; /* Find out the characteristics of the bus */ work_ccb = xpt_alloc_ccb_nowait(); if (work_ccb == NULL) { request_ccb->ccb_h.status = CAM_RESRC_UNAVAIL; xpt_done(request_ccb); return; } xpt_setup_ccb(&work_ccb->ccb_h, request_ccb->ccb_h.path, request_ccb->ccb_h.pinfo.priority); work_ccb->ccb_h.func_code = XPT_PATH_INQ; xpt_action(work_ccb); if (work_ccb->ccb_h.status != CAM_REQ_CMP) { request_ccb->ccb_h.status = work_ccb->ccb_h.status; xpt_free_ccb(work_ccb); xpt_done(request_ccb); return; } if ((work_ccb->cpi.hba_misc & PIM_NOINITIATOR) != 0) { /* * Can't scan the bus on an adapter that * cannot perform the initiator role. */ request_ccb->ccb_h.status = CAM_REQ_CMP; xpt_free_ccb(work_ccb); xpt_done(request_ccb); return; } /* We may need to reset bus first, if we haven't done it yet. */ if ((work_ccb->cpi.hba_inquiry & (PI_WIDE_32|PI_WIDE_16|PI_SDTR_ABLE)) && !(work_ccb->cpi.hba_misc & PIM_NOBUSRESET) && !timevalisset(&request_ccb->ccb_h.path->bus->last_reset) && (reset_ccb = xpt_alloc_ccb_nowait()) != NULL) { xpt_setup_ccb(&reset_ccb->ccb_h, request_ccb->ccb_h.path, CAM_PRIORITY_NONE); reset_ccb->ccb_h.func_code = XPT_RESET_BUS; xpt_action(reset_ccb); if (reset_ccb->ccb_h.status != CAM_REQ_CMP) { request_ccb->ccb_h.status = reset_ccb->ccb_h.status; xpt_free_ccb(reset_ccb); xpt_free_ccb(work_ccb); xpt_done(request_ccb); return; } xpt_free_ccb(reset_ccb); } /* Save some state for use while we probe for devices */ scan_info = (scsi_scan_bus_info *) malloc(sizeof(scsi_scan_bus_info) + (work_ccb->cpi.max_target * sizeof (u_int)), M_CAMXPT, M_ZERO|M_NOWAIT); if (scan_info == NULL) { request_ccb->ccb_h.status = CAM_RESRC_UNAVAIL; xpt_free_ccb(work_ccb); xpt_done(request_ccb); return; } CAM_DEBUG(request_ccb->ccb_h.path, CAM_DEBUG_TRACE, ("SCAN start for %p\n", scan_info)); scan_info->request_ccb = request_ccb; scan_info->cpi = &work_ccb->cpi; /* Cache on our stack so we can work asynchronously */ max_target = scan_info->cpi->max_target; low_target = 0; initiator_id = scan_info->cpi->initiator_id; /* * We can scan all targets in parallel, or do it sequentially. */ if (request_ccb->ccb_h.func_code == XPT_SCAN_TGT) { max_target = low_target = request_ccb->ccb_h.target_id; scan_info->counter = 0; } else if (scan_info->cpi->hba_misc & PIM_SEQSCAN) { max_target = 0; scan_info->counter = 0; } else { scan_info->counter = scan_info->cpi->max_target + 1; if (scan_info->cpi->initiator_id < scan_info->counter) { scan_info->counter--; } } mtx = xpt_path_mtx(scan_info->request_ccb->ccb_h.path); mtx_unlock(mtx); for (i = low_target; i <= max_target; i++) { cam_status status; if (i == initiator_id) continue; status = xpt_create_path(&path, NULL, request_ccb->ccb_h.path_id, i, 0); if (status != CAM_REQ_CMP) { printf("scsi_scan_bus: xpt_create_path failed" " with status %#x, bus scan halted\n", status); free(scan_info, M_CAMXPT); request_ccb->ccb_h.status = status; xpt_free_ccb(work_ccb); xpt_done(request_ccb); break; } work_ccb = xpt_alloc_ccb_nowait(); if (work_ccb == NULL) { xpt_free_ccb((union ccb *)scan_info->cpi); free(scan_info, M_CAMXPT); xpt_free_path(path); request_ccb->ccb_h.status = CAM_RESRC_UNAVAIL; xpt_done(request_ccb); break; } xpt_setup_ccb(&work_ccb->ccb_h, path, request_ccb->ccb_h.pinfo.priority); work_ccb->ccb_h.func_code = XPT_SCAN_LUN; work_ccb->ccb_h.cbfcnp = scsi_scan_bus; work_ccb->ccb_h.flags |= CAM_UNLOCKED; work_ccb->ccb_h.ppriv_ptr0 = scan_info; work_ccb->crcn.flags = request_ccb->crcn.flags; xpt_action(work_ccb); } mtx_lock(mtx); break; } case XPT_SCAN_LUN: { cam_status status; struct cam_path *path, *oldpath; scsi_scan_bus_info *scan_info; struct cam_et *target; struct cam_ed *device, *nextdev; int next_target; path_id_t path_id; target_id_t target_id; lun_id_t lun_id; oldpath = request_ccb->ccb_h.path; status = cam_ccb_status(request_ccb); scan_info = (scsi_scan_bus_info *)request_ccb->ccb_h.ppriv_ptr0; path_id = request_ccb->ccb_h.path_id; target_id = request_ccb->ccb_h.target_id; lun_id = request_ccb->ccb_h.target_lun; target = request_ccb->ccb_h.path->target; next_target = 1; mtx = xpt_path_mtx(scan_info->request_ccb->ccb_h.path); mtx_lock(mtx); mtx_lock(&target->luns_mtx); if (target->luns) { lun_id_t first; u_int nluns = scsi_4btoul(target->luns->length) / 8; /* * Make sure we skip over lun 0 if it's the first member * of the list as we've actually just finished probing * it. */ CAM_GET_LUN(target->luns, 0, first); if (first == 0 && scan_info->lunindex[target_id] == 0) { scan_info->lunindex[target_id]++; } /* * Skip any LUNs that the HBA can't deal with. */ while (scan_info->lunindex[target_id] < nluns) { if (scan_info->cpi->hba_misc & PIM_EXTLUNS) { CAM_GET_LUN(target->luns, scan_info->lunindex[target_id], lun_id); break; } if (CAM_CAN_GET_SIMPLE_LUN(target->luns, scan_info->lunindex[target_id])) { CAM_GET_SIMPLE_LUN(target->luns, scan_info->lunindex[target_id], lun_id); break; } scan_info->lunindex[target_id]++; } if (scan_info->lunindex[target_id] < nluns) { mtx_unlock(&target->luns_mtx); next_target = 0; CAM_DEBUG(request_ccb->ccb_h.path, CAM_DEBUG_PROBE, ("next lun to try at index %u is %jx\n", scan_info->lunindex[target_id], (uintmax_t)lun_id)); scan_info->lunindex[target_id]++; } else { mtx_unlock(&target->luns_mtx); /* We're done with scanning all luns. */ } } else { mtx_unlock(&target->luns_mtx); device = request_ccb->ccb_h.path->device; /* Continue sequential LUN scan if: */ /* -- we have more LUNs that need recheck */ mtx_lock(&target->bus->eb_mtx); nextdev = device; while ((nextdev = TAILQ_NEXT(nextdev, links)) != NULL) if ((nextdev->flags & CAM_DEV_UNCONFIGURED) == 0) break; mtx_unlock(&target->bus->eb_mtx); if (nextdev != NULL) { next_target = 0; /* -- stop if CAM_QUIRK_NOLUNS is set. */ } else if (SCSI_QUIRK(device)->quirks & CAM_QUIRK_NOLUNS) { next_target = 1; /* -- this LUN is connected and its SCSI version * allows more LUNs. */ } else if ((device->flags & CAM_DEV_UNCONFIGURED) == 0) { if (lun_id < (CAM_SCSI2_MAXLUN-1) || CAN_SRCH_HI_DENSE(device)) next_target = 0; /* -- this LUN is disconnected, its SCSI version * allows more LUNs and we guess they may be. */ } else if ((device->flags & CAM_DEV_INQUIRY_DATA_VALID) != 0) { if (lun_id < (CAM_SCSI2_MAXLUN-1) || CAN_SRCH_HI_SPARSE(device)) next_target = 0; } if (next_target == 0) { lun_id++; if (lun_id > scan_info->cpi->max_lun) next_target = 1; } } /* * Check to see if we scan any further luns. */ if (next_target) { int done; /* * Free the current request path- we're done with it. */ xpt_free_path(oldpath); hop_again: done = 0; if (scan_info->request_ccb->ccb_h.func_code == XPT_SCAN_TGT) { done = 1; } else if (scan_info->cpi->hba_misc & PIM_SEQSCAN) { scan_info->counter++; if (scan_info->counter == scan_info->cpi->initiator_id) { scan_info->counter++; } if (scan_info->counter >= scan_info->cpi->max_target+1) { done = 1; } } else { scan_info->counter--; if (scan_info->counter == 0) { done = 1; } } if (done) { mtx_unlock(mtx); xpt_free_ccb(request_ccb); xpt_free_ccb((union ccb *)scan_info->cpi); request_ccb = scan_info->request_ccb; CAM_DEBUG(request_ccb->ccb_h.path, CAM_DEBUG_TRACE, ("SCAN done for %p\n", scan_info)); free(scan_info, M_CAMXPT); request_ccb->ccb_h.status = CAM_REQ_CMP; xpt_done(request_ccb); break; } if ((scan_info->cpi->hba_misc & PIM_SEQSCAN) == 0) { mtx_unlock(mtx); xpt_free_ccb(request_ccb); break; } status = xpt_create_path(&path, NULL, scan_info->request_ccb->ccb_h.path_id, scan_info->counter, 0); if (status != CAM_REQ_CMP) { mtx_unlock(mtx); printf("scsi_scan_bus: xpt_create_path failed" " with status %#x, bus scan halted\n", status); xpt_free_ccb(request_ccb); xpt_free_ccb((union ccb *)scan_info->cpi); request_ccb = scan_info->request_ccb; free(scan_info, M_CAMXPT); request_ccb->ccb_h.status = status; xpt_done(request_ccb); break; } xpt_setup_ccb(&request_ccb->ccb_h, path, request_ccb->ccb_h.pinfo.priority); request_ccb->ccb_h.func_code = XPT_SCAN_LUN; request_ccb->ccb_h.cbfcnp = scsi_scan_bus; request_ccb->ccb_h.flags |= CAM_UNLOCKED; request_ccb->ccb_h.ppriv_ptr0 = scan_info; request_ccb->crcn.flags = scan_info->request_ccb->crcn.flags; } else { status = xpt_create_path(&path, NULL, path_id, target_id, lun_id); /* * Free the old request path- we're done with it. We * do this *after* creating the new path so that * we don't remove a target that has our lun list * in the case that lun 0 is not present. */ xpt_free_path(oldpath); if (status != CAM_REQ_CMP) { printf("scsi_scan_bus: xpt_create_path failed " "with status %#x, halting LUN scan\n", status); goto hop_again; } xpt_setup_ccb(&request_ccb->ccb_h, path, request_ccb->ccb_h.pinfo.priority); request_ccb->ccb_h.func_code = XPT_SCAN_LUN; request_ccb->ccb_h.cbfcnp = scsi_scan_bus; request_ccb->ccb_h.flags |= CAM_UNLOCKED; request_ccb->ccb_h.ppriv_ptr0 = scan_info; request_ccb->crcn.flags = scan_info->request_ccb->crcn.flags; } mtx_unlock(mtx); xpt_action(request_ccb); break; } default: break; } } static void scsi_scan_lun(struct cam_periph *periph, struct cam_path *path, cam_flags flags, union ccb *request_ccb) { struct ccb_pathinq cpi; cam_status status; struct cam_path *new_path; struct cam_periph *old_periph; int lock; CAM_DEBUG(path, CAM_DEBUG_TRACE, ("scsi_scan_lun\n")); xpt_setup_ccb(&cpi.ccb_h, path, CAM_PRIORITY_NONE); cpi.ccb_h.func_code = XPT_PATH_INQ; xpt_action((union ccb *)&cpi); if (cpi.ccb_h.status != CAM_REQ_CMP) { if (request_ccb != NULL) { request_ccb->ccb_h.status = cpi.ccb_h.status; xpt_done(request_ccb); } return; } if ((cpi.hba_misc & PIM_NOINITIATOR) != 0) { /* * Can't scan the bus on an adapter that * cannot perform the initiator role. */ if (request_ccb != NULL) { request_ccb->ccb_h.status = CAM_REQ_CMP; xpt_done(request_ccb); } return; } if (request_ccb == NULL) { request_ccb = xpt_alloc_ccb_nowait(); if (request_ccb == NULL) { xpt_print(path, "scsi_scan_lun: can't allocate CCB, " "can't continue\n"); return; } status = xpt_create_path(&new_path, NULL, path->bus->path_id, path->target->target_id, path->device->lun_id); if (status != CAM_REQ_CMP) { xpt_print(path, "scsi_scan_lun: can't create path, " "can't continue\n"); xpt_free_ccb(request_ccb); return; } xpt_setup_ccb(&request_ccb->ccb_h, new_path, CAM_PRIORITY_XPT); request_ccb->ccb_h.cbfcnp = xptscandone; request_ccb->ccb_h.func_code = XPT_SCAN_LUN; request_ccb->ccb_h.flags |= CAM_UNLOCKED; request_ccb->crcn.flags = flags; } lock = (xpt_path_owned(path) == 0); if (lock) xpt_path_lock(path); if ((old_periph = cam_periph_find(path, "probe")) != NULL) { if ((old_periph->flags & CAM_PERIPH_INVALID) == 0) { probe_softc *softc; softc = (probe_softc *)old_periph->softc; TAILQ_INSERT_TAIL(&softc->request_ccbs, &request_ccb->ccb_h, periph_links.tqe); } else { request_ccb->ccb_h.status = CAM_REQ_CMP_ERR; xpt_done(request_ccb); } } else { status = cam_periph_alloc(proberegister, NULL, probecleanup, probestart, "probe", CAM_PERIPH_BIO, request_ccb->ccb_h.path, NULL, 0, request_ccb); if (status != CAM_REQ_CMP) { xpt_print(path, "scsi_scan_lun: cam_alloc_periph " "returned an error, can't continue probe\n"); request_ccb->ccb_h.status = status; xpt_done(request_ccb); } } if (lock) xpt_path_unlock(path); } static void xptscandone(struct cam_periph *periph, union ccb *done_ccb) { xpt_free_path(done_ccb->ccb_h.path); xpt_free_ccb(done_ccb); } static struct cam_ed * scsi_alloc_device(struct cam_eb *bus, struct cam_et *target, lun_id_t lun_id) { struct scsi_quirk_entry *quirk; struct cam_ed *device; device = xpt_alloc_device(bus, target, lun_id); if (device == NULL) return (NULL); /* * Take the default quirk entry until we have inquiry * data and can determine a better quirk to use. */ quirk = &scsi_quirk_table[scsi_quirk_table_size - 1]; device->quirk = (void *)quirk; device->mintags = quirk->mintags; device->maxtags = quirk->maxtags; bzero(&device->inq_data, sizeof(device->inq_data)); device->inq_flags = 0; device->queue_flags = 0; device->serial_num = NULL; device->serial_num_len = 0; device->device_id = NULL; device->device_id_len = 0; device->supported_vpds = NULL; device->supported_vpds_len = 0; return (device); } static void scsi_devise_transport(struct cam_path *path) { struct ccb_pathinq cpi; struct ccb_trans_settings cts; struct scsi_inquiry_data *inq_buf; /* Get transport information from the SIM */ xpt_setup_ccb(&cpi.ccb_h, path, CAM_PRIORITY_NONE); cpi.ccb_h.func_code = XPT_PATH_INQ; xpt_action((union ccb *)&cpi); inq_buf = NULL; if ((path->device->flags & CAM_DEV_INQUIRY_DATA_VALID) != 0) inq_buf = &path->device->inq_data; path->device->protocol = PROTO_SCSI; path->device->protocol_version = inq_buf != NULL ? SID_ANSI_REV(inq_buf) : cpi.protocol_version; path->device->transport = cpi.transport; path->device->transport_version = cpi.transport_version; /* * Any device not using SPI3 features should * be considered SPI2 or lower. */ if (inq_buf != NULL) { if (path->device->transport == XPORT_SPI && (inq_buf->spi3data & SID_SPI_MASK) == 0 && path->device->transport_version > 2) path->device->transport_version = 2; } else { struct cam_ed* otherdev; for (otherdev = TAILQ_FIRST(&path->target->ed_entries); otherdev != NULL; otherdev = TAILQ_NEXT(otherdev, links)) { if (otherdev != path->device) break; } if (otherdev != NULL) { /* * Initially assume the same versioning as * prior luns for this target. */ path->device->protocol_version = otherdev->protocol_version; path->device->transport_version = otherdev->transport_version; } else { /* Until we know better, opt for safty */ path->device->protocol_version = 2; if (path->device->transport == XPORT_SPI) path->device->transport_version = 2; else path->device->transport_version = 0; } } /* * XXX * For a device compliant with SPC-2 we should be able * to determine the transport version supported by * scrutinizing the version descriptors in the * inquiry buffer. */ /* Tell the controller what we think */ xpt_setup_ccb(&cts.ccb_h, path, CAM_PRIORITY_NONE); cts.ccb_h.func_code = XPT_SET_TRAN_SETTINGS; cts.type = CTS_TYPE_CURRENT_SETTINGS; cts.transport = path->device->transport; cts.transport_version = path->device->transport_version; cts.protocol = path->device->protocol; cts.protocol_version = path->device->protocol_version; cts.proto_specific.valid = 0; cts.xport_specific.valid = 0; xpt_action((union ccb *)&cts); } static void scsi_dev_advinfo(union ccb *start_ccb) { struct cam_ed *device; struct ccb_dev_advinfo *cdai; off_t amt; start_ccb->ccb_h.status = CAM_REQ_INVALID; device = start_ccb->ccb_h.path->device; cdai = &start_ccb->cdai; switch(cdai->buftype) { case CDAI_TYPE_SCSI_DEVID: if (cdai->flags & CDAI_FLAG_STORE) return; cdai->provsiz = device->device_id_len; if (device->device_id_len == 0) break; amt = device->device_id_len; if (cdai->provsiz > cdai->bufsiz) amt = cdai->bufsiz; memcpy(cdai->buf, device->device_id, amt); break; case CDAI_TYPE_SERIAL_NUM: if (cdai->flags & CDAI_FLAG_STORE) return; cdai->provsiz = device->serial_num_len; if (device->serial_num_len == 0) break; amt = device->serial_num_len; if (cdai->provsiz > cdai->bufsiz) amt = cdai->bufsiz; memcpy(cdai->buf, device->serial_num, amt); break; case CDAI_TYPE_PHYS_PATH: if (cdai->flags & CDAI_FLAG_STORE) { if (device->physpath != NULL) { free(device->physpath, M_CAMXPT); device->physpath = NULL; } device->physpath_len = cdai->bufsiz; /* Clear existing buffer if zero length */ if (cdai->bufsiz == 0) break; device->physpath = malloc(cdai->bufsiz, M_CAMXPT, M_NOWAIT); if (device->physpath == NULL) { start_ccb->ccb_h.status = CAM_REQ_ABORTED; return; } memcpy(device->physpath, cdai->buf, cdai->bufsiz); } else { cdai->provsiz = device->physpath_len; if (device->physpath_len == 0) break; amt = device->physpath_len; if (cdai->provsiz > cdai->bufsiz) amt = cdai->bufsiz; memcpy(cdai->buf, device->physpath, amt); } break; case CDAI_TYPE_RCAPLONG: if (cdai->flags & CDAI_FLAG_STORE) { if (device->rcap_buf != NULL) { free(device->rcap_buf, M_CAMXPT); device->rcap_buf = NULL; } device->rcap_len = cdai->bufsiz; /* Clear existing buffer if zero length */ if (cdai->bufsiz == 0) break; device->rcap_buf = malloc(cdai->bufsiz, M_CAMXPT, M_NOWAIT); if (device->rcap_buf == NULL) { start_ccb->ccb_h.status = CAM_REQ_ABORTED; return; } memcpy(device->rcap_buf, cdai->buf, cdai->bufsiz); } else { cdai->provsiz = device->rcap_len; if (device->rcap_len == 0) break; amt = device->rcap_len; if (cdai->provsiz > cdai->bufsiz) amt = cdai->bufsiz; memcpy(cdai->buf, device->rcap_buf, amt); } break; case CDAI_TYPE_EXT_INQ: /* * We fetch extended inquiry data during probe, if * available. We don't allow changing it. */ if (cdai->flags & CDAI_FLAG_STORE) return; cdai->provsiz = device->ext_inq_len; if (device->ext_inq_len == 0) break; amt = device->ext_inq_len; if (cdai->provsiz > cdai->bufsiz) amt = cdai->bufsiz; memcpy(cdai->buf, device->ext_inq, amt); break; default: return; } start_ccb->ccb_h.status = CAM_REQ_CMP; if (cdai->flags & CDAI_FLAG_STORE) { xpt_async(AC_ADVINFO_CHANGED, start_ccb->ccb_h.path, (void *)(uintptr_t)cdai->buftype); } } static void scsi_action(union ccb *start_ccb) { switch (start_ccb->ccb_h.func_code) { case XPT_SET_TRAN_SETTINGS: { scsi_set_transfer_settings(&start_ccb->cts, start_ccb->ccb_h.path, /*async_update*/FALSE); break; } case XPT_SCAN_BUS: case XPT_SCAN_TGT: scsi_scan_bus(start_ccb->ccb_h.path->periph, start_ccb); break; case XPT_SCAN_LUN: scsi_scan_lun(start_ccb->ccb_h.path->periph, start_ccb->ccb_h.path, start_ccb->crcn.flags, start_ccb); break; case XPT_DEV_ADVINFO: { scsi_dev_advinfo(start_ccb); break; } default: xpt_action_default(start_ccb); break; } } static void scsi_set_transfer_settings(struct ccb_trans_settings *cts, struct cam_path *path, int async_update) { struct ccb_pathinq cpi; struct ccb_trans_settings cur_cts; struct ccb_trans_settings_scsi *scsi; struct ccb_trans_settings_scsi *cur_scsi; struct scsi_inquiry_data *inq_data; struct cam_ed *device; if (path == NULL || (device = path->device) == NULL) { cts->ccb_h.status = CAM_PATH_INVALID; xpt_done((union ccb *)cts); return; } if (cts->protocol == PROTO_UNKNOWN || cts->protocol == PROTO_UNSPECIFIED) { cts->protocol = device->protocol; cts->protocol_version = device->protocol_version; } if (cts->protocol_version == PROTO_VERSION_UNKNOWN || cts->protocol_version == PROTO_VERSION_UNSPECIFIED) cts->protocol_version = device->protocol_version; if (cts->protocol != device->protocol) { xpt_print(path, "Uninitialized Protocol %x:%x?\n", cts->protocol, device->protocol); cts->protocol = device->protocol; } if (cts->protocol_version > device->protocol_version) { if (bootverbose) { xpt_print(path, "Down reving Protocol " "Version from %d to %d?\n", cts->protocol_version, device->protocol_version); } cts->protocol_version = device->protocol_version; } if (cts->transport == XPORT_UNKNOWN || cts->transport == XPORT_UNSPECIFIED) { cts->transport = device->transport; cts->transport_version = device->transport_version; } if (cts->transport_version == XPORT_VERSION_UNKNOWN || cts->transport_version == XPORT_VERSION_UNSPECIFIED) cts->transport_version = device->transport_version; if (cts->transport != device->transport) { xpt_print(path, "Uninitialized Transport %x:%x?\n", cts->transport, device->transport); cts->transport = device->transport; } if (cts->transport_version > device->transport_version) { if (bootverbose) { xpt_print(path, "Down reving Transport " "Version from %d to %d?\n", cts->transport_version, device->transport_version); } cts->transport_version = device->transport_version; } /* * Nothing more of interest to do unless * this is a device connected via the * SCSI protocol. */ if (cts->protocol != PROTO_SCSI) { if (async_update == FALSE) xpt_action_default((union ccb *)cts); return; } inq_data = &device->inq_data; scsi = &cts->proto_specific.scsi; xpt_setup_ccb(&cpi.ccb_h, path, CAM_PRIORITY_NONE); cpi.ccb_h.func_code = XPT_PATH_INQ; xpt_action((union ccb *)&cpi); /* SCSI specific sanity checking */ if ((cpi.hba_inquiry & PI_TAG_ABLE) == 0 || (INQ_DATA_TQ_ENABLED(inq_data)) == 0 || (device->queue_flags & SCP_QUEUE_DQUE) != 0 || (device->mintags == 0)) { /* * Can't tag on hardware that doesn't support tags, * doesn't have it enabled, or has broken tag support. */ scsi->flags &= ~CTS_SCSI_FLAGS_TAG_ENB; } if (async_update == FALSE) { /* * Perform sanity checking against what the * controller and device can do. */ xpt_setup_ccb(&cur_cts.ccb_h, path, CAM_PRIORITY_NONE); cur_cts.ccb_h.func_code = XPT_GET_TRAN_SETTINGS; cur_cts.type = cts->type; xpt_action((union ccb *)&cur_cts); if (cam_ccb_status((union ccb *)&cur_cts) != CAM_REQ_CMP) { return; } cur_scsi = &cur_cts.proto_specific.scsi; if ((scsi->valid & CTS_SCSI_VALID_TQ) == 0) { scsi->flags &= ~CTS_SCSI_FLAGS_TAG_ENB; scsi->flags |= cur_scsi->flags & CTS_SCSI_FLAGS_TAG_ENB; } if ((cur_scsi->valid & CTS_SCSI_VALID_TQ) == 0) scsi->flags &= ~CTS_SCSI_FLAGS_TAG_ENB; } /* SPI specific sanity checking */ if (cts->transport == XPORT_SPI && async_update == FALSE) { u_int spi3caps; struct ccb_trans_settings_spi *spi; struct ccb_trans_settings_spi *cur_spi; spi = &cts->xport_specific.spi; cur_spi = &cur_cts.xport_specific.spi; /* Fill in any gaps in what the user gave us */ if ((spi->valid & CTS_SPI_VALID_SYNC_RATE) == 0) spi->sync_period = cur_spi->sync_period; if ((cur_spi->valid & CTS_SPI_VALID_SYNC_RATE) == 0) spi->sync_period = 0; if ((spi->valid & CTS_SPI_VALID_SYNC_OFFSET) == 0) spi->sync_offset = cur_spi->sync_offset; if ((cur_spi->valid & CTS_SPI_VALID_SYNC_OFFSET) == 0) spi->sync_offset = 0; if ((spi->valid & CTS_SPI_VALID_PPR_OPTIONS) == 0) spi->ppr_options = cur_spi->ppr_options; if ((cur_spi->valid & CTS_SPI_VALID_PPR_OPTIONS) == 0) spi->ppr_options = 0; if ((spi->valid & CTS_SPI_VALID_BUS_WIDTH) == 0) spi->bus_width = cur_spi->bus_width; if ((cur_spi->valid & CTS_SPI_VALID_BUS_WIDTH) == 0) spi->bus_width = 0; if ((spi->valid & CTS_SPI_VALID_DISC) == 0) { spi->flags &= ~CTS_SPI_FLAGS_DISC_ENB; spi->flags |= cur_spi->flags & CTS_SPI_FLAGS_DISC_ENB; } if ((cur_spi->valid & CTS_SPI_VALID_DISC) == 0) spi->flags &= ~CTS_SPI_FLAGS_DISC_ENB; if (((device->flags & CAM_DEV_INQUIRY_DATA_VALID) != 0 && (inq_data->flags & SID_Sync) == 0 && cts->type == CTS_TYPE_CURRENT_SETTINGS) || ((cpi.hba_inquiry & PI_SDTR_ABLE) == 0)) { /* Force async */ spi->sync_period = 0; spi->sync_offset = 0; } switch (spi->bus_width) { case MSG_EXT_WDTR_BUS_32_BIT: if (((device->flags & CAM_DEV_INQUIRY_DATA_VALID) == 0 || (inq_data->flags & SID_WBus32) != 0 || cts->type == CTS_TYPE_USER_SETTINGS) && (cpi.hba_inquiry & PI_WIDE_32) != 0) break; /* Fall Through to 16-bit */ case MSG_EXT_WDTR_BUS_16_BIT: if (((device->flags & CAM_DEV_INQUIRY_DATA_VALID) == 0 || (inq_data->flags & SID_WBus16) != 0 || cts->type == CTS_TYPE_USER_SETTINGS) && (cpi.hba_inquiry & PI_WIDE_16) != 0) { spi->bus_width = MSG_EXT_WDTR_BUS_16_BIT; break; } /* Fall Through to 8-bit */ default: /* New bus width?? */ case MSG_EXT_WDTR_BUS_8_BIT: /* All targets can do this */ spi->bus_width = MSG_EXT_WDTR_BUS_8_BIT; break; } spi3caps = cpi.xport_specific.spi.ppr_options; if ((device->flags & CAM_DEV_INQUIRY_DATA_VALID) != 0 && cts->type == CTS_TYPE_CURRENT_SETTINGS) spi3caps &= inq_data->spi3data; if ((spi3caps & SID_SPI_CLOCK_DT) == 0) spi->ppr_options &= ~MSG_EXT_PPR_DT_REQ; if ((spi3caps & SID_SPI_IUS) == 0) spi->ppr_options &= ~MSG_EXT_PPR_IU_REQ; if ((spi3caps & SID_SPI_QAS) == 0) spi->ppr_options &= ~MSG_EXT_PPR_QAS_REQ; /* No SPI Transfer settings are allowed unless we are wide */ if (spi->bus_width == 0) spi->ppr_options = 0; if ((spi->valid & CTS_SPI_VALID_DISC) && ((spi->flags & CTS_SPI_FLAGS_DISC_ENB) == 0)) { /* * Can't tag queue without disconnection. */ scsi->flags &= ~CTS_SCSI_FLAGS_TAG_ENB; scsi->valid |= CTS_SCSI_VALID_TQ; } /* * If we are currently performing tagged transactions to * this device and want to change its negotiation parameters, * go non-tagged for a bit to give the controller a chance to * negotiate unhampered by tag messages. */ if (cts->type == CTS_TYPE_CURRENT_SETTINGS && (device->inq_flags & SID_CmdQue) != 0 && (scsi->flags & CTS_SCSI_FLAGS_TAG_ENB) != 0 && (spi->flags & (CTS_SPI_VALID_SYNC_RATE| CTS_SPI_VALID_SYNC_OFFSET| CTS_SPI_VALID_BUS_WIDTH)) != 0) scsi_toggle_tags(path); } if (cts->type == CTS_TYPE_CURRENT_SETTINGS && (scsi->valid & CTS_SCSI_VALID_TQ) != 0) { int device_tagenb; /* * If we are transitioning from tags to no-tags or * vice-versa, we need to carefully freeze and restart * the queue so that we don't overlap tagged and non-tagged * commands. We also temporarily stop tags if there is * a change in transfer negotiation settings to allow * "tag-less" negotiation. */ if ((device->flags & CAM_DEV_TAG_AFTER_COUNT) != 0 || (device->inq_flags & SID_CmdQue) != 0) device_tagenb = TRUE; else device_tagenb = FALSE; if (((scsi->flags & CTS_SCSI_FLAGS_TAG_ENB) != 0 && device_tagenb == FALSE) || ((scsi->flags & CTS_SCSI_FLAGS_TAG_ENB) == 0 && device_tagenb == TRUE)) { if ((scsi->flags & CTS_SCSI_FLAGS_TAG_ENB) != 0) { /* * Delay change to use tags until after a * few commands have gone to this device so * the controller has time to perform transfer * negotiations without tagged messages getting * in the way. */ device->tag_delay_count = CAM_TAG_DELAY_COUNT; device->flags |= CAM_DEV_TAG_AFTER_COUNT; } else { xpt_stop_tags(path); } } } if (async_update == FALSE) xpt_action_default((union ccb *)cts); } static void scsi_toggle_tags(struct cam_path *path) { struct cam_ed *dev; /* * Give controllers a chance to renegotiate * before starting tag operations. We * "toggle" tagged queuing off then on * which causes the tag enable command delay * counter to come into effect. */ dev = path->device; if ((dev->flags & CAM_DEV_TAG_AFTER_COUNT) != 0 || ((dev->inq_flags & SID_CmdQue) != 0 && (dev->inq_flags & (SID_Sync|SID_WBus16|SID_WBus32)) != 0)) { struct ccb_trans_settings cts; xpt_setup_ccb(&cts.ccb_h, path, CAM_PRIORITY_NONE); cts.protocol = PROTO_SCSI; cts.protocol_version = PROTO_VERSION_UNSPECIFIED; cts.transport = XPORT_UNSPECIFIED; cts.transport_version = XPORT_VERSION_UNSPECIFIED; cts.proto_specific.scsi.flags = 0; cts.proto_specific.scsi.valid = CTS_SCSI_VALID_TQ; scsi_set_transfer_settings(&cts, path, /*async_update*/TRUE); cts.proto_specific.scsi.flags = CTS_SCSI_FLAGS_TAG_ENB; scsi_set_transfer_settings(&cts, path, /*async_update*/TRUE); } } /* * Handle any per-device event notifications that require action by the XPT. */ static void scsi_dev_async(u_int32_t async_code, struct cam_eb *bus, struct cam_et *target, struct cam_ed *device, void *async_arg) { cam_status status; struct cam_path newpath; /* * We only need to handle events for real devices. */ if (target->target_id == CAM_TARGET_WILDCARD || device->lun_id == CAM_LUN_WILDCARD) return; /* * We need our own path with wildcards expanded to * handle certain types of events. */ if ((async_code == AC_SENT_BDR) || (async_code == AC_BUS_RESET) || (async_code == AC_INQ_CHANGED)) status = xpt_compile_path(&newpath, NULL, bus->path_id, target->target_id, device->lun_id); else status = CAM_REQ_CMP_ERR; if (status == CAM_REQ_CMP) { /* * Allow transfer negotiation to occur in a * tag free environment and after settle delay. */ if (async_code == AC_SENT_BDR || async_code == AC_BUS_RESET) { cam_freeze_devq(&newpath); cam_release_devq(&newpath, RELSIM_RELEASE_AFTER_TIMEOUT, /*reduction*/0, /*timeout*/scsi_delay, /*getcount_only*/0); scsi_toggle_tags(&newpath); } if (async_code == AC_INQ_CHANGED) { /* * We've sent a start unit command, or * something similar to a device that * may have caused its inquiry data to * change. So we re-scan the device to * refresh the inquiry data for it. */ scsi_scan_lun(newpath.periph, &newpath, CAM_EXPECT_INQ_CHANGE, NULL); } xpt_release_path(&newpath); } else if (async_code == AC_LOST_DEVICE && (device->flags & CAM_DEV_UNCONFIGURED) == 0) { device->flags |= CAM_DEV_UNCONFIGURED; xpt_release_device(device); } else if (async_code == AC_TRANSFER_NEG) { struct ccb_trans_settings *settings; struct cam_path path; settings = (struct ccb_trans_settings *)async_arg; xpt_compile_path(&path, NULL, bus->path_id, target->target_id, device->lun_id); scsi_set_transfer_settings(settings, &path, /*async_update*/TRUE); xpt_release_path(&path); } } static void scsi_announce_periph(struct cam_periph *periph) { struct ccb_pathinq cpi; struct ccb_trans_settings cts; struct cam_path *path = periph->path; u_int speed; u_int freq; u_int mb; cam_periph_assert(periph, MA_OWNED); xpt_setup_ccb(&cts.ccb_h, path, CAM_PRIORITY_NORMAL); cts.ccb_h.func_code = XPT_GET_TRAN_SETTINGS; cts.type = CTS_TYPE_CURRENT_SETTINGS; xpt_action((union ccb*)&cts); if (cam_ccb_status((union ccb *)&cts) != CAM_REQ_CMP) return; /* Ask the SIM for its base transfer speed */ xpt_setup_ccb(&cpi.ccb_h, path, CAM_PRIORITY_NORMAL); cpi.ccb_h.func_code = XPT_PATH_INQ; xpt_action((union ccb *)&cpi); /* Report connection speed */ speed = cpi.base_transfer_speed; freq = 0; if (cts.ccb_h.status == CAM_REQ_CMP && cts.transport == XPORT_SPI) { struct ccb_trans_settings_spi *spi = &cts.xport_specific.spi; if ((spi->valid & CTS_SPI_VALID_SYNC_OFFSET) != 0 && spi->sync_offset != 0) { freq = scsi_calc_syncsrate(spi->sync_period); speed = freq; } if ((spi->valid & CTS_SPI_VALID_BUS_WIDTH) != 0) speed *= (0x01 << spi->bus_width); } if (cts.ccb_h.status == CAM_REQ_CMP && cts.transport == XPORT_FC) { struct ccb_trans_settings_fc *fc = &cts.xport_specific.fc; if (fc->valid & CTS_FC_VALID_SPEED) speed = fc->bitrate; } if (cts.ccb_h.status == CAM_REQ_CMP && cts.transport == XPORT_SAS) { struct ccb_trans_settings_sas *sas = &cts.xport_specific.sas; if (sas->valid & CTS_SAS_VALID_SPEED) speed = sas->bitrate; } mb = speed / 1000; if (mb > 0) printf("%s%d: %d.%03dMB/s transfers", periph->periph_name, periph->unit_number, mb, speed % 1000); else printf("%s%d: %dKB/s transfers", periph->periph_name, periph->unit_number, speed); /* Report additional information about SPI connections */ if (cts.ccb_h.status == CAM_REQ_CMP && cts.transport == XPORT_SPI) { struct ccb_trans_settings_spi *spi; spi = &cts.xport_specific.spi; if (freq != 0) { printf(" (%d.%03dMHz%s, offset %d", freq / 1000, freq % 1000, (spi->ppr_options & MSG_EXT_PPR_DT_REQ) != 0 ? " DT" : "", spi->sync_offset); } if ((spi->valid & CTS_SPI_VALID_BUS_WIDTH) != 0 && spi->bus_width > 0) { if (freq != 0) { printf(", "); } else { printf(" ("); } printf("%dbit)", 8 * (0x01 << spi->bus_width)); } else if (freq != 0) { printf(")"); } } if (cts.ccb_h.status == CAM_REQ_CMP && cts.transport == XPORT_FC) { struct ccb_trans_settings_fc *fc; fc = &cts.xport_specific.fc; if (fc->valid & CTS_FC_VALID_WWNN) printf(" WWNN 0x%llx", (long long) fc->wwnn); if (fc->valid & CTS_FC_VALID_WWPN) printf(" WWPN 0x%llx", (long long) fc->wwpn); if (fc->valid & CTS_FC_VALID_PORT) printf(" PortID 0x%x", fc->port); } printf("\n"); }