/* * Copyright (c) 2004 by Bruce M. Simpson. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. 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(S) ``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(S) 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 /* XXX */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "scsi_util.h" extern int camverbose; void dump_flexible_geometry(struct flexible_disk_page *flex) { u_int16_t tracks; u_int16_t sectorsize; tracks = scsi_2btoul(&flex->ncyl_1); sectorsize = scsi_2btoul(&flex->bytes_s_1); fprintf(stderr, "Geometry: %lu cyl %u heads %u secpercyl %lu bytespersec\n", (unsigned long)tracks, flex->nheads, flex->sec_per_track, (unsigned long)sectorsize); } /* * Convert UFI geometry to fdc(4) style geometry, using information * obtained via MODE_SENSE and READ_FORMAT_CAPACITIES. * fcl points to the chosen format_capacity_list entry (normally the * current or maximum value) which is to be used to format the disk. */ void floppy_geom_scsi_to_fdc(struct fd_type *fdt, const struct flexible_disk_page *flexpage, const struct format_capacity_descriptor *fcd) { bzero(fdt, sizeof(*fdt)); fdt->sectrac = flexpage->sec_per_track; /* * Convert sector size to a secsize code for fd_type. * Assume that 'sector' and 'block' mean the same thing here. * Assume that the sector size is always a perfect power of two. */ fdt->secsize = ffs(scsi_3btol((u_int8_t *)&fcd->block_length)) - 8; /*fdt->secsize = ffs(scsi_2btoul(&flexpage->bytes_s_1)) - 8;*/ fdt->datalen = 0; /* XXX: Gross assumption */ fdt->gap = 0; /* XXX: Gross assumption */ fdt->tracks = scsi_2btoul((u_int8_t *)&flexpage->ncyl_1); fdt->size = scsi_4btoul((u_int8_t *)&fcd->nblocks); /* # LBAs */ fdt->trans = FDC_500KBPS; /* XXX: Gross assumption */ /*scsi_2btoul(&flexpage->xfr_rate_1);*/ fdt->heads = flexpage->nheads; fdt->f_gap = 0; /* XXX */ fdt->f_inter = 0; /* XXX */ fdt->offset_side2 = 0; /* XXX */ fdt->flags = FL_MFM; /* not specified within UFI */ } /* * Fill-out the additional parameter data required by UFI/SFF8070i * for the 12-byte FORMAT_UNIT command. * * Notes: * ufid points to the structure to be filled out. * cap points to the disk format to be used, as returned by the * GET_FORMAT_CAPACITIES command. * track is the current track to be formatted, or FUFI_ALL_TRACKS * for a complete format of the unit. * side is the current side to be formatted, or FUFI_SIDE_NONE for * a complete format of the unit. */ void scsi_fillout_format_ufi_data(struct format_ufi_data *ufid, u_int8_t track, u_int8_t side, struct format_capacity_descriptor *cap) { bzero(ufid, sizeof(*ufid)); /* * Always set the Format Options Valid (FOV) and Disable * CeRTification bits. All other bits should be cleared. * * Set the single track bit if a track-by-track format * was specified. */ ufid->fdh.byte2 = FU_DLH_FOV | FU_DLH_DCRT; ufid->fdh.byte2 |= (track == FUFI_ALL_TRACKS) ? 0 : FU_DLH_STPF; /* * Set the 'upper side' bit if both a track-by-track forat * and a side were specified. * Specifies which side if it's a track-by-track format; * 0 is the bottom side (lower LBA), 1 is the upper side. */ if (track != FUFI_ALL_TRACKS && side == FUFI_SIDE_UPPER) ufid->fdh.byte2 |= FU_DLH_VS; /* Fillout format_capacity_descriptor with chosen LBA format. */ bcopy(cap, &ufid->fcd, sizeof(*cap)); ufid->fcd.byte4 = 0x00; /* Fillout defect_list_length. */ scsi_ulto2b(sizeof(ufid->fcd), ufid->fdh.defect_list_length); } /* * 12-byte version of FORMAT_UNIT, for use with UFI and SFF8070i devices. * * Notes: * The track number will come from the unit's flexible disk geometry * according to the current position in the format. * The interleave should normally be set to 0. * The LUN doesn't get filled out here. * The param_buf is a struct format_unit_ufi_data which was previously * filled out by scsi_format_unit_ufi_data(). */ void scsi_format_unit_12(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_int8_t track, u_int16_t interleave, u_int8_t *param_buf, u_int32_t param_len, u_int8_t sense_len, u_int32_t timeout) { struct scsi_format_unit_12 *scsi_cmd; scsi_cmd = (struct scsi_format_unit_12 *)&csio->cdb_io.cdb_bytes; bzero(scsi_cmd, sizeof(*scsi_cmd)); scsi_cmd->opcode = FORMAT_UNIT; scsi_cmd->byte2 = byte2 | FU_FMT_DATA | 0x07; /* LUN etc */ scsi_cmd->track = track; scsi_ulto2b(interleave, scsi_cmd->interleave); scsi_ulto2b(param_len, scsi_cmd->param_len); cam_fill_csio(csio, retries, cbfcnp, CAM_DIR_OUT, tag_action, param_buf, param_len, sense_len, sizeof(*scsi_cmd), timeout); } /* * Prepare a CDB which will return the result of the READ_FORMAT_CAPACITIES * command into the user-provided buffer fmt_buf, with length fmt_len, * once executed. * * XXX: Move this to kernel headers when ready. */ void scsi_read_format_capacities(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_int8_t *fmt_buf, u_int32_t fmt_len, u_int8_t sense_len, u_int32_t timeout) { struct scsi_read_format_capacities *scsi_cmd; cam_fill_csio(csio, retries, cbfcnp, CAM_DIR_IN, tag_action, fmt_buf, fmt_len, sense_len, sizeof(*scsi_cmd), timeout); scsi_cmd = (struct scsi_read_format_capacities *) &csio->cdb_io.cdb_bytes; bzero(scsi_cmd, sizeof(*scsi_cmd)); scsi_cmd->opcode = READ_FORMAT_CAPACITIES; scsi_cmd->byte2 = byte2; /* LUN */ scsi_ulto2b(fmt_len, scsi_cmd->alloc_length); } /* * Execute and return the result of the READ_FORMAT_CAPACITIES command. */ int scsi_get_format_capacities(struct cam_device *device, int retry_count, int timeout, u_int8_t *fmt_buf, u_int32_t fmt_len) { union ccb *ccb; int error = 0; ccb = cam_getccb(device); if (ccb == NULL) { warnx("couldn't allocate CCB"); return (1); } bzero(&(&ccb->ccb_h)[1], sizeof(struct ccb_scsiio) - sizeof(struct ccb_hdr)); bzero(fmt_buf, fmt_len); scsi_read_format_capacities(&ccb->csio, retry_count, NULL, MSG_SIMPLE_Q_TAG, 0, /* XXX: byte2 should contain the LUN */ fmt_buf, fmt_len, SSD_FULL_SIZE, timeout ? timeout : 5000); /* Disable freezing the device queue */ ccb->ccb_h.flags |= CAM_DEV_QFRZDIS; #if 0 if (arglist & CAM_ARG_ERR_RECOVER) ccb->ccb_h.flags |= CAM_PASS_ERR_RECOVER; #endif if (cam_send_ccb(device, ccb) < 0) { perror("error sending SCSI get_format_capacities"); if (camverbose) cam_error_print(device, ccb, CAM_ESF_ALL, CAM_EPF_ALL, stderr); cam_freeccb(ccb); return (1); } if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) { error = 1; if (camverbose) cam_error_print(device, ccb, CAM_ESF_ALL, CAM_EPF_ALL, stderr); } cam_freeccb(ccb); return (0); } /* * Perform a SCSI INQUIRY command and return the results in the user * provided buffer inq_buf. */ int scsi_get_inquiry(struct cam_device *device, int retry_count, int timeout, struct scsi_inquiry_data *inq_buf) { union ccb *ccb; int error = 0; ccb = cam_getccb(device); if (ccb == NULL) { warnx("couldn't allocate CCB"); return (1); } bzero(&(&ccb->ccb_h)[1], sizeof(struct ccb_scsiio) - sizeof(struct ccb_hdr)); bzero(inq_buf, sizeof(*inq_buf)); scsi_inquiry(&ccb->csio, /* retries */ retry_count, /* cbfcnp */ NULL, /* tag_action */ MSG_SIMPLE_Q_TAG, /* inq_buf */ (u_int8_t *)inq_buf, /* inq_len */ SHORT_INQUIRY_LENGTH, /* evpd */ 0, /* page_code */ 0, /* sense_len */ SSD_FULL_SIZE, /* timeout */ timeout ? timeout : 5000); /* Disable freezing the device queue */ ccb->ccb_h.flags |= CAM_DEV_QFRZDIS; #if 0 if (arglist & CAM_ARG_ERR_RECOVER) ccb->ccb_h.flags |= CAM_PASS_ERR_RECOVER; #endif if (cam_send_ccb(device, ccb) < 0) { perror("error sending SCSI inquiry"); if (camverbose) cam_error_print(device, ccb, CAM_ESF_ALL, CAM_EPF_ALL, stderr); cam_freeccb(ccb); return (1); } if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) { error = 1; if (camverbose) cam_error_print(device, ccb, CAM_ESF_ALL, CAM_EPF_ALL, stderr); } cam_freeccb(ccb); return (error); } /* * Return the results of MODE_SENSE for the SMS_FLEXIBLE_GEOMETRY_PAGE * into the buffer page_data, but don't attempt to parse it. */ int scsi_get_flexible_disk_page(struct cam_device *cam_dev, struct flexible_disk_page *page_data, int min_mode_cmd_size, int timeout) { struct scsi_mode_header_6 *mh6; struct scsi_mode_header_10 *mh10; union disk_pages *mode_pars; /* embeds the scsi_mode_page_header */ u_int8_t *data; size_t data_len; int retval; data_len = sizeof(union disk_pages); data_len += (min_mode_cmd_size < 10) ? sizeof(struct scsi_mode_header_6) : sizeof(struct scsi_mode_header_10); data = malloc(data_len); if (data == NULL) errx(EX_OSERR, "error allocating mode sense buffer"); retval = scsi_get_mode_sense(cam_dev, SMS_FLEXIBLE_GEOMETRY_PAGE, SMS_PAGE_CTRL_CURRENT, 0, 0, timeout, (u_int8_t *)data, data_len, min_mode_cmd_size); if (retval != 0) { free(data); errx(EX_OSERR, "error sending mode sense command"); } mh6 = (struct scsi_mode_header_6 *)data; mh10 = (struct scsi_mode_header_10 *)data; mode_pars = (union disk_pages *)((min_mode_cmd_size < 10) ? MODE_PAGE_HEADER_6(mh6) : MODE_PAGE_HEADER_10(mh10)); bcopy(&mode_pars->flexible_disk, page_data, sizeof(struct flexible_disk_page)); free(data); return (retval); } /* * Return the result of the MODE SENSE command into the user-provided * buffer 'data'. */ int scsi_get_mode_sense(struct cam_device *device, int mode_page, int page_control, int dbd, int retry_count, int timeout, u_int8_t *data, int datalen, int min_mode_cmd_size) { union ccb *ccb; int retval; ccb = cam_getccb(device); if (ccb == NULL) errx(1, "mode_sense: couldn't allocate CCB"); bzero(&(&ccb->ccb_h)[1], sizeof(struct ccb_scsiio) - sizeof(struct ccb_hdr)); bzero(data, datalen); scsi_mode_sense_len(&ccb->csio, /* retries */ retry_count, /* cbfcnp */ NULL, /* tag_action */ MSG_SIMPLE_Q_TAG, /* dbd */ dbd, /* page_code */ page_control << 6, /* page */ mode_page, /* param_buf */ data, /* param_len */ datalen, /* minimum_cmd_size */ min_mode_cmd_size, /* sense_len */ SSD_FULL_SIZE, /* timeout */ timeout ? timeout : 5000); /* Disable freezing the device queue */ ccb->ccb_h.flags |= CAM_DEV_QFRZDIS; if (((retval = cam_send_ccb(device, ccb)) < 0) || ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP)) { if (camverbose) cam_error_print(device, ccb, CAM_ESF_ALL, CAM_EPF_ALL, stderr); retval = 1; } cam_freeccb(ccb); return (retval); } void sfds_dump_desc(char *hdr, int dump_code_type, struct format_capacity_descriptor *pfd) { u_int32_t block_length, nblocks; char *type = ""; nblocks = scsi_4btoul(pfd->nblocks); block_length = scsi_3btol(pfd->block_length); switch (pfd->byte4 & FCD_CODE_MASK) { case FCD_UNFORMATTED: type = "unformatted"; break; case FCD_FORMATTED: type = "formatted"; break; case FCD_NOMEDIA: type = "no disk present"; break; } if (dump_code_type) { hdr = ((pfd->byte4 & FCD_CODE_MASK) == FCD_NOMEDIA) ? "maximum" : "current"; } fprintf(stderr, "%s: %lu blocks, %lu bytes-per-block %s\n", hdr, (unsigned long)nblocks, (unsigned long)block_length, type); } void sfds_dump_supported_formats(struct supported_format_descriptors *sfds) { char hdr[16]; struct format_capacity_descriptor *pfd; int i, cnt, listlen; listlen = sfds->fh.capacity_list_length; cnt = listlen / sizeof(sfds->fdsupp[0]) - 1; if ((listlen % sizeof(sfds->fdsupp[0])) > 0) { fprintf(stderr, "WARNING: format capacity list may have been truncated.\n"); } sfds_dump_desc("currmax", 1, &sfds->fdcurrmax); pfd = &sfds->fdsupp[0]; for (i = 0; i < cnt; i++, pfd++) { snprintf(hdr, sizeof(hdr), "%d", i); sfds_dump_desc(hdr, 0, pfd); } }