Index: releng/11.2/sys/cam/scsi/scsi_cd.c =================================================================== --- releng/11.2/sys/cam/scsi/scsi_cd.c (revision 349624) +++ releng/11.2/sys/cam/scsi/scsi_cd.c (revision 349625) @@ -1,3719 +1,3709 @@ /*- * 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[32], tmpstr2[16]; 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, nitems(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), "%juMB (%ju %u byte sectors)", ((uintmax_t)cdp->disksize * cdp->blksize) / (1024 * 1024), (uintmax_t)cdp->disksize, 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 || error_code == SSD_DESC_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) { u_int i; for (i = 0; i < nitems(cd_page_size_table); 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; + int 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); - } + error = copyout(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); } Index: releng/11.2/sys/compat/linux/linux_ioctl.c =================================================================== --- releng/11.2/sys/compat/linux/linux_ioctl.c (revision 349624) +++ releng/11.2/sys/compat/linux/linux_ioctl.c (revision 349625) @@ -1,3812 +1,3822 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 1994-1995 Søren Schmidt * 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. * * 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 "opt_compat.h" #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 #include #include #include #include #include #include #include #include #include #include #ifdef COMPAT_LINUX32 #include #include #else #include #include #endif #include #include #include #include #include #include #include #include #include #include CTASSERT(LINUX_IFNAMSIZ == IFNAMSIZ); static linux_ioctl_function_t linux_ioctl_cdrom; static linux_ioctl_function_t linux_ioctl_vfat; static linux_ioctl_function_t linux_ioctl_console; static linux_ioctl_function_t linux_ioctl_hdio; static linux_ioctl_function_t linux_ioctl_disk; static linux_ioctl_function_t linux_ioctl_socket; static linux_ioctl_function_t linux_ioctl_sound; static linux_ioctl_function_t linux_ioctl_termio; static linux_ioctl_function_t linux_ioctl_private; static linux_ioctl_function_t linux_ioctl_drm; static linux_ioctl_function_t linux_ioctl_sg; static linux_ioctl_function_t linux_ioctl_v4l; static linux_ioctl_function_t linux_ioctl_v4l2; static linux_ioctl_function_t linux_ioctl_special; static linux_ioctl_function_t linux_ioctl_fbsd_usb; static linux_ioctl_function_t linux_ioctl_evdev; static struct linux_ioctl_handler cdrom_handler = { linux_ioctl_cdrom, LINUX_IOCTL_CDROM_MIN, LINUX_IOCTL_CDROM_MAX }; static struct linux_ioctl_handler vfat_handler = { linux_ioctl_vfat, LINUX_IOCTL_VFAT_MIN, LINUX_IOCTL_VFAT_MAX }; static struct linux_ioctl_handler console_handler = { linux_ioctl_console, LINUX_IOCTL_CONSOLE_MIN, LINUX_IOCTL_CONSOLE_MAX }; static struct linux_ioctl_handler hdio_handler = { linux_ioctl_hdio, LINUX_IOCTL_HDIO_MIN, LINUX_IOCTL_HDIO_MAX }; static struct linux_ioctl_handler disk_handler = { linux_ioctl_disk, LINUX_IOCTL_DISK_MIN, LINUX_IOCTL_DISK_MAX }; static struct linux_ioctl_handler socket_handler = { linux_ioctl_socket, LINUX_IOCTL_SOCKET_MIN, LINUX_IOCTL_SOCKET_MAX }; static struct linux_ioctl_handler sound_handler = { linux_ioctl_sound, LINUX_IOCTL_SOUND_MIN, LINUX_IOCTL_SOUND_MAX }; static struct linux_ioctl_handler termio_handler = { linux_ioctl_termio, LINUX_IOCTL_TERMIO_MIN, LINUX_IOCTL_TERMIO_MAX }; static struct linux_ioctl_handler private_handler = { linux_ioctl_private, LINUX_IOCTL_PRIVATE_MIN, LINUX_IOCTL_PRIVATE_MAX }; static struct linux_ioctl_handler drm_handler = { linux_ioctl_drm, LINUX_IOCTL_DRM_MIN, LINUX_IOCTL_DRM_MAX }; static struct linux_ioctl_handler sg_handler = { linux_ioctl_sg, LINUX_IOCTL_SG_MIN, LINUX_IOCTL_SG_MAX }; static struct linux_ioctl_handler video_handler = { linux_ioctl_v4l, LINUX_IOCTL_VIDEO_MIN, LINUX_IOCTL_VIDEO_MAX }; static struct linux_ioctl_handler video2_handler = { linux_ioctl_v4l2, LINUX_IOCTL_VIDEO2_MIN, LINUX_IOCTL_VIDEO2_MAX }; static struct linux_ioctl_handler fbsd_usb = { linux_ioctl_fbsd_usb, FBSD_LUSB_MIN, FBSD_LUSB_MAX }; static struct linux_ioctl_handler evdev_handler = { linux_ioctl_evdev, LINUX_IOCTL_EVDEV_MIN, LINUX_IOCTL_EVDEV_MAX }; DATA_SET(linux_ioctl_handler_set, cdrom_handler); DATA_SET(linux_ioctl_handler_set, vfat_handler); DATA_SET(linux_ioctl_handler_set, console_handler); DATA_SET(linux_ioctl_handler_set, hdio_handler); DATA_SET(linux_ioctl_handler_set, disk_handler); DATA_SET(linux_ioctl_handler_set, socket_handler); DATA_SET(linux_ioctl_handler_set, sound_handler); DATA_SET(linux_ioctl_handler_set, termio_handler); DATA_SET(linux_ioctl_handler_set, private_handler); DATA_SET(linux_ioctl_handler_set, drm_handler); DATA_SET(linux_ioctl_handler_set, sg_handler); DATA_SET(linux_ioctl_handler_set, video_handler); DATA_SET(linux_ioctl_handler_set, video2_handler); DATA_SET(linux_ioctl_handler_set, fbsd_usb); DATA_SET(linux_ioctl_handler_set, evdev_handler); struct handler_element { TAILQ_ENTRY(handler_element) list; int (*func)(struct thread *, struct linux_ioctl_args *); int low, high, span; }; static TAILQ_HEAD(, handler_element) handlers = TAILQ_HEAD_INITIALIZER(handlers); static struct sx linux_ioctl_sx; SX_SYSINIT(linux_ioctl, &linux_ioctl_sx, "linux ioctl handlers"); /* * hdio related ioctls for VMWare support */ struct linux_hd_geometry { u_int8_t heads; u_int8_t sectors; u_int16_t cylinders; u_int32_t start; }; struct linux_hd_big_geometry { u_int8_t heads; u_int8_t sectors; u_int32_t cylinders; u_int32_t start; }; static int linux_ioctl_hdio(struct thread *td, struct linux_ioctl_args *args) { cap_rights_t rights; struct file *fp; int error; u_int sectorsize, fwcylinders, fwheads, fwsectors; off_t mediasize, bytespercyl; error = fget(td, args->fd, cap_rights_init(&rights, CAP_IOCTL), &fp); if (error != 0) return (error); switch (args->cmd & 0xffff) { case LINUX_HDIO_GET_GEO: case LINUX_HDIO_GET_GEO_BIG: error = fo_ioctl(fp, DIOCGMEDIASIZE, (caddr_t)&mediasize, td->td_ucred, td); if (!error) error = fo_ioctl(fp, DIOCGSECTORSIZE, (caddr_t)§orsize, td->td_ucred, td); if (!error) error = fo_ioctl(fp, DIOCGFWHEADS, (caddr_t)&fwheads, td->td_ucred, td); if (!error) error = fo_ioctl(fp, DIOCGFWSECTORS, (caddr_t)&fwsectors, td->td_ucred, td); /* * XXX: DIOCGFIRSTOFFSET is not yet implemented, so * so pretend that GEOM always says 0. This is NOT VALID * for slices or partitions, only the per-disk raw devices. */ fdrop(fp, td); if (error) return (error); /* * 1. Calculate the number of bytes in a cylinder, * given the firmware's notion of heads and sectors * per cylinder. * 2. Calculate the number of cylinders, given the total * size of the media. * All internal calculations should have 64-bit precision. */ bytespercyl = (off_t) sectorsize * fwheads * fwsectors; fwcylinders = mediasize / bytespercyl; #if defined(DEBUG) linux_msg(td, "HDIO_GET_GEO: mediasize %jd, c/h/s %d/%d/%d, " "bpc %jd", (intmax_t)mediasize, fwcylinders, fwheads, fwsectors, (intmax_t)bytespercyl); #endif if ((args->cmd & 0xffff) == LINUX_HDIO_GET_GEO) { struct linux_hd_geometry hdg; hdg.cylinders = fwcylinders; hdg.heads = fwheads; hdg.sectors = fwsectors; hdg.start = 0; error = copyout(&hdg, (void *)args->arg, sizeof(hdg)); } else if ((args->cmd & 0xffff) == LINUX_HDIO_GET_GEO_BIG) { struct linux_hd_big_geometry hdbg; memset(&hdbg, 0, sizeof(hdbg)); hdbg.cylinders = fwcylinders; hdbg.heads = fwheads; hdbg.sectors = fwsectors; hdbg.start = 0; error = copyout(&hdbg, (void *)args->arg, sizeof(hdbg)); } return (error); break; default: /* XXX */ linux_msg(td, "ioctl fd=%d, cmd=0x%x ('%c',%d) is not implemented", args->fd, (int)(args->cmd & 0xffff), (int)(args->cmd & 0xff00) >> 8, (int)(args->cmd & 0xff)); break; } fdrop(fp, td); return (ENOIOCTL); } static int linux_ioctl_disk(struct thread *td, struct linux_ioctl_args *args) { cap_rights_t rights; struct file *fp; int error; u_int sectorsize; off_t mediasize; error = fget(td, args->fd, cap_rights_init(&rights, CAP_IOCTL), &fp); if (error != 0) return (error); switch (args->cmd & 0xffff) { case LINUX_BLKGETSIZE: error = fo_ioctl(fp, DIOCGSECTORSIZE, (caddr_t)§orsize, td->td_ucred, td); if (!error) error = fo_ioctl(fp, DIOCGMEDIASIZE, (caddr_t)&mediasize, td->td_ucred, td); fdrop(fp, td); if (error) return (error); sectorsize = mediasize / sectorsize; /* * XXX: How do we know we return the right size of integer ? */ return (copyout(§orsize, (void *)args->arg, sizeof(sectorsize))); break; case LINUX_BLKSSZGET: error = fo_ioctl(fp, DIOCGSECTORSIZE, (caddr_t)§orsize, td->td_ucred, td); fdrop(fp, td); if (error) return (error); return (copyout(§orsize, (void *)args->arg, sizeof(sectorsize))); break; } fdrop(fp, td); return (ENOIOCTL); } /* * termio related ioctls */ struct linux_termio { unsigned short c_iflag; unsigned short c_oflag; unsigned short c_cflag; unsigned short c_lflag; unsigned char c_line; unsigned char c_cc[LINUX_NCC]; }; struct linux_termios { unsigned int c_iflag; unsigned int c_oflag; unsigned int c_cflag; unsigned int c_lflag; unsigned char c_line; unsigned char c_cc[LINUX_NCCS]; }; struct linux_winsize { unsigned short ws_row, ws_col; unsigned short ws_xpixel, ws_ypixel; }; struct speedtab { int sp_speed; /* Speed. */ int sp_code; /* Code. */ }; static struct speedtab sptab[] = { { B0, LINUX_B0 }, { B50, LINUX_B50 }, { B75, LINUX_B75 }, { B110, LINUX_B110 }, { B134, LINUX_B134 }, { B150, LINUX_B150 }, { B200, LINUX_B200 }, { B300, LINUX_B300 }, { B600, LINUX_B600 }, { B1200, LINUX_B1200 }, { B1800, LINUX_B1800 }, { B2400, LINUX_B2400 }, { B4800, LINUX_B4800 }, { B9600, LINUX_B9600 }, { B19200, LINUX_B19200 }, { B38400, LINUX_B38400 }, { B57600, LINUX_B57600 }, { B115200, LINUX_B115200 }, {-1, -1 } }; struct linux_serial_struct { int type; int line; int port; int irq; int flags; int xmit_fifo_size; int custom_divisor; int baud_base; unsigned short close_delay; char reserved_char[2]; int hub6; unsigned short closing_wait; unsigned short closing_wait2; int reserved[4]; }; static int linux_to_bsd_speed(int code, struct speedtab *table) { for ( ; table->sp_code != -1; table++) if (table->sp_code == code) return (table->sp_speed); return -1; } static int bsd_to_linux_speed(int speed, struct speedtab *table) { for ( ; table->sp_speed != -1; table++) if (table->sp_speed == speed) return (table->sp_code); return -1; } static void bsd_to_linux_termios(struct termios *bios, struct linux_termios *lios) { int i; #ifdef DEBUG if (ldebug(ioctl)) { printf("LINUX: BSD termios structure (input):\n"); printf("i=%08x o=%08x c=%08x l=%08x ispeed=%d ospeed=%d\n", bios->c_iflag, bios->c_oflag, bios->c_cflag, bios->c_lflag, bios->c_ispeed, bios->c_ospeed); printf("c_cc "); for (i=0; ic_cc[i]); printf("\n"); } #endif lios->c_iflag = 0; if (bios->c_iflag & IGNBRK) lios->c_iflag |= LINUX_IGNBRK; if (bios->c_iflag & BRKINT) lios->c_iflag |= LINUX_BRKINT; if (bios->c_iflag & IGNPAR) lios->c_iflag |= LINUX_IGNPAR; if (bios->c_iflag & PARMRK) lios->c_iflag |= LINUX_PARMRK; if (bios->c_iflag & INPCK) lios->c_iflag |= LINUX_INPCK; if (bios->c_iflag & ISTRIP) lios->c_iflag |= LINUX_ISTRIP; if (bios->c_iflag & INLCR) lios->c_iflag |= LINUX_INLCR; if (bios->c_iflag & IGNCR) lios->c_iflag |= LINUX_IGNCR; if (bios->c_iflag & ICRNL) lios->c_iflag |= LINUX_ICRNL; if (bios->c_iflag & IXON) lios->c_iflag |= LINUX_IXON; if (bios->c_iflag & IXANY) lios->c_iflag |= LINUX_IXANY; if (bios->c_iflag & IXOFF) lios->c_iflag |= LINUX_IXOFF; if (bios->c_iflag & IMAXBEL) lios->c_iflag |= LINUX_IMAXBEL; lios->c_oflag = 0; if (bios->c_oflag & OPOST) lios->c_oflag |= LINUX_OPOST; if (bios->c_oflag & ONLCR) lios->c_oflag |= LINUX_ONLCR; if (bios->c_oflag & TAB3) lios->c_oflag |= LINUX_XTABS; lios->c_cflag = bsd_to_linux_speed(bios->c_ispeed, sptab); lios->c_cflag |= (bios->c_cflag & CSIZE) >> 4; if (bios->c_cflag & CSTOPB) lios->c_cflag |= LINUX_CSTOPB; if (bios->c_cflag & CREAD) lios->c_cflag |= LINUX_CREAD; if (bios->c_cflag & PARENB) lios->c_cflag |= LINUX_PARENB; if (bios->c_cflag & PARODD) lios->c_cflag |= LINUX_PARODD; if (bios->c_cflag & HUPCL) lios->c_cflag |= LINUX_HUPCL; if (bios->c_cflag & CLOCAL) lios->c_cflag |= LINUX_CLOCAL; if (bios->c_cflag & CRTSCTS) lios->c_cflag |= LINUX_CRTSCTS; lios->c_lflag = 0; if (bios->c_lflag & ISIG) lios->c_lflag |= LINUX_ISIG; if (bios->c_lflag & ICANON) lios->c_lflag |= LINUX_ICANON; if (bios->c_lflag & ECHO) lios->c_lflag |= LINUX_ECHO; if (bios->c_lflag & ECHOE) lios->c_lflag |= LINUX_ECHOE; if (bios->c_lflag & ECHOK) lios->c_lflag |= LINUX_ECHOK; if (bios->c_lflag & ECHONL) lios->c_lflag |= LINUX_ECHONL; if (bios->c_lflag & NOFLSH) lios->c_lflag |= LINUX_NOFLSH; if (bios->c_lflag & TOSTOP) lios->c_lflag |= LINUX_TOSTOP; if (bios->c_lflag & ECHOCTL) lios->c_lflag |= LINUX_ECHOCTL; if (bios->c_lflag & ECHOPRT) lios->c_lflag |= LINUX_ECHOPRT; if (bios->c_lflag & ECHOKE) lios->c_lflag |= LINUX_ECHOKE; if (bios->c_lflag & FLUSHO) lios->c_lflag |= LINUX_FLUSHO; if (bios->c_lflag & PENDIN) lios->c_lflag |= LINUX_PENDIN; if (bios->c_lflag & IEXTEN) lios->c_lflag |= LINUX_IEXTEN; for (i=0; ic_cc[i] = LINUX_POSIX_VDISABLE; lios->c_cc[LINUX_VINTR] = bios->c_cc[VINTR]; lios->c_cc[LINUX_VQUIT] = bios->c_cc[VQUIT]; lios->c_cc[LINUX_VERASE] = bios->c_cc[VERASE]; lios->c_cc[LINUX_VKILL] = bios->c_cc[VKILL]; lios->c_cc[LINUX_VEOF] = bios->c_cc[VEOF]; lios->c_cc[LINUX_VEOL] = bios->c_cc[VEOL]; lios->c_cc[LINUX_VMIN] = bios->c_cc[VMIN]; lios->c_cc[LINUX_VTIME] = bios->c_cc[VTIME]; lios->c_cc[LINUX_VEOL2] = bios->c_cc[VEOL2]; lios->c_cc[LINUX_VSUSP] = bios->c_cc[VSUSP]; lios->c_cc[LINUX_VSTART] = bios->c_cc[VSTART]; lios->c_cc[LINUX_VSTOP] = bios->c_cc[VSTOP]; lios->c_cc[LINUX_VREPRINT] = bios->c_cc[VREPRINT]; lios->c_cc[LINUX_VDISCARD] = bios->c_cc[VDISCARD]; lios->c_cc[LINUX_VWERASE] = bios->c_cc[VWERASE]; lios->c_cc[LINUX_VLNEXT] = bios->c_cc[VLNEXT]; for (i=0; ic_cc[i] == _POSIX_VDISABLE) lios->c_cc[i] = LINUX_POSIX_VDISABLE; } lios->c_line = 0; #ifdef DEBUG if (ldebug(ioctl)) { printf("LINUX: LINUX termios structure (output):\n"); printf("i=%08x o=%08x c=%08x l=%08x line=%d\n", lios->c_iflag, lios->c_oflag, lios->c_cflag, lios->c_lflag, (int)lios->c_line); printf("c_cc "); for (i=0; ic_cc[i]); printf("\n"); } #endif } static void linux_to_bsd_termios(struct linux_termios *lios, struct termios *bios) { int i; #ifdef DEBUG if (ldebug(ioctl)) { printf("LINUX: LINUX termios structure (input):\n"); printf("i=%08x o=%08x c=%08x l=%08x line=%d\n", lios->c_iflag, lios->c_oflag, lios->c_cflag, lios->c_lflag, (int)lios->c_line); printf("c_cc "); for (i=0; ic_cc[i]); printf("\n"); } #endif bios->c_iflag = 0; if (lios->c_iflag & LINUX_IGNBRK) bios->c_iflag |= IGNBRK; if (lios->c_iflag & LINUX_BRKINT) bios->c_iflag |= BRKINT; if (lios->c_iflag & LINUX_IGNPAR) bios->c_iflag |= IGNPAR; if (lios->c_iflag & LINUX_PARMRK) bios->c_iflag |= PARMRK; if (lios->c_iflag & LINUX_INPCK) bios->c_iflag |= INPCK; if (lios->c_iflag & LINUX_ISTRIP) bios->c_iflag |= ISTRIP; if (lios->c_iflag & LINUX_INLCR) bios->c_iflag |= INLCR; if (lios->c_iflag & LINUX_IGNCR) bios->c_iflag |= IGNCR; if (lios->c_iflag & LINUX_ICRNL) bios->c_iflag |= ICRNL; if (lios->c_iflag & LINUX_IXON) bios->c_iflag |= IXON; if (lios->c_iflag & LINUX_IXANY) bios->c_iflag |= IXANY; if (lios->c_iflag & LINUX_IXOFF) bios->c_iflag |= IXOFF; if (lios->c_iflag & LINUX_IMAXBEL) bios->c_iflag |= IMAXBEL; bios->c_oflag = 0; if (lios->c_oflag & LINUX_OPOST) bios->c_oflag |= OPOST; if (lios->c_oflag & LINUX_ONLCR) bios->c_oflag |= ONLCR; if (lios->c_oflag & LINUX_XTABS) bios->c_oflag |= TAB3; bios->c_cflag = (lios->c_cflag & LINUX_CSIZE) << 4; if (lios->c_cflag & LINUX_CSTOPB) bios->c_cflag |= CSTOPB; if (lios->c_cflag & LINUX_CREAD) bios->c_cflag |= CREAD; if (lios->c_cflag & LINUX_PARENB) bios->c_cflag |= PARENB; if (lios->c_cflag & LINUX_PARODD) bios->c_cflag |= PARODD; if (lios->c_cflag & LINUX_HUPCL) bios->c_cflag |= HUPCL; if (lios->c_cflag & LINUX_CLOCAL) bios->c_cflag |= CLOCAL; if (lios->c_cflag & LINUX_CRTSCTS) bios->c_cflag |= CRTSCTS; bios->c_lflag = 0; if (lios->c_lflag & LINUX_ISIG) bios->c_lflag |= ISIG; if (lios->c_lflag & LINUX_ICANON) bios->c_lflag |= ICANON; if (lios->c_lflag & LINUX_ECHO) bios->c_lflag |= ECHO; if (lios->c_lflag & LINUX_ECHOE) bios->c_lflag |= ECHOE; if (lios->c_lflag & LINUX_ECHOK) bios->c_lflag |= ECHOK; if (lios->c_lflag & LINUX_ECHONL) bios->c_lflag |= ECHONL; if (lios->c_lflag & LINUX_NOFLSH) bios->c_lflag |= NOFLSH; if (lios->c_lflag & LINUX_TOSTOP) bios->c_lflag |= TOSTOP; if (lios->c_lflag & LINUX_ECHOCTL) bios->c_lflag |= ECHOCTL; if (lios->c_lflag & LINUX_ECHOPRT) bios->c_lflag |= ECHOPRT; if (lios->c_lflag & LINUX_ECHOKE) bios->c_lflag |= ECHOKE; if (lios->c_lflag & LINUX_FLUSHO) bios->c_lflag |= FLUSHO; if (lios->c_lflag & LINUX_PENDIN) bios->c_lflag |= PENDIN; if (lios->c_lflag & LINUX_IEXTEN) bios->c_lflag |= IEXTEN; for (i=0; ic_cc[i] = _POSIX_VDISABLE; bios->c_cc[VINTR] = lios->c_cc[LINUX_VINTR]; bios->c_cc[VQUIT] = lios->c_cc[LINUX_VQUIT]; bios->c_cc[VERASE] = lios->c_cc[LINUX_VERASE]; bios->c_cc[VKILL] = lios->c_cc[LINUX_VKILL]; bios->c_cc[VEOF] = lios->c_cc[LINUX_VEOF]; bios->c_cc[VEOL] = lios->c_cc[LINUX_VEOL]; bios->c_cc[VMIN] = lios->c_cc[LINUX_VMIN]; bios->c_cc[VTIME] = lios->c_cc[LINUX_VTIME]; bios->c_cc[VEOL2] = lios->c_cc[LINUX_VEOL2]; bios->c_cc[VSUSP] = lios->c_cc[LINUX_VSUSP]; bios->c_cc[VSTART] = lios->c_cc[LINUX_VSTART]; bios->c_cc[VSTOP] = lios->c_cc[LINUX_VSTOP]; bios->c_cc[VREPRINT] = lios->c_cc[LINUX_VREPRINT]; bios->c_cc[VDISCARD] = lios->c_cc[LINUX_VDISCARD]; bios->c_cc[VWERASE] = lios->c_cc[LINUX_VWERASE]; bios->c_cc[VLNEXT] = lios->c_cc[LINUX_VLNEXT]; for (i=0; ic_cc[i] == LINUX_POSIX_VDISABLE) bios->c_cc[i] = _POSIX_VDISABLE; } bios->c_ispeed = bios->c_ospeed = linux_to_bsd_speed(lios->c_cflag & LINUX_CBAUD, sptab); #ifdef DEBUG if (ldebug(ioctl)) { printf("LINUX: BSD termios structure (output):\n"); printf("i=%08x o=%08x c=%08x l=%08x ispeed=%d ospeed=%d\n", bios->c_iflag, bios->c_oflag, bios->c_cflag, bios->c_lflag, bios->c_ispeed, bios->c_ospeed); printf("c_cc "); for (i=0; ic_cc[i]); printf("\n"); } #endif } static void bsd_to_linux_termio(struct termios *bios, struct linux_termio *lio) { struct linux_termios lios; bsd_to_linux_termios(bios, &lios); lio->c_iflag = lios.c_iflag; lio->c_oflag = lios.c_oflag; lio->c_cflag = lios.c_cflag; lio->c_lflag = lios.c_lflag; lio->c_line = lios.c_line; memcpy(lio->c_cc, lios.c_cc, LINUX_NCC); } static void linux_to_bsd_termio(struct linux_termio *lio, struct termios *bios) { struct linux_termios lios; int i; lios.c_iflag = lio->c_iflag; lios.c_oflag = lio->c_oflag; lios.c_cflag = lio->c_cflag; lios.c_lflag = lio->c_lflag; for (i=LINUX_NCC; ic_cc, LINUX_NCC); linux_to_bsd_termios(&lios, bios); } static int linux_ioctl_termio(struct thread *td, struct linux_ioctl_args *args) { struct termios bios; struct linux_termios lios; struct linux_termio lio; cap_rights_t rights; struct file *fp; int error; error = fget(td, args->fd, cap_rights_init(&rights, CAP_IOCTL), &fp); if (error != 0) return (error); switch (args->cmd & 0xffff) { case LINUX_TCGETS: error = fo_ioctl(fp, TIOCGETA, (caddr_t)&bios, td->td_ucred, td); if (error) break; bsd_to_linux_termios(&bios, &lios); error = copyout(&lios, (void *)args->arg, sizeof(lios)); break; case LINUX_TCSETS: error = copyin((void *)args->arg, &lios, sizeof(lios)); if (error) break; linux_to_bsd_termios(&lios, &bios); error = (fo_ioctl(fp, TIOCSETA, (caddr_t)&bios, td->td_ucred, td)); break; case LINUX_TCSETSW: error = copyin((void *)args->arg, &lios, sizeof(lios)); if (error) break; linux_to_bsd_termios(&lios, &bios); error = (fo_ioctl(fp, TIOCSETAW, (caddr_t)&bios, td->td_ucred, td)); break; case LINUX_TCSETSF: error = copyin((void *)args->arg, &lios, sizeof(lios)); if (error) break; linux_to_bsd_termios(&lios, &bios); error = (fo_ioctl(fp, TIOCSETAF, (caddr_t)&bios, td->td_ucred, td)); break; case LINUX_TCGETA: error = fo_ioctl(fp, TIOCGETA, (caddr_t)&bios, td->td_ucred, td); if (error) break; bsd_to_linux_termio(&bios, &lio); error = (copyout(&lio, (void *)args->arg, sizeof(lio))); break; case LINUX_TCSETA: error = copyin((void *)args->arg, &lio, sizeof(lio)); if (error) break; linux_to_bsd_termio(&lio, &bios); error = (fo_ioctl(fp, TIOCSETA, (caddr_t)&bios, td->td_ucred, td)); break; case LINUX_TCSETAW: error = copyin((void *)args->arg, &lio, sizeof(lio)); if (error) break; linux_to_bsd_termio(&lio, &bios); error = (fo_ioctl(fp, TIOCSETAW, (caddr_t)&bios, td->td_ucred, td)); break; case LINUX_TCSETAF: error = copyin((void *)args->arg, &lio, sizeof(lio)); if (error) break; linux_to_bsd_termio(&lio, &bios); error = (fo_ioctl(fp, TIOCSETAF, (caddr_t)&bios, td->td_ucred, td)); break; /* LINUX_TCSBRK */ case LINUX_TCXONC: { switch (args->arg) { case LINUX_TCOOFF: args->cmd = TIOCSTOP; break; case LINUX_TCOON: args->cmd = TIOCSTART; break; case LINUX_TCIOFF: case LINUX_TCION: { int c; struct write_args wr; error = fo_ioctl(fp, TIOCGETA, (caddr_t)&bios, td->td_ucred, td); if (error) break; fdrop(fp, td); c = (args->arg == LINUX_TCIOFF) ? VSTOP : VSTART; c = bios.c_cc[c]; if (c != _POSIX_VDISABLE) { wr.fd = args->fd; wr.buf = &c; wr.nbyte = sizeof(c); return (sys_write(td, &wr)); } else return (0); } default: fdrop(fp, td); return (EINVAL); } args->arg = 0; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; } case LINUX_TCFLSH: { int val; switch (args->arg) { case LINUX_TCIFLUSH: val = FREAD; break; case LINUX_TCOFLUSH: val = FWRITE; break; case LINUX_TCIOFLUSH: val = FREAD | FWRITE; break; default: fdrop(fp, td); return (EINVAL); } error = (fo_ioctl(fp,TIOCFLUSH,(caddr_t)&val,td->td_ucred,td)); break; } case LINUX_TIOCEXCL: args->cmd = TIOCEXCL; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; case LINUX_TIOCNXCL: args->cmd = TIOCNXCL; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; case LINUX_TIOCSCTTY: args->cmd = TIOCSCTTY; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; case LINUX_TIOCGPGRP: args->cmd = TIOCGPGRP; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; case LINUX_TIOCSPGRP: args->cmd = TIOCSPGRP; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; /* LINUX_TIOCOUTQ */ /* LINUX_TIOCSTI */ case LINUX_TIOCGWINSZ: args->cmd = TIOCGWINSZ; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; case LINUX_TIOCSWINSZ: args->cmd = TIOCSWINSZ; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; case LINUX_TIOCMGET: args->cmd = TIOCMGET; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; case LINUX_TIOCMBIS: args->cmd = TIOCMBIS; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; case LINUX_TIOCMBIC: args->cmd = TIOCMBIC; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; case LINUX_TIOCMSET: args->cmd = TIOCMSET; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; /* TIOCGSOFTCAR */ /* TIOCSSOFTCAR */ case LINUX_FIONREAD: /* LINUX_TIOCINQ */ args->cmd = FIONREAD; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; /* LINUX_TIOCLINUX */ case LINUX_TIOCCONS: args->cmd = TIOCCONS; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; case LINUX_TIOCGSERIAL: { struct linux_serial_struct lss; bzero(&lss, sizeof(lss)); lss.type = LINUX_PORT_16550A; lss.flags = 0; lss.close_delay = 0; error = copyout(&lss, (void *)args->arg, sizeof(lss)); break; } case LINUX_TIOCSSERIAL: { struct linux_serial_struct lss; error = copyin((void *)args->arg, &lss, sizeof(lss)); if (error) break; /* XXX - It really helps to have an implementation that * does nothing. NOT! */ error = 0; break; } case LINUX_TIOCPKT: args->cmd = TIOCPKT; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; case LINUX_FIONBIO: args->cmd = FIONBIO; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; case LINUX_TIOCNOTTY: args->cmd = TIOCNOTTY; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; case LINUX_TIOCSETD: { int line; switch (args->arg) { case LINUX_N_TTY: line = TTYDISC; break; case LINUX_N_SLIP: line = SLIPDISC; break; case LINUX_N_PPP: line = PPPDISC; break; default: fdrop(fp, td); return (EINVAL); } error = (fo_ioctl(fp, TIOCSETD, (caddr_t)&line, td->td_ucred, td)); break; } case LINUX_TIOCGETD: { int linux_line; int bsd_line = TTYDISC; error = fo_ioctl(fp, TIOCGETD, (caddr_t)&bsd_line, td->td_ucred, td); if (error) break; switch (bsd_line) { case TTYDISC: linux_line = LINUX_N_TTY; break; case SLIPDISC: linux_line = LINUX_N_SLIP; break; case PPPDISC: linux_line = LINUX_N_PPP; break; default: fdrop(fp, td); return (EINVAL); } error = (copyout(&linux_line, (void *)args->arg, sizeof(int))); break; } /* LINUX_TCSBRKP */ /* LINUX_TIOCTTYGSTRUCT */ case LINUX_FIONCLEX: args->cmd = FIONCLEX; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; case LINUX_FIOCLEX: args->cmd = FIOCLEX; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; case LINUX_FIOASYNC: args->cmd = FIOASYNC; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; /* LINUX_TIOCSERCONFIG */ /* LINUX_TIOCSERGWILD */ /* LINUX_TIOCSERSWILD */ /* LINUX_TIOCGLCKTRMIOS */ /* LINUX_TIOCSLCKTRMIOS */ case LINUX_TIOCSBRK: args->cmd = TIOCSBRK; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; case LINUX_TIOCCBRK: args->cmd = TIOCCBRK; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; case LINUX_TIOCGPTN: { int nb; error = fo_ioctl(fp, TIOCGPTN, (caddr_t)&nb, td->td_ucred, td); if (!error) error = copyout(&nb, (void *)args->arg, sizeof(int)); break; } case LINUX_TIOCSPTLCK: /* Our unlockpt() does nothing. */ error = 0; break; default: error = ENOIOCTL; break; } fdrop(fp, td); return (error); } /* * CDROM related ioctls */ struct linux_cdrom_msf { u_char cdmsf_min0; u_char cdmsf_sec0; u_char cdmsf_frame0; u_char cdmsf_min1; u_char cdmsf_sec1; u_char cdmsf_frame1; }; struct linux_cdrom_tochdr { u_char cdth_trk0; u_char cdth_trk1; }; union linux_cdrom_addr { struct { u_char minute; u_char second; u_char frame; } msf; int lba; }; struct linux_cdrom_tocentry { u_char cdte_track; u_char cdte_adr:4; u_char cdte_ctrl:4; u_char cdte_format; union linux_cdrom_addr cdte_addr; u_char cdte_datamode; }; struct linux_cdrom_subchnl { u_char cdsc_format; u_char cdsc_audiostatus; u_char cdsc_adr:4; u_char cdsc_ctrl:4; u_char cdsc_trk; u_char cdsc_ind; union linux_cdrom_addr cdsc_absaddr; union linux_cdrom_addr cdsc_reladdr; }; struct l_cdrom_read_audio { union linux_cdrom_addr addr; u_char addr_format; l_int nframes; u_char *buf; }; struct l_dvd_layer { u_char book_version:4; u_char book_type:4; u_char min_rate:4; u_char disc_size:4; u_char layer_type:4; u_char track_path:1; u_char nlayers:2; u_char track_density:4; u_char linear_density:4; u_char bca:1; u_int32_t start_sector; u_int32_t end_sector; u_int32_t end_sector_l0; }; struct l_dvd_physical { u_char type; u_char layer_num; struct l_dvd_layer layer[4]; }; struct l_dvd_copyright { u_char type; u_char layer_num; u_char cpst; u_char rmi; }; struct l_dvd_disckey { u_char type; l_uint agid:2; u_char value[2048]; }; struct l_dvd_bca { u_char type; l_int len; u_char value[188]; }; struct l_dvd_manufact { u_char type; u_char layer_num; l_int len; u_char value[2048]; }; typedef union { u_char type; struct l_dvd_physical physical; struct l_dvd_copyright copyright; struct l_dvd_disckey disckey; struct l_dvd_bca bca; struct l_dvd_manufact manufact; } l_dvd_struct; typedef u_char l_dvd_key[5]; typedef u_char l_dvd_challenge[10]; struct l_dvd_lu_send_agid { u_char type; l_uint agid:2; }; struct l_dvd_host_send_challenge { u_char type; l_uint agid:2; l_dvd_challenge chal; }; struct l_dvd_send_key { u_char type; l_uint agid:2; l_dvd_key key; }; struct l_dvd_lu_send_challenge { u_char type; l_uint agid:2; l_dvd_challenge chal; }; struct l_dvd_lu_send_title_key { u_char type; l_uint agid:2; l_dvd_key title_key; l_int lba; l_uint cpm:1; l_uint cp_sec:1; l_uint cgms:2; }; struct l_dvd_lu_send_asf { u_char type; l_uint agid:2; l_uint asf:1; }; struct l_dvd_host_send_rpcstate { u_char type; u_char pdrc; }; struct l_dvd_lu_send_rpcstate { u_char type:2; u_char vra:3; u_char ucca:3; u_char region_mask; u_char rpc_scheme; }; typedef union { u_char type; struct l_dvd_lu_send_agid lsa; struct l_dvd_host_send_challenge hsc; struct l_dvd_send_key lsk; struct l_dvd_lu_send_challenge lsc; struct l_dvd_send_key hsk; struct l_dvd_lu_send_title_key lstk; struct l_dvd_lu_send_asf lsasf; struct l_dvd_host_send_rpcstate hrpcs; struct l_dvd_lu_send_rpcstate lrpcs; } l_dvd_authinfo; static void bsd_to_linux_msf_lba(u_char af, union msf_lba *bp, union linux_cdrom_addr *lp) { if (af == CD_LBA_FORMAT) lp->lba = bp->lba; else { lp->msf.minute = bp->msf.minute; lp->msf.second = bp->msf.second; lp->msf.frame = bp->msf.frame; } } static void set_linux_cdrom_addr(union linux_cdrom_addr *addr, int format, int lba) { if (format == LINUX_CDROM_MSF) { addr->msf.frame = lba % 75; lba /= 75; lba += 2; addr->msf.second = lba % 60; addr->msf.minute = lba / 60; } else addr->lba = lba; } static int linux_to_bsd_dvd_struct(l_dvd_struct *lp, struct dvd_struct *bp) { bp->format = lp->type; switch (bp->format) { case DVD_STRUCT_PHYSICAL: if (bp->layer_num >= 4) return (EINVAL); bp->layer_num = lp->physical.layer_num; break; case DVD_STRUCT_COPYRIGHT: bp->layer_num = lp->copyright.layer_num; break; case DVD_STRUCT_DISCKEY: bp->agid = lp->disckey.agid; break; case DVD_STRUCT_BCA: case DVD_STRUCT_MANUFACT: break; default: return (EINVAL); } return (0); } static int bsd_to_linux_dvd_struct(struct dvd_struct *bp, l_dvd_struct *lp) { switch (bp->format) { case DVD_STRUCT_PHYSICAL: { struct dvd_layer *blp = (struct dvd_layer *)bp->data; struct l_dvd_layer *llp = &lp->physical.layer[bp->layer_num]; memset(llp, 0, sizeof(*llp)); llp->book_version = blp->book_version; llp->book_type = blp->book_type; llp->min_rate = blp->max_rate; llp->disc_size = blp->disc_size; llp->layer_type = blp->layer_type; llp->track_path = blp->track_path; llp->nlayers = blp->nlayers; llp->track_density = blp->track_density; llp->linear_density = blp->linear_density; llp->bca = blp->bca; llp->start_sector = blp->start_sector; llp->end_sector = blp->end_sector; llp->end_sector_l0 = blp->end_sector_l0; break; } case DVD_STRUCT_COPYRIGHT: lp->copyright.cpst = bp->cpst; lp->copyright.rmi = bp->rmi; break; case DVD_STRUCT_DISCKEY: memcpy(lp->disckey.value, bp->data, sizeof(lp->disckey.value)); break; case DVD_STRUCT_BCA: lp->bca.len = bp->length; memcpy(lp->bca.value, bp->data, sizeof(lp->bca.value)); break; case DVD_STRUCT_MANUFACT: lp->manufact.len = bp->length; memcpy(lp->manufact.value, bp->data, sizeof(lp->manufact.value)); /* lp->manufact.layer_num is unused in linux (redhat 7.0) */ break; default: return (EINVAL); } return (0); } static int linux_to_bsd_dvd_authinfo(l_dvd_authinfo *lp, int *bcode, struct dvd_authinfo *bp) { switch (lp->type) { case LINUX_DVD_LU_SEND_AGID: *bcode = DVDIOCREPORTKEY; bp->format = DVD_REPORT_AGID; bp->agid = lp->lsa.agid; break; case LINUX_DVD_HOST_SEND_CHALLENGE: *bcode = DVDIOCSENDKEY; bp->format = DVD_SEND_CHALLENGE; bp->agid = lp->hsc.agid; memcpy(bp->keychal, lp->hsc.chal, 10); break; case LINUX_DVD_LU_SEND_KEY1: *bcode = DVDIOCREPORTKEY; bp->format = DVD_REPORT_KEY1; bp->agid = lp->lsk.agid; break; case LINUX_DVD_LU_SEND_CHALLENGE: *bcode = DVDIOCREPORTKEY; bp->format = DVD_REPORT_CHALLENGE; bp->agid = lp->lsc.agid; break; case LINUX_DVD_HOST_SEND_KEY2: *bcode = DVDIOCSENDKEY; bp->format = DVD_SEND_KEY2; bp->agid = lp->hsk.agid; memcpy(bp->keychal, lp->hsk.key, 5); break; case LINUX_DVD_LU_SEND_TITLE_KEY: *bcode = DVDIOCREPORTKEY; bp->format = DVD_REPORT_TITLE_KEY; bp->agid = lp->lstk.agid; bp->lba = lp->lstk.lba; break; case LINUX_DVD_LU_SEND_ASF: *bcode = DVDIOCREPORTKEY; bp->format = DVD_REPORT_ASF; bp->agid = lp->lsasf.agid; break; case LINUX_DVD_INVALIDATE_AGID: *bcode = DVDIOCREPORTKEY; bp->format = DVD_INVALIDATE_AGID; bp->agid = lp->lsa.agid; break; case LINUX_DVD_LU_SEND_RPC_STATE: *bcode = DVDIOCREPORTKEY; bp->format = DVD_REPORT_RPC; break; case LINUX_DVD_HOST_SEND_RPC_STATE: *bcode = DVDIOCSENDKEY; bp->format = DVD_SEND_RPC; bp->region = lp->hrpcs.pdrc; break; default: return (EINVAL); } return (0); } static int bsd_to_linux_dvd_authinfo(struct dvd_authinfo *bp, l_dvd_authinfo *lp) { switch (lp->type) { case LINUX_DVD_LU_SEND_AGID: lp->lsa.agid = bp->agid; break; case LINUX_DVD_HOST_SEND_CHALLENGE: lp->type = LINUX_DVD_LU_SEND_KEY1; break; case LINUX_DVD_LU_SEND_KEY1: memcpy(lp->lsk.key, bp->keychal, sizeof(lp->lsk.key)); break; case LINUX_DVD_LU_SEND_CHALLENGE: memcpy(lp->lsc.chal, bp->keychal, sizeof(lp->lsc.chal)); break; case LINUX_DVD_HOST_SEND_KEY2: lp->type = LINUX_DVD_AUTH_ESTABLISHED; break; case LINUX_DVD_LU_SEND_TITLE_KEY: memcpy(lp->lstk.title_key, bp->keychal, sizeof(lp->lstk.title_key)); lp->lstk.cpm = bp->cpm; lp->lstk.cp_sec = bp->cp_sec; lp->lstk.cgms = bp->cgms; break; case LINUX_DVD_LU_SEND_ASF: lp->lsasf.asf = bp->asf; break; case LINUX_DVD_INVALIDATE_AGID: break; case LINUX_DVD_LU_SEND_RPC_STATE: lp->lrpcs.type = bp->reg_type; lp->lrpcs.vra = bp->vend_rsts; lp->lrpcs.ucca = bp->user_rsts; lp->lrpcs.region_mask = bp->region; lp->lrpcs.rpc_scheme = bp->rpc_scheme; break; case LINUX_DVD_HOST_SEND_RPC_STATE: break; default: return (EINVAL); } return (0); } static int linux_ioctl_cdrom(struct thread *td, struct linux_ioctl_args *args) { cap_rights_t rights; struct file *fp; int error; error = fget(td, args->fd, cap_rights_init(&rights, CAP_IOCTL), &fp); if (error != 0) return (error); switch (args->cmd & 0xffff) { case LINUX_CDROMPAUSE: args->cmd = CDIOCPAUSE; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; case LINUX_CDROMRESUME: args->cmd = CDIOCRESUME; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; case LINUX_CDROMPLAYMSF: args->cmd = CDIOCPLAYMSF; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; case LINUX_CDROMPLAYTRKIND: args->cmd = CDIOCPLAYTRACKS; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; case LINUX_CDROMREADTOCHDR: { struct ioc_toc_header th; struct linux_cdrom_tochdr lth; error = fo_ioctl(fp, CDIOREADTOCHEADER, (caddr_t)&th, td->td_ucred, td); if (!error) { lth.cdth_trk0 = th.starting_track; lth.cdth_trk1 = th.ending_track; copyout(<h, (void *)args->arg, sizeof(lth)); } break; } case LINUX_CDROMREADTOCENTRY: { struct linux_cdrom_tocentry lte; struct ioc_read_toc_single_entry irtse; error = copyin((void *)args->arg, <e, sizeof(lte)); if (error) break; irtse.address_format = lte.cdte_format; irtse.track = lte.cdte_track; error = fo_ioctl(fp, CDIOREADTOCENTRY, (caddr_t)&irtse, td->td_ucred, td); if (!error) { lte.cdte_ctrl = irtse.entry.control; lte.cdte_adr = irtse.entry.addr_type; bsd_to_linux_msf_lba(irtse.address_format, &irtse.entry.addr, <e.cdte_addr); error = copyout(<e, (void *)args->arg, sizeof(lte)); } break; } case LINUX_CDROMSTOP: args->cmd = CDIOCSTOP; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; case LINUX_CDROMSTART: args->cmd = CDIOCSTART; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; case LINUX_CDROMEJECT: args->cmd = CDIOCEJECT; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; /* LINUX_CDROMVOLCTRL */ case LINUX_CDROMSUBCHNL: { struct linux_cdrom_subchnl sc; struct ioc_read_subchannel bsdsc; struct cd_sub_channel_info bsdinfo; + error = copyin((void *)args->arg, &sc, sizeof(sc)); + if (error) + break; + + /* + * Invoke the native ioctl and bounce the returned data through + * the userspace buffer. This works because the Linux structure + * is the same size as our structures for the subchannel header + * and position data. + */ bsdsc.address_format = CD_LBA_FORMAT; bsdsc.data_format = CD_CURRENT_POSITION; bsdsc.track = 0; - bsdsc.data_len = sizeof(bsdinfo); - bsdsc.data = &bsdinfo; - error = fo_ioctl(fp, CDIOCREADSUBCHANNEL_SYSSPACE, - (caddr_t)&bsdsc, td->td_ucred, td); + bsdsc.data_len = sizeof(sc); + bsdsc.data = (void *)args->arg; + error = fo_ioctl(fp, CDIOCREADSUBCHANNEL, (caddr_t)&bsdsc, + td->td_ucred, td); if (error) break; - error = copyin((void *)args->arg, &sc, sizeof(sc)); + error = copyin((void *)args->arg, &bsdinfo, sizeof(bsdinfo)); if (error) break; sc.cdsc_audiostatus = bsdinfo.header.audio_status; sc.cdsc_adr = bsdinfo.what.position.addr_type; sc.cdsc_ctrl = bsdinfo.what.position.control; sc.cdsc_trk = bsdinfo.what.position.track_number; sc.cdsc_ind = bsdinfo.what.position.index_number; set_linux_cdrom_addr(&sc.cdsc_absaddr, sc.cdsc_format, bsdinfo.what.position.absaddr.lba); set_linux_cdrom_addr(&sc.cdsc_reladdr, sc.cdsc_format, bsdinfo.what.position.reladdr.lba); error = copyout(&sc, (void *)args->arg, sizeof(sc)); break; } /* LINUX_CDROMREADMODE2 */ /* LINUX_CDROMREADMODE1 */ /* LINUX_CDROMREADAUDIO */ /* LINUX_CDROMEJECT_SW */ /* LINUX_CDROMMULTISESSION */ /* LINUX_CDROM_GET_UPC */ case LINUX_CDROMRESET: args->cmd = CDIOCRESET; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; /* LINUX_CDROMVOLREAD */ /* LINUX_CDROMREADRAW */ /* LINUX_CDROMREADCOOKED */ /* LINUX_CDROMSEEK */ /* LINUX_CDROMPLAYBLK */ /* LINUX_CDROMREADALL */ /* LINUX_CDROMCLOSETRAY */ /* LINUX_CDROMLOADFROMSLOT */ /* LINUX_CDROMGETSPINDOWN */ /* LINUX_CDROMSETSPINDOWN */ /* LINUX_CDROM_SET_OPTIONS */ /* LINUX_CDROM_CLEAR_OPTIONS */ /* LINUX_CDROM_SELECT_SPEED */ /* LINUX_CDROM_SELECT_DISC */ /* LINUX_CDROM_MEDIA_CHANGED */ /* LINUX_CDROM_DRIVE_STATUS */ /* LINUX_CDROM_DISC_STATUS */ /* LINUX_CDROM_CHANGER_NSLOTS */ /* LINUX_CDROM_LOCKDOOR */ /* LINUX_CDROM_DEBUG */ /* LINUX_CDROM_GET_CAPABILITY */ /* LINUX_CDROMAUDIOBUFSIZ */ case LINUX_DVD_READ_STRUCT: { l_dvd_struct *lds; struct dvd_struct *bds; lds = malloc(sizeof(*lds), M_LINUX, M_WAITOK); bds = malloc(sizeof(*bds), M_LINUX, M_WAITOK); error = copyin((void *)args->arg, lds, sizeof(*lds)); if (error) goto out; error = linux_to_bsd_dvd_struct(lds, bds); if (error) goto out; error = fo_ioctl(fp, DVDIOCREADSTRUCTURE, (caddr_t)bds, td->td_ucred, td); if (error) goto out; error = bsd_to_linux_dvd_struct(bds, lds); if (error) goto out; error = copyout(lds, (void *)args->arg, sizeof(*lds)); out: free(bds, M_LINUX); free(lds, M_LINUX); break; } /* LINUX_DVD_WRITE_STRUCT */ case LINUX_DVD_AUTH: { l_dvd_authinfo lda; struct dvd_authinfo bda; int bcode; error = copyin((void *)args->arg, &lda, sizeof(lda)); if (error) break; error = linux_to_bsd_dvd_authinfo(&lda, &bcode, &bda); if (error) break; error = fo_ioctl(fp, bcode, (caddr_t)&bda, td->td_ucred, td); if (error) { if (lda.type == LINUX_DVD_HOST_SEND_KEY2) { lda.type = LINUX_DVD_AUTH_FAILURE; copyout(&lda, (void *)args->arg, sizeof(lda)); } break; } error = bsd_to_linux_dvd_authinfo(&bda, &lda); if (error) break; error = copyout(&lda, (void *)args->arg, sizeof(lda)); break; } case LINUX_SCSI_GET_BUS_NUMBER: { struct sg_scsi_id id; error = fo_ioctl(fp, SG_GET_SCSI_ID, (caddr_t)&id, td->td_ucred, td); if (error) break; error = copyout(&id.channel, (void *)args->arg, sizeof(int)); break; } case LINUX_SCSI_GET_IDLUN: { struct sg_scsi_id id; struct scsi_idlun idl; error = fo_ioctl(fp, SG_GET_SCSI_ID, (caddr_t)&id, td->td_ucred, td); if (error) break; idl.dev_id = (id.scsi_id & 0xff) + ((id.lun & 0xff) << 8) + ((id.channel & 0xff) << 16) + ((id.host_no & 0xff) << 24); idl.host_unique_id = id.host_no; error = copyout(&idl, (void *)args->arg, sizeof(idl)); break; } /* LINUX_CDROM_SEND_PACKET */ /* LINUX_CDROM_NEXT_WRITABLE */ /* LINUX_CDROM_LAST_WRITTEN */ default: error = ENOIOCTL; break; } fdrop(fp, td); return (error); } static int linux_ioctl_vfat(struct thread *td, struct linux_ioctl_args *args) { return (ENOTTY); } /* * Sound related ioctls */ struct linux_old_mixer_info { char id[16]; char name[32]; }; static u_int32_t dirbits[4] = { IOC_VOID, IOC_IN, IOC_OUT, IOC_INOUT }; #define SETDIR(c) (((c) & ~IOC_DIRMASK) | dirbits[args->cmd >> 30]) static int linux_ioctl_sound(struct thread *td, struct linux_ioctl_args *args) { switch (args->cmd & 0xffff) { case LINUX_SOUND_MIXER_WRITE_VOLUME: args->cmd = SETDIR(SOUND_MIXER_WRITE_VOLUME); return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SOUND_MIXER_WRITE_BASS: args->cmd = SETDIR(SOUND_MIXER_WRITE_BASS); return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SOUND_MIXER_WRITE_TREBLE: args->cmd = SETDIR(SOUND_MIXER_WRITE_TREBLE); return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SOUND_MIXER_WRITE_SYNTH: args->cmd = SETDIR(SOUND_MIXER_WRITE_SYNTH); return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SOUND_MIXER_WRITE_PCM: args->cmd = SETDIR(SOUND_MIXER_WRITE_PCM); return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SOUND_MIXER_WRITE_SPEAKER: args->cmd = SETDIR(SOUND_MIXER_WRITE_SPEAKER); return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SOUND_MIXER_WRITE_LINE: args->cmd = SETDIR(SOUND_MIXER_WRITE_LINE); return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SOUND_MIXER_WRITE_MIC: args->cmd = SETDIR(SOUND_MIXER_WRITE_MIC); return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SOUND_MIXER_WRITE_CD: args->cmd = SETDIR(SOUND_MIXER_WRITE_CD); return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SOUND_MIXER_WRITE_IMIX: args->cmd = SETDIR(SOUND_MIXER_WRITE_IMIX); return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SOUND_MIXER_WRITE_ALTPCM: args->cmd = SETDIR(SOUND_MIXER_WRITE_ALTPCM); return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SOUND_MIXER_WRITE_RECLEV: args->cmd = SETDIR(SOUND_MIXER_WRITE_RECLEV); return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SOUND_MIXER_WRITE_IGAIN: args->cmd = SETDIR(SOUND_MIXER_WRITE_IGAIN); return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SOUND_MIXER_WRITE_OGAIN: args->cmd = SETDIR(SOUND_MIXER_WRITE_OGAIN); return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SOUND_MIXER_WRITE_LINE1: args->cmd = SETDIR(SOUND_MIXER_WRITE_LINE1); return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SOUND_MIXER_WRITE_LINE2: args->cmd = SETDIR(SOUND_MIXER_WRITE_LINE2); return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SOUND_MIXER_WRITE_LINE3: args->cmd = SETDIR(SOUND_MIXER_WRITE_LINE3); return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SOUND_MIXER_INFO: { /* Key on encoded length */ switch ((args->cmd >> 16) & 0x1fff) { case 0x005c: { /* SOUND_MIXER_INFO */ args->cmd = SOUND_MIXER_INFO; return (sys_ioctl(td, (struct ioctl_args *)args)); } case 0x0030: { /* SOUND_OLD_MIXER_INFO */ struct linux_old_mixer_info info; bzero(&info, sizeof(info)); strncpy(info.id, "OSS", sizeof(info.id) - 1); strncpy(info.name, "FreeBSD OSS Mixer", sizeof(info.name) - 1); copyout(&info, (void *)args->arg, sizeof(info)); return (0); } default: return (ENOIOCTL); } break; } case LINUX_OSS_GETVERSION: { int version = linux_get_oss_version(td); return (copyout(&version, (void *)args->arg, sizeof(int))); } case LINUX_SOUND_MIXER_READ_STEREODEVS: args->cmd = SOUND_MIXER_READ_STEREODEVS; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SOUND_MIXER_READ_CAPS: args->cmd = SOUND_MIXER_READ_CAPS; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SOUND_MIXER_READ_RECMASK: args->cmd = SOUND_MIXER_READ_RECMASK; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SOUND_MIXER_READ_DEVMASK: args->cmd = SOUND_MIXER_READ_DEVMASK; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SOUND_MIXER_WRITE_RECSRC: args->cmd = SETDIR(SOUND_MIXER_WRITE_RECSRC); return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SNDCTL_DSP_RESET: args->cmd = SNDCTL_DSP_RESET; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SNDCTL_DSP_SYNC: args->cmd = SNDCTL_DSP_SYNC; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SNDCTL_DSP_SPEED: args->cmd = SNDCTL_DSP_SPEED; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SNDCTL_DSP_STEREO: args->cmd = SNDCTL_DSP_STEREO; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SNDCTL_DSP_GETBLKSIZE: /* LINUX_SNDCTL_DSP_SETBLKSIZE */ args->cmd = SNDCTL_DSP_GETBLKSIZE; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SNDCTL_DSP_SETFMT: args->cmd = SNDCTL_DSP_SETFMT; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SOUND_PCM_WRITE_CHANNELS: args->cmd = SOUND_PCM_WRITE_CHANNELS; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SOUND_PCM_WRITE_FILTER: args->cmd = SOUND_PCM_WRITE_FILTER; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SNDCTL_DSP_POST: args->cmd = SNDCTL_DSP_POST; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SNDCTL_DSP_SUBDIVIDE: args->cmd = SNDCTL_DSP_SUBDIVIDE; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SNDCTL_DSP_SETFRAGMENT: args->cmd = SNDCTL_DSP_SETFRAGMENT; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SNDCTL_DSP_GETFMTS: args->cmd = SNDCTL_DSP_GETFMTS; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SNDCTL_DSP_GETOSPACE: args->cmd = SNDCTL_DSP_GETOSPACE; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SNDCTL_DSP_GETISPACE: args->cmd = SNDCTL_DSP_GETISPACE; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SNDCTL_DSP_NONBLOCK: args->cmd = SNDCTL_DSP_NONBLOCK; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SNDCTL_DSP_GETCAPS: args->cmd = SNDCTL_DSP_GETCAPS; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SNDCTL_DSP_SETTRIGGER: /* LINUX_SNDCTL_GETTRIGGER */ args->cmd = SNDCTL_DSP_SETTRIGGER; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SNDCTL_DSP_GETIPTR: args->cmd = SNDCTL_DSP_GETIPTR; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SNDCTL_DSP_GETOPTR: args->cmd = SNDCTL_DSP_GETOPTR; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SNDCTL_DSP_SETDUPLEX: args->cmd = SNDCTL_DSP_SETDUPLEX; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SNDCTL_DSP_GETODELAY: args->cmd = SNDCTL_DSP_GETODELAY; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SNDCTL_SEQ_RESET: args->cmd = SNDCTL_SEQ_RESET; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SNDCTL_SEQ_SYNC: args->cmd = SNDCTL_SEQ_SYNC; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SNDCTL_SYNTH_INFO: args->cmd = SNDCTL_SYNTH_INFO; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SNDCTL_SEQ_CTRLRATE: args->cmd = SNDCTL_SEQ_CTRLRATE; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SNDCTL_SEQ_GETOUTCOUNT: args->cmd = SNDCTL_SEQ_GETOUTCOUNT; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SNDCTL_SEQ_GETINCOUNT: args->cmd = SNDCTL_SEQ_GETINCOUNT; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SNDCTL_SEQ_PERCMODE: args->cmd = SNDCTL_SEQ_PERCMODE; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SNDCTL_FM_LOAD_INSTR: args->cmd = SNDCTL_FM_LOAD_INSTR; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SNDCTL_SEQ_TESTMIDI: args->cmd = SNDCTL_SEQ_TESTMIDI; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SNDCTL_SEQ_RESETSAMPLES: args->cmd = SNDCTL_SEQ_RESETSAMPLES; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SNDCTL_SEQ_NRSYNTHS: args->cmd = SNDCTL_SEQ_NRSYNTHS; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SNDCTL_SEQ_NRMIDIS: args->cmd = SNDCTL_SEQ_NRMIDIS; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SNDCTL_MIDI_INFO: args->cmd = SNDCTL_MIDI_INFO; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SNDCTL_SEQ_TRESHOLD: args->cmd = SNDCTL_SEQ_TRESHOLD; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SNDCTL_SYNTH_MEMAVL: args->cmd = SNDCTL_SYNTH_MEMAVL; return (sys_ioctl(td, (struct ioctl_args *)args)); } return (ENOIOCTL); } /* * Console related ioctls */ static int linux_ioctl_console(struct thread *td, struct linux_ioctl_args *args) { cap_rights_t rights; struct file *fp; int error; error = fget(td, args->fd, cap_rights_init(&rights, CAP_IOCTL), &fp); if (error != 0) return (error); switch (args->cmd & 0xffff) { case LINUX_KIOCSOUND: args->cmd = KIOCSOUND; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; case LINUX_KDMKTONE: args->cmd = KDMKTONE; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; case LINUX_KDGETLED: args->cmd = KDGETLED; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; case LINUX_KDSETLED: args->cmd = KDSETLED; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; case LINUX_KDSETMODE: args->cmd = KDSETMODE; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; case LINUX_KDGETMODE: args->cmd = KDGETMODE; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; case LINUX_KDGKBMODE: args->cmd = KDGKBMODE; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; case LINUX_KDSKBMODE: { int kbdmode; switch (args->arg) { case LINUX_KBD_RAW: kbdmode = K_RAW; break; case LINUX_KBD_XLATE: kbdmode = K_XLATE; break; case LINUX_KBD_MEDIUMRAW: kbdmode = K_RAW; break; default: fdrop(fp, td); return (EINVAL); } error = (fo_ioctl(fp, KDSKBMODE, (caddr_t)&kbdmode, td->td_ucred, td)); break; } case LINUX_VT_OPENQRY: args->cmd = VT_OPENQRY; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; case LINUX_VT_GETMODE: args->cmd = VT_GETMODE; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; case LINUX_VT_SETMODE: { struct vt_mode mode; if ((error = copyin((void *)args->arg, &mode, sizeof(mode)))) break; if (LINUX_SIG_VALID(mode.relsig)) mode.relsig = linux_to_bsd_signal(mode.relsig); else mode.relsig = 0; if (LINUX_SIG_VALID(mode.acqsig)) mode.acqsig = linux_to_bsd_signal(mode.acqsig); else mode.acqsig = 0; /* XXX. Linux ignores frsig and set it to 0. */ mode.frsig = 0; if ((error = copyout(&mode, (void *)args->arg, sizeof(mode)))) break; args->cmd = VT_SETMODE; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; } case LINUX_VT_GETSTATE: args->cmd = VT_GETACTIVE; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; case LINUX_VT_RELDISP: args->cmd = VT_RELDISP; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; case LINUX_VT_ACTIVATE: args->cmd = VT_ACTIVATE; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; case LINUX_VT_WAITACTIVE: args->cmd = VT_WAITACTIVE; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; default: error = ENOIOCTL; break; } fdrop(fp, td); return (error); } /* * Criteria for interface name translation */ #define IFP_IS_ETH(ifp) (ifp->if_type == IFT_ETHER) /* * Translate a Linux interface name to a FreeBSD interface name, * and return the associated ifnet structure * bsdname and lxname need to be least IFNAMSIZ bytes long, but * can point to the same buffer. */ static struct ifnet * ifname_linux_to_bsd(struct thread *td, const char *lxname, char *bsdname) { struct ifnet *ifp; int len, unit; char *ep; int is_eth, index; for (len = 0; len < LINUX_IFNAMSIZ; ++len) if (!isalpha(lxname[len])) break; if (len == 0 || len == LINUX_IFNAMSIZ) return (NULL); unit = (int)strtoul(lxname + len, &ep, 10); if (ep == NULL || ep == lxname + len || ep >= lxname + LINUX_IFNAMSIZ) return (NULL); index = 0; is_eth = (len == 3 && !strncmp(lxname, "eth", len)) ? 1 : 0; CURVNET_SET(TD_TO_VNET(td)); IFNET_RLOCK(); TAILQ_FOREACH(ifp, &V_ifnet, if_link) { /* * Allow Linux programs to use FreeBSD names. Don't presume * we never have an interface named "eth", so don't make * the test optional based on is_eth. */ if (strncmp(ifp->if_xname, lxname, LINUX_IFNAMSIZ) == 0) break; if (is_eth && IFP_IS_ETH(ifp) && unit == index++) break; } IFNET_RUNLOCK(); CURVNET_RESTORE(); if (ifp != NULL) strlcpy(bsdname, ifp->if_xname, IFNAMSIZ); return (ifp); } /* * Implement the SIOCGIFNAME ioctl */ static int linux_ioctl_ifname(struct thread *td, struct l_ifreq *uifr) { struct l_ifreq ifr; struct ifnet *ifp; int error, ethno, index; error = copyin(uifr, &ifr, sizeof(ifr)); if (error != 0) return (error); CURVNET_SET(TD_TO_VNET(curthread)); IFNET_RLOCK(); index = 1; /* ifr.ifr_ifindex starts from 1 */ ethno = 0; error = ENODEV; TAILQ_FOREACH(ifp, &V_ifnet, if_link) { if (ifr.ifr_ifindex == index) { if (IFP_IS_ETH(ifp)) snprintf(ifr.ifr_name, LINUX_IFNAMSIZ, "eth%d", ethno); else strlcpy(ifr.ifr_name, ifp->if_xname, LINUX_IFNAMSIZ); error = 0; break; } if (IFP_IS_ETH(ifp)) ethno++; index++; } IFNET_RUNLOCK(); if (error == 0) error = copyout(&ifr, uifr, sizeof(ifr)); CURVNET_RESTORE(); return (error); } /* * Implement the SIOCGIFCONF ioctl */ static int linux_ifconf(struct thread *td, struct ifconf *uifc) { #ifdef COMPAT_LINUX32 struct l_ifconf ifc; #else struct ifconf ifc; #endif struct l_ifreq ifr; struct ifnet *ifp; struct ifaddr *ifa; struct sbuf *sb; int error, ethno, full = 0, valid_len, max_len; error = copyin(uifc, &ifc, sizeof(ifc)); if (error != 0) return (error); max_len = MAXPHYS - 1; CURVNET_SET(TD_TO_VNET(td)); /* handle the 'request buffer size' case */ if ((l_uintptr_t)ifc.ifc_buf == PTROUT(NULL)) { ifc.ifc_len = 0; IFNET_RLOCK(); TAILQ_FOREACH(ifp, &V_ifnet, if_link) { TAILQ_FOREACH(ifa, &ifp->if_addrhead, ifa_link) { struct sockaddr *sa = ifa->ifa_addr; if (sa->sa_family == AF_INET) ifc.ifc_len += sizeof(ifr); } } IFNET_RUNLOCK(); error = copyout(&ifc, uifc, sizeof(ifc)); CURVNET_RESTORE(); return (error); } if (ifc.ifc_len <= 0) { CURVNET_RESTORE(); return (EINVAL); } again: /* Keep track of eth interfaces */ ethno = 0; if (ifc.ifc_len <= max_len) { max_len = ifc.ifc_len; full = 1; } sb = sbuf_new(NULL, NULL, max_len + 1, SBUF_FIXEDLEN); max_len = 0; valid_len = 0; /* Return all AF_INET addresses of all interfaces */ IFNET_RLOCK(); TAILQ_FOREACH(ifp, &V_ifnet, if_link) { int addrs = 0; bzero(&ifr, sizeof(ifr)); if (IFP_IS_ETH(ifp)) snprintf(ifr.ifr_name, LINUX_IFNAMSIZ, "eth%d", ethno++); else strlcpy(ifr.ifr_name, ifp->if_xname, LINUX_IFNAMSIZ); /* Walk the address list */ TAILQ_FOREACH(ifa, &ifp->if_addrhead, ifa_link) { struct sockaddr *sa = ifa->ifa_addr; if (sa->sa_family == AF_INET) { ifr.ifr_addr.sa_family = LINUX_AF_INET; memcpy(ifr.ifr_addr.sa_data, sa->sa_data, sizeof(ifr.ifr_addr.sa_data)); sbuf_bcat(sb, &ifr, sizeof(ifr)); max_len += sizeof(ifr); addrs++; } if (sbuf_error(sb) == 0) valid_len = sbuf_len(sb); } if (addrs == 0) { bzero((caddr_t)&ifr.ifr_addr, sizeof(ifr.ifr_addr)); sbuf_bcat(sb, &ifr, sizeof(ifr)); max_len += sizeof(ifr); if (sbuf_error(sb) == 0) valid_len = sbuf_len(sb); } } IFNET_RUNLOCK(); if (valid_len != max_len && !full) { sbuf_delete(sb); goto again; } ifc.ifc_len = valid_len; sbuf_finish(sb); error = copyout(sbuf_data(sb), PTRIN(ifc.ifc_buf), ifc.ifc_len); if (error == 0) error = copyout(&ifc, uifc, sizeof(ifc)); sbuf_delete(sb); CURVNET_RESTORE(); return (error); } static int linux_gifflags(struct thread *td, struct ifnet *ifp, struct l_ifreq *ifr) { l_short flags; flags = (ifp->if_flags | ifp->if_drv_flags) & 0xffff; /* these flags have no Linux equivalent */ flags &= ~(IFF_DRV_OACTIVE|IFF_SIMPLEX| IFF_LINK0|IFF_LINK1|IFF_LINK2); /* Linux' multicast flag is in a different bit */ if (flags & IFF_MULTICAST) { flags &= ~IFF_MULTICAST; flags |= 0x1000; } return (copyout(&flags, &ifr->ifr_flags, sizeof(flags))); } #define ARPHRD_ETHER 1 #define ARPHRD_LOOPBACK 772 static int linux_gifhwaddr(struct ifnet *ifp, struct l_ifreq *ifr) { struct ifaddr *ifa; struct sockaddr_dl *sdl; struct l_sockaddr lsa; if (ifp->if_type == IFT_LOOP) { bzero(&lsa, sizeof(lsa)); lsa.sa_family = ARPHRD_LOOPBACK; return (copyout(&lsa, &ifr->ifr_hwaddr, sizeof(lsa))); } if (ifp->if_type != IFT_ETHER) return (ENOENT); TAILQ_FOREACH(ifa, &ifp->if_addrhead, ifa_link) { sdl = (struct sockaddr_dl*)ifa->ifa_addr; if (sdl != NULL && (sdl->sdl_family == AF_LINK) && (sdl->sdl_type == IFT_ETHER)) { bzero(&lsa, sizeof(lsa)); lsa.sa_family = ARPHRD_ETHER; bcopy(LLADDR(sdl), lsa.sa_data, LINUX_IFHWADDRLEN); return (copyout(&lsa, &ifr->ifr_hwaddr, sizeof(lsa))); } } return (ENOENT); } /* * If we fault in bsd_to_linux_ifreq() then we will fault when we call * the native ioctl(). Thus, we don't really need to check the return * value of this function. */ static int bsd_to_linux_ifreq(struct ifreq *arg) { struct ifreq ifr; size_t ifr_len = sizeof(struct ifreq); int error; if ((error = copyin(arg, &ifr, ifr_len))) return (error); *(u_short *)&ifr.ifr_addr = ifr.ifr_addr.sa_family; error = copyout(&ifr, arg, ifr_len); return (error); } /* * Socket related ioctls */ static int linux_ioctl_socket(struct thread *td, struct linux_ioctl_args *args) { char lifname[LINUX_IFNAMSIZ], ifname[IFNAMSIZ]; cap_rights_t rights; struct ifnet *ifp; struct file *fp; int error, type; ifp = NULL; error = 0; error = fget(td, args->fd, cap_rights_init(&rights, CAP_IOCTL), &fp); if (error != 0) return (error); type = fp->f_type; fdrop(fp, td); if (type != DTYPE_SOCKET) { /* not a socket - probably a tap / vmnet device */ switch (args->cmd) { case LINUX_SIOCGIFADDR: case LINUX_SIOCSIFADDR: case LINUX_SIOCGIFFLAGS: return (linux_ioctl_special(td, args)); default: return (ENOIOCTL); } } switch (args->cmd & 0xffff) { case LINUX_FIOGETOWN: case LINUX_FIOSETOWN: case LINUX_SIOCADDMULTI: case LINUX_SIOCATMARK: case LINUX_SIOCDELMULTI: case LINUX_SIOCGIFNAME: case LINUX_SIOCGIFCONF: case LINUX_SIOCGPGRP: case LINUX_SIOCSPGRP: case LINUX_SIOCGIFCOUNT: /* these ioctls don't take an interface name */ #ifdef DEBUG printf("%s(): ioctl %d\n", __func__, args->cmd & 0xffff); #endif break; case LINUX_SIOCGIFFLAGS: case LINUX_SIOCGIFADDR: case LINUX_SIOCSIFADDR: case LINUX_SIOCGIFDSTADDR: case LINUX_SIOCGIFBRDADDR: case LINUX_SIOCGIFNETMASK: case LINUX_SIOCSIFNETMASK: case LINUX_SIOCGIFMTU: case LINUX_SIOCSIFMTU: case LINUX_SIOCSIFNAME: case LINUX_SIOCGIFHWADDR: case LINUX_SIOCSIFHWADDR: case LINUX_SIOCDEVPRIVATE: case LINUX_SIOCDEVPRIVATE+1: case LINUX_SIOCGIFINDEX: /* copy in the interface name and translate it. */ error = copyin((void *)args->arg, lifname, LINUX_IFNAMSIZ); if (error != 0) return (error); #ifdef DEBUG printf("%s(): ioctl %d on %.*s\n", __func__, args->cmd & 0xffff, LINUX_IFNAMSIZ, lifname); #endif memset(ifname, 0, sizeof(ifname)); ifp = ifname_linux_to_bsd(td, lifname, ifname); if (ifp == NULL) return (EINVAL); /* * We need to copy it back out in case we pass the * request on to our native ioctl(), which will expect * the ifreq to be in user space and have the correct * interface name. */ error = copyout(ifname, (void *)args->arg, IFNAMSIZ); if (error != 0) return (error); #ifdef DEBUG printf("%s(): %s translated to %s\n", __func__, lifname, ifname); #endif break; default: return (ENOIOCTL); } switch (args->cmd & 0xffff) { case LINUX_FIOSETOWN: args->cmd = FIOSETOWN; error = sys_ioctl(td, (struct ioctl_args *)args); break; case LINUX_SIOCSPGRP: args->cmd = SIOCSPGRP; error = sys_ioctl(td, (struct ioctl_args *)args); break; case LINUX_FIOGETOWN: args->cmd = FIOGETOWN; error = sys_ioctl(td, (struct ioctl_args *)args); break; case LINUX_SIOCGPGRP: args->cmd = SIOCGPGRP; error = sys_ioctl(td, (struct ioctl_args *)args); break; case LINUX_SIOCATMARK: args->cmd = SIOCATMARK; error = sys_ioctl(td, (struct ioctl_args *)args); break; /* LINUX_SIOCGSTAMP */ case LINUX_SIOCGIFNAME: error = linux_ioctl_ifname(td, (struct l_ifreq *)args->arg); break; case LINUX_SIOCGIFCONF: error = linux_ifconf(td, (struct ifconf *)args->arg); break; case LINUX_SIOCGIFFLAGS: args->cmd = SIOCGIFFLAGS; error = linux_gifflags(td, ifp, (struct l_ifreq *)args->arg); break; case LINUX_SIOCGIFADDR: args->cmd = SIOCGIFADDR; error = sys_ioctl(td, (struct ioctl_args *)args); bsd_to_linux_ifreq((struct ifreq *)args->arg); break; case LINUX_SIOCSIFADDR: /* XXX probably doesn't work, included for completeness */ args->cmd = SIOCSIFADDR; error = sys_ioctl(td, (struct ioctl_args *)args); break; case LINUX_SIOCGIFDSTADDR: args->cmd = SIOCGIFDSTADDR; error = sys_ioctl(td, (struct ioctl_args *)args); bsd_to_linux_ifreq((struct ifreq *)args->arg); break; case LINUX_SIOCGIFBRDADDR: args->cmd = SIOCGIFBRDADDR; error = sys_ioctl(td, (struct ioctl_args *)args); bsd_to_linux_ifreq((struct ifreq *)args->arg); break; case LINUX_SIOCGIFNETMASK: args->cmd = SIOCGIFNETMASK; error = sys_ioctl(td, (struct ioctl_args *)args); bsd_to_linux_ifreq((struct ifreq *)args->arg); break; case LINUX_SIOCSIFNETMASK: error = ENOIOCTL; break; case LINUX_SIOCGIFMTU: args->cmd = SIOCGIFMTU; error = sys_ioctl(td, (struct ioctl_args *)args); break; case LINUX_SIOCSIFMTU: args->cmd = SIOCSIFMTU; error = sys_ioctl(td, (struct ioctl_args *)args); break; case LINUX_SIOCSIFNAME: error = ENOIOCTL; break; case LINUX_SIOCGIFHWADDR: error = linux_gifhwaddr(ifp, (struct l_ifreq *)args->arg); break; case LINUX_SIOCSIFHWADDR: error = ENOIOCTL; break; case LINUX_SIOCADDMULTI: args->cmd = SIOCADDMULTI; error = sys_ioctl(td, (struct ioctl_args *)args); break; case LINUX_SIOCDELMULTI: args->cmd = SIOCDELMULTI; error = sys_ioctl(td, (struct ioctl_args *)args); break; case LINUX_SIOCGIFINDEX: args->cmd = SIOCGIFINDEX; error = sys_ioctl(td, (struct ioctl_args *)args); break; case LINUX_SIOCGIFCOUNT: error = 0; break; /* * XXX This is slightly bogus, but these ioctls are currently * XXX only used by the aironet (if_an) network driver. */ case LINUX_SIOCDEVPRIVATE: args->cmd = SIOCGPRIVATE_0; error = sys_ioctl(td, (struct ioctl_args *)args); break; case LINUX_SIOCDEVPRIVATE+1: args->cmd = SIOCGPRIVATE_1; error = sys_ioctl(td, (struct ioctl_args *)args); break; } if (ifp != NULL) /* restore the original interface name */ copyout(lifname, (void *)args->arg, LINUX_IFNAMSIZ); #ifdef DEBUG printf("%s(): returning %d\n", __func__, error); #endif return (error); } /* * Device private ioctl handler */ static int linux_ioctl_private(struct thread *td, struct linux_ioctl_args *args) { cap_rights_t rights; struct file *fp; int error, type; error = fget(td, args->fd, cap_rights_init(&rights, CAP_IOCTL), &fp); if (error != 0) return (error); type = fp->f_type; fdrop(fp, td); if (type == DTYPE_SOCKET) return (linux_ioctl_socket(td, args)); return (ENOIOCTL); } /* * DRM ioctl handler (sys/dev/drm) */ static int linux_ioctl_drm(struct thread *td, struct linux_ioctl_args *args) { args->cmd = SETDIR(args->cmd); return sys_ioctl(td, (struct ioctl_args *)args); } #ifdef COMPAT_LINUX32 #define CP(src,dst,fld) do { (dst).fld = (src).fld; } while (0) #define PTRIN_CP(src,dst,fld) \ do { (dst).fld = PTRIN((src).fld); } while (0) #define PTROUT_CP(src,dst,fld) \ do { (dst).fld = PTROUT((src).fld); } while (0) static int linux_ioctl_sg_io(struct thread *td, struct linux_ioctl_args *args) { struct sg_io_hdr io; struct sg_io_hdr32 io32; cap_rights_t rights; struct file *fp; int error; error = fget(td, args->fd, cap_rights_init(&rights, CAP_IOCTL), &fp); if (error != 0) { printf("sg_linux_ioctl: fget returned %d\n", error); return (error); } if ((error = copyin((void *)args->arg, &io32, sizeof(io32))) != 0) goto out; CP(io32, io, interface_id); CP(io32, io, dxfer_direction); CP(io32, io, cmd_len); CP(io32, io, mx_sb_len); CP(io32, io, iovec_count); CP(io32, io, dxfer_len); PTRIN_CP(io32, io, dxferp); PTRIN_CP(io32, io, cmdp); PTRIN_CP(io32, io, sbp); CP(io32, io, timeout); CP(io32, io, flags); CP(io32, io, pack_id); PTRIN_CP(io32, io, usr_ptr); CP(io32, io, status); CP(io32, io, masked_status); CP(io32, io, msg_status); CP(io32, io, sb_len_wr); CP(io32, io, host_status); CP(io32, io, driver_status); CP(io32, io, resid); CP(io32, io, duration); CP(io32, io, info); if ((error = fo_ioctl(fp, SG_IO, (caddr_t)&io, td->td_ucred, td)) != 0) goto out; CP(io, io32, interface_id); CP(io, io32, dxfer_direction); CP(io, io32, cmd_len); CP(io, io32, mx_sb_len); CP(io, io32, iovec_count); CP(io, io32, dxfer_len); PTROUT_CP(io, io32, dxferp); PTROUT_CP(io, io32, cmdp); PTROUT_CP(io, io32, sbp); CP(io, io32, timeout); CP(io, io32, flags); CP(io, io32, pack_id); PTROUT_CP(io, io32, usr_ptr); CP(io, io32, status); CP(io, io32, masked_status); CP(io, io32, msg_status); CP(io, io32, sb_len_wr); CP(io, io32, host_status); CP(io, io32, driver_status); CP(io, io32, resid); CP(io, io32, duration); CP(io, io32, info); error = copyout(&io32, (void *)args->arg, sizeof(io32)); out: fdrop(fp, td); return (error); } #endif static int linux_ioctl_sg(struct thread *td, struct linux_ioctl_args *args) { switch (args->cmd) { case LINUX_SG_GET_VERSION_NUM: args->cmd = SG_GET_VERSION_NUM; break; case LINUX_SG_SET_TIMEOUT: args->cmd = SG_SET_TIMEOUT; break; case LINUX_SG_GET_TIMEOUT: args->cmd = SG_GET_TIMEOUT; break; case LINUX_SG_IO: args->cmd = SG_IO; #ifdef COMPAT_LINUX32 return (linux_ioctl_sg_io(td, args)); #endif break; case LINUX_SG_GET_RESERVED_SIZE: args->cmd = SG_GET_RESERVED_SIZE; break; case LINUX_SG_GET_SCSI_ID: args->cmd = SG_GET_SCSI_ID; break; case LINUX_SG_GET_SG_TABLESIZE: args->cmd = SG_GET_SG_TABLESIZE; break; default: return (ENODEV); } return (sys_ioctl(td, (struct ioctl_args *)args)); } /* * Video4Linux (V4L) ioctl handler */ static int linux_to_bsd_v4l_tuner(struct l_video_tuner *lvt, struct video_tuner *vt) { vt->tuner = lvt->tuner; strlcpy(vt->name, lvt->name, LINUX_VIDEO_TUNER_NAME_SIZE); vt->rangelow = lvt->rangelow; /* possible long size conversion */ vt->rangehigh = lvt->rangehigh; /* possible long size conversion */ vt->flags = lvt->flags; vt->mode = lvt->mode; vt->signal = lvt->signal; return (0); } static int bsd_to_linux_v4l_tuner(struct video_tuner *vt, struct l_video_tuner *lvt) { lvt->tuner = vt->tuner; strlcpy(lvt->name, vt->name, LINUX_VIDEO_TUNER_NAME_SIZE); lvt->rangelow = vt->rangelow; /* possible long size conversion */ lvt->rangehigh = vt->rangehigh; /* possible long size conversion */ lvt->flags = vt->flags; lvt->mode = vt->mode; lvt->signal = vt->signal; return (0); } #ifdef COMPAT_LINUX_V4L_CLIPLIST static int linux_to_bsd_v4l_clip(struct l_video_clip *lvc, struct video_clip *vc) { vc->x = lvc->x; vc->y = lvc->y; vc->width = lvc->width; vc->height = lvc->height; vc->next = PTRIN(lvc->next); /* possible pointer size conversion */ return (0); } #endif static int linux_to_bsd_v4l_window(struct l_video_window *lvw, struct video_window *vw) { vw->x = lvw->x; vw->y = lvw->y; vw->width = lvw->width; vw->height = lvw->height; vw->chromakey = lvw->chromakey; vw->flags = lvw->flags; vw->clips = PTRIN(lvw->clips); /* possible pointer size conversion */ vw->clipcount = lvw->clipcount; return (0); } static int bsd_to_linux_v4l_window(struct video_window *vw, struct l_video_window *lvw) { lvw->x = vw->x; lvw->y = vw->y; lvw->width = vw->width; lvw->height = vw->height; lvw->chromakey = vw->chromakey; lvw->flags = vw->flags; lvw->clips = PTROUT(vw->clips); /* possible pointer size conversion */ lvw->clipcount = vw->clipcount; return (0); } static int linux_to_bsd_v4l_buffer(struct l_video_buffer *lvb, struct video_buffer *vb) { vb->base = PTRIN(lvb->base); /* possible pointer size conversion */ vb->height = lvb->height; vb->width = lvb->width; vb->depth = lvb->depth; vb->bytesperline = lvb->bytesperline; return (0); } static int bsd_to_linux_v4l_buffer(struct video_buffer *vb, struct l_video_buffer *lvb) { lvb->base = PTROUT(vb->base); /* possible pointer size conversion */ lvb->height = vb->height; lvb->width = vb->width; lvb->depth = vb->depth; lvb->bytesperline = vb->bytesperline; return (0); } static int linux_to_bsd_v4l_code(struct l_video_code *lvc, struct video_code *vc) { strlcpy(vc->loadwhat, lvc->loadwhat, LINUX_VIDEO_CODE_LOADWHAT_SIZE); vc->datasize = lvc->datasize; vc->data = PTRIN(lvc->data); /* possible pointer size conversion */ return (0); } #ifdef COMPAT_LINUX_V4L_CLIPLIST static int linux_v4l_clip_copy(void *lvc, struct video_clip **ppvc) { int error; struct video_clip vclip; struct l_video_clip l_vclip; error = copyin(lvc, &l_vclip, sizeof(l_vclip)); if (error) return (error); linux_to_bsd_v4l_clip(&l_vclip, &vclip); /* XXX: If there can be no concurrency: s/M_NOWAIT/M_WAITOK/ */ if ((*ppvc = malloc(sizeof(**ppvc), M_LINUX, M_NOWAIT)) == NULL) return (ENOMEM); /* XXX: linux has no ENOMEM here */ memcpy(*ppvc, &vclip, sizeof(vclip)); (*ppvc)->next = NULL; return (0); } static int linux_v4l_cliplist_free(struct video_window *vw) { struct video_clip **ppvc; struct video_clip **ppvc_next; for (ppvc = &(vw->clips); *ppvc != NULL; ppvc = ppvc_next) { ppvc_next = &((*ppvc)->next); free(*ppvc, M_LINUX); } vw->clips = NULL; return (0); } static int linux_v4l_cliplist_copy(struct l_video_window *lvw, struct video_window *vw) { int error; int clipcount; void *plvc; struct video_clip **ppvc; /* * XXX: The cliplist is used to pass in a list of clipping * rectangles or, if clipcount == VIDEO_CLIP_BITMAP, a * clipping bitmap. Some Linux apps, however, appear to * leave cliplist and clips uninitialized. In any case, * the cliplist is not used by pwc(4), at the time of * writing, FreeBSD's only V4L driver. When a driver * that uses the cliplist is developed, this code may * need re-examiniation. */ error = 0; clipcount = vw->clipcount; if (clipcount == VIDEO_CLIP_BITMAP) { /* * In this case, the pointer (clips) is overloaded * to be a "void *" to a bitmap, therefore there * is no struct video_clip to copy now. */ } else if (clipcount > 0 && clipcount <= 16384) { /* * Clips points to list of clip rectangles, so * copy the list. * * XXX: Upper limit of 16384 was used here to try to * avoid cases when clipcount and clips pointer * are uninitialized and therefore have high random * values, as is the case in the Linux Skype * application. The value 16384 was chosen as that * is what is used in the Linux stradis(4) MPEG * decoder driver, the only place we found an * example of cliplist use. */ plvc = PTRIN(lvw->clips); vw->clips = NULL; ppvc = &(vw->clips); while (clipcount-- > 0) { if (plvc == NULL) { error = EFAULT; break; } else { error = linux_v4l_clip_copy(plvc, ppvc); if (error) { linux_v4l_cliplist_free(vw); break; } } ppvc = &((*ppvc)->next); plvc = PTRIN(((struct l_video_clip *) plvc)->next); } } else { /* * clipcount == 0 or negative (but not VIDEO_CLIP_BITMAP) * Force cliplist to null. */ vw->clipcount = 0; vw->clips = NULL; } return (error); } #endif static int linux_ioctl_v4l(struct thread *td, struct linux_ioctl_args *args) { cap_rights_t rights; struct file *fp; int error; struct video_tuner vtun; struct video_window vwin; struct video_buffer vbuf; struct video_code vcode; struct l_video_tuner l_vtun; struct l_video_window l_vwin; struct l_video_buffer l_vbuf; struct l_video_code l_vcode; switch (args->cmd & 0xffff) { case LINUX_VIDIOCGCAP: args->cmd = VIDIOCGCAP; break; case LINUX_VIDIOCGCHAN: args->cmd = VIDIOCGCHAN; break; case LINUX_VIDIOCSCHAN: args->cmd = VIDIOCSCHAN; break; case LINUX_VIDIOCGTUNER: error = fget(td, args->fd, cap_rights_init(&rights, CAP_IOCTL), &fp); if (error != 0) return (error); error = copyin((void *) args->arg, &l_vtun, sizeof(l_vtun)); if (error) { fdrop(fp, td); return (error); } linux_to_bsd_v4l_tuner(&l_vtun, &vtun); error = fo_ioctl(fp, VIDIOCGTUNER, &vtun, td->td_ucred, td); if (!error) { bsd_to_linux_v4l_tuner(&vtun, &l_vtun); error = copyout(&l_vtun, (void *) args->arg, sizeof(l_vtun)); } fdrop(fp, td); return (error); case LINUX_VIDIOCSTUNER: error = fget(td, args->fd, cap_rights_init(&rights, CAP_IOCTL), &fp); if (error != 0) return (error); error = copyin((void *) args->arg, &l_vtun, sizeof(l_vtun)); if (error) { fdrop(fp, td); return (error); } linux_to_bsd_v4l_tuner(&l_vtun, &vtun); error = fo_ioctl(fp, VIDIOCSTUNER, &vtun, td->td_ucred, td); fdrop(fp, td); return (error); case LINUX_VIDIOCGPICT: args->cmd = VIDIOCGPICT; break; case LINUX_VIDIOCSPICT: args->cmd = VIDIOCSPICT; break; case LINUX_VIDIOCCAPTURE: args->cmd = VIDIOCCAPTURE; break; case LINUX_VIDIOCGWIN: error = fget(td, args->fd, cap_rights_init(&rights, CAP_IOCTL), &fp); if (error != 0) return (error); error = fo_ioctl(fp, VIDIOCGWIN, &vwin, td->td_ucred, td); if (!error) { bsd_to_linux_v4l_window(&vwin, &l_vwin); error = copyout(&l_vwin, (void *) args->arg, sizeof(l_vwin)); } fdrop(fp, td); return (error); case LINUX_VIDIOCSWIN: error = fget(td, args->fd, cap_rights_init(&rights, CAP_IOCTL), &fp); if (error != 0) return (error); error = copyin((void *) args->arg, &l_vwin, sizeof(l_vwin)); if (error) { fdrop(fp, td); return (error); } linux_to_bsd_v4l_window(&l_vwin, &vwin); #ifdef COMPAT_LINUX_V4L_CLIPLIST error = linux_v4l_cliplist_copy(&l_vwin, &vwin); if (error) { fdrop(fp, td); return (error); } #endif error = fo_ioctl(fp, VIDIOCSWIN, &vwin, td->td_ucred, td); fdrop(fp, td); #ifdef COMPAT_LINUX_V4L_CLIPLIST linux_v4l_cliplist_free(&vwin); #endif return (error); case LINUX_VIDIOCGFBUF: error = fget(td, args->fd, cap_rights_init(&rights, CAP_IOCTL), &fp); if (error != 0) return (error); error = fo_ioctl(fp, VIDIOCGFBUF, &vbuf, td->td_ucred, td); if (!error) { bsd_to_linux_v4l_buffer(&vbuf, &l_vbuf); error = copyout(&l_vbuf, (void *) args->arg, sizeof(l_vbuf)); } fdrop(fp, td); return (error); case LINUX_VIDIOCSFBUF: error = fget(td, args->fd, cap_rights_init(&rights, CAP_IOCTL), &fp); if (error != 0) return (error); error = copyin((void *) args->arg, &l_vbuf, sizeof(l_vbuf)); if (error) { fdrop(fp, td); return (error); } linux_to_bsd_v4l_buffer(&l_vbuf, &vbuf); error = fo_ioctl(fp, VIDIOCSFBUF, &vbuf, td->td_ucred, td); fdrop(fp, td); return (error); case LINUX_VIDIOCKEY: args->cmd = VIDIOCKEY; break; case LINUX_VIDIOCGFREQ: args->cmd = VIDIOCGFREQ; break; case LINUX_VIDIOCSFREQ: args->cmd = VIDIOCSFREQ; break; case LINUX_VIDIOCGAUDIO: args->cmd = VIDIOCGAUDIO; break; case LINUX_VIDIOCSAUDIO: args->cmd = VIDIOCSAUDIO; break; case LINUX_VIDIOCSYNC: args->cmd = VIDIOCSYNC; break; case LINUX_VIDIOCMCAPTURE: args->cmd = VIDIOCMCAPTURE; break; case LINUX_VIDIOCGMBUF: args->cmd = VIDIOCGMBUF; break; case LINUX_VIDIOCGUNIT: args->cmd = VIDIOCGUNIT; break; case LINUX_VIDIOCGCAPTURE: args->cmd = VIDIOCGCAPTURE; break; case LINUX_VIDIOCSCAPTURE: args->cmd = VIDIOCSCAPTURE; break; case LINUX_VIDIOCSPLAYMODE: args->cmd = VIDIOCSPLAYMODE; break; case LINUX_VIDIOCSWRITEMODE: args->cmd = VIDIOCSWRITEMODE; break; case LINUX_VIDIOCGPLAYINFO: args->cmd = VIDIOCGPLAYINFO; break; case LINUX_VIDIOCSMICROCODE: error = fget(td, args->fd, cap_rights_init(&rights, CAP_IOCTL), &fp); if (error != 0) return (error); error = copyin((void *) args->arg, &l_vcode, sizeof(l_vcode)); if (error) { fdrop(fp, td); return (error); } linux_to_bsd_v4l_code(&l_vcode, &vcode); error = fo_ioctl(fp, VIDIOCSMICROCODE, &vcode, td->td_ucred, td); fdrop(fp, td); return (error); case LINUX_VIDIOCGVBIFMT: args->cmd = VIDIOCGVBIFMT; break; case LINUX_VIDIOCSVBIFMT: args->cmd = VIDIOCSVBIFMT; break; default: return (ENOIOCTL); } error = sys_ioctl(td, (struct ioctl_args *)args); return (error); } /* * Special ioctl handler */ static int linux_ioctl_special(struct thread *td, struct linux_ioctl_args *args) { int error; switch (args->cmd) { case LINUX_SIOCGIFADDR: args->cmd = SIOCGIFADDR; error = sys_ioctl(td, (struct ioctl_args *)args); break; case LINUX_SIOCSIFADDR: args->cmd = SIOCSIFADDR; error = sys_ioctl(td, (struct ioctl_args *)args); break; case LINUX_SIOCGIFFLAGS: args->cmd = SIOCGIFFLAGS; error = sys_ioctl(td, (struct ioctl_args *)args); break; default: error = ENOIOCTL; } return (error); } static int linux_to_bsd_v4l2_standard(struct l_v4l2_standard *lvstd, struct v4l2_standard *vstd) { vstd->index = lvstd->index; vstd->id = lvstd->id; CTASSERT(sizeof(vstd->name) == sizeof(lvstd->name)); memcpy(vstd->name, lvstd->name, sizeof(vstd->name)); vstd->frameperiod = lvstd->frameperiod; vstd->framelines = lvstd->framelines; CTASSERT(sizeof(vstd->reserved) == sizeof(lvstd->reserved)); memcpy(vstd->reserved, lvstd->reserved, sizeof(vstd->reserved)); return (0); } static int bsd_to_linux_v4l2_standard(struct v4l2_standard *vstd, struct l_v4l2_standard *lvstd) { lvstd->index = vstd->index; lvstd->id = vstd->id; CTASSERT(sizeof(vstd->name) == sizeof(lvstd->name)); memcpy(lvstd->name, vstd->name, sizeof(lvstd->name)); lvstd->frameperiod = vstd->frameperiod; lvstd->framelines = vstd->framelines; CTASSERT(sizeof(vstd->reserved) == sizeof(lvstd->reserved)); memcpy(lvstd->reserved, vstd->reserved, sizeof(lvstd->reserved)); return (0); } static int linux_to_bsd_v4l2_buffer(struct l_v4l2_buffer *lvb, struct v4l2_buffer *vb) { vb->index = lvb->index; vb->type = lvb->type; vb->bytesused = lvb->bytesused; vb->flags = lvb->flags; vb->field = lvb->field; vb->timestamp.tv_sec = lvb->timestamp.tv_sec; vb->timestamp.tv_usec = lvb->timestamp.tv_usec; memcpy(&vb->timecode, &lvb->timecode, sizeof (lvb->timecode)); vb->sequence = lvb->sequence; vb->memory = lvb->memory; if (lvb->memory == V4L2_MEMORY_USERPTR) /* possible pointer size conversion */ vb->m.userptr = (unsigned long)PTRIN(lvb->m.userptr); else vb->m.offset = lvb->m.offset; vb->length = lvb->length; vb->input = lvb->input; vb->reserved = lvb->reserved; return (0); } static int bsd_to_linux_v4l2_buffer(struct v4l2_buffer *vb, struct l_v4l2_buffer *lvb) { lvb->index = vb->index; lvb->type = vb->type; lvb->bytesused = vb->bytesused; lvb->flags = vb->flags; lvb->field = vb->field; lvb->timestamp.tv_sec = vb->timestamp.tv_sec; lvb->timestamp.tv_usec = vb->timestamp.tv_usec; memcpy(&lvb->timecode, &vb->timecode, sizeof (vb->timecode)); lvb->sequence = vb->sequence; lvb->memory = vb->memory; if (vb->memory == V4L2_MEMORY_USERPTR) /* possible pointer size conversion */ lvb->m.userptr = PTROUT(vb->m.userptr); else lvb->m.offset = vb->m.offset; lvb->length = vb->length; lvb->input = vb->input; lvb->reserved = vb->reserved; return (0); } static int linux_to_bsd_v4l2_format(struct l_v4l2_format *lvf, struct v4l2_format *vf) { vf->type = lvf->type; if (lvf->type == V4L2_BUF_TYPE_VIDEO_OVERLAY #ifdef V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY || lvf->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY #endif ) /* * XXX TODO - needs 32 -> 64 bit conversion: * (unused by webcams?) */ return EINVAL; memcpy(&vf->fmt, &lvf->fmt, sizeof(vf->fmt)); return 0; } static int bsd_to_linux_v4l2_format(struct v4l2_format *vf, struct l_v4l2_format *lvf) { lvf->type = vf->type; if (vf->type == V4L2_BUF_TYPE_VIDEO_OVERLAY #ifdef V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY || vf->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY #endif ) /* * XXX TODO - needs 32 -> 64 bit conversion: * (unused by webcams?) */ return EINVAL; memcpy(&lvf->fmt, &vf->fmt, sizeof(vf->fmt)); return 0; } static int linux_ioctl_v4l2(struct thread *td, struct linux_ioctl_args *args) { cap_rights_t rights; struct file *fp; int error; struct v4l2_format vformat; struct l_v4l2_format l_vformat; struct v4l2_standard vstd; struct l_v4l2_standard l_vstd; struct l_v4l2_buffer l_vbuf; struct v4l2_buffer vbuf; struct v4l2_input vinp; switch (args->cmd & 0xffff) { case LINUX_VIDIOC_RESERVED: case LINUX_VIDIOC_LOG_STATUS: if ((args->cmd & IOC_DIRMASK) != LINUX_IOC_VOID) return ENOIOCTL; args->cmd = (args->cmd & 0xffff) | IOC_VOID; break; case LINUX_VIDIOC_OVERLAY: case LINUX_VIDIOC_STREAMON: case LINUX_VIDIOC_STREAMOFF: case LINUX_VIDIOC_S_STD: case LINUX_VIDIOC_S_TUNER: case LINUX_VIDIOC_S_AUDIO: case LINUX_VIDIOC_S_AUDOUT: case LINUX_VIDIOC_S_MODULATOR: case LINUX_VIDIOC_S_FREQUENCY: case LINUX_VIDIOC_S_CROP: case LINUX_VIDIOC_S_JPEGCOMP: case LINUX_VIDIOC_S_PRIORITY: case LINUX_VIDIOC_DBG_S_REGISTER: case LINUX_VIDIOC_S_HW_FREQ_SEEK: case LINUX_VIDIOC_SUBSCRIBE_EVENT: case LINUX_VIDIOC_UNSUBSCRIBE_EVENT: args->cmd = (args->cmd & ~IOC_DIRMASK) | IOC_IN; break; case LINUX_VIDIOC_QUERYCAP: case LINUX_VIDIOC_G_STD: case LINUX_VIDIOC_G_AUDIO: case LINUX_VIDIOC_G_INPUT: case LINUX_VIDIOC_G_OUTPUT: case LINUX_VIDIOC_G_AUDOUT: case LINUX_VIDIOC_G_JPEGCOMP: case LINUX_VIDIOC_QUERYSTD: case LINUX_VIDIOC_G_PRIORITY: case LINUX_VIDIOC_QUERY_DV_PRESET: args->cmd = (args->cmd & ~IOC_DIRMASK) | IOC_OUT; break; case LINUX_VIDIOC_ENUM_FMT: case LINUX_VIDIOC_REQBUFS: case LINUX_VIDIOC_G_PARM: case LINUX_VIDIOC_S_PARM: case LINUX_VIDIOC_G_CTRL: case LINUX_VIDIOC_S_CTRL: case LINUX_VIDIOC_G_TUNER: case LINUX_VIDIOC_QUERYCTRL: case LINUX_VIDIOC_QUERYMENU: case LINUX_VIDIOC_S_INPUT: case LINUX_VIDIOC_S_OUTPUT: case LINUX_VIDIOC_ENUMOUTPUT: case LINUX_VIDIOC_G_MODULATOR: case LINUX_VIDIOC_G_FREQUENCY: case LINUX_VIDIOC_CROPCAP: case LINUX_VIDIOC_G_CROP: case LINUX_VIDIOC_ENUMAUDIO: case LINUX_VIDIOC_ENUMAUDOUT: case LINUX_VIDIOC_G_SLICED_VBI_CAP: #ifdef VIDIOC_ENUM_FRAMESIZES case LINUX_VIDIOC_ENUM_FRAMESIZES: case LINUX_VIDIOC_ENUM_FRAMEINTERVALS: case LINUX_VIDIOC_ENCODER_CMD: case LINUX_VIDIOC_TRY_ENCODER_CMD: #endif case LINUX_VIDIOC_DBG_G_REGISTER: case LINUX_VIDIOC_DBG_G_CHIP_IDENT: case LINUX_VIDIOC_ENUM_DV_PRESETS: case LINUX_VIDIOC_S_DV_PRESET: case LINUX_VIDIOC_G_DV_PRESET: case LINUX_VIDIOC_S_DV_TIMINGS: case LINUX_VIDIOC_G_DV_TIMINGS: args->cmd = (args->cmd & ~IOC_DIRMASK) | IOC_INOUT; break; case LINUX_VIDIOC_G_FMT: case LINUX_VIDIOC_S_FMT: case LINUX_VIDIOC_TRY_FMT: error = copyin((void *)args->arg, &l_vformat, sizeof(l_vformat)); if (error) return (error); error = fget(td, args->fd, cap_rights_init(&rights, CAP_IOCTL), &fp); if (error) return (error); if (linux_to_bsd_v4l2_format(&l_vformat, &vformat) != 0) error = EINVAL; else if ((args->cmd & 0xffff) == LINUX_VIDIOC_G_FMT) error = fo_ioctl(fp, VIDIOC_G_FMT, &vformat, td->td_ucred, td); else if ((args->cmd & 0xffff) == LINUX_VIDIOC_S_FMT) error = fo_ioctl(fp, VIDIOC_S_FMT, &vformat, td->td_ucred, td); else error = fo_ioctl(fp, VIDIOC_TRY_FMT, &vformat, td->td_ucred, td); bsd_to_linux_v4l2_format(&vformat, &l_vformat); copyout(&l_vformat, (void *)args->arg, sizeof(l_vformat)); fdrop(fp, td); return (error); case LINUX_VIDIOC_ENUMSTD: error = copyin((void *)args->arg, &l_vstd, sizeof(l_vstd)); if (error) return (error); linux_to_bsd_v4l2_standard(&l_vstd, &vstd); error = fget(td, args->fd, cap_rights_init(&rights, CAP_IOCTL), &fp); if (error) return (error); error = fo_ioctl(fp, VIDIOC_ENUMSTD, (caddr_t)&vstd, td->td_ucred, td); if (error) { fdrop(fp, td); return (error); } bsd_to_linux_v4l2_standard(&vstd, &l_vstd); error = copyout(&l_vstd, (void *)args->arg, sizeof(l_vstd)); fdrop(fp, td); return (error); case LINUX_VIDIOC_ENUMINPUT: /* * The Linux struct l_v4l2_input differs only in size, * it has no padding at the end. */ error = copyin((void *)args->arg, &vinp, sizeof(struct l_v4l2_input)); if (error != 0) return (error); error = fget(td, args->fd, cap_rights_init(&rights, CAP_IOCTL), &fp); if (error != 0) return (error); error = fo_ioctl(fp, VIDIOC_ENUMINPUT, (caddr_t)&vinp, td->td_ucred, td); if (error) { fdrop(fp, td); return (error); } error = copyout(&vinp, (void *)args->arg, sizeof(struct l_v4l2_input)); fdrop(fp, td); return (error); case LINUX_VIDIOC_QUERYBUF: case LINUX_VIDIOC_QBUF: case LINUX_VIDIOC_DQBUF: error = copyin((void *)args->arg, &l_vbuf, sizeof(l_vbuf)); if (error) return (error); error = fget(td, args->fd, cap_rights_init(&rights, CAP_IOCTL), &fp); if (error) return (error); linux_to_bsd_v4l2_buffer(&l_vbuf, &vbuf); if ((args->cmd & 0xffff) == LINUX_VIDIOC_QUERYBUF) error = fo_ioctl(fp, VIDIOC_QUERYBUF, &vbuf, td->td_ucred, td); else if ((args->cmd & 0xffff) == LINUX_VIDIOC_QBUF) error = fo_ioctl(fp, VIDIOC_QBUF, &vbuf, td->td_ucred, td); else error = fo_ioctl(fp, VIDIOC_DQBUF, &vbuf, td->td_ucred, td); bsd_to_linux_v4l2_buffer(&vbuf, &l_vbuf); copyout(&l_vbuf, (void *)args->arg, sizeof(l_vbuf)); fdrop(fp, td); return (error); /* * XXX TODO - these need 32 -> 64 bit conversion: * (are any of them needed for webcams?) */ case LINUX_VIDIOC_G_FBUF: case LINUX_VIDIOC_S_FBUF: case LINUX_VIDIOC_G_EXT_CTRLS: case LINUX_VIDIOC_S_EXT_CTRLS: case LINUX_VIDIOC_TRY_EXT_CTRLS: case LINUX_VIDIOC_DQEVENT: default: return (ENOIOCTL); } error = sys_ioctl(td, (struct ioctl_args *)args); return (error); } /* * Support for emulators/linux-libusb. This port uses FBSD_LUSB* macros * instead of USB* ones. This lets us to provide correct values for cmd. * 0xffffffe0 -- 0xffffffff range seemed to be the least collision-prone. */ static int linux_ioctl_fbsd_usb(struct thread *td, struct linux_ioctl_args *args) { int error; error = 0; switch (args->cmd) { case FBSD_LUSB_DEVICEENUMERATE: args->cmd = USB_DEVICEENUMERATE; break; case FBSD_LUSB_DEV_QUIRK_ADD: args->cmd = USB_DEV_QUIRK_ADD; break; case FBSD_LUSB_DEV_QUIRK_GET: args->cmd = USB_DEV_QUIRK_GET; break; case FBSD_LUSB_DEV_QUIRK_REMOVE: args->cmd = USB_DEV_QUIRK_REMOVE; break; case FBSD_LUSB_DO_REQUEST: args->cmd = USB_DO_REQUEST; break; case FBSD_LUSB_FS_CLEAR_STALL_SYNC: args->cmd = USB_FS_CLEAR_STALL_SYNC; break; case FBSD_LUSB_FS_CLOSE: args->cmd = USB_FS_CLOSE; break; case FBSD_LUSB_FS_COMPLETE: args->cmd = USB_FS_COMPLETE; break; case FBSD_LUSB_FS_INIT: args->cmd = USB_FS_INIT; break; case FBSD_LUSB_FS_OPEN: args->cmd = USB_FS_OPEN; break; case FBSD_LUSB_FS_START: args->cmd = USB_FS_START; break; case FBSD_LUSB_FS_STOP: args->cmd = USB_FS_STOP; break; case FBSD_LUSB_FS_UNINIT: args->cmd = USB_FS_UNINIT; break; case FBSD_LUSB_GET_CONFIG: args->cmd = USB_GET_CONFIG; break; case FBSD_LUSB_GET_DEVICEINFO: args->cmd = USB_GET_DEVICEINFO; break; case FBSD_LUSB_GET_DEVICE_DESC: args->cmd = USB_GET_DEVICE_DESC; break; case FBSD_LUSB_GET_FULL_DESC: args->cmd = USB_GET_FULL_DESC; break; case FBSD_LUSB_GET_IFACE_DRIVER: args->cmd = USB_GET_IFACE_DRIVER; break; case FBSD_LUSB_GET_PLUGTIME: args->cmd = USB_GET_PLUGTIME; break; case FBSD_LUSB_GET_POWER_MODE: args->cmd = USB_GET_POWER_MODE; break; case FBSD_LUSB_GET_REPORT_DESC: args->cmd = USB_GET_REPORT_DESC; break; case FBSD_LUSB_GET_REPORT_ID: args->cmd = USB_GET_REPORT_ID; break; case FBSD_LUSB_GET_TEMPLATE: args->cmd = USB_GET_TEMPLATE; break; case FBSD_LUSB_IFACE_DRIVER_ACTIVE: args->cmd = USB_IFACE_DRIVER_ACTIVE; break; case FBSD_LUSB_IFACE_DRIVER_DETACH: args->cmd = USB_IFACE_DRIVER_DETACH; break; case FBSD_LUSB_QUIRK_NAME_GET: args->cmd = USB_QUIRK_NAME_GET; break; case FBSD_LUSB_READ_DIR: args->cmd = USB_READ_DIR; break; case FBSD_LUSB_SET_ALTINTERFACE: args->cmd = USB_SET_ALTINTERFACE; break; case FBSD_LUSB_SET_CONFIG: args->cmd = USB_SET_CONFIG; break; case FBSD_LUSB_SET_IMMED: args->cmd = USB_SET_IMMED; break; case FBSD_LUSB_SET_POWER_MODE: args->cmd = USB_SET_POWER_MODE; break; case FBSD_LUSB_SET_TEMPLATE: args->cmd = USB_SET_TEMPLATE; break; case FBSD_LUSB_FS_OPEN_STREAM: args->cmd = USB_FS_OPEN_STREAM; break; case FBSD_LUSB_GET_DEV_PORT_PATH: args->cmd = USB_GET_DEV_PORT_PATH; break; case FBSD_LUSB_GET_POWER_USAGE: args->cmd = USB_GET_POWER_USAGE; break; default: error = ENOIOCTL; } if (error != ENOIOCTL) error = sys_ioctl(td, (struct ioctl_args *)args); return (error); } /* * Some evdev ioctls must be translated. * - EVIOCGMTSLOTS is a IOC_READ ioctl on Linux although it has input data * (must be IOC_INOUT on FreeBSD). * - On Linux, EVIOCGRAB, EVIOCREVOKE and EVIOCRMFF are defined as _IOW with * an int argument. You don't pass an int pointer to the ioctl(), however, * but just the int directly. On FreeBSD, they are defined as _IOWINT for * this to work. */ static int linux_ioctl_evdev(struct thread *td, struct linux_ioctl_args *args) { cap_rights_t rights; struct file *fp; clockid_t clock; int error; args->cmd = SETDIR(args->cmd); switch (args->cmd) { case (EVIOCGRAB & ~IOC_DIRMASK) | IOC_IN: args->cmd = EVIOCGRAB; break; case (EVIOCREVOKE & ~IOC_DIRMASK) | IOC_IN: args->cmd = EVIOCREVOKE; break; case (EVIOCRMFF & ~IOC_DIRMASK) | IOC_IN: args->cmd = EVIOCRMFF; break; case EVIOCSCLOCKID: { error = copyin(PTRIN(args->arg), &clock, sizeof(clock)); if (error != 0) return (error); if (clock & ~(LINUX_IOCTL_EVDEV_CLK)) return (EINVAL); error = linux_to_native_clockid(&clock, clock); if (error != 0) return (error); error = fget(td, args->fd, cap_rights_init(&rights, CAP_IOCTL), &fp); if (error != 0) return (error); error = fo_ioctl(fp, EVIOCSCLOCKID, &clock, td->td_ucred, td); fdrop(fp, td); return (error); } default: break; } if (IOCBASECMD(args->cmd) == ((EVIOCGMTSLOTS(0) & ~IOC_DIRMASK) | IOC_OUT)) args->cmd = (args->cmd & ~IOC_DIRMASK) | IOC_INOUT; return (sys_ioctl(td, (struct ioctl_args *)args)); } /* * main ioctl syscall function */ int linux_ioctl(struct thread *td, struct linux_ioctl_args *args) { cap_rights_t rights; struct file *fp; struct handler_element *he; int error, cmd; #ifdef DEBUG if (ldebug(ioctl)) printf(ARGS(ioctl, "%d, %04lx, *"), args->fd, (unsigned long)args->cmd); #endif error = fget(td, args->fd, cap_rights_init(&rights, CAP_IOCTL), &fp); if (error != 0) return (error); if ((fp->f_flag & (FREAD|FWRITE)) == 0) { fdrop(fp, td); return (EBADF); } /* Iterate over the ioctl handlers */ cmd = args->cmd & 0xffff; sx_slock(&linux_ioctl_sx); mtx_lock(&Giant); TAILQ_FOREACH(he, &handlers, list) { if (cmd >= he->low && cmd <= he->high) { error = (*he->func)(td, args); if (error != ENOIOCTL) { mtx_unlock(&Giant); sx_sunlock(&linux_ioctl_sx); fdrop(fp, td); return (error); } } } mtx_unlock(&Giant); sx_sunlock(&linux_ioctl_sx); fdrop(fp, td); switch (args->cmd & 0xffff) { case LINUX_BTRFS_IOC_CLONE: return (ENOTSUP); default: linux_msg(td, "ioctl fd=%d, cmd=0x%x ('%c',%d) is not implemented", args->fd, (int)(args->cmd & 0xffff), (int)(args->cmd & 0xff00) >> 8, (int)(args->cmd & 0xff)); break; } return (EINVAL); } int linux_ioctl_register_handler(struct linux_ioctl_handler *h) { struct handler_element *he, *cur; if (h == NULL || h->func == NULL) return (EINVAL); /* * Reuse the element if the handler is already on the list, otherwise * create a new element. */ sx_xlock(&linux_ioctl_sx); TAILQ_FOREACH(he, &handlers, list) { if (he->func == h->func) break; } if (he == NULL) { he = malloc(sizeof(*he), M_LINUX, M_WAITOK); he->func = h->func; } else TAILQ_REMOVE(&handlers, he, list); /* Initialize range information. */ he->low = h->low; he->high = h->high; he->span = h->high - h->low + 1; /* Add the element to the list, sorted on span. */ TAILQ_FOREACH(cur, &handlers, list) { if (cur->span > he->span) { TAILQ_INSERT_BEFORE(cur, he, list); sx_xunlock(&linux_ioctl_sx); return (0); } } TAILQ_INSERT_TAIL(&handlers, he, list); sx_xunlock(&linux_ioctl_sx); return (0); } int linux_ioctl_unregister_handler(struct linux_ioctl_handler *h) { struct handler_element *he; if (h == NULL || h->func == NULL) return (EINVAL); sx_xlock(&linux_ioctl_sx); TAILQ_FOREACH(he, &handlers, list) { if (he->func == h->func) { TAILQ_REMOVE(&handlers, he, list); sx_xunlock(&linux_ioctl_sx); free(he, M_LINUX); return (0); } } sx_xunlock(&linux_ioctl_sx); return (EINVAL); } Index: releng/11.2/sys/dev/mcd/mcd.c =================================================================== --- releng/11.2/sys/dev/mcd/mcd.c (revision 349624) +++ releng/11.2/sys/dev/mcd/mcd.c (revision 349625) @@ -1,1654 +1,1648 @@ /*- * Copyright 1993 by Holger Veit (data part) * Copyright 1993 by Brian Moore (audio part) * Changes Copyright 1993 by Gary Clark II * Changes Copyright (C) 1994-1995 by Andrey A. Chernov, Moscow, Russia * * Rewrote probe routine to work on newer Mitsumi drives. * Additional changes (C) 1994 by Jordan K. Hubbard * * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This software was developed by Holger Veit and Brian Moore * for use with "386BSD" and similar operating systems. * "Similar operating systems" includes mainly non-profit oriented * systems for research and education, including but not restricted to * "NetBSD", "FreeBSD", "Mach" (by CMU). * 4. Neither the name of the developer(s) nor the name "386BSD" * may be used to endorse or promote products derived from this * software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE DEVELOPER(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 DEVELOPER(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$"); static const char __used COPYRIGHT[] = "mcd-driver (C)1993 by H.Veit & B.Moore"; #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define MCD_TRACE(format, args...) \ { \ if (sc->debug) { \ device_printf(sc->dev, "status=0x%02x: ", \ sc->data.status); \ printf(format, ## args); \ } \ } #define RAW_PART 2 /* flags */ #define MCDVALID 0x0001 /* parameters loaded */ #define MCDINIT 0x0002 /* device is init'd */ #define MCDNEWMODEL 0x0004 /* device is new model */ #define MCDLABEL 0x0008 /* label is read */ #define MCDPROBING 0x0010 /* probing */ #define MCDREADRAW 0x0020 /* read raw mode (2352 bytes) */ #define MCDVOLINFO 0x0040 /* already read volinfo */ #define MCDTOC 0x0080 /* already read toc */ #define MCDMBXBSY 0x0100 /* local mbx is busy */ /* status */ #define MCDAUDIOBSY MCD_ST_AUDIOBSY /* playing audio */ #define MCDDSKCHNG MCD_ST_DSKCHNG /* sensed change of disk */ #define MCDDSKIN MCD_ST_DSKIN /* sensed disk in drive */ #define MCDDOOROPEN MCD_ST_DOOROPEN /* sensed door open */ /* These are apparently the different states a mitsumi can get up to */ #define MCDCDABSENT 0x0030 #define MCDCDPRESENT 0x0020 #define MCDSCLOSED 0x0080 #define MCDSOPEN 0x00a0 #define MCD_MD_UNKNOWN (-1) #define MCD_TYPE_UNKNOWN 0 #define MCD_TYPE_LU002S 1 #define MCD_TYPE_LU005S 2 #define MCD_TYPE_LU006S 3 #define MCD_TYPE_FX001 4 #define MCD_TYPE_FX001D 5 /* reader state machine */ #define MCD_S_BEGIN 0 #define MCD_S_BEGIN1 1 #define MCD_S_WAITSTAT 2 #define MCD_S_WAITMODE 3 #define MCD_S_WAITREAD 4 /* prototypes */ static void mcd_start(struct mcd_softc *); #ifdef NOTYET static void mcd_configure(struct mcd_softc *sc); #endif static int mcd_get(struct mcd_softc *, char *buf, int nmax); static int mcd_setflags(struct mcd_softc *); static int mcd_getstat(struct mcd_softc *,int sflg); static int mcd_send(struct mcd_softc *, int cmd,int nretrys); static void hsg2msf(int hsg, bcd_t *msf); static int msf2hsg(bcd_t *msf, int relative); static int mcd_volinfo(struct mcd_softc *); static int mcd_waitrdy(struct mcd_softc *,int dly); static void mcd_timeout(void *arg); static void mcd_doread(struct mcd_softc *, int state, struct mcd_mbx *mbxin); static void mcd_soft_reset(struct mcd_softc *); static int mcd_hard_reset(struct mcd_softc *); static int mcd_setmode(struct mcd_softc *, int mode); static int mcd_getqchan(struct mcd_softc *, struct mcd_qchninfo *q); -static int mcd_subchan(struct mcd_softc *, struct ioc_read_subchannel *sc, - int nocopyout); +static int mcd_subchan(struct mcd_softc *, struct ioc_read_subchannel *sc); static int mcd_toc_header(struct mcd_softc *, struct ioc_toc_header *th); static int mcd_read_toc(struct mcd_softc *); static int mcd_toc_entrys(struct mcd_softc *, struct ioc_read_toc_entry *te); #if 0 static int mcd_toc_entry(struct mcd_softc *, struct ioc_read_toc_single_entry *te); #endif static int mcd_stop(struct mcd_softc *); static int mcd_eject(struct mcd_softc *); static int mcd_inject(struct mcd_softc *); static int mcd_playtracks(struct mcd_softc *, struct ioc_play_track *pt); static int mcd_play(struct mcd_softc *, struct mcd_read2 *pb); static int mcd_playmsf(struct mcd_softc *, struct ioc_play_msf *pt); static int mcd_playblocks(struct mcd_softc *, struct ioc_play_blocks *); static int mcd_pause(struct mcd_softc *); static int mcd_resume(struct mcd_softc *); static int mcd_lock_door(struct mcd_softc *, int lock); static int mcd_close_tray(struct mcd_softc *); static int mcd_size(struct cdev *dev); static d_open_t mcdopen; static d_close_t mcdclose; static d_ioctl_t mcdioctl; static d_strategy_t mcdstrategy; static struct cdevsw mcd_cdevsw = { .d_version = D_VERSION, .d_open = mcdopen, .d_close = mcdclose, .d_read = physread, .d_ioctl = mcdioctl, .d_strategy = mcdstrategy, .d_name = "mcd", .d_flags = D_DISK, }; #define MCD_RETRYS 5 #define MCD_RDRETRYS 8 #define CLOSE_TRAY_SECS 8 #define DISK_SENSE_SECS 3 #define WAIT_FRAC 4 /* several delays */ #define RDELAY_WAITSTAT 300 #define RDELAY_WAITMODE 300 #define RDELAY_WAITREAD 800 #define MIN_DELAY 15 #define DELAY_GETREPLY 5000000 int mcd_attach(struct mcd_softc *sc) { int unit; unit = device_get_unit(sc->dev); MCD_LOCK(sc); sc->data.flags |= MCDINIT; mcd_soft_reset(sc); bioq_init(&sc->data.head); #ifdef NOTYET /* wire controller for interrupts and dma */ mcd_configure(sc); #endif MCD_UNLOCK(sc); /* name filled in probe */ sc->mcd_dev_t = make_dev(&mcd_cdevsw, 8 * unit, UID_ROOT, GID_OPERATOR, 0640, "mcd%d", unit); sc->mcd_dev_t->si_drv1 = (void *)sc; callout_init_mtx(&sc->timer, &sc->mtx, 0); device_printf(sc->dev, "WARNING: This driver is deprecated and will be removed.\n"); return (0); } static int mcdopen(struct cdev *dev, int flags, int fmt, struct thread *td) { struct mcd_softc *sc; int r,retry; sc = (struct mcd_softc *)dev->si_drv1; /* invalidated in the meantime? mark all open part's invalid */ MCD_LOCK(sc); if (!(sc->data.flags & MCDVALID) && sc->data.openflags) { MCD_UNLOCK(sc); return (ENXIO); } if (mcd_getstat(sc, 1) == -1) { MCD_UNLOCK(sc); return (EIO); } if ( (sc->data.status & (MCDDSKCHNG|MCDDOOROPEN)) || !(sc->data.status & MCDDSKIN)) for (retry = 0; retry < DISK_SENSE_SECS * WAIT_FRAC; retry++) { (void) mtx_sleep(sc, &sc->mtx, PSOCK | PCATCH, "mcdsn1", hz/WAIT_FRAC); if ((r = mcd_getstat(sc, 1)) == -1) { MCD_UNLOCK(sc); return (EIO); } if (r != -2) break; } if (sc->data.status & MCDDOOROPEN) { MCD_UNLOCK(sc); device_printf(sc->dev, "door is open\n"); return (ENXIO); } if (!(sc->data.status & MCDDSKIN)) { MCD_UNLOCK(sc); device_printf(sc->dev, "no CD inside\n"); return (ENXIO); } if (sc->data.status & MCDDSKCHNG) { MCD_UNLOCK(sc); device_printf(sc->dev, "CD not sensed\n"); return (ENXIO); } if (mcd_size(dev) < 0) { MCD_UNLOCK(sc); device_printf(sc->dev, "failed to get disk size\n"); return (ENXIO); } sc->data.openflags = 1; sc->data.partflags |= MCDREADRAW; sc->data.flags |= MCDVALID; (void) mcd_lock_door(sc, MCD_LK_LOCK); if (!(sc->data.flags & MCDVALID)) { MCD_UNLOCK(sc); return (ENXIO); } r = mcd_read_toc(sc); MCD_UNLOCK(sc); return (r); } static int mcdclose(struct cdev *dev, int flags, int fmt, struct thread *td) { struct mcd_softc *sc; sc = (struct mcd_softc *)dev->si_drv1; MCD_LOCK(sc); KASSERT(sc->data.openflags, ("device not open")); (void) mcd_lock_door(sc, MCD_LK_UNLOCK); sc->data.openflags = 0; sc->data.partflags &= ~MCDREADRAW; MCD_UNLOCK(sc); return (0); } static void mcdstrategy(struct bio *bp) { struct mcd_softc *sc; sc = (struct mcd_softc *)bp->bio_dev->si_drv1; /* if device invalidated (e.g. media change, door open), error */ MCD_LOCK(sc); if (!(sc->data.flags & MCDVALID)) { device_printf(sc->dev, "media changed\n"); bp->bio_error = EIO; goto bad; } /* read only */ if (!(bp->bio_cmd == BIO_READ)) { bp->bio_error = EROFS; goto bad; } /* no data to read */ if (bp->bio_bcount == 0) goto done; if (!(sc->data.flags & MCDTOC)) { bp->bio_error = EIO; goto bad; } bp->bio_resid = 0; /* queue it */ bioq_disksort(&sc->data.head, bp); /* now check whether we can perform processing */ mcd_start(sc); MCD_UNLOCK(sc); return; bad: bp->bio_flags |= BIO_ERROR; done: MCD_UNLOCK(sc); bp->bio_resid = bp->bio_bcount; biodone(bp); return; } static void mcd_start(struct mcd_softc *sc) { struct bio *bp; MCD_ASSERT_LOCKED(sc); if (sc->data.flags & MCDMBXBSY) { return; } bp = bioq_takefirst(&sc->data.head); if (bp != 0) { /* block found to process, dequeue */ /*MCD_TRACE("mcd_start: found block bp=0x%x\n",bp,0,0,0);*/ sc->data.flags |= MCDMBXBSY; } else { /* nothing to do */ return; } sc->data.mbx.retry = MCD_RETRYS; sc->data.mbx.bp = bp; mcd_doread(sc, MCD_S_BEGIN,&(sc->data.mbx)); return; } static int mcdioctl(struct cdev *dev, u_long cmd, caddr_t addr, int flags, struct thread *td) { struct mcd_softc *sc; int retry,r; sc = (struct mcd_softc *)dev->si_drv1; MCD_LOCK(sc); if (mcd_getstat(sc, 1) == -1) { /* detect disk change too */ MCD_UNLOCK(sc); return (EIO); } MCD_TRACE("ioctl called 0x%lx\n", cmd); switch (cmd) { case CDIOCSETPATCH: case CDIOCGETVOL: case CDIOCSETVOL: case CDIOCSETMONO: case CDIOCSETSTERIO: case CDIOCSETMUTE: case CDIOCSETLEFT: case CDIOCSETRIGHT: MCD_UNLOCK(sc); return (EINVAL); case CDIOCEJECT: r = mcd_eject(sc); MCD_UNLOCK(sc); return (r); case CDIOCSETDEBUG: sc->data.debug = 1; MCD_UNLOCK(sc); return (0); case CDIOCCLRDEBUG: sc->data.debug = 0; MCD_UNLOCK(sc); return (0); case CDIOCRESET: r = mcd_hard_reset(sc); MCD_UNLOCK(sc); return (r); case CDIOCALLOW: r = mcd_lock_door(sc, MCD_LK_UNLOCK); MCD_UNLOCK(sc); return (r); case CDIOCPREVENT: r = mcd_lock_door(sc, MCD_LK_LOCK); MCD_UNLOCK(sc); return (r); case CDIOCCLOSE: r = mcd_inject(sc); MCD_UNLOCK(sc); return (r); } if (!(sc->data.flags & MCDVALID)) { if ( (sc->data.status & (MCDDSKCHNG|MCDDOOROPEN)) || !(sc->data.status & MCDDSKIN)) for (retry = 0; retry < DISK_SENSE_SECS * WAIT_FRAC; retry++) { (void) mtx_sleep(sc, &sc->mtx, PSOCK | PCATCH, "mcdsn2", hz/WAIT_FRAC); if ((r = mcd_getstat(sc, 1)) == -1) { MCD_UNLOCK(sc); return (EIO); } if (r != -2) break; } if ( (sc->data.status & (MCDDOOROPEN|MCDDSKCHNG)) || !(sc->data.status & MCDDSKIN) || mcd_size(dev) < 0 ) { MCD_UNLOCK(sc); return (ENXIO); } sc->data.flags |= MCDVALID; sc->data.partflags |= MCDREADRAW; (void) mcd_lock_door(sc, MCD_LK_LOCK); if (!(sc->data.flags & MCDVALID)) { MCD_UNLOCK(sc); return (ENXIO); } } switch (cmd) { case DIOCGMEDIASIZE: *(off_t *)addr = (off_t)sc->data.disksize * sc->data.blksize; r = 0; break; case DIOCGSECTORSIZE: *(u_int *)addr = sc->data.blksize; r = 0; break; case CDIOCPLAYTRACKS: r = mcd_playtracks(sc, (struct ioc_play_track *) addr); break; case CDIOCPLAYBLOCKS: r = mcd_playblocks(sc, (struct ioc_play_blocks *) addr); break; case CDIOCPLAYMSF: r = mcd_playmsf(sc, (struct ioc_play_msf *) addr); break; - case CDIOCREADSUBCHANNEL_SYSSPACE: - return mcd_subchan(sc, (struct ioc_read_subchannel *) addr, 1); case CDIOCREADSUBCHANNEL: - return mcd_subchan(sc, (struct ioc_read_subchannel *) addr, 0); + return mcd_subchan(sc, (struct ioc_read_subchannel *) addr); case CDIOREADTOCHEADER: r = mcd_toc_header(sc, (struct ioc_toc_header *) addr); break; case CDIOREADTOCENTRYS: return mcd_toc_entrys(sc, (struct ioc_read_toc_entry *) addr); case CDIOCRESUME: r = mcd_resume(sc); break; case CDIOCPAUSE: r = mcd_pause(sc); break; case CDIOCSTART: if (mcd_setmode(sc, MCD_MD_COOKED) != 0) r = EIO; else r = 0; break; case CDIOCSTOP: r = mcd_stop(sc); break; default: r = ENOTTY; } MCD_UNLOCK(sc); return (r); } static int mcd_size(struct cdev *dev) { struct mcd_softc *sc; int size; sc = (struct mcd_softc *)dev->si_drv1; if (mcd_volinfo(sc) == 0) { sc->data.blksize = MCDBLK; size = msf2hsg(sc->data.volinfo.vol_msf, 0); sc->data.disksize = size * (MCDBLK/DEV_BSIZE); return (0); } return (-1); } /*************************************************************** * lower level of driver starts here **************************************************************/ #ifdef NOTDEF static char irqs[] = { 0x00,0x00,0x10,0x20,0x00,0x30,0x00,0x00, 0x00,0x10,0x40,0x50,0x00,0x00,0x00,0x00 }; static char drqs[] = { 0x00,0x01,0x00,0x03,0x00,0x05,0x06,0x07, }; #endif #ifdef NOT_YET static void mcd_configure(struct mcd_softc *sc) { MCD_WRITE(sc, MCD_REG_CONFIG, sc->data.config); } #endif /* Wait for non-busy - return 0 on timeout */ static int twiddle_thumbs(struct mcd_softc *sc, int count, char *whine) { int i; for (i = 0; i < count; i++) { if (!(MCD_READ(sc, MCD_FLAGS) & MFL_STATUS_NOT_AVAIL)) return (1); } if (bootverbose) device_printf(sc->dev, "timeout %s\n", whine); return (0); } /* check to see if a Mitsumi CD-ROM is attached to the ISA bus */ int mcd_probe(struct mcd_softc *sc) { int i, j; unsigned char stbytes[3]; sc->data.flags = MCDPROBING; #ifdef NOTDEF /* get irq/drq configuration word */ sc->data.config = irqs[dev->id_irq]; /* | drqs[dev->id_drq];*/ #else sc->data.config = 0; #endif /* send a reset */ MCD_WRITE(sc, MCD_FLAGS, M_RESET); /* * delay awhile by getting any pending garbage (old data) and * throwing it away. */ for (i = 1000000; i != 0; i--) (void)MCD_READ(sc, MCD_FLAGS); /* Get status */ MCD_WRITE(sc, MCD_DATA, MCD_CMDGETSTAT); if (!twiddle_thumbs(sc, 1000000, "getting status")) return (ENXIO); /* Timeout */ /* Get version information */ MCD_WRITE(sc, MCD_DATA, MCD_CMDCONTINFO); for (j = 0; j < 3; j++) { if (!twiddle_thumbs(sc, 3000, "getting version info")) return (ENXIO); stbytes[j] = (MCD_READ(sc, MCD_DATA) & 0xFF); } if (stbytes[1] == stbytes[2]) return (ENXIO); if (stbytes[2] >= 4 || stbytes[1] != 'M') { MCD_WRITE(sc, MCD_CTRL, M_PICKLE); sc->data.flags |= MCDNEWMODEL; } sc->data.read_command = MCD_CMDSINGLESPEEDREAD; switch (stbytes[1]) { case 'M': if (stbytes[2] <= 2) { sc->data.type = MCD_TYPE_LU002S; sc->data.name = "Mitsumi LU002S"; } else if (stbytes[2] <= 5) { sc->data.type = MCD_TYPE_LU005S; sc->data.name = "Mitsumi LU005S"; } else { sc->data.type = MCD_TYPE_LU006S; sc->data.name = "Mitsumi LU006S"; } break; case 'F': sc->data.type = MCD_TYPE_FX001; sc->data.name = "Mitsumi FX001"; break; case 'D': sc->data.type = MCD_TYPE_FX001D; sc->data.name = "Mitsumi FX001D"; sc->data.read_command = MCD_CMDDOUBLESPEEDREAD; break; default: sc->data.type = MCD_TYPE_UNKNOWN; sc->data.name = "Mitsumi ???"; break; } if (bootverbose) device_printf(sc->dev, "type %s, version info: %c %x\n", sc->data.name, stbytes[1], stbytes[2]); return (0); } static int mcd_waitrdy(struct mcd_softc *sc, int dly) { int i; /* wait until flag port senses status ready */ for (i=0; idev, "timeout getreply\n"); return (-1); } /* get the data */ return (MCD_READ(sc, MCD_REG_STATUS) & 0xFF); } static int mcd_getstat(struct mcd_softc *sc, int sflg) { int i; /* get the status */ if (sflg) MCD_WRITE(sc, MCD_REG_COMMAND, MCD_CMDGETSTAT); i = mcd_getreply(sc, DELAY_GETREPLY); if (i<0 || (i & MCD_ST_CMDCHECK)) { sc->data.curr_mode = MCD_MD_UNKNOWN; return (-1); } sc->data.status = i; if (mcd_setflags(sc) < 0) return (-2); return (sc->data.status); } static int mcd_setflags(struct mcd_softc *sc) { /* check flags */ if ( (sc->data.status & (MCDDSKCHNG|MCDDOOROPEN)) || !(sc->data.status & MCDDSKIN)) { MCD_TRACE("setflags: sensed DSKCHNG or DOOROPEN or !DSKIN\n"); mcd_soft_reset(sc); return (-1); } if (sc->data.status & MCDAUDIOBSY) sc->data.audio_status = CD_AS_PLAY_IN_PROGRESS; else if (sc->data.audio_status == CD_AS_PLAY_IN_PROGRESS) sc->data.audio_status = CD_AS_PLAY_COMPLETED; return (0); } static int mcd_get(struct mcd_softc *sc, char *buf, int nmax) { int i,k; for (i=0; idev, "timeout mcd_get\n"); return (-1); } buf[i] = k; } return (i); } static int mcd_send(struct mcd_softc *sc, int cmd,int nretrys) { int i,k=0; /*MCD_TRACE("mcd_send: command = 0x%02x\n",cmd,0,0,0);*/ for (i=0; idev, "media changed\n"); return (-1); } if (i == nretrys) { device_printf(sc->dev, "mcd_send retry cnt exceeded\n"); return (-1); } /*MCD_TRACE("mcd_send: done\n",0,0,0,0);*/ return (0); } static void hsg2msf(int hsg, bcd_t *msf) { hsg += 150; F_msf(msf) = bin2bcd(hsg % 75); hsg /= 75; S_msf(msf) = bin2bcd(hsg % 60); hsg /= 60; M_msf(msf) = bin2bcd(hsg); } static int msf2hsg(bcd_t *msf, int relative) { return (bcd2bin(M_msf(msf)) * 60 + bcd2bin(S_msf(msf))) * 75 + bcd2bin(F_msf(msf)) - (!relative) * 150; } static int mcd_volinfo(struct mcd_softc *sc) { /* Just return if we already have it */ if (sc->data.flags & MCDVOLINFO) return (0); /*MCD_TRACE("mcd_volinfo: enter\n",0,0,0,0);*/ /* send volume info command */ if (mcd_send(sc, MCD_CMDGETVOLINFO,MCD_RETRYS) < 0) return (EIO); /* get data */ if (mcd_get(sc, (char*) &sc->data.volinfo,sizeof(struct mcd_volinfo)) < 0) { device_printf(sc->dev, "mcd_volinfo: error read data\n"); return (EIO); } if (sc->data.volinfo.trk_low > 0 && sc->data.volinfo.trk_high >= sc->data.volinfo.trk_low ) { sc->data.flags |= MCDVOLINFO; /* volinfo is OK */ return (0); } return (EINVAL); } /* state machine to process read requests * initialize with MCD_S_BEGIN: calculate sizes, and read status * MCD_S_WAITSTAT: wait for status reply, set mode * MCD_S_WAITMODE: waits for status reply from set mode, set read command * MCD_S_WAITREAD: wait for read ready, read data */ static void mcd_timeout(void *arg) { struct mcd_softc *sc; sc = (struct mcd_softc *)arg; MCD_ASSERT_LOCKED(sc); mcd_doread(sc, sc->ch_state, sc->ch_mbxsave); } static void mcd_doread(struct mcd_softc *sc, int state, struct mcd_mbx *mbxin) { struct mcd_mbx *mbx; struct bio *bp; int rm, i, k; struct mcd_read2 rbuf; int blknum; caddr_t addr; MCD_ASSERT_LOCKED(sc); mbx = (state!=MCD_S_BEGIN) ? sc->ch_mbxsave : mbxin; bp = mbx->bp; loop: switch (state) { case MCD_S_BEGIN: mbx = sc->ch_mbxsave = mbxin; case MCD_S_BEGIN1: retry_status: /* get status */ MCD_WRITE(sc, MCD_REG_COMMAND, MCD_CMDGETSTAT); mbx->count = RDELAY_WAITSTAT; sc->ch_state = MCD_S_WAITSTAT; callout_reset(&sc->timer, hz/100, mcd_timeout, sc); /* XXX */ return; case MCD_S_WAITSTAT: sc->ch_state = MCD_S_WAITSTAT; callout_stop(&sc->timer); if (mbx->count-- >= 0) { if (MCD_READ(sc, MCD_FLAGS) & MFL_STATUS_NOT_AVAIL) { sc->ch_state = MCD_S_WAITSTAT; callout_reset(&sc->timer, hz/100, mcd_timeout, sc); /* XXX */ return; } sc->data.status = MCD_READ(sc, MCD_REG_STATUS) & 0xFF; if (sc->data.status & MCD_ST_CMDCHECK) goto retry_status; if (mcd_setflags(sc) < 0) goto changed; MCD_TRACE("got WAITSTAT delay=%d\n", RDELAY_WAITSTAT-mbx->count); /* reject, if audio active */ if (sc->data.status & MCDAUDIOBSY) { device_printf(sc->dev, "audio is active\n"); goto readerr; } retry_mode: /* to check for raw/cooked mode */ if (sc->data.flags & MCDREADRAW) { rm = MCD_MD_RAW; mbx->sz = MCDRBLK; } else { rm = MCD_MD_COOKED; mbx->sz = sc->data.blksize; } if (rm == sc->data.curr_mode) goto modedone; mbx->count = RDELAY_WAITMODE; sc->data.curr_mode = MCD_MD_UNKNOWN; mbx->mode = rm; MCD_WRITE(sc, MCD_REG_COMMAND, MCD_CMDSETMODE); MCD_WRITE(sc, MCD_REG_COMMAND, rm); sc->ch_state = MCD_S_WAITMODE; callout_reset(&sc->timer, hz / 100, mcd_timeout, sc); /* XXX */ return; } else { device_printf(sc->dev, "timeout getstatus\n"); goto readerr; } case MCD_S_WAITMODE: sc->ch_state = MCD_S_WAITMODE; callout_stop(&sc->timer); if (mbx->count-- < 0) { device_printf(sc->dev, "timeout set mode\n"); goto readerr; } if (MCD_READ(sc, MCD_FLAGS) & MFL_STATUS_NOT_AVAIL) { sc->ch_state = MCD_S_WAITMODE; callout_reset(&sc->timer, hz / 100, mcd_timeout, sc); return; } sc->data.status = MCD_READ(sc, MCD_REG_STATUS) & 0xFF; if (sc->data.status & MCD_ST_CMDCHECK) { sc->data.curr_mode = MCD_MD_UNKNOWN; goto retry_mode; } if (mcd_setflags(sc) < 0) goto changed; sc->data.curr_mode = mbx->mode; MCD_TRACE("got WAITMODE delay=%d\n", RDELAY_WAITMODE-mbx->count); modedone: /* for first block */ mbx->nblk = howmany(bp->bio_bcount, mbx->sz); mbx->skip = 0; nextblock: blknum = bp->bio_offset / mbx->sz + mbx->skip/mbx->sz; MCD_TRACE("mcd_doread: read blknum=%d for bp=%p\n", blknum, bp); /* build parameter block */ hsg2msf(blknum,rbuf.start_msf); retry_read: /* send the read command */ MCD_WRITE(sc, MCD_REG_COMMAND, sc->data.read_command); MCD_WRITE(sc, MCD_REG_COMMAND, rbuf.start_msf[0]); MCD_WRITE(sc, MCD_REG_COMMAND, rbuf.start_msf[1]); MCD_WRITE(sc, MCD_REG_COMMAND, rbuf.start_msf[2]); MCD_WRITE(sc, MCD_REG_COMMAND, 0); MCD_WRITE(sc, MCD_REG_COMMAND, 0); MCD_WRITE(sc, MCD_REG_COMMAND, 1); /* Spin briefly (<= 2ms) to avoid missing next block */ for (i = 0; i < 20; i++) { k = MCD_READ(sc, MCD_FLAGS); if (!(k & MFL_DATA_NOT_AVAIL)) goto got_it; DELAY(100); } mbx->count = RDELAY_WAITREAD; sc->ch_state = MCD_S_WAITREAD; callout_reset(&sc->timer, hz / 100, mcd_timeout, sc); /* XXX */ return; case MCD_S_WAITREAD: sc->ch_state = MCD_S_WAITREAD; callout_stop(&sc->timer); if (mbx->count-- > 0) { k = MCD_READ(sc, MCD_FLAGS); if (!(k & MFL_DATA_NOT_AVAIL)) { /* XXX */ MCD_TRACE("got data delay=%d\n", RDELAY_WAITREAD-mbx->count); got_it: /* data is ready */ addr = bp->bio_data + mbx->skip; MCD_WRITE(sc, MCD_REG_CTL2,0x04); /* XXX */ for (i=0; isz; i++) *addr++ = MCD_READ(sc, MCD_REG_RDATA); MCD_WRITE(sc, MCD_REG_CTL2,0x0c); /* XXX */ k = MCD_READ(sc, MCD_FLAGS); /* If we still have some junk, read it too */ if (!(k & MFL_DATA_NOT_AVAIL)) { MCD_WRITE(sc, MCD_REG_CTL2, 0x04); /* XXX */ (void)MCD_READ(sc, MCD_REG_RDATA); (void)MCD_READ(sc, MCD_REG_RDATA); MCD_WRITE(sc, MCD_REG_CTL2, 0x0c); /* XXX */ } if (--mbx->nblk > 0) { mbx->skip += mbx->sz; goto nextblock; } /* return buffer */ bp->bio_resid = 0; biodone(bp); sc->data.flags &= ~(MCDMBXBSY|MCDREADRAW); mcd_start(sc); return; } if (!(k & MFL_STATUS_NOT_AVAIL)) { sc->data.status = MCD_READ(sc, MCD_REG_STATUS) & 0xFF; if (sc->data.status & MCD_ST_CMDCHECK) goto retry_read; if (mcd_setflags(sc) < 0) goto changed; } sc->ch_state = MCD_S_WAITREAD; callout_reset(&sc->timer, hz / 100, mcd_timeout, sc); /* XXX */ return; } else { device_printf(sc->dev, "timeout read data\n"); goto readerr; } } readerr: if (mbx->retry-- > 0) { device_printf(sc->dev, "retrying\n"); state = MCD_S_BEGIN1; goto loop; } harderr: /* invalidate the buffer */ bp->bio_flags |= BIO_ERROR; bp->bio_resid = bp->bio_bcount; biodone(bp); sc->data.flags &= ~(MCDMBXBSY|MCDREADRAW); mcd_start(sc); return; changed: device_printf(sc->dev, "media changed\n"); goto harderr; #ifdef NOTDEF device_printf(sc->dev, "unit timeout, resetting\n"); MCD_WRITE(sc, MCD_REG_RESET, MCD_CMDRESET); DELAY(300000); (void)mcd_getstat(sc, 1); (void)mcd_getstat(sc, 1); /*sc->data.status &= ~MCDDSKCHNG; */ sc->data.debug = 1; /* preventive set debug mode */ #endif } static int mcd_lock_door(struct mcd_softc *sc, int lock) { MCD_WRITE(sc, MCD_REG_COMMAND, MCD_CMDLOCKDRV); MCD_WRITE(sc, MCD_REG_COMMAND, lock); if (mcd_getstat(sc, 0) == -1) return (EIO); return (0); } static int mcd_close_tray(struct mcd_softc *sc) { int retry, r; if (mcd_getstat(sc, 1) == -1) return (EIO); if (sc->data.status & MCDDOOROPEN) { MCD_WRITE(sc, MCD_REG_COMMAND, MCD_CMDCLOSETRAY); for (retry = 0; retry < CLOSE_TRAY_SECS * WAIT_FRAC; retry++) { if (MCD_READ(sc, MCD_FLAGS) & MFL_STATUS_NOT_AVAIL) (void) mtx_sleep(sc, &sc->mtx, PSOCK | PCATCH, "mcdcls", hz/WAIT_FRAC); else { if ((r = mcd_getstat(sc, 0)) == -1) return (EIO); return (0); } } return (ENXIO); } return (0); } static int mcd_eject(struct mcd_softc *sc) { int r; if (mcd_getstat(sc, 1) == -1) /* detect disk change too */ return (EIO); if (sc->data.status & MCDDOOROPEN) return (0); if ((r = mcd_stop(sc)) == EIO) return (r); MCD_WRITE(sc, MCD_REG_COMMAND, MCD_CMDEJECTDISK); if (mcd_getstat(sc, 0) == -1) return (EIO); return (0); } static int mcd_inject(struct mcd_softc *sc) { if (mcd_getstat(sc, 1) == -1) /* detect disk change too */ return (EIO); if (sc->data.status & MCDDOOROPEN) return mcd_close_tray(sc); return (0); } static int mcd_hard_reset(struct mcd_softc *sc) { MCD_WRITE(sc, MCD_REG_RESET, MCD_CMDRESET); sc->data.curr_mode = MCD_MD_UNKNOWN; sc->data.audio_status = CD_AS_AUDIO_INVALID; return (0); } static void mcd_soft_reset(struct mcd_softc *sc) { sc->data.flags &= (MCDINIT|MCDPROBING|MCDNEWMODEL); sc->data.curr_mode = MCD_MD_UNKNOWN; sc->data.partflags = 0; sc->data.audio_status = CD_AS_AUDIO_INVALID; } static int mcd_setmode(struct mcd_softc *sc, int mode) { int retry, st; if (sc->data.curr_mode == mode) return (0); if (sc->data.debug) device_printf(sc->dev, "setting mode to %d\n", mode); for(retry=0; retrydata.curr_mode = MCD_MD_UNKNOWN; MCD_WRITE(sc, MCD_REG_COMMAND, MCD_CMDSETMODE); MCD_WRITE(sc, MCD_REG_COMMAND, mode); if ((st = mcd_getstat(sc, 0)) >= 0) { sc->data.curr_mode = mode; return (0); } if (st == -2) { device_printf(sc->dev, "media changed\n"); break; } } return (-1); } static int mcd_toc_header(struct mcd_softc *sc, struct ioc_toc_header *th) { int r; if ((r = mcd_volinfo(sc)) != 0) return (r); th->starting_track = bcd2bin(sc->data.volinfo.trk_low); th->ending_track = bcd2bin(sc->data.volinfo.trk_high); th->len = 2 * sizeof(u_char) /* start & end tracks */ + (th->ending_track + 1 - th->starting_track + 1) * sizeof(struct cd_toc_entry); return (0); } static int mcd_read_toc(struct mcd_softc *sc) { struct ioc_toc_header th; struct mcd_qchninfo q; int rc, trk, idx, retry; /* Only read TOC if needed */ if (sc->data.flags & MCDTOC) return (0); if (sc->data.debug) device_printf(sc->dev, "reading toc header\n"); if ((rc = mcd_toc_header(sc, &th)) != 0) return (rc); if (mcd_send(sc, MCD_CMDSTOPAUDIO, MCD_RETRYS) < 0) return (EIO); if (mcd_setmode(sc, MCD_MD_TOC) != 0) return (EIO); if (sc->data.debug) device_printf(sc->dev, "get_toc reading qchannel info\n"); for(trk=th.starting_track; trk<=th.ending_track; trk++) sc->data.toc[trk].idx_no = 0; trk = th.ending_track - th.starting_track + 1; for(retry=0; retry<600 && trk>0; retry++) { if (mcd_getqchan(sc, &q) < 0) break; idx = bcd2bin(q.idx_no); if (idx>=th.starting_track && idx<=th.ending_track && q.trk_no==0) { if (sc->data.toc[idx].idx_no == 0) { sc->data.toc[idx] = q; trk--; } } } if (mcd_setmode(sc, MCD_MD_COOKED) != 0) return (EIO); if (trk != 0) return (ENXIO); /* add a fake last+1 */ idx = th.ending_track + 1; sc->data.toc[idx].control = sc->data.toc[idx-1].control; sc->data.toc[idx].addr_type = sc->data.toc[idx-1].addr_type; sc->data.toc[idx].trk_no = 0; sc->data.toc[idx].idx_no = MCD_LASTPLUS1; sc->data.toc[idx].hd_pos_msf[0] = sc->data.volinfo.vol_msf[0]; sc->data.toc[idx].hd_pos_msf[1] = sc->data.volinfo.vol_msf[1]; sc->data.toc[idx].hd_pos_msf[2] = sc->data.volinfo.vol_msf[2]; if (sc->data.debug) { int i; for (i = th.starting_track; i <= idx; i++) device_printf(sc->dev, "trk %d idx %d pos %d %d %d\n", i, sc->data.toc[i].idx_no > 0x99 ? sc->data.toc[i].idx_no : bcd2bin(sc->data.toc[i].idx_no), bcd2bin(sc->data.toc[i].hd_pos_msf[0]), bcd2bin(sc->data.toc[i].hd_pos_msf[1]), bcd2bin(sc->data.toc[i].hd_pos_msf[2])); } sc->data.flags |= MCDTOC; return (0); } #if 0 static int mcd_toc_entry(struct mcd_softc *sc, struct ioc_read_toc_single_entry *te) { struct ioc_toc_header th; int rc, trk; if (te->address_format != CD_MSF_FORMAT && te->address_format != CD_LBA_FORMAT) return (EINVAL); /* Copy the toc header */ if ((rc = mcd_toc_header(sc, &th)) != 0) return (rc); /* verify starting track */ trk = te->track; if (trk == 0) trk = th.starting_track; else if (trk == MCD_LASTPLUS1) trk = th.ending_track + 1; else if (trk < th.starting_track || trk > th.ending_track + 1) return (EINVAL); /* Make sure we have a valid toc */ if ((rc=mcd_read_toc(sc)) != 0) return (rc); /* Copy the TOC data. */ if (sc->data.toc[trk].idx_no == 0) return (EIO); te->entry.control = sc->data.toc[trk].control; te->entry.addr_type = sc->data.toc[trk].addr_type; te->entry.track = sc->data.toc[trk].idx_no > 0x99 ? sc->data.toc[trk].idx_no : bcd2bin(sc->data.toc[trk].idx_no); switch (te->address_format) { case CD_MSF_FORMAT: te->entry.addr.msf.unused = 0; te->entry.addr.msf.minute = bcd2bin(sc->data.toc[trk].hd_pos_msf[0]); te->entry.addr.msf.second = bcd2bin(sc->data.toc[trk].hd_pos_msf[1]); te->entry.addr.msf.frame = bcd2bin(sc->data.toc[trk].hd_pos_msf[2]); break; case CD_LBA_FORMAT: te->entry.addr.lba = htonl(msf2hsg(sc->data.toc[trk].hd_pos_msf, 0)); break; } return (0); } #endif static int mcd_toc_entrys(struct mcd_softc *sc, struct ioc_read_toc_entry *te) { struct cd_toc_entry entries[MCD_MAXTOCS]; struct ioc_toc_header th; int rc, n, trk, len; if ( te->data_len < sizeof(entries[0]) || (te->data_len % sizeof(entries[0])) != 0 || (te->address_format != CD_MSF_FORMAT && te->address_format != CD_LBA_FORMAT) ) return (EINVAL); /* Copy the toc header */ if ((rc = mcd_toc_header(sc, &th)) != 0) return (rc); /* verify starting track */ trk = te->starting_track; if (trk == 0) trk = th.starting_track; else if (trk == MCD_LASTPLUS1) trk = th.ending_track + 1; else if (trk < th.starting_track || trk > th.ending_track + 1) return (EINVAL); len = ((th.ending_track + 1 - trk) + 1) * sizeof(entries[0]); if (te->data_len < len) len = te->data_len; if (len > sizeof(entries)) return (EINVAL); /* Make sure we have a valid toc */ if ((rc=mcd_read_toc(sc)) != 0) return (rc); /* Copy the TOC data. */ for (n = 0; len > 0 && trk <= th.ending_track + 1; trk++) { if (sc->data.toc[trk].idx_no == 0) continue; entries[n].control = sc->data.toc[trk].control; entries[n].addr_type = sc->data.toc[trk].addr_type; entries[n].track = sc->data.toc[trk].idx_no > 0x99 ? sc->data.toc[trk].idx_no : bcd2bin(sc->data.toc[trk].idx_no); switch (te->address_format) { case CD_MSF_FORMAT: entries[n].addr.msf.unused = 0; entries[n].addr.msf.minute = bcd2bin(sc->data.toc[trk].hd_pos_msf[0]); entries[n].addr.msf.second = bcd2bin(sc->data.toc[trk].hd_pos_msf[1]); entries[n].addr.msf.frame = bcd2bin(sc->data.toc[trk].hd_pos_msf[2]); break; case CD_LBA_FORMAT: entries[n].addr.lba = htonl(msf2hsg(sc->data.toc[trk].hd_pos_msf, 0)); break; } len -= sizeof(struct cd_toc_entry); n++; } /* copy the data back */ MCD_UNLOCK(sc); return copyout(entries, te->data, n * sizeof(struct cd_toc_entry)); } static int mcd_stop(struct mcd_softc *sc) { /* Verify current status */ if (sc->data.audio_status != CD_AS_PLAY_IN_PROGRESS && sc->data.audio_status != CD_AS_PLAY_PAUSED && sc->data.audio_status != CD_AS_PLAY_COMPLETED) { if (sc->data.debug) device_printf(sc->dev, "stop attempted when not playing, audio status %d\n", sc->data.audio_status); return (EINVAL); } if (sc->data.audio_status == CD_AS_PLAY_IN_PROGRESS) if (mcd_send(sc, MCD_CMDSTOPAUDIO, MCD_RETRYS) < 0) return (EIO); sc->data.audio_status = CD_AS_PLAY_COMPLETED; return (0); } static int mcd_getqchan(struct mcd_softc *sc, struct mcd_qchninfo *q) { if (mcd_send(sc, MCD_CMDGETQCHN, MCD_RETRYS) < 0) return (-1); if (mcd_get(sc, (char *) q, sizeof(struct mcd_qchninfo)) < 0) return (-1); if (sc->data.debug) { device_printf(sc->dev, "getqchan control=0x%x addr_type=0x%x trk=%d ind=%d ttm=%d:%d.%d dtm=%d:%d.%d\n", q->control, q->addr_type, bcd2bin(q->trk_no), bcd2bin(q->idx_no), bcd2bin(q->trk_size_msf[0]), bcd2bin(q->trk_size_msf[1]), bcd2bin(q->trk_size_msf[2]), bcd2bin(q->hd_pos_msf[0]), bcd2bin(q->hd_pos_msf[1]), bcd2bin(q->hd_pos_msf[2])); } return (0); } static int -mcd_subchan(struct mcd_softc *sc, struct ioc_read_subchannel *sch, int nocopyout) +mcd_subchan(struct mcd_softc *sc, struct ioc_read_subchannel *sch) { struct mcd_qchninfo q; struct cd_sub_channel_info data; int lba; if (sc->data.debug) device_printf(sc->dev, "subchan af=%d, df=%d\n", sch->address_format, sch->data_format); if (sch->address_format != CD_MSF_FORMAT && sch->address_format != CD_LBA_FORMAT) return (EINVAL); if (sch->data_format != CD_CURRENT_POSITION && sch->data_format != CD_MEDIA_CATALOG) return (EINVAL); if (mcd_setmode(sc, MCD_MD_COOKED) != 0) return (EIO); if (mcd_getqchan(sc, &q) < 0) return (EIO); data.header.audio_status = sc->data.audio_status; data.what.position.data_format = sch->data_format; switch (sch->data_format) { case CD_MEDIA_CATALOG: data.what.media_catalog.mc_valid = 1; data.what.media_catalog.mc_number[0] = '\0'; break; case CD_CURRENT_POSITION: data.what.position.control = q.control; data.what.position.addr_type = q.addr_type; data.what.position.track_number = bcd2bin(q.trk_no); data.what.position.index_number = bcd2bin(q.idx_no); switch (sch->address_format) { case CD_MSF_FORMAT: data.what.position.reladdr.msf.unused = 0; data.what.position.reladdr.msf.minute = bcd2bin(q.trk_size_msf[0]); data.what.position.reladdr.msf.second = bcd2bin(q.trk_size_msf[1]); data.what.position.reladdr.msf.frame = bcd2bin(q.trk_size_msf[2]); data.what.position.absaddr.msf.unused = 0; data.what.position.absaddr.msf.minute = bcd2bin(q.hd_pos_msf[0]); data.what.position.absaddr.msf.second = bcd2bin(q.hd_pos_msf[1]); data.what.position.absaddr.msf.frame = bcd2bin(q.hd_pos_msf[2]); break; case CD_LBA_FORMAT: lba = msf2hsg(q.trk_size_msf, 1); /* * Pre-gap has index number of 0, and decreasing MSF * address. Must be converted to negative LBA, per * SCSI spec. */ if (data.what.position.index_number == 0) lba = -lba; data.what.position.reladdr.lba = htonl(lba); data.what.position.absaddr.lba = htonl(msf2hsg(q.hd_pos_msf, 0)); break; } break; } MCD_UNLOCK(sc); - if (nocopyout == 0) - return copyout(&data, sch->data, min(sizeof(struct cd_sub_channel_info), sch->data_len)); - bcopy(&data, sch->data, min(sizeof(struct cd_sub_channel_info), sch->data_len)); - return (0); + return (copyout(&data, sch->data, min(sizeof(struct cd_sub_channel_info), sch->data_len))); } static int mcd_playmsf(struct mcd_softc *sc, struct ioc_play_msf *p) { struct mcd_read2 pb; if (sc->data.debug) device_printf(sc->dev, "playmsf: from %d:%d.%d to %d:%d.%d\n", p->start_m, p->start_s, p->start_f, p->end_m, p->end_s, p->end_f); if ((p->start_m * 60 * 75 + p->start_s * 75 + p->start_f) >= (p->end_m * 60 * 75 + p->end_s * 75 + p->end_f) || (p->end_m * 60 * 75 + p->end_s * 75 + p->end_f) > M_msf(sc->data.volinfo.vol_msf) * 60 * 75 + S_msf(sc->data.volinfo.vol_msf) * 75 + F_msf(sc->data.volinfo.vol_msf)) return (EINVAL); pb.start_msf[0] = bin2bcd(p->start_m); pb.start_msf[1] = bin2bcd(p->start_s); pb.start_msf[2] = bin2bcd(p->start_f); pb.end_msf[0] = bin2bcd(p->end_m); pb.end_msf[1] = bin2bcd(p->end_s); pb.end_msf[2] = bin2bcd(p->end_f); if (mcd_setmode(sc, MCD_MD_COOKED) != 0) return (EIO); return mcd_play(sc, &pb); } static int mcd_playtracks(struct mcd_softc *sc, struct ioc_play_track *pt) { struct mcd_read2 pb; int a = pt->start_track; int z = pt->end_track; int rc, i; if ((rc = mcd_read_toc(sc)) != 0) return (rc); if (sc->data.debug) device_printf(sc->dev, "playtracks from %d:%d to %d:%d\n", a, pt->start_index, z, pt->end_index); if ( a < bcd2bin(sc->data.volinfo.trk_low) || a > bcd2bin(sc->data.volinfo.trk_high) || a > z || z < bcd2bin(sc->data.volinfo.trk_low) || z > bcd2bin(sc->data.volinfo.trk_high)) return (EINVAL); for (i = 0; i < 3; i++) { pb.start_msf[i] = sc->data.toc[a].hd_pos_msf[i]; pb.end_msf[i] = sc->data.toc[z+1].hd_pos_msf[i]; } if (mcd_setmode(sc, MCD_MD_COOKED) != 0) return (EIO); return mcd_play(sc, &pb); } static int mcd_playblocks(struct mcd_softc *sc, struct ioc_play_blocks *p) { struct mcd_read2 pb; if (sc->data.debug) device_printf(sc->dev, "playblocks: blkno %d length %d\n", p->blk, p->len); if (p->blk > sc->data.disksize || p->len > sc->data.disksize || p->blk < 0 || p->len < 0 || (p->blk + p->len) > sc->data.disksize) return (EINVAL); hsg2msf(p->blk, pb.start_msf); hsg2msf(p->blk + p->len, pb.end_msf); if (mcd_setmode(sc, MCD_MD_COOKED) != 0) return (EIO); return mcd_play(sc, &pb); } static int mcd_play(struct mcd_softc *sc, struct mcd_read2 *pb) { int retry, st = -1, status; sc->data.lastpb = *pb; for(retry=0; retrystart_msf[0]); MCD_WRITE(sc, MCD_REG_COMMAND, pb->start_msf[1]); MCD_WRITE(sc, MCD_REG_COMMAND, pb->start_msf[2]); MCD_WRITE(sc, MCD_REG_COMMAND, pb->end_msf[0]); MCD_WRITE(sc, MCD_REG_COMMAND, pb->end_msf[1]); MCD_WRITE(sc, MCD_REG_COMMAND, pb->end_msf[2]); critical_exit(); status=mcd_getstat(sc, 0); if (status == -1) continue; else if (status != -2) st = 0; break; } if (status == -2) { device_printf(sc->dev, "media changed\n"); return (ENXIO); } if (sc->data.debug) device_printf(sc->dev, "mcd_play retry=%d, status=0x%02x\n", retry, status); if (st < 0) return (ENXIO); sc->data.audio_status = CD_AS_PLAY_IN_PROGRESS; return (0); } static int mcd_pause(struct mcd_softc *sc) { struct mcd_qchninfo q; int rc; /* Verify current status */ if (sc->data.audio_status != CD_AS_PLAY_IN_PROGRESS && sc->data.audio_status != CD_AS_PLAY_PAUSED) { if (sc->data.debug) device_printf(sc->dev, "pause attempted when not playing, audio status %d\n", sc->data.audio_status); return (EINVAL); } /* Get the current position */ if (mcd_getqchan(sc, &q) < 0) return (EIO); /* Copy it into lastpb */ sc->data.lastpb.start_msf[0] = q.hd_pos_msf[0]; sc->data.lastpb.start_msf[1] = q.hd_pos_msf[1]; sc->data.lastpb.start_msf[2] = q.hd_pos_msf[2]; /* Stop playing */ if ((rc=mcd_stop(sc)) != 0) return (rc); /* Set the proper status and exit */ sc->data.audio_status = CD_AS_PLAY_PAUSED; return (0); } static int mcd_resume(struct mcd_softc *sc) { if (sc->data.audio_status != CD_AS_PLAY_PAUSED) return (EINVAL); return mcd_play(sc, &sc->data.lastpb); } Index: releng/11.2/sys/dev/scd/scd.c =================================================================== --- releng/11.2/sys/dev/scd/scd.c (revision 349624) +++ releng/11.2/sys/dev/scd/scd.c (revision 349625) @@ -1,1433 +1,1427 @@ /*- * Copyright (c) 1995 Mikael Hybsch * All rights reserved. * * Portions of this file are copied from mcd.c * which has the following copyrights: * * Copyright 1993 by Holger Veit (data part) * Copyright 1993 by Brian Moore (audio part) * Changes Copyright 1993 by Gary Clark II * Changes Copyright (C) 1994 by Andrew A. Chernov * * Rewrote probe routine to work on newer Mitsumi drives. * Additional changes (C) 1994 by Jordan K. Hubbard * * 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 * in this position and unchanged. * 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 ``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$"); #undef SCD_DEBUG #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* flags */ #define SCDOPEN 0x0001 /* device opened */ #define SCDVALID 0x0002 /* parameters loaded */ #define SCDINIT 0x0004 /* device is init'd */ #define SCDPROBING 0x0020 /* probing */ #define SCDTOC 0x0100 /* already read toc */ #define SCDMBXBSY 0x0200 /* local mbx is busy */ #define SCDSPINNING 0x0400 /* drive is spun up */ #define SCD_S_BEGIN 0 #define SCD_S_BEGIN1 1 #define SCD_S_WAITSTAT 2 #define SCD_S_WAITFIFO 3 #define SCD_S_WAITSPIN 4 #define SCD_S_WAITREAD 5 #define SCD_S_WAITPARAM 6 #define RDELAY_WAIT 300 #define RDELAY_WAITREAD 300 #define SCDBLKSIZE 2048 #ifdef SCD_DEBUG static int scd_debuglevel = SCD_DEBUG; # define XDEBUG(sc, level, fmt, args...) \ do { \ if (scd_debuglevel >= level) \ device_printf(sc->dev, fmt, ## args); \ } while (0) #else # define XDEBUG(sc, level, fmt, args...) #endif #define IS_ATTENTION(sc) ((SCD_READ(sc, IREG_STATUS) & SBIT_ATTENTION) != 0) #define IS_BUSY(sc) ((SCD_READ(sc, IREG_STATUS) & SBIT_BUSY) != 0) #define IS_DATA_RDY(sc) ((SCD_READ(sc, IREG_STATUS) & SBIT_DATA_READY) != 0) #define STATUS_BIT(sc, bit) ((SCD_READ(sc, IREG_STATUS) & (bit)) != 0) #define FSTATUS_BIT(sc, bit) ((SCD_READ(sc, IREG_FSTATUS) & (bit)) != 0) /* prototypes */ static void hsg2msf(int hsg, bcd_t *msf); static int msf2hsg(bcd_t *msf); static void process_attention(struct scd_softc *); static int waitfor_status_bits(struct scd_softc *, int bits_set, int bits_clear); static int send_cmd(struct scd_softc *, u_char cmd, u_int nargs, ...); static void init_drive(struct scd_softc *); static int spin_up(struct scd_softc *); static int read_toc(struct scd_softc *); static int get_result(struct scd_softc *, int result_len, u_char *result); static void print_error(struct scd_softc *, int errcode); static void scd_start(struct scd_softc *); static void scd_timeout(void *); static void scd_doread(struct scd_softc *, int state, struct scd_mbx *mbxin); static int scd_eject(struct scd_softc *); static int scd_stop(struct scd_softc *); static int scd_pause(struct scd_softc *); static int scd_resume(struct scd_softc *); static int scd_playtracks(struct scd_softc *, struct ioc_play_track *pt); static int scd_playmsf(struct scd_softc *, struct ioc_play_msf *msf); static int scd_play(struct scd_softc *, struct ioc_play_msf *msf); -static int scd_subchan(struct scd_softc *, struct ioc_read_subchannel *sch, int nocopyout); +static int scd_subchan(struct scd_softc *, struct ioc_read_subchannel *sch); static int read_subcode(struct scd_softc *, struct sony_subchannel_position_data *sch); /* for xcdplayer */ static int scd_toc_header(struct scd_softc *, struct ioc_toc_header *th); static int scd_toc_entrys(struct scd_softc *, struct ioc_read_toc_entry *te); static int scd_toc_entry(struct scd_softc *, struct ioc_read_toc_single_entry *te); #define SCD_LASTPLUS1 170 /* don't ask, xcdplayer passes this in */ static d_open_t scdopen; static d_close_t scdclose; static d_ioctl_t scdioctl; static d_strategy_t scdstrategy; static struct cdevsw scd_cdevsw = { .d_version = D_VERSION, .d_open = scdopen, .d_close = scdclose, .d_read = physread, .d_ioctl = scdioctl, .d_strategy = scdstrategy, .d_name = "scd", .d_flags = D_DISK, }; int scd_attach(struct scd_softc *sc) { int unit; unit = device_get_unit(sc->dev); SCD_LOCK(sc); init_drive(sc); sc->data.flags = SCDINIT; sc->data.audio_status = CD_AS_AUDIO_INVALID; bioq_init(&sc->data.head); SCD_UNLOCK(sc); sc->scd_dev_t = make_dev(&scd_cdevsw, 8 * unit, UID_ROOT, GID_OPERATOR, 0640, "scd%d", unit); sc->scd_dev_t->si_drv1 = (void *)sc; device_printf(sc->dev, "WARNING: This driver is deprecated and will be removed.\n"); return (0); } static int scdopen(struct cdev *dev, int flags, int fmt, struct thread *td) { struct scd_softc *sc; int rc; sc = (struct scd_softc *)dev->si_drv1; /* mark all open part's invalid */ SCD_LOCK(sc); if (sc->data.openflag) { SCD_UNLOCK(sc); return (ENXIO); } XDEBUG(sc, 1, "DEBUG: status = 0x%x\n", SCD_READ(sc, IREG_STATUS)); if ((rc = spin_up(sc)) != 0) { print_error(sc, rc); SCD_UNLOCK(sc); return (EIO); } if (!(sc->data.flags & SCDTOC)) { int loop_count = 3; while (loop_count-- > 0 && (rc = read_toc(sc)) != 0) { if (rc == ERR_NOT_SPINNING) { rc = spin_up(sc); if (rc) { print_error(sc, rc); SCD_UNLOCK(sc); return (EIO); } continue; } device_printf(sc->dev, "TOC read error 0x%x\n", rc); SCD_UNLOCK(sc); return (EIO); } } sc->data.openflag = 1; sc->data.flags |= SCDVALID; SCD_UNLOCK(sc); return (0); } static int scdclose(struct cdev *dev, int flags, int fmt, struct thread *td) { struct scd_softc *sc; sc = (struct scd_softc *)dev->si_drv1; SCD_LOCK(sc); KASSERT(sc->data.openflag, ("device not open")); if (sc->data.audio_status != CD_AS_PLAY_IN_PROGRESS) { (void)send_cmd(sc, CMD_SPIN_DOWN, 0); sc->data.flags &= ~SCDSPINNING; } /* close channel */ sc->data.openflag = 0; SCD_UNLOCK(sc); return (0); } static void scdstrategy(struct bio *bp) { struct scd_softc *sc; sc = (struct scd_softc *)bp->bio_dev->si_drv1; /* if device invalidated (e.g. media change, door open), error */ SCD_LOCK(sc); if (!(sc->data.flags & SCDVALID)) { device_printf(sc->dev, "media changed\n"); bp->bio_error = EIO; goto bad; } /* read only */ if (!(bp->bio_cmd == BIO_READ)) { bp->bio_error = EROFS; goto bad; } /* no data to read */ if (bp->bio_bcount == 0) goto done; if (!(sc->data.flags & SCDTOC)) { bp->bio_error = EIO; goto bad; } bp->bio_resid = 0; /* queue it */ bioq_disksort(&sc->data.head, bp); /* now check whether we can perform processing */ scd_start(sc); SCD_UNLOCK(sc); return; bad: bp->bio_flags |= BIO_ERROR; done: SCD_UNLOCK(sc); bp->bio_resid = bp->bio_bcount; biodone(bp); return; } static void scd_start(struct scd_softc *sc) { struct bio *bp; SCD_ASSERT_LOCKED(sc); if (sc->data.flags & SCDMBXBSY) return; bp = bioq_takefirst(&sc->data.head); if (bp != 0) { /* block found to process, dequeue */ sc->data.flags |= SCDMBXBSY; } else { /* nothing to do */ return; } sc->data.mbx.retry = 3; sc->data.mbx.bp = bp; scd_doread(sc, SCD_S_BEGIN, &(sc->data.mbx)); return; } static int scdioctl(struct cdev *dev, u_long cmd, caddr_t addr, int flags, struct thread *td) { struct scd_softc *sc; int error; sc = (struct scd_softc *)dev->si_drv1; XDEBUG(sc, 1, "ioctl: cmd=0x%lx\n", cmd); SCD_LOCK(sc); if (!(sc->data.flags & SCDVALID)) { SCD_UNLOCK(sc); return (EIO); } error = 0; switch (cmd) { case DIOCGMEDIASIZE: *(off_t *)addr = (off_t)sc->data.disksize * sc->data.blksize; break; case DIOCGSECTORSIZE: *(u_int *)addr = sc->data.blksize; break; case CDIOCPLAYTRACKS: error = scd_playtracks(sc, (struct ioc_play_track *) addr); break; case CDIOCPLAYBLOCKS: error = EINVAL; break; case CDIOCPLAYMSF: error = scd_playmsf(sc, (struct ioc_play_msf *) addr); break; - case CDIOCREADSUBCHANNEL_SYSSPACE: - return scd_subchan(sc, (struct ioc_read_subchannel *) addr, 1); case CDIOCREADSUBCHANNEL: - return scd_subchan(sc, (struct ioc_read_subchannel *) addr, 0); + return scd_subchan(sc, (struct ioc_read_subchannel *) addr); case CDIOREADTOCHEADER: error = scd_toc_header (sc, (struct ioc_toc_header *) addr); break; case CDIOREADTOCENTRYS: return scd_toc_entrys (sc, (struct ioc_read_toc_entry*) addr); case CDIOREADTOCENTRY: error = scd_toc_entry (sc, (struct ioc_read_toc_single_entry*) addr); break; case CDIOCSETPATCH: case CDIOCGETVOL: case CDIOCSETVOL: case CDIOCSETMONO: case CDIOCSETSTERIO: case CDIOCSETMUTE: case CDIOCSETLEFT: case CDIOCSETRIGHT: error = EINVAL; break; case CDIOCRESUME: error = scd_resume(sc); break; case CDIOCPAUSE: error = scd_pause(sc); break; case CDIOCSTART: error = EINVAL; break; case CDIOCSTOP: error = scd_stop(sc); break; case CDIOCEJECT: error = scd_eject(sc); break; case CDIOCALLOW: break; case CDIOCSETDEBUG: #ifdef SCD_DEBUG scd_debuglevel++; #endif break; case CDIOCCLRDEBUG: #ifdef SCD_DEBUG scd_debuglevel = 0; #endif break; default: device_printf(sc->dev, "unsupported ioctl (cmd=0x%lx)\n", cmd); error = ENOTTY; break; } SCD_UNLOCK(sc); return (error); } /*************************************************************** * lower level of driver starts here **************************************************************/ static int scd_playtracks(struct scd_softc *sc, struct ioc_play_track *pt) { struct ioc_play_msf msf; int a = pt->start_track; int z = pt->end_track; int rc; if (!(sc->data.flags & SCDTOC) && (rc = read_toc(sc)) != 0) { if (rc == -ERR_NOT_SPINNING) { if (spin_up(sc) != 0) return (EIO); rc = read_toc(sc); } if (rc != 0) { print_error(sc, rc); return (EIO); } } XDEBUG(sc, 1, "playtracks from %d:%d to %d:%d\n", a, pt->start_index, z, pt->end_index); if ( a < sc->data.first_track || a > sc->data.last_track || a > z || z > sc->data.last_track) return (EINVAL); bcopy(sc->data.toc[a].start_msf, &msf.start_m, 3); hsg2msf(msf2hsg(sc->data.toc[z+1].start_msf)-1, &msf.end_m); return scd_play(sc, &msf); } /* The start/end msf is expected to be in bin format */ static int scd_playmsf(struct scd_softc *sc, struct ioc_play_msf *msfin) { struct ioc_play_msf msf; msf.start_m = bin2bcd(msfin->start_m); msf.start_s = bin2bcd(msfin->start_s); msf.start_f = bin2bcd(msfin->start_f); msf.end_m = bin2bcd(msfin->end_m); msf.end_s = bin2bcd(msfin->end_s); msf.end_f = bin2bcd(msfin->end_f); return scd_play(sc, &msf); } /* The start/end msf is expected to be in bcd format */ static int scd_play(struct scd_softc *sc, struct ioc_play_msf *msf) { int i, rc; XDEBUG(sc, 1, "playing: %02x:%02x:%02x -> %02x:%02x:%02x\n", msf->start_m, msf->start_s, msf->start_f, msf->end_m, msf->end_s, msf->end_f); for (i = 0; i < 2; i++) { rc = send_cmd(sc, CMD_PLAY_AUDIO, 7, 0x03, msf->start_m, msf->start_s, msf->start_f, msf->end_m, msf->end_s, msf->end_f); if (rc == -ERR_NOT_SPINNING) { sc->data.flags &= ~SCDSPINNING; if (spin_up(sc) != 0) return (EIO); } else if (rc < 0) { print_error(sc, rc); return (EIO); } else { break; } } sc->data.audio_status = CD_AS_PLAY_IN_PROGRESS; bcopy((char *)msf, (char *)&sc->data.last_play, sizeof(struct ioc_play_msf)); return (0); } static int scd_stop(struct scd_softc *sc) { (void)send_cmd(sc, CMD_STOP_AUDIO, 0); sc->data.audio_status = CD_AS_PLAY_COMPLETED; return (0); } static int scd_pause(struct scd_softc *sc) { struct sony_subchannel_position_data subpos; if (sc->data.audio_status != CD_AS_PLAY_IN_PROGRESS) return (EINVAL); if (read_subcode(sc, &subpos) != 0) return (EIO); if (send_cmd(sc, CMD_STOP_AUDIO, 0) != 0) return (EIO); sc->data.last_play.start_m = subpos.abs_msf[0]; sc->data.last_play.start_s = subpos.abs_msf[1]; sc->data.last_play.start_f = subpos.abs_msf[2]; sc->data.audio_status = CD_AS_PLAY_PAUSED; XDEBUG(sc, 1, "pause @ %02x:%02x:%02x\n", sc->data.last_play.start_m, sc->data.last_play.start_s, sc->data.last_play.start_f); return (0); } static int scd_resume(struct scd_softc *sc) { if (sc->data.audio_status != CD_AS_PLAY_PAUSED) return (EINVAL); return scd_play(sc, &sc->data.last_play); } static int scd_eject(struct scd_softc *sc) { sc->data.audio_status = CD_AS_AUDIO_INVALID; sc->data.flags &= ~(SCDSPINNING|SCDTOC); if (send_cmd(sc, CMD_STOP_AUDIO, 0) != 0 || send_cmd(sc, CMD_SPIN_DOWN, 0) != 0 || send_cmd(sc, CMD_EJECT, 0) != 0) { return (EIO); } return (0); } static int -scd_subchan(struct scd_softc *sc, struct ioc_read_subchannel *sch, int nocopyout) +scd_subchan(struct scd_softc *sc, struct ioc_read_subchannel *sch) { struct sony_subchannel_position_data q; struct cd_sub_channel_info data; XDEBUG(sc, 1, "subchan af=%d, df=%d\n", sch->address_format, sch->data_format); if (sch->address_format != CD_MSF_FORMAT) return (EINVAL); if (sch->data_format != CD_CURRENT_POSITION) return (EINVAL); if (read_subcode(sc, &q) != 0) return (EIO); data.header.audio_status = sc->data.audio_status; data.what.position.data_format = CD_MSF_FORMAT; data.what.position.track_number = bcd2bin(q.track_number); data.what.position.reladdr.msf.unused = 0; data.what.position.reladdr.msf.minute = bcd2bin(q.rel_msf[0]); data.what.position.reladdr.msf.second = bcd2bin(q.rel_msf[1]); data.what.position.reladdr.msf.frame = bcd2bin(q.rel_msf[2]); data.what.position.absaddr.msf.unused = 0; data.what.position.absaddr.msf.minute = bcd2bin(q.abs_msf[0]); data.what.position.absaddr.msf.second = bcd2bin(q.abs_msf[1]); data.what.position.absaddr.msf.frame = bcd2bin(q.abs_msf[2]); SCD_UNLOCK(sc); - if (nocopyout == 0) { - if (copyout(&data, sch->data, min(sizeof(struct cd_sub_channel_info), sch->data_len))!=0) - return (EFAULT); - } else { - bcopy(&data, sch->data, min(sizeof(struct cd_sub_channel_info), sch->data_len)); - } + if (copyout(&data, sch->data, min(sizeof(struct cd_sub_channel_info), sch->data_len))!=0) + return (EFAULT); return (0); } int scd_probe(struct scd_softc *sc) { struct sony_drive_configuration drive_config; int rc; static char namebuf[8+16+8+3]; char *s = namebuf; int loop_count = 0; sc->data.flags = SCDPROBING; bzero(&drive_config, sizeof(drive_config)); again: /* Reset drive */ SCD_WRITE(sc, OREG_CONTROL, CBIT_RESET_DRIVE); /* Calm down */ DELAY(300000); /* Only the ATTENTION bit may be set */ if ((SCD_READ(sc, IREG_STATUS) & ~1) != 0) { XDEBUG(sc, 1, "too many bits set. probe failed.\n"); return (ENXIO); } rc = send_cmd(sc, CMD_GET_DRIVE_CONFIG, 0); if (rc != sizeof(drive_config)) { /* Sometimes if the drive is playing audio I get */ /* the bad result 82. Fix by repeating the reset */ if (rc > 0 && loop_count++ == 0) goto again; return (ENXIO); } if (get_result(sc, rc, (u_char *)&drive_config) != 0) return (ENXIO); bcopy(drive_config.vendor, namebuf, 8); s = namebuf+8; while (*(s-1) == ' ') /* Strip trailing spaces */ s--; *s++ = ' '; bcopy(drive_config.product, s, 16); s += 16; while (*(s-1) == ' ') s--; *s++ = ' '; bcopy(drive_config.revision, s, 8); s += 8; while (*(s-1) == ' ') s--; *s = 0; sc->data.name = namebuf; if (drive_config.config & 0x10) sc->data.double_speed = 1; else sc->data.double_speed = 0; return (0); } static int read_subcode(struct scd_softc *sc, struct sony_subchannel_position_data *scp) { int rc; rc = send_cmd(sc, CMD_GET_SUBCHANNEL_DATA, 0); if (rc < 0 || rc < sizeof(*scp)) return (EIO); if (get_result(sc, rc, (u_char *)scp) != 0) return (EIO); return (0); } /* State machine copied from mcd.c */ /* This (and the code in mcd.c) will not work with more than one drive */ /* because there is only one sc->ch_mbxsave below. Should fix that some day. */ /* (sc->ch_mbxsave & state should probably be included in the scd_data struct and */ /* the unit number used as first argument to scd_doread().) /Micke */ /* state machine to process read requests * initialize with SCD_S_BEGIN: reset state machine * SCD_S_WAITSTAT: wait for ready (!busy) * SCD_S_WAITSPIN: wait for drive to spin up (if not spinning) * SCD_S_WAITFIFO: wait for param fifo to get ready, them exec. command. * SCD_S_WAITREAD: wait for data ready, read data * SCD_S_WAITPARAM: wait for command result params, read them, error if bad data read. */ static void scd_timeout(void *arg) { struct scd_softc *sc; sc = (struct scd_softc *)arg; SCD_ASSERT_LOCKED(sc); scd_doread(sc, sc->ch_state, sc->ch_mbxsave); } static void scd_doread(struct scd_softc *sc, int state, struct scd_mbx *mbxin) { struct scd_mbx *mbx = (state!=SCD_S_BEGIN) ? sc->ch_mbxsave : mbxin; struct bio *bp = mbx->bp; int i; int blknum; caddr_t addr; static char sdata[3]; /* Must be preserved between calls to this function */ SCD_ASSERT_LOCKED(sc); loop: switch (state) { case SCD_S_BEGIN: mbx = sc->ch_mbxsave = mbxin; case SCD_S_BEGIN1: /* get status */ mbx->count = RDELAY_WAIT; process_attention(sc); goto trystat; case SCD_S_WAITSTAT: sc->ch_state = SCD_S_WAITSTAT; callout_stop(&sc->timer); if (mbx->count-- <= 0) { device_printf(sc->dev, "timeout. drive busy.\n"); goto harderr; } trystat: if (IS_BUSY(sc)) { sc->ch_state = SCD_S_WAITSTAT; callout_reset(&sc->timer, hz / 100, scd_timeout, sc); /* XXX */ return; } process_attention(sc); /* reject, if audio active */ if (sc->data.audio_status & CD_AS_PLAY_IN_PROGRESS) { device_printf(sc->dev, "audio is active\n"); goto harderr; } mbx->sz = sc->data.blksize; /* for first block */ mbx->nblk = howmany(bp->bio_bcount, mbx->sz); mbx->skip = 0; nextblock: if (!(sc->data.flags & SCDVALID)) goto changed; blknum = bp->bio_offset / mbx->sz + mbx->skip/mbx->sz; XDEBUG(sc, 2, "scd_doread: read blknum=%d\n", blknum); /* build parameter block */ hsg2msf(blknum, sdata); SCD_WRITE(sc, OREG_CONTROL, CBIT_RESULT_READY_CLEAR); SCD_WRITE(sc, OREG_CONTROL, CBIT_RPARAM_CLEAR); SCD_WRITE(sc, OREG_CONTROL, CBIT_DATA_READY_CLEAR); if (FSTATUS_BIT(sc, FBIT_WPARAM_READY)) goto writeparam; mbx->count = 100; sc->ch_state = SCD_S_WAITFIFO; callout_reset(&sc->timer, hz / 100, scd_timeout, sc); /* XXX */ return; case SCD_S_WAITSPIN: sc->ch_state = SCD_S_WAITSPIN; callout_stop(&sc->timer); if (mbx->count-- <= 0) { device_printf(sc->dev, "timeout waiting for drive to spin up.\n"); goto harderr; } if (!STATUS_BIT(sc, SBIT_RESULT_READY)) { sc->ch_state = SCD_S_WAITSPIN; callout_reset(&sc->timer, hz / 100, scd_timeout, sc); /* XXX */ return; } SCD_WRITE(sc, OREG_CONTROL, CBIT_RESULT_READY_CLEAR); switch ((i = SCD_READ(sc, IREG_RESULT)) & 0xf0) { case 0x20: i = SCD_READ(sc, IREG_RESULT); print_error(sc, i); goto harderr; case 0x00: (void)SCD_READ(sc, IREG_RESULT); sc->data.flags |= SCDSPINNING; break; } XDEBUG(sc, 1, "DEBUG: spin up complete\n"); state = SCD_S_BEGIN1; goto loop; case SCD_S_WAITFIFO: sc->ch_state = SCD_S_WAITFIFO; callout_stop(&sc->timer); if (mbx->count-- <= 0) { device_printf(sc->dev, "timeout. write param not ready.\n"); goto harderr; } if (!FSTATUS_BIT(sc, FBIT_WPARAM_READY)) { sc->ch_state = SCD_S_WAITFIFO; callout_reset(&sc->timer, hz / 100, scd_timeout, sc); /* XXX */ return; } XDEBUG(sc, 1, "mbx->count (writeparamwait) = %d(%d)\n", mbx->count, 100); writeparam: /* The reason this test isn't done 'till now is to make sure */ /* that it is ok to send the SPIN_UP cmd below. */ if (!(sc->data.flags & SCDSPINNING)) { XDEBUG(sc, 1, "spinning up drive ...\n"); SCD_WRITE(sc, OREG_COMMAND, CMD_SPIN_UP); mbx->count = 300; sc->ch_state = SCD_S_WAITSPIN; callout_reset(&sc->timer, hz / 100, scd_timeout, sc); /* XXX */ return; } /* send the read command */ SCD_WRITE(sc, OREG_WPARAMS, sdata[0]); SCD_WRITE(sc, OREG_WPARAMS, sdata[1]); SCD_WRITE(sc, OREG_WPARAMS, sdata[2]); SCD_WRITE(sc, OREG_WPARAMS, 0); SCD_WRITE(sc, OREG_WPARAMS, 0); SCD_WRITE(sc, OREG_WPARAMS, 1); SCD_WRITE(sc, OREG_COMMAND, CMD_READ); mbx->count = RDELAY_WAITREAD; for (i = 0; i < 50; i++) { if (STATUS_BIT(sc, SBIT_DATA_READY)) goto got_data; DELAY(100); } sc->ch_state = SCD_S_WAITREAD; callout_reset(&sc->timer, hz / 100, scd_timeout, sc); /* XXX */ return; case SCD_S_WAITREAD: sc->ch_state = SCD_S_WAITREAD; callout_stop(&sc->timer); if (mbx->count-- <= 0) { if (STATUS_BIT(sc, SBIT_RESULT_READY)) goto got_param; device_printf(sc->dev, "timeout while reading data\n"); goto readerr; } if (!STATUS_BIT(sc, SBIT_DATA_READY)) { process_attention(sc); if (!(sc->data.flags & SCDVALID)) goto changed; sc->ch_state = SCD_S_WAITREAD; callout_reset(&sc->timer, hz / 100, scd_timeout, sc); /* XXX */ return; } XDEBUG(sc, 2, "mbx->count (after RDY_BIT) = %d(%d)\n", mbx->count, RDELAY_WAITREAD); got_data: /* data is ready */ addr = bp->bio_data + mbx->skip; SCD_WRITE(sc, OREG_CONTROL, CBIT_DATA_READY_CLEAR); SCD_READ_MULTI(sc, IREG_DATA, addr, mbx->sz); mbx->count = 100; for (i = 0; i < 20; i++) { if (STATUS_BIT(sc, SBIT_RESULT_READY)) goto waitfor_param; DELAY(100); } goto waitfor_param; case SCD_S_WAITPARAM: sc->ch_state = SCD_S_WAITPARAM; callout_stop(&sc->timer); if (mbx->count-- <= 0) { device_printf(sc->dev, "timeout waiting for params\n"); goto readerr; } waitfor_param: if (!STATUS_BIT(sc, SBIT_RESULT_READY)) { sc->ch_state = SCD_S_WAITPARAM; callout_reset(&sc->timer, hz / 100, scd_timeout, sc); /* XXX */ return; } #ifdef SCD_DEBUG if (mbx->count < 100 && scd_debuglevel > 0) device_printf(sc->dev, "mbx->count (paramwait) = %d(%d)\n", mbx->count, 100); #endif got_param: SCD_WRITE(sc, OREG_CONTROL, CBIT_RESULT_READY_CLEAR); switch ((i = SCD_READ(sc, IREG_RESULT)) & 0xf0) { case 0x50: switch (i) { case ERR_FATAL_READ_ERROR1: case ERR_FATAL_READ_ERROR2: device_printf(sc->dev, "unrecoverable read error 0x%x\n", i); goto harderr; } break; case 0x20: i = SCD_READ(sc, IREG_RESULT); switch (i) { case ERR_NOT_SPINNING: XDEBUG(sc, 1, "read error: drive not spinning\n"); if (mbx->retry-- > 0) { state = SCD_S_BEGIN1; sc->data.flags &= ~SCDSPINNING; goto loop; } goto harderr; default: print_error(sc, i); goto readerr; } case 0x00: i = SCD_READ(sc, IREG_RESULT); break; } if (--mbx->nblk > 0) { mbx->skip += mbx->sz; goto nextblock; } /* return buffer */ bp->bio_resid = 0; biodone(bp); sc->data.flags &= ~SCDMBXBSY; scd_start(sc); return; } readerr: if (mbx->retry-- > 0) { device_printf(sc->dev, "retrying ...\n"); state = SCD_S_BEGIN1; goto loop; } harderr: /* invalidate the buffer */ bp->bio_error = EIO; bp->bio_flags |= BIO_ERROR; bp->bio_resid = bp->bio_bcount; biodone(bp); sc->data.flags &= ~SCDMBXBSY; scd_start(sc); return; changed: device_printf(sc->dev, "media changed\n"); goto harderr; } static void hsg2msf(int hsg, bcd_t *msf) { hsg += 150; M_msf(msf) = bin2bcd(hsg / 4500); hsg %= 4500; S_msf(msf) = bin2bcd(hsg / 75); F_msf(msf) = bin2bcd(hsg % 75); } static int msf2hsg(bcd_t *msf) { return (bcd2bin(M_msf(msf)) * 60 + bcd2bin(S_msf(msf))) * 75 + bcd2bin(F_msf(msf)) - 150; } static void process_attention(struct scd_softc *sc) { unsigned char code; int count = 0; while (IS_ATTENTION(sc) && count++ < 30) { SCD_WRITE(sc, OREG_CONTROL, CBIT_ATTENTION_CLEAR); code = SCD_READ(sc, IREG_RESULT); #ifdef SCD_DEBUG if (scd_debuglevel > 0) { if (count == 1) device_printf(sc->dev, "DEBUG: ATTENTIONS = 0x%x", code); else printf(",0x%x", code); } #endif switch (code) { case ATTEN_SPIN_DOWN: sc->data.flags &= ~SCDSPINNING; break; case ATTEN_SPIN_UP_DONE: sc->data.flags |= SCDSPINNING; break; case ATTEN_AUDIO_DONE: sc->data.audio_status = CD_AS_PLAY_COMPLETED; break; case ATTEN_DRIVE_LOADED: sc->data.flags &= ~(SCDTOC|SCDSPINNING|SCDVALID); sc->data.audio_status = CD_AS_AUDIO_INVALID; break; case ATTEN_EJECT_PUSHED: sc->data.flags &= ~SCDVALID; break; } DELAY(100); } #ifdef SCD_DEBUG if (scd_debuglevel > 0 && count > 0) printf("\n"); #endif } /* Returns 0 OR sony error code */ static int spin_up(struct scd_softc *sc) { unsigned char res_reg[12]; unsigned int res_size; int rc; int loop_count = 0; again: rc = send_cmd(sc, CMD_SPIN_UP, 0, 0, res_reg, &res_size); if (rc != 0) { XDEBUG(sc, 2, "CMD_SPIN_UP error 0x%x\n", rc); return (rc); } if (!(sc->data.flags & SCDTOC)) { rc = send_cmd(sc, CMD_READ_TOC, 0); if (rc == ERR_NOT_SPINNING) { if (loop_count++ < 3) goto again; return (rc); } if (rc != 0) return (rc); } sc->data.flags |= SCDSPINNING; return (0); } static struct sony_tracklist * get_tl(struct sony_toc *toc, int size) { struct sony_tracklist *tl = &toc->tracks[0]; if (tl->track != 0xb0) return (tl); if (tl->track != 0xb1) return (tl); tl = (struct sony_tracklist *)((char *)tl + 9); if (tl->track != 0xb2) return (tl); tl = (struct sony_tracklist *)((char *)tl + 9); if (tl->track != 0xb3) return (tl); tl = (struct sony_tracklist *)((char *)tl + 9); if (tl->track != 0xb4) return (tl); tl = (struct sony_tracklist *)((char *)tl + 9); if (tl->track != 0xc0) return (tl); tl = (struct sony_tracklist *)((char *)tl + 9); return (tl); } static int read_toc(struct scd_softc *sc) { struct sony_toc toc; struct sony_tracklist *tl; int rc, i, j; u_long first, last; rc = send_cmd(sc, CMD_GET_TOC, 1, 1); if (rc < 0) return (rc); if (rc > sizeof(toc)) { device_printf(sc->dev, "program error: toc too large (%d)\n", rc); return (EIO); } if (get_result(sc, rc, (u_char *)&toc) != 0) return (EIO); XDEBUG(sc, 1, "toc read. len = %d, sizeof(toc) = %d\n", rc, sizeof(toc)); tl = get_tl(&toc, rc); first = msf2hsg(tl->start_msf); last = msf2hsg(toc.lead_out_start_msf); sc->data.blksize = SCDBLKSIZE; sc->data.disksize = last*sc->data.blksize/DEV_BSIZE; XDEBUG(sc, 1, "firstsector = %ld, lastsector = %ld", first, last); sc->data.first_track = bcd2bin(toc.first_track); sc->data.last_track = bcd2bin(toc.last_track); if (sc->data.last_track > (MAX_TRACKS-2)) sc->data.last_track = MAX_TRACKS-2; for (j = 0, i = sc->data.first_track; i <= sc->data.last_track; i++, j++) { sc->data.toc[i].adr = tl[j].adr; sc->data.toc[i].ctl = tl[j].ctl; /* for xcdplayer */ bcopy(tl[j].start_msf, sc->data.toc[i].start_msf, 3); #ifdef SCD_DEBUG if (scd_debuglevel > 0) { if ((j % 3) == 0) { printf("\n"); device_printf(sc->dev, "tracks "); } printf("[%03d: %2d %2d %2d] ", i, bcd2bin(sc->data.toc[i].start_msf[0]), bcd2bin(sc->data.toc[i].start_msf[1]), bcd2bin(sc->data.toc[i].start_msf[2])); } #endif } bcopy(toc.lead_out_start_msf, sc->data.toc[sc->data.last_track+1].start_msf, 3); #ifdef SCD_DEBUG if (scd_debuglevel > 0) { i = sc->data.last_track+1; printf("[END: %2d %2d %2d]\n", bcd2bin(sc->data.toc[i].start_msf[0]), bcd2bin(sc->data.toc[i].start_msf[1]), bcd2bin(sc->data.toc[i].start_msf[2])); } #endif sc->data.flags |= SCDTOC; return (0); } static void init_drive(struct scd_softc *sc) { int rc; rc = send_cmd(sc, CMD_SET_DRIVE_PARAM, 2, 0x05, 0x03 | ((sc->data.double_speed) ? 0x04: 0)); if (rc != 0) device_printf(sc->dev, "Unable to set parameters. Errcode = 0x%x\n", rc); } /* Returns 0 or errno */ static int get_result(struct scd_softc *sc, int result_len, u_char *result) { int loop_index = 2; /* send_cmd() reads two bytes ... */ XDEBUG(sc, 1, "DEBUG: get_result: bytes=%d\n", result_len); while (result_len-- > 0) { if (loop_index++ >= 10) { loop_index = 1; if (waitfor_status_bits(sc, SBIT_RESULT_READY, 0)) return (EIO); SCD_WRITE(sc, OREG_CONTROL, CBIT_RESULT_READY_CLEAR); } if (result) *result++ = SCD_READ(sc, IREG_RESULT); else (void)SCD_READ(sc, IREG_RESULT); } return (0); } /* Returns -0x100 for timeout, -(drive error code) OR number of result bytes */ static int send_cmd(struct scd_softc *sc, u_char cmd, u_int nargs, ...) { va_list ap; u_char c; int rc; int i; if (waitfor_status_bits(sc, 0, SBIT_BUSY)) { device_printf(sc->dev, "drive busy\n"); return (-0x100); } XDEBUG(sc, 1, "DEBUG: send_cmd: cmd=0x%x nargs=%d", cmd, nargs); SCD_WRITE(sc, OREG_CONTROL, CBIT_RESULT_READY_CLEAR); SCD_WRITE(sc, OREG_CONTROL, CBIT_RPARAM_CLEAR); for (i = 0; i < 100; i++) if (FSTATUS_BIT(sc, FBIT_WPARAM_READY)) break; if (!FSTATUS_BIT(sc, FBIT_WPARAM_READY)) { XDEBUG(sc, 1, "\nwparam timeout\n"); return (-EIO); } va_start(ap, nargs); for (i = 0; i < nargs; i++) { c = (u_char)va_arg(ap, int); SCD_WRITE(sc, OREG_WPARAMS, c); XDEBUG(sc, 1, ",{0x%x}", c); } va_end(ap); XDEBUG(sc, 1, "\n"); SCD_WRITE(sc, OREG_COMMAND, cmd); rc = waitfor_status_bits(sc, SBIT_RESULT_READY, SBIT_BUSY); if (rc) return (-0x100); SCD_WRITE(sc, OREG_CONTROL, CBIT_RESULT_READY_CLEAR); switch ((rc = SCD_READ(sc, IREG_RESULT)) & 0xf0) { case 0x20: rc = SCD_READ(sc, IREG_RESULT); /* FALLTHROUGH */ case 0x50: XDEBUG(sc, 1, "DEBUG: send_cmd: drive_error=0x%x\n", rc); return (-rc); case 0x00: default: rc = SCD_READ(sc, IREG_RESULT); XDEBUG(sc, 1, "DEBUG: send_cmd: result_len=%d\n", rc); return (rc); } } static void print_error(struct scd_softc *sc, int errcode) { switch (errcode) { case -ERR_CD_NOT_LOADED: device_printf(sc->dev, "door is open\n"); break; case -ERR_NO_CD_INSIDE: device_printf(sc->dev, "no cd inside\n"); break; default: if (errcode == -0x100 || errcode > 0) device_printf(sc->dev, "device timeout\n"); else device_printf(sc->dev, "unexpected error 0x%x\n", -errcode); break; } } /* Returns 0 or errno value */ static int waitfor_status_bits(struct scd_softc *sc, int bits_set, int bits_clear) { u_int flags = sc->data.flags; u_int max_loop; u_char c = 0; if (flags & SCDPROBING) { max_loop = 0; while (max_loop++ < 1000) { c = SCD_READ(sc, IREG_STATUS); if (c == 0xff) return (EIO); if (c & SBIT_ATTENTION) { process_attention(sc); continue; } if ((c & bits_set) == bits_set && (c & bits_clear) == 0) { break; } DELAY(10000); } } else { max_loop = 100; while (max_loop-- > 0) { c = SCD_READ(sc, IREG_STATUS); if (c & SBIT_ATTENTION) { process_attention(sc); continue; } if ((c & bits_set) == bits_set && (c & bits_clear) == 0) { break; } SCD_UNLOCK(sc); pause("waitfor", hz/10); SCD_LOCK(sc); } } if ((c & bits_set) == bits_set && (c & bits_clear) == 0) { return (0); } #ifdef SCD_DEBUG if (scd_debuglevel > 0) device_printf(sc->dev, "DEBUG: waitfor: TIMEOUT (0x%x,(0x%x,0x%x))\n", c, bits_set, bits_clear); else #endif device_printf(sc->dev, "timeout.\n"); return (EIO); } /* these two routines for xcdplayer - "borrowed" from mcd.c */ static int scd_toc_header (struct scd_softc *sc, struct ioc_toc_header* th) { int rc; if (!(sc->data.flags & SCDTOC) && (rc = read_toc(sc)) != 0) { print_error(sc, rc); return (EIO); } th->starting_track = sc->data.first_track; th->ending_track = sc->data.last_track; th->len = 0; /* not used */ return (0); } static int scd_toc_entrys (struct scd_softc *sc, struct ioc_read_toc_entry *te) { struct cd_toc_entry toc_entry; int rc, i, len = te->data_len; if (!(sc->data.flags & SCDTOC) && (rc = read_toc(sc)) != 0) { print_error(sc, rc); return (EIO); } /* find the toc to copy*/ i = te->starting_track; if (i == SCD_LASTPLUS1) i = sc->data.last_track + 1; /* verify starting track */ if (i < sc->data.first_track || i > sc->data.last_track+1) return (EINVAL); /* valid length ? */ if (len < sizeof(struct cd_toc_entry) || (len % sizeof(struct cd_toc_entry)) != 0) return (EINVAL); /* copy the toc data */ toc_entry.control = sc->data.toc[i].ctl; toc_entry.addr_type = te->address_format; toc_entry.track = i; if (te->address_format == CD_MSF_FORMAT) { toc_entry.addr.msf.unused = 0; toc_entry.addr.msf.minute = bcd2bin(sc->data.toc[i].start_msf[0]); toc_entry.addr.msf.second = bcd2bin(sc->data.toc[i].start_msf[1]); toc_entry.addr.msf.frame = bcd2bin(sc->data.toc[i].start_msf[2]); } SCD_UNLOCK(sc); /* copy the data back */ if (copyout(&toc_entry, te->data, sizeof(struct cd_toc_entry)) != 0) return (EFAULT); return (0); } static int scd_toc_entry (struct scd_softc *sc, struct ioc_read_toc_single_entry *te) { struct cd_toc_entry toc_entry; int rc, i; if (!(sc->data.flags & SCDTOC) && (rc = read_toc(sc)) != 0) { print_error(sc, rc); return (EIO); } /* find the toc to copy*/ i = te->track; if (i == SCD_LASTPLUS1) i = sc->data.last_track + 1; /* verify starting track */ if (i < sc->data.first_track || i > sc->data.last_track+1) return (EINVAL); /* copy the toc data */ toc_entry.control = sc->data.toc[i].ctl; toc_entry.addr_type = te->address_format; toc_entry.track = i; if (te->address_format == CD_MSF_FORMAT) { toc_entry.addr.msf.unused = 0; toc_entry.addr.msf.minute = bcd2bin(sc->data.toc[i].start_msf[0]); toc_entry.addr.msf.second = bcd2bin(sc->data.toc[i].start_msf[1]); toc_entry.addr.msf.frame = bcd2bin(sc->data.toc[i].start_msf[2]); } /* copy the data back */ bcopy(&toc_entry, &te->entry, sizeof(struct cd_toc_entry)); return (0); } Index: releng/11.2/sys/sys/cdio.h =================================================================== --- releng/11.2/sys/sys/cdio.h (revision 349624) +++ releng/11.2/sys/sys/cdio.h (revision 349625) @@ -1,284 +1,277 @@ /* * 16 Feb 93 Julian Elischer (julian@dialix.oz.au) * * $FreeBSD$ */ /* <1> Fixed a conflict with ioctl usage. There were two different functions using code #25. Made file formatting consistent. Added two new ioctl codes: door closing and audio pitch playback. Added a STEREO union called STEREO. 5-Mar-95 Frank Durda IV bsdmail@nemesis.lonestar.org <2> Added a new ioctl that allows you to find out what capabilities a drive has and what commands it will accept. This allows a user application to only offer controls (buttons, sliders, etc) for functions that drive can actually do. Things it can't do can disappear or be greyed-out (like some other system). If the driver doesn't respond to this call, well, handle it the way you used to do it. 2-Apr-95 Frank Durda IV bsdmail@nemesis.lonestar.org */ /* Shared between kernel & process */ #ifndef _SYS_CDIO_H_ #define _SYS_CDIO_H_ #ifndef _KERNEL #include #endif #include union msf_lba { struct { unsigned char unused; unsigned char minute; unsigned char second; unsigned char frame; } msf; int lba; /* network byte order */ u_char addr[4]; }; struct cd_toc_entry { #if BYTE_ORDER == LITTLE_ENDIAN u_int :8; u_int control:4; u_int addr_type:4; #else u_int :8; u_int addr_type:4; u_int control:4; #endif u_char track; u_int :8; union msf_lba addr; }; struct cd_sub_channel_header { u_int :8; u_char audio_status; #define CD_AS_AUDIO_INVALID 0x00 #define CD_AS_PLAY_IN_PROGRESS 0x11 #define CD_AS_PLAY_PAUSED 0x12 #define CD_AS_PLAY_COMPLETED 0x13 #define CD_AS_PLAY_ERROR 0x14 #define CD_AS_NO_STATUS 0x15 u_char data_len[2]; }; struct cd_sub_channel_position_data { u_char data_format; u_int control:4; u_int addr_type:4; u_char track_number; u_char index_number; union msf_lba absaddr; union msf_lba reladdr; }; struct cd_sub_channel_media_catalog { u_char data_format; u_int :8; u_int :8; u_int :8; u_int :7; u_int mc_valid:1; u_char mc_number[15]; }; struct cd_sub_channel_track_info { u_char data_format; u_int :8; u_char track_number; u_int :8; u_int :7; u_int ti_valid:1; u_char ti_number[15]; }; struct cd_sub_channel_info { struct cd_sub_channel_header header; union { struct cd_sub_channel_position_data position; struct cd_sub_channel_media_catalog media_catalog; struct cd_sub_channel_track_info track_info; } what; }; /***************************************************************\ * Ioctls for the CD drive * \***************************************************************/ struct ioc_play_track { u_char start_track; u_char start_index; u_char end_track; u_char end_index; }; #define CDIOCPLAYTRACKS _IOW('c',1,struct ioc_play_track) struct ioc_play_blocks { int blk; int len; }; #define CDIOCPLAYBLOCKS _IOW('c',2,struct ioc_play_blocks) struct ioc_read_subchannel { u_char address_format; #define CD_LBA_FORMAT 1 #define CD_MSF_FORMAT 2 u_char data_format; #define CD_SUBQ_DATA 0 #define CD_CURRENT_POSITION 1 #define CD_MEDIA_CATALOG 2 #define CD_TRACK_INFO 3 u_char track; int data_len; struct cd_sub_channel_info *data; }; #define CDIOCREADSUBCHANNEL _IOWR('c', 3 , struct ioc_read_subchannel ) struct ioc_toc_header { u_short len; u_char starting_track; u_char ending_track; }; #define CDIOREADTOCHEADER _IOR('c',4,struct ioc_toc_header) struct ioc_read_toc_entry { u_char address_format; u_char starting_track; u_short data_len; struct cd_toc_entry *data; }; #define CDIOREADTOCENTRYS _IOWR('c',5,struct ioc_read_toc_entry) struct ioc_read_toc_single_entry { u_char address_format; u_char track; struct cd_toc_entry entry; }; #define CDIOREADTOCENTRY _IOWR('c',6,struct ioc_read_toc_single_entry) struct ioc_patch { u_char patch[4]; /* one for each channel */ }; #define CDIOCSETPATCH _IOW('c',9,struct ioc_patch) struct ioc_vol { u_char vol[4]; /* one for each channel */ }; #define CDIOCGETVOL _IOR('c',10,struct ioc_vol) #define CDIOCSETVOL _IOW('c',11,struct ioc_vol) #define CDIOCSETMONO _IO('c',12) #define CDIOCSETSTERIO _IO('c',13) #define CDIOCSETSTEREO _IO('c',13) #define CDIOCSETMUTE _IO('c',14) #define CDIOCSETLEFT _IO('c',15) #define CDIOCSETRIGHT _IO('c',16) #define CDIOCSETDEBUG _IO('c',17) #define CDIOCCLRDEBUG _IO('c',18) #define CDIOCPAUSE _IO('c',19) #define CDIOCRESUME _IO('c',20) #define CDIOCRESET _IO('c',21) #define CDIOCSTART _IO('c',22) #define CDIOCSTOP _IO('c',23) #define CDIOCEJECT _IO('c',24) struct ioc_play_msf { u_char start_m; u_char start_s; u_char start_f; u_char end_m; u_char end_s; u_char end_f; }; #define CDIOCPLAYMSF _IOW('c',25,struct ioc_play_msf) #define CDIOCALLOW _IO('c',26) #define CDIOCPREVENT _IO('c',27) /*<1>For drives that support it, this*/ /*<1>causes the drive to close its door*/ /*<1>and make the media (if any) ready*/ #define CDIOCCLOSE _IO('c',28) /*<1>*/ struct ioc_pitch { /*<1>For drives that support it, this*/ /*<1>call instructs the drive to play the*/ short speed; /*<1>audio at a faster or slower-than-normal*/ }; /*<1>rate. -32767 to -1 is slower, 0==normal,*/ /*<1>and 1 to 32767 is faster. LSB bits are*/ /*<1>discarded first by drives with less res.*/ #define CDIOCPITCH _IOW('c',29,struct ioc_pitch) /*<1>*/ struct ioc_capability { /*<2>*/ u_long play_function; /*<2>*/ #define CDDOPLAYTRK 0x00000001 /*<2>Can Play tracks/index*/ #define CDDOPLAYMSF 0x00000002 /*<2>Can Play msf to msf*/ #define CDDOPLAYBLOCKS 0x00000004 /*<2>Can Play range of blocks*/ #define CDDOPAUSE 0x00000100 /*<2>Output can be paused*/ #define CDDORESUME 0x00000200 /*<2>Output can be resumed*/ #define CDDORESET 0x00000400 /*<2>Drive can be completely reset*/ #define CDDOSTART 0x00000800 /*<2>Audio can be started*/ #define CDDOSTOP 0x00001000 /*<2>Audio can be stopped*/ #define CDDOPITCH 0x00002000 /*<2>Audio pitch */ u_long routing_function; /*<2>*/ #define CDREADVOLUME 0x00000001 /*<2>Volume settings can be read*/ #define CDSETVOLUME 0x00000002 /*<2>Volume settings can be set*/ #define CDSETMONO 0x00000100 /*<2>Output can be set to mono*/ #define CDSETSTEREO 0x00000200 /*<2>Output can be set to stereo (def)*/ #define CDSETLEFT 0x00000400 /*<2>Output can be set to left only*/ #define CDSETRIGHT 0x00000800 /*<2>Output can be set to right only*/ #define CDSETMUTE 0x00001000 /*<2>Output can be muted*/ #define CDSETPATCH 0x00008000 /*<2>Direct routing control allowed*/ u_long special_function; /*<2>*/ #define CDDOEJECT 0x00000001 /*<2>The tray can be opened*/ #define CDDOCLOSE 0x00000002 /*<2>The tray can be closed*/ #define CDDOLOCK 0x00000004 /*<2>The tray can be locked*/ #define CDREADHEADER 0x00000100 /*<2>Can read Table of Contents*/ #define CDREADENTRIES 0x00000200 /*<2>Can read TOC Entries*/ #define CDREADSUBQ 0x00000200 /*<2>Can read Subchannel info*/ #define CDREADRW 0x00000400 /*<2>Can read subcodes R-W*/ #define CDHASDEBUG 0x00004000 /*<2>The tray has dynamic debugging*/ }; /*<2>*/ #define CDIOCCAPABILITY _IOR('c',30,struct ioc_capability) /*<2>*/ -/* - * Special version of CDIOCREADSUBCHANNEL which assumes that - * ioc_read_subchannel->data points to the kernel memory. For - * use in compatibility layers. - */ -#define CDIOCREADSUBCHANNEL_SYSSPACE _IOWR('c', 31, struct ioc_read_subchannel) - #endif /* !_SYS_CDIO_H_ */ Index: releng/11.3/sys/cam/scsi/scsi_cd.c =================================================================== --- releng/11.3/sys/cam/scsi/scsi_cd.c (revision 349624) +++ releng/11.3/sys/cam/scsi/scsi_cd.c (revision 349625) @@ -1,3719 +1,3709 @@ /*- * 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[32], tmpstr2[16]; 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, nitems(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), "%juMB (%ju %u byte sectors)", ((uintmax_t)cdp->disksize * cdp->blksize) / (1024 * 1024), (uintmax_t)cdp->disksize, 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 || error_code == SSD_DESC_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) { u_int i; for (i = 0; i < nitems(cd_page_size_table); 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; + int 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); - } + error = copyout(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); } Index: releng/11.3/sys/compat/linux/linux_ioctl.c =================================================================== --- releng/11.3/sys/compat/linux/linux_ioctl.c (revision 349624) +++ releng/11.3/sys/compat/linux/linux_ioctl.c (revision 349625) @@ -1,3896 +1,3906 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 1994-1995 Søren Schmidt * 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. * * 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 "opt_compat.h" #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 #include #include #include #include #include #include #include #include #include #include #ifdef COMPAT_LINUX32 #include #include #else #include #include #endif #include #include #include #include #include #include #include #include #include #include CTASSERT(LINUX_IFNAMSIZ == IFNAMSIZ); static linux_ioctl_function_t linux_ioctl_cdrom; static linux_ioctl_function_t linux_ioctl_vfat; static linux_ioctl_function_t linux_ioctl_console; static linux_ioctl_function_t linux_ioctl_hdio; static linux_ioctl_function_t linux_ioctl_disk; static linux_ioctl_function_t linux_ioctl_socket; static linux_ioctl_function_t linux_ioctl_sound; static linux_ioctl_function_t linux_ioctl_termio; static linux_ioctl_function_t linux_ioctl_private; static linux_ioctl_function_t linux_ioctl_drm; static linux_ioctl_function_t linux_ioctl_sg; static linux_ioctl_function_t linux_ioctl_v4l; static linux_ioctl_function_t linux_ioctl_v4l2; static linux_ioctl_function_t linux_ioctl_special; static linux_ioctl_function_t linux_ioctl_fbsd_usb; static linux_ioctl_function_t linux_ioctl_evdev; static struct linux_ioctl_handler cdrom_handler = { linux_ioctl_cdrom, LINUX_IOCTL_CDROM_MIN, LINUX_IOCTL_CDROM_MAX }; static struct linux_ioctl_handler vfat_handler = { linux_ioctl_vfat, LINUX_IOCTL_VFAT_MIN, LINUX_IOCTL_VFAT_MAX }; static struct linux_ioctl_handler console_handler = { linux_ioctl_console, LINUX_IOCTL_CONSOLE_MIN, LINUX_IOCTL_CONSOLE_MAX }; static struct linux_ioctl_handler hdio_handler = { linux_ioctl_hdio, LINUX_IOCTL_HDIO_MIN, LINUX_IOCTL_HDIO_MAX }; static struct linux_ioctl_handler disk_handler = { linux_ioctl_disk, LINUX_IOCTL_DISK_MIN, LINUX_IOCTL_DISK_MAX }; static struct linux_ioctl_handler socket_handler = { linux_ioctl_socket, LINUX_IOCTL_SOCKET_MIN, LINUX_IOCTL_SOCKET_MAX }; static struct linux_ioctl_handler sound_handler = { linux_ioctl_sound, LINUX_IOCTL_SOUND_MIN, LINUX_IOCTL_SOUND_MAX }; static struct linux_ioctl_handler termio_handler = { linux_ioctl_termio, LINUX_IOCTL_TERMIO_MIN, LINUX_IOCTL_TERMIO_MAX }; static struct linux_ioctl_handler private_handler = { linux_ioctl_private, LINUX_IOCTL_PRIVATE_MIN, LINUX_IOCTL_PRIVATE_MAX }; static struct linux_ioctl_handler drm_handler = { linux_ioctl_drm, LINUX_IOCTL_DRM_MIN, LINUX_IOCTL_DRM_MAX }; static struct linux_ioctl_handler sg_handler = { linux_ioctl_sg, LINUX_IOCTL_SG_MIN, LINUX_IOCTL_SG_MAX }; static struct linux_ioctl_handler video_handler = { linux_ioctl_v4l, LINUX_IOCTL_VIDEO_MIN, LINUX_IOCTL_VIDEO_MAX }; static struct linux_ioctl_handler video2_handler = { linux_ioctl_v4l2, LINUX_IOCTL_VIDEO2_MIN, LINUX_IOCTL_VIDEO2_MAX }; static struct linux_ioctl_handler fbsd_usb = { linux_ioctl_fbsd_usb, FBSD_LUSB_MIN, FBSD_LUSB_MAX }; static struct linux_ioctl_handler evdev_handler = { linux_ioctl_evdev, LINUX_IOCTL_EVDEV_MIN, LINUX_IOCTL_EVDEV_MAX }; DATA_SET(linux_ioctl_handler_set, cdrom_handler); DATA_SET(linux_ioctl_handler_set, vfat_handler); DATA_SET(linux_ioctl_handler_set, console_handler); DATA_SET(linux_ioctl_handler_set, hdio_handler); DATA_SET(linux_ioctl_handler_set, disk_handler); DATA_SET(linux_ioctl_handler_set, socket_handler); DATA_SET(linux_ioctl_handler_set, sound_handler); DATA_SET(linux_ioctl_handler_set, termio_handler); DATA_SET(linux_ioctl_handler_set, private_handler); DATA_SET(linux_ioctl_handler_set, drm_handler); DATA_SET(linux_ioctl_handler_set, sg_handler); DATA_SET(linux_ioctl_handler_set, video_handler); DATA_SET(linux_ioctl_handler_set, video2_handler); DATA_SET(linux_ioctl_handler_set, fbsd_usb); DATA_SET(linux_ioctl_handler_set, evdev_handler); #ifdef __i386__ static TAILQ_HEAD(, linux_ioctl_handler_element) linux_ioctl_handlers = TAILQ_HEAD_INITIALIZER(linux_ioctl_handlers); static struct sx linux_ioctl_sx; SX_SYSINIT(linux_ioctl, &linux_ioctl_sx, "Linux ioctl handlers"); #else extern TAILQ_HEAD(, linux_ioctl_handler_element) linux_ioctl_handlers; extern struct sx linux_ioctl_sx; #endif #ifdef COMPAT_LINUX32 static TAILQ_HEAD(, linux_ioctl_handler_element) linux32_ioctl_handlers = TAILQ_HEAD_INITIALIZER(linux32_ioctl_handlers); #endif /* * hdio related ioctls for VMWare support */ struct linux_hd_geometry { u_int8_t heads; u_int8_t sectors; u_int16_t cylinders; u_int32_t start; }; struct linux_hd_big_geometry { u_int8_t heads; u_int8_t sectors; u_int32_t cylinders; u_int32_t start; }; static int linux_ioctl_hdio(struct thread *td, struct linux_ioctl_args *args) { cap_rights_t rights; struct file *fp; int error; u_int sectorsize, fwcylinders, fwheads, fwsectors; off_t mediasize, bytespercyl; error = fget(td, args->fd, cap_rights_init(&rights, CAP_IOCTL), &fp); if (error != 0) return (error); switch (args->cmd & 0xffff) { case LINUX_HDIO_GET_GEO: case LINUX_HDIO_GET_GEO_BIG: error = fo_ioctl(fp, DIOCGMEDIASIZE, (caddr_t)&mediasize, td->td_ucred, td); if (!error) error = fo_ioctl(fp, DIOCGSECTORSIZE, (caddr_t)§orsize, td->td_ucred, td); if (!error) error = fo_ioctl(fp, DIOCGFWHEADS, (caddr_t)&fwheads, td->td_ucred, td); if (!error) error = fo_ioctl(fp, DIOCGFWSECTORS, (caddr_t)&fwsectors, td->td_ucred, td); /* * XXX: DIOCGFIRSTOFFSET is not yet implemented, so * so pretend that GEOM always says 0. This is NOT VALID * for slices or partitions, only the per-disk raw devices. */ fdrop(fp, td); if (error) return (error); /* * 1. Calculate the number of bytes in a cylinder, * given the firmware's notion of heads and sectors * per cylinder. * 2. Calculate the number of cylinders, given the total * size of the media. * All internal calculations should have 64-bit precision. */ bytespercyl = (off_t) sectorsize * fwheads * fwsectors; fwcylinders = mediasize / bytespercyl; #if defined(DEBUG) linux_msg(td, "HDIO_GET_GEO: mediasize %jd, c/h/s %d/%d/%d, " "bpc %jd", (intmax_t)mediasize, fwcylinders, fwheads, fwsectors, (intmax_t)bytespercyl); #endif if ((args->cmd & 0xffff) == LINUX_HDIO_GET_GEO) { struct linux_hd_geometry hdg; hdg.cylinders = fwcylinders; hdg.heads = fwheads; hdg.sectors = fwsectors; hdg.start = 0; error = copyout(&hdg, (void *)args->arg, sizeof(hdg)); } else if ((args->cmd & 0xffff) == LINUX_HDIO_GET_GEO_BIG) { struct linux_hd_big_geometry hdbg; memset(&hdbg, 0, sizeof(hdbg)); hdbg.cylinders = fwcylinders; hdbg.heads = fwheads; hdbg.sectors = fwsectors; hdbg.start = 0; error = copyout(&hdbg, (void *)args->arg, sizeof(hdbg)); } return (error); break; default: /* XXX */ linux_msg(td, "ioctl fd=%d, cmd=0x%x ('%c',%d) is not implemented", args->fd, (int)(args->cmd & 0xffff), (int)(args->cmd & 0xff00) >> 8, (int)(args->cmd & 0xff)); break; } fdrop(fp, td); return (ENOIOCTL); } static int linux_ioctl_disk(struct thread *td, struct linux_ioctl_args *args) { cap_rights_t rights; struct file *fp; int error; u_int sectorsize; off_t mediasize; error = fget(td, args->fd, cap_rights_init(&rights, CAP_IOCTL), &fp); if (error != 0) return (error); switch (args->cmd & 0xffff) { case LINUX_BLKGETSIZE: error = fo_ioctl(fp, DIOCGSECTORSIZE, (caddr_t)§orsize, td->td_ucred, td); if (!error) error = fo_ioctl(fp, DIOCGMEDIASIZE, (caddr_t)&mediasize, td->td_ucred, td); fdrop(fp, td); if (error) return (error); sectorsize = mediasize / sectorsize; /* * XXX: How do we know we return the right size of integer ? */ return (copyout(§orsize, (void *)args->arg, sizeof(sectorsize))); break; case LINUX_BLKSSZGET: error = fo_ioctl(fp, DIOCGSECTORSIZE, (caddr_t)§orsize, td->td_ucred, td); fdrop(fp, td); if (error) return (error); return (copyout(§orsize, (void *)args->arg, sizeof(sectorsize))); break; } fdrop(fp, td); return (ENOIOCTL); } /* * termio related ioctls */ struct linux_termio { unsigned short c_iflag; unsigned short c_oflag; unsigned short c_cflag; unsigned short c_lflag; unsigned char c_line; unsigned char c_cc[LINUX_NCC]; }; struct linux_termios { unsigned int c_iflag; unsigned int c_oflag; unsigned int c_cflag; unsigned int c_lflag; unsigned char c_line; unsigned char c_cc[LINUX_NCCS]; }; struct linux_winsize { unsigned short ws_row, ws_col; unsigned short ws_xpixel, ws_ypixel; }; struct speedtab { int sp_speed; /* Speed. */ int sp_code; /* Code. */ }; static struct speedtab sptab[] = { { B0, LINUX_B0 }, { B50, LINUX_B50 }, { B75, LINUX_B75 }, { B110, LINUX_B110 }, { B134, LINUX_B134 }, { B150, LINUX_B150 }, { B200, LINUX_B200 }, { B300, LINUX_B300 }, { B600, LINUX_B600 }, { B1200, LINUX_B1200 }, { B1800, LINUX_B1800 }, { B2400, LINUX_B2400 }, { B4800, LINUX_B4800 }, { B9600, LINUX_B9600 }, { B19200, LINUX_B19200 }, { B38400, LINUX_B38400 }, { B57600, LINUX_B57600 }, { B115200, LINUX_B115200 }, {-1, -1 } }; struct linux_serial_struct { int type; int line; int port; int irq; int flags; int xmit_fifo_size; int custom_divisor; int baud_base; unsigned short close_delay; char reserved_char[2]; int hub6; unsigned short closing_wait; unsigned short closing_wait2; int reserved[4]; }; static int linux_to_bsd_speed(int code, struct speedtab *table) { for ( ; table->sp_code != -1; table++) if (table->sp_code == code) return (table->sp_speed); return (-1); } static int bsd_to_linux_speed(int speed, struct speedtab *table) { for ( ; table->sp_speed != -1; table++) if (table->sp_speed == speed) return (table->sp_code); return (-1); } static void bsd_to_linux_termios(struct termios *bios, struct linux_termios *lios) { int i; #ifdef DEBUG if (ldebug(ioctl)) { printf("LINUX: BSD termios structure (input):\n"); printf("i=%08x o=%08x c=%08x l=%08x ispeed=%d ospeed=%d\n", bios->c_iflag, bios->c_oflag, bios->c_cflag, bios->c_lflag, bios->c_ispeed, bios->c_ospeed); printf("c_cc "); for (i=0; ic_cc[i]); printf("\n"); } #endif lios->c_iflag = 0; if (bios->c_iflag & IGNBRK) lios->c_iflag |= LINUX_IGNBRK; if (bios->c_iflag & BRKINT) lios->c_iflag |= LINUX_BRKINT; if (bios->c_iflag & IGNPAR) lios->c_iflag |= LINUX_IGNPAR; if (bios->c_iflag & PARMRK) lios->c_iflag |= LINUX_PARMRK; if (bios->c_iflag & INPCK) lios->c_iflag |= LINUX_INPCK; if (bios->c_iflag & ISTRIP) lios->c_iflag |= LINUX_ISTRIP; if (bios->c_iflag & INLCR) lios->c_iflag |= LINUX_INLCR; if (bios->c_iflag & IGNCR) lios->c_iflag |= LINUX_IGNCR; if (bios->c_iflag & ICRNL) lios->c_iflag |= LINUX_ICRNL; if (bios->c_iflag & IXON) lios->c_iflag |= LINUX_IXON; if (bios->c_iflag & IXANY) lios->c_iflag |= LINUX_IXANY; if (bios->c_iflag & IXOFF) lios->c_iflag |= LINUX_IXOFF; if (bios->c_iflag & IMAXBEL) lios->c_iflag |= LINUX_IMAXBEL; lios->c_oflag = 0; if (bios->c_oflag & OPOST) lios->c_oflag |= LINUX_OPOST; if (bios->c_oflag & ONLCR) lios->c_oflag |= LINUX_ONLCR; if (bios->c_oflag & TAB3) lios->c_oflag |= LINUX_XTABS; lios->c_cflag = bsd_to_linux_speed(bios->c_ispeed, sptab); lios->c_cflag |= (bios->c_cflag & CSIZE) >> 4; if (bios->c_cflag & CSTOPB) lios->c_cflag |= LINUX_CSTOPB; if (bios->c_cflag & CREAD) lios->c_cflag |= LINUX_CREAD; if (bios->c_cflag & PARENB) lios->c_cflag |= LINUX_PARENB; if (bios->c_cflag & PARODD) lios->c_cflag |= LINUX_PARODD; if (bios->c_cflag & HUPCL) lios->c_cflag |= LINUX_HUPCL; if (bios->c_cflag & CLOCAL) lios->c_cflag |= LINUX_CLOCAL; if (bios->c_cflag & CRTSCTS) lios->c_cflag |= LINUX_CRTSCTS; lios->c_lflag = 0; if (bios->c_lflag & ISIG) lios->c_lflag |= LINUX_ISIG; if (bios->c_lflag & ICANON) lios->c_lflag |= LINUX_ICANON; if (bios->c_lflag & ECHO) lios->c_lflag |= LINUX_ECHO; if (bios->c_lflag & ECHOE) lios->c_lflag |= LINUX_ECHOE; if (bios->c_lflag & ECHOK) lios->c_lflag |= LINUX_ECHOK; if (bios->c_lflag & ECHONL) lios->c_lflag |= LINUX_ECHONL; if (bios->c_lflag & NOFLSH) lios->c_lflag |= LINUX_NOFLSH; if (bios->c_lflag & TOSTOP) lios->c_lflag |= LINUX_TOSTOP; if (bios->c_lflag & ECHOCTL) lios->c_lflag |= LINUX_ECHOCTL; if (bios->c_lflag & ECHOPRT) lios->c_lflag |= LINUX_ECHOPRT; if (bios->c_lflag & ECHOKE) lios->c_lflag |= LINUX_ECHOKE; if (bios->c_lflag & FLUSHO) lios->c_lflag |= LINUX_FLUSHO; if (bios->c_lflag & PENDIN) lios->c_lflag |= LINUX_PENDIN; if (bios->c_lflag & IEXTEN) lios->c_lflag |= LINUX_IEXTEN; for (i=0; ic_cc[i] = LINUX_POSIX_VDISABLE; lios->c_cc[LINUX_VINTR] = bios->c_cc[VINTR]; lios->c_cc[LINUX_VQUIT] = bios->c_cc[VQUIT]; lios->c_cc[LINUX_VERASE] = bios->c_cc[VERASE]; lios->c_cc[LINUX_VKILL] = bios->c_cc[VKILL]; lios->c_cc[LINUX_VEOF] = bios->c_cc[VEOF]; lios->c_cc[LINUX_VEOL] = bios->c_cc[VEOL]; lios->c_cc[LINUX_VMIN] = bios->c_cc[VMIN]; lios->c_cc[LINUX_VTIME] = bios->c_cc[VTIME]; lios->c_cc[LINUX_VEOL2] = bios->c_cc[VEOL2]; lios->c_cc[LINUX_VSUSP] = bios->c_cc[VSUSP]; lios->c_cc[LINUX_VSTART] = bios->c_cc[VSTART]; lios->c_cc[LINUX_VSTOP] = bios->c_cc[VSTOP]; lios->c_cc[LINUX_VREPRINT] = bios->c_cc[VREPRINT]; lios->c_cc[LINUX_VDISCARD] = bios->c_cc[VDISCARD]; lios->c_cc[LINUX_VWERASE] = bios->c_cc[VWERASE]; lios->c_cc[LINUX_VLNEXT] = bios->c_cc[VLNEXT]; for (i=0; ic_cc[i] == _POSIX_VDISABLE) lios->c_cc[i] = LINUX_POSIX_VDISABLE; } lios->c_line = 0; #ifdef DEBUG if (ldebug(ioctl)) { printf("LINUX: LINUX termios structure (output):\n"); printf("i=%08x o=%08x c=%08x l=%08x line=%d\n", lios->c_iflag, lios->c_oflag, lios->c_cflag, lios->c_lflag, (int)lios->c_line); printf("c_cc "); for (i=0; ic_cc[i]); printf("\n"); } #endif } static void linux_to_bsd_termios(struct linux_termios *lios, struct termios *bios) { int i; #ifdef DEBUG if (ldebug(ioctl)) { printf("LINUX: LINUX termios structure (input):\n"); printf("i=%08x o=%08x c=%08x l=%08x line=%d\n", lios->c_iflag, lios->c_oflag, lios->c_cflag, lios->c_lflag, (int)lios->c_line); printf("c_cc "); for (i=0; ic_cc[i]); printf("\n"); } #endif bios->c_iflag = 0; if (lios->c_iflag & LINUX_IGNBRK) bios->c_iflag |= IGNBRK; if (lios->c_iflag & LINUX_BRKINT) bios->c_iflag |= BRKINT; if (lios->c_iflag & LINUX_IGNPAR) bios->c_iflag |= IGNPAR; if (lios->c_iflag & LINUX_PARMRK) bios->c_iflag |= PARMRK; if (lios->c_iflag & LINUX_INPCK) bios->c_iflag |= INPCK; if (lios->c_iflag & LINUX_ISTRIP) bios->c_iflag |= ISTRIP; if (lios->c_iflag & LINUX_INLCR) bios->c_iflag |= INLCR; if (lios->c_iflag & LINUX_IGNCR) bios->c_iflag |= IGNCR; if (lios->c_iflag & LINUX_ICRNL) bios->c_iflag |= ICRNL; if (lios->c_iflag & LINUX_IXON) bios->c_iflag |= IXON; if (lios->c_iflag & LINUX_IXANY) bios->c_iflag |= IXANY; if (lios->c_iflag & LINUX_IXOFF) bios->c_iflag |= IXOFF; if (lios->c_iflag & LINUX_IMAXBEL) bios->c_iflag |= IMAXBEL; bios->c_oflag = 0; if (lios->c_oflag & LINUX_OPOST) bios->c_oflag |= OPOST; if (lios->c_oflag & LINUX_ONLCR) bios->c_oflag |= ONLCR; if (lios->c_oflag & LINUX_XTABS) bios->c_oflag |= TAB3; bios->c_cflag = (lios->c_cflag & LINUX_CSIZE) << 4; if (lios->c_cflag & LINUX_CSTOPB) bios->c_cflag |= CSTOPB; if (lios->c_cflag & LINUX_CREAD) bios->c_cflag |= CREAD; if (lios->c_cflag & LINUX_PARENB) bios->c_cflag |= PARENB; if (lios->c_cflag & LINUX_PARODD) bios->c_cflag |= PARODD; if (lios->c_cflag & LINUX_HUPCL) bios->c_cflag |= HUPCL; if (lios->c_cflag & LINUX_CLOCAL) bios->c_cflag |= CLOCAL; if (lios->c_cflag & LINUX_CRTSCTS) bios->c_cflag |= CRTSCTS; bios->c_lflag = 0; if (lios->c_lflag & LINUX_ISIG) bios->c_lflag |= ISIG; if (lios->c_lflag & LINUX_ICANON) bios->c_lflag |= ICANON; if (lios->c_lflag & LINUX_ECHO) bios->c_lflag |= ECHO; if (lios->c_lflag & LINUX_ECHOE) bios->c_lflag |= ECHOE; if (lios->c_lflag & LINUX_ECHOK) bios->c_lflag |= ECHOK; if (lios->c_lflag & LINUX_ECHONL) bios->c_lflag |= ECHONL; if (lios->c_lflag & LINUX_NOFLSH) bios->c_lflag |= NOFLSH; if (lios->c_lflag & LINUX_TOSTOP) bios->c_lflag |= TOSTOP; if (lios->c_lflag & LINUX_ECHOCTL) bios->c_lflag |= ECHOCTL; if (lios->c_lflag & LINUX_ECHOPRT) bios->c_lflag |= ECHOPRT; if (lios->c_lflag & LINUX_ECHOKE) bios->c_lflag |= ECHOKE; if (lios->c_lflag & LINUX_FLUSHO) bios->c_lflag |= FLUSHO; if (lios->c_lflag & LINUX_PENDIN) bios->c_lflag |= PENDIN; if (lios->c_lflag & LINUX_IEXTEN) bios->c_lflag |= IEXTEN; for (i=0; ic_cc[i] = _POSIX_VDISABLE; bios->c_cc[VINTR] = lios->c_cc[LINUX_VINTR]; bios->c_cc[VQUIT] = lios->c_cc[LINUX_VQUIT]; bios->c_cc[VERASE] = lios->c_cc[LINUX_VERASE]; bios->c_cc[VKILL] = lios->c_cc[LINUX_VKILL]; bios->c_cc[VEOF] = lios->c_cc[LINUX_VEOF]; bios->c_cc[VEOL] = lios->c_cc[LINUX_VEOL]; bios->c_cc[VMIN] = lios->c_cc[LINUX_VMIN]; bios->c_cc[VTIME] = lios->c_cc[LINUX_VTIME]; bios->c_cc[VEOL2] = lios->c_cc[LINUX_VEOL2]; bios->c_cc[VSUSP] = lios->c_cc[LINUX_VSUSP]; bios->c_cc[VSTART] = lios->c_cc[LINUX_VSTART]; bios->c_cc[VSTOP] = lios->c_cc[LINUX_VSTOP]; bios->c_cc[VREPRINT] = lios->c_cc[LINUX_VREPRINT]; bios->c_cc[VDISCARD] = lios->c_cc[LINUX_VDISCARD]; bios->c_cc[VWERASE] = lios->c_cc[LINUX_VWERASE]; bios->c_cc[VLNEXT] = lios->c_cc[LINUX_VLNEXT]; for (i=0; ic_cc[i] == LINUX_POSIX_VDISABLE) bios->c_cc[i] = _POSIX_VDISABLE; } bios->c_ispeed = bios->c_ospeed = linux_to_bsd_speed(lios->c_cflag & LINUX_CBAUD, sptab); #ifdef DEBUG if (ldebug(ioctl)) { printf("LINUX: BSD termios structure (output):\n"); printf("i=%08x o=%08x c=%08x l=%08x ispeed=%d ospeed=%d\n", bios->c_iflag, bios->c_oflag, bios->c_cflag, bios->c_lflag, bios->c_ispeed, bios->c_ospeed); printf("c_cc "); for (i=0; ic_cc[i]); printf("\n"); } #endif } static void bsd_to_linux_termio(struct termios *bios, struct linux_termio *lio) { struct linux_termios lios; memset(lio, 0, sizeof(*lio)); bsd_to_linux_termios(bios, &lios); lio->c_iflag = lios.c_iflag; lio->c_oflag = lios.c_oflag; lio->c_cflag = lios.c_cflag; lio->c_lflag = lios.c_lflag; lio->c_line = lios.c_line; memcpy(lio->c_cc, lios.c_cc, LINUX_NCC); } static void linux_to_bsd_termio(struct linux_termio *lio, struct termios *bios) { struct linux_termios lios; int i; lios.c_iflag = lio->c_iflag; lios.c_oflag = lio->c_oflag; lios.c_cflag = lio->c_cflag; lios.c_lflag = lio->c_lflag; for (i=LINUX_NCC; ic_cc, LINUX_NCC); linux_to_bsd_termios(&lios, bios); } static int linux_ioctl_termio(struct thread *td, struct linux_ioctl_args *args) { struct termios bios; struct linux_termios lios; struct linux_termio lio; cap_rights_t rights; struct file *fp; int error; error = fget(td, args->fd, cap_rights_init(&rights, CAP_IOCTL), &fp); if (error != 0) return (error); switch (args->cmd & 0xffff) { case LINUX_TCGETS: error = fo_ioctl(fp, TIOCGETA, (caddr_t)&bios, td->td_ucred, td); if (error) break; bsd_to_linux_termios(&bios, &lios); error = copyout(&lios, (void *)args->arg, sizeof(lios)); break; case LINUX_TCSETS: error = copyin((void *)args->arg, &lios, sizeof(lios)); if (error) break; linux_to_bsd_termios(&lios, &bios); error = (fo_ioctl(fp, TIOCSETA, (caddr_t)&bios, td->td_ucred, td)); break; case LINUX_TCSETSW: error = copyin((void *)args->arg, &lios, sizeof(lios)); if (error) break; linux_to_bsd_termios(&lios, &bios); error = (fo_ioctl(fp, TIOCSETAW, (caddr_t)&bios, td->td_ucred, td)); break; case LINUX_TCSETSF: error = copyin((void *)args->arg, &lios, sizeof(lios)); if (error) break; linux_to_bsd_termios(&lios, &bios); error = (fo_ioctl(fp, TIOCSETAF, (caddr_t)&bios, td->td_ucred, td)); break; case LINUX_TCGETA: error = fo_ioctl(fp, TIOCGETA, (caddr_t)&bios, td->td_ucred, td); if (error) break; bsd_to_linux_termio(&bios, &lio); error = (copyout(&lio, (void *)args->arg, sizeof(lio))); break; case LINUX_TCSETA: error = copyin((void *)args->arg, &lio, sizeof(lio)); if (error) break; linux_to_bsd_termio(&lio, &bios); error = (fo_ioctl(fp, TIOCSETA, (caddr_t)&bios, td->td_ucred, td)); break; case LINUX_TCSETAW: error = copyin((void *)args->arg, &lio, sizeof(lio)); if (error) break; linux_to_bsd_termio(&lio, &bios); error = (fo_ioctl(fp, TIOCSETAW, (caddr_t)&bios, td->td_ucred, td)); break; case LINUX_TCSETAF: error = copyin((void *)args->arg, &lio, sizeof(lio)); if (error) break; linux_to_bsd_termio(&lio, &bios); error = (fo_ioctl(fp, TIOCSETAF, (caddr_t)&bios, td->td_ucred, td)); break; /* LINUX_TCSBRK */ case LINUX_TCXONC: { switch (args->arg) { case LINUX_TCOOFF: args->cmd = TIOCSTOP; break; case LINUX_TCOON: args->cmd = TIOCSTART; break; case LINUX_TCIOFF: case LINUX_TCION: { int c; struct write_args wr; error = fo_ioctl(fp, TIOCGETA, (caddr_t)&bios, td->td_ucred, td); if (error) break; fdrop(fp, td); c = (args->arg == LINUX_TCIOFF) ? VSTOP : VSTART; c = bios.c_cc[c]; if (c != _POSIX_VDISABLE) { wr.fd = args->fd; wr.buf = &c; wr.nbyte = sizeof(c); return (sys_write(td, &wr)); } else return (0); } default: fdrop(fp, td); return (EINVAL); } args->arg = 0; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; } case LINUX_TCFLSH: { int val; switch (args->arg) { case LINUX_TCIFLUSH: val = FREAD; break; case LINUX_TCOFLUSH: val = FWRITE; break; case LINUX_TCIOFLUSH: val = FREAD | FWRITE; break; default: fdrop(fp, td); return (EINVAL); } error = (fo_ioctl(fp,TIOCFLUSH,(caddr_t)&val,td->td_ucred,td)); break; } case LINUX_TIOCEXCL: args->cmd = TIOCEXCL; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; case LINUX_TIOCNXCL: args->cmd = TIOCNXCL; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; case LINUX_TIOCSCTTY: args->cmd = TIOCSCTTY; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; case LINUX_TIOCGPGRP: args->cmd = TIOCGPGRP; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; case LINUX_TIOCSPGRP: args->cmd = TIOCSPGRP; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; /* LINUX_TIOCOUTQ */ /* LINUX_TIOCSTI */ case LINUX_TIOCGWINSZ: args->cmd = TIOCGWINSZ; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; case LINUX_TIOCSWINSZ: args->cmd = TIOCSWINSZ; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; case LINUX_TIOCMGET: args->cmd = TIOCMGET; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; case LINUX_TIOCMBIS: args->cmd = TIOCMBIS; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; case LINUX_TIOCMBIC: args->cmd = TIOCMBIC; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; case LINUX_TIOCMSET: args->cmd = TIOCMSET; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; /* TIOCGSOFTCAR */ /* TIOCSSOFTCAR */ case LINUX_FIONREAD: /* LINUX_TIOCINQ */ args->cmd = FIONREAD; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; /* LINUX_TIOCLINUX */ case LINUX_TIOCCONS: args->cmd = TIOCCONS; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; case LINUX_TIOCGSERIAL: { struct linux_serial_struct lss; bzero(&lss, sizeof(lss)); lss.type = LINUX_PORT_16550A; lss.flags = 0; lss.close_delay = 0; error = copyout(&lss, (void *)args->arg, sizeof(lss)); break; } case LINUX_TIOCSSERIAL: { struct linux_serial_struct lss; error = copyin((void *)args->arg, &lss, sizeof(lss)); if (error) break; /* XXX - It really helps to have an implementation that * does nothing. NOT! */ error = 0; break; } case LINUX_TIOCPKT: args->cmd = TIOCPKT; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; case LINUX_FIONBIO: args->cmd = FIONBIO; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; case LINUX_TIOCNOTTY: args->cmd = TIOCNOTTY; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; case LINUX_TIOCSETD: { int line; switch (args->arg) { case LINUX_N_TTY: line = TTYDISC; break; case LINUX_N_SLIP: line = SLIPDISC; break; case LINUX_N_PPP: line = PPPDISC; break; default: fdrop(fp, td); return (EINVAL); } error = (fo_ioctl(fp, TIOCSETD, (caddr_t)&line, td->td_ucred, td)); break; } case LINUX_TIOCGETD: { int linux_line; int bsd_line = TTYDISC; error = fo_ioctl(fp, TIOCGETD, (caddr_t)&bsd_line, td->td_ucred, td); if (error) break; switch (bsd_line) { case TTYDISC: linux_line = LINUX_N_TTY; break; case SLIPDISC: linux_line = LINUX_N_SLIP; break; case PPPDISC: linux_line = LINUX_N_PPP; break; default: fdrop(fp, td); return (EINVAL); } error = (copyout(&linux_line, (void *)args->arg, sizeof(int))); break; } /* LINUX_TCSBRKP */ /* LINUX_TIOCTTYGSTRUCT */ case LINUX_FIONCLEX: args->cmd = FIONCLEX; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; case LINUX_FIOCLEX: args->cmd = FIOCLEX; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; case LINUX_FIOASYNC: args->cmd = FIOASYNC; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; /* LINUX_TIOCSERCONFIG */ /* LINUX_TIOCSERGWILD */ /* LINUX_TIOCSERSWILD */ /* LINUX_TIOCGLCKTRMIOS */ /* LINUX_TIOCSLCKTRMIOS */ case LINUX_TIOCSBRK: args->cmd = TIOCSBRK; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; case LINUX_TIOCCBRK: args->cmd = TIOCCBRK; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; case LINUX_TIOCGPTN: { int nb; error = fo_ioctl(fp, TIOCGPTN, (caddr_t)&nb, td->td_ucred, td); if (!error) error = copyout(&nb, (void *)args->arg, sizeof(int)); break; } case LINUX_TIOCSPTLCK: /* Our unlockpt() does nothing. */ error = 0; break; default: error = ENOIOCTL; break; } fdrop(fp, td); return (error); } /* * CDROM related ioctls */ struct linux_cdrom_msf { u_char cdmsf_min0; u_char cdmsf_sec0; u_char cdmsf_frame0; u_char cdmsf_min1; u_char cdmsf_sec1; u_char cdmsf_frame1; }; struct linux_cdrom_tochdr { u_char cdth_trk0; u_char cdth_trk1; }; union linux_cdrom_addr { struct { u_char minute; u_char second; u_char frame; } msf; int lba; }; struct linux_cdrom_tocentry { u_char cdte_track; u_char cdte_adr:4; u_char cdte_ctrl:4; u_char cdte_format; union linux_cdrom_addr cdte_addr; u_char cdte_datamode; }; struct linux_cdrom_subchnl { u_char cdsc_format; u_char cdsc_audiostatus; u_char cdsc_adr:4; u_char cdsc_ctrl:4; u_char cdsc_trk; u_char cdsc_ind; union linux_cdrom_addr cdsc_absaddr; union linux_cdrom_addr cdsc_reladdr; }; struct l_cdrom_read_audio { union linux_cdrom_addr addr; u_char addr_format; l_int nframes; u_char *buf; }; struct l_dvd_layer { u_char book_version:4; u_char book_type:4; u_char min_rate:4; u_char disc_size:4; u_char layer_type:4; u_char track_path:1; u_char nlayers:2; u_char track_density:4; u_char linear_density:4; u_char bca:1; u_int32_t start_sector; u_int32_t end_sector; u_int32_t end_sector_l0; }; struct l_dvd_physical { u_char type; u_char layer_num; struct l_dvd_layer layer[4]; }; struct l_dvd_copyright { u_char type; u_char layer_num; u_char cpst; u_char rmi; }; struct l_dvd_disckey { u_char type; l_uint agid:2; u_char value[2048]; }; struct l_dvd_bca { u_char type; l_int len; u_char value[188]; }; struct l_dvd_manufact { u_char type; u_char layer_num; l_int len; u_char value[2048]; }; typedef union { u_char type; struct l_dvd_physical physical; struct l_dvd_copyright copyright; struct l_dvd_disckey disckey; struct l_dvd_bca bca; struct l_dvd_manufact manufact; } l_dvd_struct; typedef u_char l_dvd_key[5]; typedef u_char l_dvd_challenge[10]; struct l_dvd_lu_send_agid { u_char type; l_uint agid:2; }; struct l_dvd_host_send_challenge { u_char type; l_uint agid:2; l_dvd_challenge chal; }; struct l_dvd_send_key { u_char type; l_uint agid:2; l_dvd_key key; }; struct l_dvd_lu_send_challenge { u_char type; l_uint agid:2; l_dvd_challenge chal; }; struct l_dvd_lu_send_title_key { u_char type; l_uint agid:2; l_dvd_key title_key; l_int lba; l_uint cpm:1; l_uint cp_sec:1; l_uint cgms:2; }; struct l_dvd_lu_send_asf { u_char type; l_uint agid:2; l_uint asf:1; }; struct l_dvd_host_send_rpcstate { u_char type; u_char pdrc; }; struct l_dvd_lu_send_rpcstate { u_char type:2; u_char vra:3; u_char ucca:3; u_char region_mask; u_char rpc_scheme; }; typedef union { u_char type; struct l_dvd_lu_send_agid lsa; struct l_dvd_host_send_challenge hsc; struct l_dvd_send_key lsk; struct l_dvd_lu_send_challenge lsc; struct l_dvd_send_key hsk; struct l_dvd_lu_send_title_key lstk; struct l_dvd_lu_send_asf lsasf; struct l_dvd_host_send_rpcstate hrpcs; struct l_dvd_lu_send_rpcstate lrpcs; } l_dvd_authinfo; static void bsd_to_linux_msf_lba(u_char af, union msf_lba *bp, union linux_cdrom_addr *lp) { if (af == CD_LBA_FORMAT) lp->lba = bp->lba; else { lp->msf.minute = bp->msf.minute; lp->msf.second = bp->msf.second; lp->msf.frame = bp->msf.frame; } } static void set_linux_cdrom_addr(union linux_cdrom_addr *addr, int format, int lba) { if (format == LINUX_CDROM_MSF) { addr->msf.frame = lba % 75; lba /= 75; lba += 2; addr->msf.second = lba % 60; addr->msf.minute = lba / 60; } else addr->lba = lba; } static int linux_to_bsd_dvd_struct(l_dvd_struct *lp, struct dvd_struct *bp) { bp->format = lp->type; switch (bp->format) { case DVD_STRUCT_PHYSICAL: if (bp->layer_num >= 4) return (EINVAL); bp->layer_num = lp->physical.layer_num; break; case DVD_STRUCT_COPYRIGHT: bp->layer_num = lp->copyright.layer_num; break; case DVD_STRUCT_DISCKEY: bp->agid = lp->disckey.agid; break; case DVD_STRUCT_BCA: case DVD_STRUCT_MANUFACT: break; default: return (EINVAL); } return (0); } static int bsd_to_linux_dvd_struct(struct dvd_struct *bp, l_dvd_struct *lp) { switch (bp->format) { case DVD_STRUCT_PHYSICAL: { struct dvd_layer *blp = (struct dvd_layer *)bp->data; struct l_dvd_layer *llp = &lp->physical.layer[bp->layer_num]; memset(llp, 0, sizeof(*llp)); llp->book_version = blp->book_version; llp->book_type = blp->book_type; llp->min_rate = blp->max_rate; llp->disc_size = blp->disc_size; llp->layer_type = blp->layer_type; llp->track_path = blp->track_path; llp->nlayers = blp->nlayers; llp->track_density = blp->track_density; llp->linear_density = blp->linear_density; llp->bca = blp->bca; llp->start_sector = blp->start_sector; llp->end_sector = blp->end_sector; llp->end_sector_l0 = blp->end_sector_l0; break; } case DVD_STRUCT_COPYRIGHT: lp->copyright.cpst = bp->cpst; lp->copyright.rmi = bp->rmi; break; case DVD_STRUCT_DISCKEY: memcpy(lp->disckey.value, bp->data, sizeof(lp->disckey.value)); break; case DVD_STRUCT_BCA: lp->bca.len = bp->length; memcpy(lp->bca.value, bp->data, sizeof(lp->bca.value)); break; case DVD_STRUCT_MANUFACT: lp->manufact.len = bp->length; memcpy(lp->manufact.value, bp->data, sizeof(lp->manufact.value)); /* lp->manufact.layer_num is unused in Linux (redhat 7.0). */ break; default: return (EINVAL); } return (0); } static int linux_to_bsd_dvd_authinfo(l_dvd_authinfo *lp, int *bcode, struct dvd_authinfo *bp) { switch (lp->type) { case LINUX_DVD_LU_SEND_AGID: *bcode = DVDIOCREPORTKEY; bp->format = DVD_REPORT_AGID; bp->agid = lp->lsa.agid; break; case LINUX_DVD_HOST_SEND_CHALLENGE: *bcode = DVDIOCSENDKEY; bp->format = DVD_SEND_CHALLENGE; bp->agid = lp->hsc.agid; memcpy(bp->keychal, lp->hsc.chal, 10); break; case LINUX_DVD_LU_SEND_KEY1: *bcode = DVDIOCREPORTKEY; bp->format = DVD_REPORT_KEY1; bp->agid = lp->lsk.agid; break; case LINUX_DVD_LU_SEND_CHALLENGE: *bcode = DVDIOCREPORTKEY; bp->format = DVD_REPORT_CHALLENGE; bp->agid = lp->lsc.agid; break; case LINUX_DVD_HOST_SEND_KEY2: *bcode = DVDIOCSENDKEY; bp->format = DVD_SEND_KEY2; bp->agid = lp->hsk.agid; memcpy(bp->keychal, lp->hsk.key, 5); break; case LINUX_DVD_LU_SEND_TITLE_KEY: *bcode = DVDIOCREPORTKEY; bp->format = DVD_REPORT_TITLE_KEY; bp->agid = lp->lstk.agid; bp->lba = lp->lstk.lba; break; case LINUX_DVD_LU_SEND_ASF: *bcode = DVDIOCREPORTKEY; bp->format = DVD_REPORT_ASF; bp->agid = lp->lsasf.agid; break; case LINUX_DVD_INVALIDATE_AGID: *bcode = DVDIOCREPORTKEY; bp->format = DVD_INVALIDATE_AGID; bp->agid = lp->lsa.agid; break; case LINUX_DVD_LU_SEND_RPC_STATE: *bcode = DVDIOCREPORTKEY; bp->format = DVD_REPORT_RPC; break; case LINUX_DVD_HOST_SEND_RPC_STATE: *bcode = DVDIOCSENDKEY; bp->format = DVD_SEND_RPC; bp->region = lp->hrpcs.pdrc; break; default: return (EINVAL); } return (0); } static int bsd_to_linux_dvd_authinfo(struct dvd_authinfo *bp, l_dvd_authinfo *lp) { switch (lp->type) { case LINUX_DVD_LU_SEND_AGID: lp->lsa.agid = bp->agid; break; case LINUX_DVD_HOST_SEND_CHALLENGE: lp->type = LINUX_DVD_LU_SEND_KEY1; break; case LINUX_DVD_LU_SEND_KEY1: memcpy(lp->lsk.key, bp->keychal, sizeof(lp->lsk.key)); break; case LINUX_DVD_LU_SEND_CHALLENGE: memcpy(lp->lsc.chal, bp->keychal, sizeof(lp->lsc.chal)); break; case LINUX_DVD_HOST_SEND_KEY2: lp->type = LINUX_DVD_AUTH_ESTABLISHED; break; case LINUX_DVD_LU_SEND_TITLE_KEY: memcpy(lp->lstk.title_key, bp->keychal, sizeof(lp->lstk.title_key)); lp->lstk.cpm = bp->cpm; lp->lstk.cp_sec = bp->cp_sec; lp->lstk.cgms = bp->cgms; break; case LINUX_DVD_LU_SEND_ASF: lp->lsasf.asf = bp->asf; break; case LINUX_DVD_INVALIDATE_AGID: break; case LINUX_DVD_LU_SEND_RPC_STATE: lp->lrpcs.type = bp->reg_type; lp->lrpcs.vra = bp->vend_rsts; lp->lrpcs.ucca = bp->user_rsts; lp->lrpcs.region_mask = bp->region; lp->lrpcs.rpc_scheme = bp->rpc_scheme; break; case LINUX_DVD_HOST_SEND_RPC_STATE: break; default: return (EINVAL); } return (0); } static int linux_ioctl_cdrom(struct thread *td, struct linux_ioctl_args *args) { cap_rights_t rights; struct file *fp; int error; error = fget(td, args->fd, cap_rights_init(&rights, CAP_IOCTL), &fp); if (error != 0) return (error); switch (args->cmd & 0xffff) { case LINUX_CDROMPAUSE: args->cmd = CDIOCPAUSE; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; case LINUX_CDROMRESUME: args->cmd = CDIOCRESUME; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; case LINUX_CDROMPLAYMSF: args->cmd = CDIOCPLAYMSF; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; case LINUX_CDROMPLAYTRKIND: args->cmd = CDIOCPLAYTRACKS; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; case LINUX_CDROMREADTOCHDR: { struct ioc_toc_header th; struct linux_cdrom_tochdr lth; error = fo_ioctl(fp, CDIOREADTOCHEADER, (caddr_t)&th, td->td_ucred, td); if (!error) { lth.cdth_trk0 = th.starting_track; lth.cdth_trk1 = th.ending_track; copyout(<h, (void *)args->arg, sizeof(lth)); } break; } case LINUX_CDROMREADTOCENTRY: { struct linux_cdrom_tocentry lte; struct ioc_read_toc_single_entry irtse; error = copyin((void *)args->arg, <e, sizeof(lte)); if (error) break; irtse.address_format = lte.cdte_format; irtse.track = lte.cdte_track; error = fo_ioctl(fp, CDIOREADTOCENTRY, (caddr_t)&irtse, td->td_ucred, td); if (!error) { lte.cdte_ctrl = irtse.entry.control; lte.cdte_adr = irtse.entry.addr_type; bsd_to_linux_msf_lba(irtse.address_format, &irtse.entry.addr, <e.cdte_addr); error = copyout(<e, (void *)args->arg, sizeof(lte)); } break; } case LINUX_CDROMSTOP: args->cmd = CDIOCSTOP; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; case LINUX_CDROMSTART: args->cmd = CDIOCSTART; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; case LINUX_CDROMEJECT: args->cmd = CDIOCEJECT; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; /* LINUX_CDROMVOLCTRL */ case LINUX_CDROMSUBCHNL: { struct linux_cdrom_subchnl sc; struct ioc_read_subchannel bsdsc; struct cd_sub_channel_info bsdinfo; + error = copyin((void *)args->arg, &sc, sizeof(sc)); + if (error) + break; + + /* + * Invoke the native ioctl and bounce the returned data through + * the userspace buffer. This works because the Linux structure + * is the same size as our structures for the subchannel header + * and position data. + */ bsdsc.address_format = CD_LBA_FORMAT; bsdsc.data_format = CD_CURRENT_POSITION; bsdsc.track = 0; - bsdsc.data_len = sizeof(bsdinfo); - bsdsc.data = &bsdinfo; - error = fo_ioctl(fp, CDIOCREADSUBCHANNEL_SYSSPACE, - (caddr_t)&bsdsc, td->td_ucred, td); + bsdsc.data_len = sizeof(sc); + bsdsc.data = (void *)args->arg; + error = fo_ioctl(fp, CDIOCREADSUBCHANNEL, (caddr_t)&bsdsc, + td->td_ucred, td); if (error) break; - error = copyin((void *)args->arg, &sc, sizeof(sc)); + error = copyin((void *)args->arg, &bsdinfo, sizeof(bsdinfo)); if (error) break; sc.cdsc_audiostatus = bsdinfo.header.audio_status; sc.cdsc_adr = bsdinfo.what.position.addr_type; sc.cdsc_ctrl = bsdinfo.what.position.control; sc.cdsc_trk = bsdinfo.what.position.track_number; sc.cdsc_ind = bsdinfo.what.position.index_number; set_linux_cdrom_addr(&sc.cdsc_absaddr, sc.cdsc_format, bsdinfo.what.position.absaddr.lba); set_linux_cdrom_addr(&sc.cdsc_reladdr, sc.cdsc_format, bsdinfo.what.position.reladdr.lba); error = copyout(&sc, (void *)args->arg, sizeof(sc)); break; } /* LINUX_CDROMREADMODE2 */ /* LINUX_CDROMREADMODE1 */ /* LINUX_CDROMREADAUDIO */ /* LINUX_CDROMEJECT_SW */ /* LINUX_CDROMMULTISESSION */ /* LINUX_CDROM_GET_UPC */ case LINUX_CDROMRESET: args->cmd = CDIOCRESET; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; /* LINUX_CDROMVOLREAD */ /* LINUX_CDROMREADRAW */ /* LINUX_CDROMREADCOOKED */ /* LINUX_CDROMSEEK */ /* LINUX_CDROMPLAYBLK */ /* LINUX_CDROMREADALL */ /* LINUX_CDROMCLOSETRAY */ /* LINUX_CDROMLOADFROMSLOT */ /* LINUX_CDROMGETSPINDOWN */ /* LINUX_CDROMSETSPINDOWN */ /* LINUX_CDROM_SET_OPTIONS */ /* LINUX_CDROM_CLEAR_OPTIONS */ /* LINUX_CDROM_SELECT_SPEED */ /* LINUX_CDROM_SELECT_DISC */ /* LINUX_CDROM_MEDIA_CHANGED */ /* LINUX_CDROM_DRIVE_STATUS */ /* LINUX_CDROM_DISC_STATUS */ /* LINUX_CDROM_CHANGER_NSLOTS */ /* LINUX_CDROM_LOCKDOOR */ /* LINUX_CDROM_DEBUG */ /* LINUX_CDROM_GET_CAPABILITY */ /* LINUX_CDROMAUDIOBUFSIZ */ case LINUX_DVD_READ_STRUCT: { l_dvd_struct *lds; struct dvd_struct *bds; lds = malloc(sizeof(*lds), M_LINUX, M_WAITOK); bds = malloc(sizeof(*bds), M_LINUX, M_WAITOK); error = copyin((void *)args->arg, lds, sizeof(*lds)); if (error) goto out; error = linux_to_bsd_dvd_struct(lds, bds); if (error) goto out; error = fo_ioctl(fp, DVDIOCREADSTRUCTURE, (caddr_t)bds, td->td_ucred, td); if (error) goto out; error = bsd_to_linux_dvd_struct(bds, lds); if (error) goto out; error = copyout(lds, (void *)args->arg, sizeof(*lds)); out: free(bds, M_LINUX); free(lds, M_LINUX); break; } /* LINUX_DVD_WRITE_STRUCT */ case LINUX_DVD_AUTH: { l_dvd_authinfo lda; struct dvd_authinfo bda; int bcode; error = copyin((void *)args->arg, &lda, sizeof(lda)); if (error) break; error = linux_to_bsd_dvd_authinfo(&lda, &bcode, &bda); if (error) break; error = fo_ioctl(fp, bcode, (caddr_t)&bda, td->td_ucred, td); if (error) { if (lda.type == LINUX_DVD_HOST_SEND_KEY2) { lda.type = LINUX_DVD_AUTH_FAILURE; copyout(&lda, (void *)args->arg, sizeof(lda)); } break; } error = bsd_to_linux_dvd_authinfo(&bda, &lda); if (error) break; error = copyout(&lda, (void *)args->arg, sizeof(lda)); break; } case LINUX_SCSI_GET_BUS_NUMBER: { struct sg_scsi_id id; error = fo_ioctl(fp, SG_GET_SCSI_ID, (caddr_t)&id, td->td_ucred, td); if (error) break; error = copyout(&id.channel, (void *)args->arg, sizeof(int)); break; } case LINUX_SCSI_GET_IDLUN: { struct sg_scsi_id id; struct scsi_idlun idl; error = fo_ioctl(fp, SG_GET_SCSI_ID, (caddr_t)&id, td->td_ucred, td); if (error) break; idl.dev_id = (id.scsi_id & 0xff) + ((id.lun & 0xff) << 8) + ((id.channel & 0xff) << 16) + ((id.host_no & 0xff) << 24); idl.host_unique_id = id.host_no; error = copyout(&idl, (void *)args->arg, sizeof(idl)); break; } /* LINUX_CDROM_SEND_PACKET */ /* LINUX_CDROM_NEXT_WRITABLE */ /* LINUX_CDROM_LAST_WRITTEN */ default: error = ENOIOCTL; break; } fdrop(fp, td); return (error); } static int linux_ioctl_vfat(struct thread *td, struct linux_ioctl_args *args) { return (ENOTTY); } /* * Sound related ioctls */ struct linux_old_mixer_info { char id[16]; char name[32]; }; static u_int32_t dirbits[4] = { IOC_VOID, IOC_IN, IOC_OUT, IOC_INOUT }; #define SETDIR(c) (((c) & ~IOC_DIRMASK) | dirbits[args->cmd >> 30]) static int linux_ioctl_sound(struct thread *td, struct linux_ioctl_args *args) { switch (args->cmd & 0xffff) { case LINUX_SOUND_MIXER_WRITE_VOLUME: args->cmd = SETDIR(SOUND_MIXER_WRITE_VOLUME); return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SOUND_MIXER_WRITE_BASS: args->cmd = SETDIR(SOUND_MIXER_WRITE_BASS); return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SOUND_MIXER_WRITE_TREBLE: args->cmd = SETDIR(SOUND_MIXER_WRITE_TREBLE); return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SOUND_MIXER_WRITE_SYNTH: args->cmd = SETDIR(SOUND_MIXER_WRITE_SYNTH); return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SOUND_MIXER_WRITE_PCM: args->cmd = SETDIR(SOUND_MIXER_WRITE_PCM); return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SOUND_MIXER_WRITE_SPEAKER: args->cmd = SETDIR(SOUND_MIXER_WRITE_SPEAKER); return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SOUND_MIXER_WRITE_LINE: args->cmd = SETDIR(SOUND_MIXER_WRITE_LINE); return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SOUND_MIXER_WRITE_MIC: args->cmd = SETDIR(SOUND_MIXER_WRITE_MIC); return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SOUND_MIXER_WRITE_CD: args->cmd = SETDIR(SOUND_MIXER_WRITE_CD); return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SOUND_MIXER_WRITE_IMIX: args->cmd = SETDIR(SOUND_MIXER_WRITE_IMIX); return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SOUND_MIXER_WRITE_ALTPCM: args->cmd = SETDIR(SOUND_MIXER_WRITE_ALTPCM); return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SOUND_MIXER_WRITE_RECLEV: args->cmd = SETDIR(SOUND_MIXER_WRITE_RECLEV); return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SOUND_MIXER_WRITE_IGAIN: args->cmd = SETDIR(SOUND_MIXER_WRITE_IGAIN); return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SOUND_MIXER_WRITE_OGAIN: args->cmd = SETDIR(SOUND_MIXER_WRITE_OGAIN); return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SOUND_MIXER_WRITE_LINE1: args->cmd = SETDIR(SOUND_MIXER_WRITE_LINE1); return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SOUND_MIXER_WRITE_LINE2: args->cmd = SETDIR(SOUND_MIXER_WRITE_LINE2); return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SOUND_MIXER_WRITE_LINE3: args->cmd = SETDIR(SOUND_MIXER_WRITE_LINE3); return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SOUND_MIXER_INFO: { /* Key on encoded length */ switch ((args->cmd >> 16) & 0x1fff) { case 0x005c: { /* SOUND_MIXER_INFO */ args->cmd = SOUND_MIXER_INFO; return (sys_ioctl(td, (struct ioctl_args *)args)); } case 0x0030: { /* SOUND_OLD_MIXER_INFO */ struct linux_old_mixer_info info; bzero(&info, sizeof(info)); strncpy(info.id, "OSS", sizeof(info.id) - 1); strncpy(info.name, "FreeBSD OSS Mixer", sizeof(info.name) - 1); copyout(&info, (void *)args->arg, sizeof(info)); return (0); } default: return (ENOIOCTL); } break; } case LINUX_OSS_GETVERSION: { int version = linux_get_oss_version(td); return (copyout(&version, (void *)args->arg, sizeof(int))); } case LINUX_SOUND_MIXER_READ_STEREODEVS: args->cmd = SOUND_MIXER_READ_STEREODEVS; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SOUND_MIXER_READ_CAPS: args->cmd = SOUND_MIXER_READ_CAPS; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SOUND_MIXER_READ_RECMASK: args->cmd = SOUND_MIXER_READ_RECMASK; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SOUND_MIXER_READ_DEVMASK: args->cmd = SOUND_MIXER_READ_DEVMASK; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SOUND_MIXER_WRITE_RECSRC: args->cmd = SETDIR(SOUND_MIXER_WRITE_RECSRC); return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SNDCTL_DSP_RESET: args->cmd = SNDCTL_DSP_RESET; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SNDCTL_DSP_SYNC: args->cmd = SNDCTL_DSP_SYNC; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SNDCTL_DSP_SPEED: args->cmd = SNDCTL_DSP_SPEED; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SNDCTL_DSP_STEREO: args->cmd = SNDCTL_DSP_STEREO; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SNDCTL_DSP_GETBLKSIZE: /* LINUX_SNDCTL_DSP_SETBLKSIZE */ args->cmd = SNDCTL_DSP_GETBLKSIZE; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SNDCTL_DSP_SETFMT: args->cmd = SNDCTL_DSP_SETFMT; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SOUND_PCM_WRITE_CHANNELS: args->cmd = SOUND_PCM_WRITE_CHANNELS; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SOUND_PCM_WRITE_FILTER: args->cmd = SOUND_PCM_WRITE_FILTER; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SNDCTL_DSP_POST: args->cmd = SNDCTL_DSP_POST; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SNDCTL_DSP_SUBDIVIDE: args->cmd = SNDCTL_DSP_SUBDIVIDE; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SNDCTL_DSP_SETFRAGMENT: args->cmd = SNDCTL_DSP_SETFRAGMENT; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SNDCTL_DSP_GETFMTS: args->cmd = SNDCTL_DSP_GETFMTS; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SNDCTL_DSP_GETOSPACE: args->cmd = SNDCTL_DSP_GETOSPACE; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SNDCTL_DSP_GETISPACE: args->cmd = SNDCTL_DSP_GETISPACE; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SNDCTL_DSP_NONBLOCK: args->cmd = SNDCTL_DSP_NONBLOCK; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SNDCTL_DSP_GETCAPS: args->cmd = SNDCTL_DSP_GETCAPS; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SNDCTL_DSP_SETTRIGGER: /* LINUX_SNDCTL_GETTRIGGER */ args->cmd = SNDCTL_DSP_SETTRIGGER; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SNDCTL_DSP_GETIPTR: args->cmd = SNDCTL_DSP_GETIPTR; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SNDCTL_DSP_GETOPTR: args->cmd = SNDCTL_DSP_GETOPTR; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SNDCTL_DSP_SETDUPLEX: args->cmd = SNDCTL_DSP_SETDUPLEX; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SNDCTL_DSP_GETODELAY: args->cmd = SNDCTL_DSP_GETODELAY; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SNDCTL_SEQ_RESET: args->cmd = SNDCTL_SEQ_RESET; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SNDCTL_SEQ_SYNC: args->cmd = SNDCTL_SEQ_SYNC; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SNDCTL_SYNTH_INFO: args->cmd = SNDCTL_SYNTH_INFO; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SNDCTL_SEQ_CTRLRATE: args->cmd = SNDCTL_SEQ_CTRLRATE; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SNDCTL_SEQ_GETOUTCOUNT: args->cmd = SNDCTL_SEQ_GETOUTCOUNT; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SNDCTL_SEQ_GETINCOUNT: args->cmd = SNDCTL_SEQ_GETINCOUNT; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SNDCTL_SEQ_PERCMODE: args->cmd = SNDCTL_SEQ_PERCMODE; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SNDCTL_FM_LOAD_INSTR: args->cmd = SNDCTL_FM_LOAD_INSTR; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SNDCTL_SEQ_TESTMIDI: args->cmd = SNDCTL_SEQ_TESTMIDI; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SNDCTL_SEQ_RESETSAMPLES: args->cmd = SNDCTL_SEQ_RESETSAMPLES; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SNDCTL_SEQ_NRSYNTHS: args->cmd = SNDCTL_SEQ_NRSYNTHS; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SNDCTL_SEQ_NRMIDIS: args->cmd = SNDCTL_SEQ_NRMIDIS; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SNDCTL_MIDI_INFO: args->cmd = SNDCTL_MIDI_INFO; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SNDCTL_SEQ_TRESHOLD: args->cmd = SNDCTL_SEQ_TRESHOLD; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SNDCTL_SYNTH_MEMAVL: args->cmd = SNDCTL_SYNTH_MEMAVL; return (sys_ioctl(td, (struct ioctl_args *)args)); } return (ENOIOCTL); } /* * Console related ioctls */ static int linux_ioctl_console(struct thread *td, struct linux_ioctl_args *args) { cap_rights_t rights; struct file *fp; int error; error = fget(td, args->fd, cap_rights_init(&rights, CAP_IOCTL), &fp); if (error != 0) return (error); switch (args->cmd & 0xffff) { case LINUX_KIOCSOUND: args->cmd = KIOCSOUND; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; case LINUX_KDMKTONE: args->cmd = KDMKTONE; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; case LINUX_KDGETLED: args->cmd = KDGETLED; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; case LINUX_KDSETLED: args->cmd = KDSETLED; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; case LINUX_KDSETMODE: args->cmd = KDSETMODE; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; case LINUX_KDGETMODE: args->cmd = KDGETMODE; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; case LINUX_KDGKBMODE: args->cmd = KDGKBMODE; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; case LINUX_KDSKBMODE: { int kbdmode; switch (args->arg) { case LINUX_KBD_RAW: kbdmode = K_RAW; break; case LINUX_KBD_XLATE: kbdmode = K_XLATE; break; case LINUX_KBD_MEDIUMRAW: kbdmode = K_RAW; break; default: fdrop(fp, td); return (EINVAL); } error = (fo_ioctl(fp, KDSKBMODE, (caddr_t)&kbdmode, td->td_ucred, td)); break; } case LINUX_VT_OPENQRY: args->cmd = VT_OPENQRY; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; case LINUX_VT_GETMODE: args->cmd = VT_GETMODE; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; case LINUX_VT_SETMODE: { struct vt_mode mode; if ((error = copyin((void *)args->arg, &mode, sizeof(mode)))) break; if (LINUX_SIG_VALID(mode.relsig)) mode.relsig = linux_to_bsd_signal(mode.relsig); else mode.relsig = 0; if (LINUX_SIG_VALID(mode.acqsig)) mode.acqsig = linux_to_bsd_signal(mode.acqsig); else mode.acqsig = 0; /* XXX. Linux ignores frsig and set it to 0. */ mode.frsig = 0; if ((error = copyout(&mode, (void *)args->arg, sizeof(mode)))) break; args->cmd = VT_SETMODE; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; } case LINUX_VT_GETSTATE: args->cmd = VT_GETACTIVE; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; case LINUX_VT_RELDISP: args->cmd = VT_RELDISP; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; case LINUX_VT_ACTIVATE: args->cmd = VT_ACTIVATE; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; case LINUX_VT_WAITACTIVE: args->cmd = VT_WAITACTIVE; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; default: error = ENOIOCTL; break; } fdrop(fp, td); return (error); } /* * Criteria for interface name translation */ #define IFP_IS_ETH(ifp) (ifp->if_type == IFT_ETHER) /* * Translate a Linux interface name to a FreeBSD interface name, * and return the associated ifnet structure * bsdname and lxname need to be least IFNAMSIZ bytes long, but * can point to the same buffer. */ static struct ifnet * ifname_linux_to_bsd(struct thread *td, const char *lxname, char *bsdname) { struct ifnet *ifp; int len, unit; char *ep; int is_eth, index; for (len = 0; len < LINUX_IFNAMSIZ; ++len) if (!isalpha(lxname[len])) break; if (len == 0 || len == LINUX_IFNAMSIZ) return (NULL); unit = (int)strtoul(lxname + len, &ep, 10); if (ep == NULL || ep == lxname + len || ep >= lxname + LINUX_IFNAMSIZ) return (NULL); index = 0; is_eth = (len == 3 && !strncmp(lxname, "eth", len)) ? 1 : 0; CURVNET_SET(TD_TO_VNET(td)); IFNET_RLOCK(); TAILQ_FOREACH(ifp, &V_ifnet, if_link) { /* * Allow Linux programs to use FreeBSD names. Don't presume * we never have an interface named "eth", so don't make * the test optional based on is_eth. */ if (strncmp(ifp->if_xname, lxname, LINUX_IFNAMSIZ) == 0) break; if (is_eth && IFP_IS_ETH(ifp) && unit == index++) break; } IFNET_RUNLOCK(); CURVNET_RESTORE(); if (ifp != NULL) strlcpy(bsdname, ifp->if_xname, IFNAMSIZ); return (ifp); } /* * Implement the SIOCGIFNAME ioctl */ static int linux_ioctl_ifname(struct thread *td, struct l_ifreq *uifr) { struct l_ifreq ifr; struct ifnet *ifp; int error, ethno, index; error = copyin(uifr, &ifr, sizeof(ifr)); if (error != 0) return (error); CURVNET_SET(TD_TO_VNET(curthread)); IFNET_RLOCK(); index = 1; /* ifr.ifr_ifindex starts from 1 */ ethno = 0; error = ENODEV; TAILQ_FOREACH(ifp, &V_ifnet, if_link) { if (ifr.ifr_ifindex == index) { if (IFP_IS_ETH(ifp)) snprintf(ifr.ifr_name, LINUX_IFNAMSIZ, "eth%d", ethno); else strlcpy(ifr.ifr_name, ifp->if_xname, LINUX_IFNAMSIZ); error = 0; break; } if (IFP_IS_ETH(ifp)) ethno++; index++; } IFNET_RUNLOCK(); if (error == 0) error = copyout(&ifr, uifr, sizeof(ifr)); CURVNET_RESTORE(); return (error); } /* * Implement the SIOCGIFCONF ioctl */ static int linux_ifconf(struct thread *td, struct ifconf *uifc) { #ifdef COMPAT_LINUX32 struct l_ifconf ifc; #else struct ifconf ifc; #endif struct l_ifreq ifr; struct ifnet *ifp; struct ifaddr *ifa; struct sbuf *sb; int error, ethno, full = 0, valid_len, max_len; error = copyin(uifc, &ifc, sizeof(ifc)); if (error != 0) return (error); max_len = MAXPHYS - 1; CURVNET_SET(TD_TO_VNET(td)); /* handle the 'request buffer size' case */ if ((l_uintptr_t)ifc.ifc_buf == PTROUT(NULL)) { ifc.ifc_len = 0; IFNET_RLOCK(); TAILQ_FOREACH(ifp, &V_ifnet, if_link) { TAILQ_FOREACH(ifa, &ifp->if_addrhead, ifa_link) { struct sockaddr *sa = ifa->ifa_addr; if (sa->sa_family == AF_INET) ifc.ifc_len += sizeof(ifr); } } IFNET_RUNLOCK(); error = copyout(&ifc, uifc, sizeof(ifc)); CURVNET_RESTORE(); return (error); } if (ifc.ifc_len <= 0) { CURVNET_RESTORE(); return (EINVAL); } again: /* Keep track of eth interfaces */ ethno = 0; if (ifc.ifc_len <= max_len) { max_len = ifc.ifc_len; full = 1; } sb = sbuf_new(NULL, NULL, max_len + 1, SBUF_FIXEDLEN); max_len = 0; valid_len = 0; /* Return all AF_INET addresses of all interfaces */ IFNET_RLOCK(); TAILQ_FOREACH(ifp, &V_ifnet, if_link) { int addrs = 0; bzero(&ifr, sizeof(ifr)); if (IFP_IS_ETH(ifp)) snprintf(ifr.ifr_name, LINUX_IFNAMSIZ, "eth%d", ethno++); else strlcpy(ifr.ifr_name, ifp->if_xname, LINUX_IFNAMSIZ); /* Walk the address list */ TAILQ_FOREACH(ifa, &ifp->if_addrhead, ifa_link) { struct sockaddr *sa = ifa->ifa_addr; if (sa->sa_family == AF_INET) { ifr.ifr_addr.sa_family = LINUX_AF_INET; memcpy(ifr.ifr_addr.sa_data, sa->sa_data, sizeof(ifr.ifr_addr.sa_data)); sbuf_bcat(sb, &ifr, sizeof(ifr)); max_len += sizeof(ifr); addrs++; } if (sbuf_error(sb) == 0) valid_len = sbuf_len(sb); } if (addrs == 0) { bzero((caddr_t)&ifr.ifr_addr, sizeof(ifr.ifr_addr)); sbuf_bcat(sb, &ifr, sizeof(ifr)); max_len += sizeof(ifr); if (sbuf_error(sb) == 0) valid_len = sbuf_len(sb); } } IFNET_RUNLOCK(); if (valid_len != max_len && !full) { sbuf_delete(sb); goto again; } ifc.ifc_len = valid_len; sbuf_finish(sb); error = copyout(sbuf_data(sb), PTRIN(ifc.ifc_buf), ifc.ifc_len); if (error == 0) error = copyout(&ifc, uifc, sizeof(ifc)); sbuf_delete(sb); CURVNET_RESTORE(); return (error); } static int linux_gifflags(struct thread *td, struct ifnet *ifp, struct l_ifreq *ifr) { l_short flags; flags = (ifp->if_flags | ifp->if_drv_flags) & 0xffff; /* these flags have no Linux equivalent */ flags &= ~(IFF_DRV_OACTIVE|IFF_SIMPLEX| IFF_LINK0|IFF_LINK1|IFF_LINK2); /* Linux' multicast flag is in a different bit */ if (flags & IFF_MULTICAST) { flags &= ~IFF_MULTICAST; flags |= 0x1000; } return (copyout(&flags, &ifr->ifr_flags, sizeof(flags))); } #define ARPHRD_ETHER 1 #define ARPHRD_LOOPBACK 772 static int linux_gifhwaddr(struct ifnet *ifp, struct l_ifreq *ifr) { struct ifaddr *ifa; struct sockaddr_dl *sdl; struct l_sockaddr lsa; if (ifp->if_type == IFT_LOOP) { bzero(&lsa, sizeof(lsa)); lsa.sa_family = ARPHRD_LOOPBACK; return (copyout(&lsa, &ifr->ifr_hwaddr, sizeof(lsa))); } if (ifp->if_type != IFT_ETHER) return (ENOENT); TAILQ_FOREACH(ifa, &ifp->if_addrhead, ifa_link) { sdl = (struct sockaddr_dl*)ifa->ifa_addr; if (sdl != NULL && (sdl->sdl_family == AF_LINK) && (sdl->sdl_type == IFT_ETHER)) { bzero(&lsa, sizeof(lsa)); lsa.sa_family = ARPHRD_ETHER; bcopy(LLADDR(sdl), lsa.sa_data, LINUX_IFHWADDRLEN); return (copyout(&lsa, &ifr->ifr_hwaddr, sizeof(lsa))); } } return (ENOENT); } /* * If we fault in bsd_to_linux_ifreq() then we will fault when we call * the native ioctl(). Thus, we don't really need to check the return * value of this function. */ static int bsd_to_linux_ifreq(struct ifreq *arg) { struct ifreq ifr; size_t ifr_len = sizeof(struct ifreq); int error; if ((error = copyin(arg, &ifr, ifr_len))) return (error); *(u_short *)&ifr.ifr_addr = ifr.ifr_addr.sa_family; error = copyout(&ifr, arg, ifr_len); return (error); } /* * Socket related ioctls */ static int linux_ioctl_socket(struct thread *td, struct linux_ioctl_args *args) { char lifname[LINUX_IFNAMSIZ], ifname[IFNAMSIZ]; cap_rights_t rights; struct ifnet *ifp; struct file *fp; int error, type; ifp = NULL; error = 0; error = fget(td, args->fd, cap_rights_init(&rights, CAP_IOCTL), &fp); if (error != 0) return (error); type = fp->f_type; fdrop(fp, td); if (type != DTYPE_SOCKET) { /* not a socket - probably a tap / vmnet device */ switch (args->cmd) { case LINUX_SIOCGIFADDR: case LINUX_SIOCSIFADDR: case LINUX_SIOCGIFFLAGS: return (linux_ioctl_special(td, args)); default: return (ENOIOCTL); } } switch (args->cmd & 0xffff) { case LINUX_FIOGETOWN: case LINUX_FIOSETOWN: case LINUX_SIOCADDMULTI: case LINUX_SIOCATMARK: case LINUX_SIOCDELMULTI: case LINUX_SIOCGIFNAME: case LINUX_SIOCGIFCONF: case LINUX_SIOCGPGRP: case LINUX_SIOCSPGRP: case LINUX_SIOCGIFCOUNT: /* these ioctls don't take an interface name */ #ifdef DEBUG printf("%s(): ioctl %d\n", __func__, args->cmd & 0xffff); #endif break; case LINUX_SIOCGIFFLAGS: case LINUX_SIOCGIFADDR: case LINUX_SIOCSIFADDR: case LINUX_SIOCGIFDSTADDR: case LINUX_SIOCGIFBRDADDR: case LINUX_SIOCGIFNETMASK: case LINUX_SIOCSIFNETMASK: case LINUX_SIOCGIFMTU: case LINUX_SIOCSIFMTU: case LINUX_SIOCSIFNAME: case LINUX_SIOCGIFHWADDR: case LINUX_SIOCSIFHWADDR: case LINUX_SIOCDEVPRIVATE: case LINUX_SIOCDEVPRIVATE+1: case LINUX_SIOCGIFINDEX: /* copy in the interface name and translate it. */ error = copyin((void *)args->arg, lifname, LINUX_IFNAMSIZ); if (error != 0) return (error); #ifdef DEBUG printf("%s(): ioctl %d on %.*s\n", __func__, args->cmd & 0xffff, LINUX_IFNAMSIZ, lifname); #endif memset(ifname, 0, sizeof(ifname)); ifp = ifname_linux_to_bsd(td, lifname, ifname); if (ifp == NULL) return (EINVAL); /* * We need to copy it back out in case we pass the * request on to our native ioctl(), which will expect * the ifreq to be in user space and have the correct * interface name. */ error = copyout(ifname, (void *)args->arg, IFNAMSIZ); if (error != 0) return (error); #ifdef DEBUG printf("%s(): %s translated to %s\n", __func__, lifname, ifname); #endif break; default: return (ENOIOCTL); } switch (args->cmd & 0xffff) { case LINUX_FIOSETOWN: args->cmd = FIOSETOWN; error = sys_ioctl(td, (struct ioctl_args *)args); break; case LINUX_SIOCSPGRP: args->cmd = SIOCSPGRP; error = sys_ioctl(td, (struct ioctl_args *)args); break; case LINUX_FIOGETOWN: args->cmd = FIOGETOWN; error = sys_ioctl(td, (struct ioctl_args *)args); break; case LINUX_SIOCGPGRP: args->cmd = SIOCGPGRP; error = sys_ioctl(td, (struct ioctl_args *)args); break; case LINUX_SIOCATMARK: args->cmd = SIOCATMARK; error = sys_ioctl(td, (struct ioctl_args *)args); break; /* LINUX_SIOCGSTAMP */ case LINUX_SIOCGIFNAME: error = linux_ioctl_ifname(td, (struct l_ifreq *)args->arg); break; case LINUX_SIOCGIFCONF: error = linux_ifconf(td, (struct ifconf *)args->arg); break; case LINUX_SIOCGIFFLAGS: args->cmd = SIOCGIFFLAGS; error = linux_gifflags(td, ifp, (struct l_ifreq *)args->arg); break; case LINUX_SIOCGIFADDR: args->cmd = SIOCGIFADDR; error = sys_ioctl(td, (struct ioctl_args *)args); bsd_to_linux_ifreq((struct ifreq *)args->arg); break; case LINUX_SIOCSIFADDR: /* XXX probably doesn't work, included for completeness */ args->cmd = SIOCSIFADDR; error = sys_ioctl(td, (struct ioctl_args *)args); break; case LINUX_SIOCGIFDSTADDR: args->cmd = SIOCGIFDSTADDR; error = sys_ioctl(td, (struct ioctl_args *)args); bsd_to_linux_ifreq((struct ifreq *)args->arg); break; case LINUX_SIOCGIFBRDADDR: args->cmd = SIOCGIFBRDADDR; error = sys_ioctl(td, (struct ioctl_args *)args); bsd_to_linux_ifreq((struct ifreq *)args->arg); break; case LINUX_SIOCGIFNETMASK: args->cmd = SIOCGIFNETMASK; error = sys_ioctl(td, (struct ioctl_args *)args); bsd_to_linux_ifreq((struct ifreq *)args->arg); break; case LINUX_SIOCSIFNETMASK: error = ENOIOCTL; break; case LINUX_SIOCGIFMTU: args->cmd = SIOCGIFMTU; error = sys_ioctl(td, (struct ioctl_args *)args); break; case LINUX_SIOCSIFMTU: args->cmd = SIOCSIFMTU; error = sys_ioctl(td, (struct ioctl_args *)args); break; case LINUX_SIOCSIFNAME: error = ENOIOCTL; break; case LINUX_SIOCGIFHWADDR: error = linux_gifhwaddr(ifp, (struct l_ifreq *)args->arg); break; case LINUX_SIOCSIFHWADDR: error = ENOIOCTL; break; case LINUX_SIOCADDMULTI: args->cmd = SIOCADDMULTI; error = sys_ioctl(td, (struct ioctl_args *)args); break; case LINUX_SIOCDELMULTI: args->cmd = SIOCDELMULTI; error = sys_ioctl(td, (struct ioctl_args *)args); break; case LINUX_SIOCGIFINDEX: args->cmd = SIOCGIFINDEX; error = sys_ioctl(td, (struct ioctl_args *)args); break; case LINUX_SIOCGIFCOUNT: error = 0; break; /* * XXX This is slightly bogus, but these ioctls are currently * XXX only used by the aironet (if_an) network driver. */ case LINUX_SIOCDEVPRIVATE: args->cmd = SIOCGPRIVATE_0; error = sys_ioctl(td, (struct ioctl_args *)args); break; case LINUX_SIOCDEVPRIVATE+1: args->cmd = SIOCGPRIVATE_1; error = sys_ioctl(td, (struct ioctl_args *)args); break; } if (ifp != NULL) /* restore the original interface name */ copyout(lifname, (void *)args->arg, LINUX_IFNAMSIZ); #ifdef DEBUG printf("%s(): returning %d\n", __func__, error); #endif return (error); } /* * Device private ioctl handler */ static int linux_ioctl_private(struct thread *td, struct linux_ioctl_args *args) { cap_rights_t rights; struct file *fp; int error, type; error = fget(td, args->fd, cap_rights_init(&rights, CAP_IOCTL), &fp); if (error != 0) return (error); type = fp->f_type; fdrop(fp, td); if (type == DTYPE_SOCKET) return (linux_ioctl_socket(td, args)); return (ENOIOCTL); } /* * DRM ioctl handler (sys/dev/drm) */ static int linux_ioctl_drm(struct thread *td, struct linux_ioctl_args *args) { args->cmd = SETDIR(args->cmd); return (sys_ioctl(td, (struct ioctl_args *)args)); } #ifdef COMPAT_LINUX32 #define CP(src,dst,fld) do { (dst).fld = (src).fld; } while (0) #define PTRIN_CP(src,dst,fld) \ do { (dst).fld = PTRIN((src).fld); } while (0) #define PTROUT_CP(src,dst,fld) \ do { (dst).fld = PTROUT((src).fld); } while (0) static int linux_ioctl_sg_io(struct thread *td, struct linux_ioctl_args *args) { struct sg_io_hdr io; struct sg_io_hdr32 io32; cap_rights_t rights; struct file *fp; int error; error = fget(td, args->fd, cap_rights_init(&rights, CAP_IOCTL), &fp); if (error != 0) { printf("sg_linux_ioctl: fget returned %d\n", error); return (error); } if ((error = copyin((void *)args->arg, &io32, sizeof(io32))) != 0) goto out; CP(io32, io, interface_id); CP(io32, io, dxfer_direction); CP(io32, io, cmd_len); CP(io32, io, mx_sb_len); CP(io32, io, iovec_count); CP(io32, io, dxfer_len); PTRIN_CP(io32, io, dxferp); PTRIN_CP(io32, io, cmdp); PTRIN_CP(io32, io, sbp); CP(io32, io, timeout); CP(io32, io, flags); CP(io32, io, pack_id); PTRIN_CP(io32, io, usr_ptr); CP(io32, io, status); CP(io32, io, masked_status); CP(io32, io, msg_status); CP(io32, io, sb_len_wr); CP(io32, io, host_status); CP(io32, io, driver_status); CP(io32, io, resid); CP(io32, io, duration); CP(io32, io, info); if ((error = fo_ioctl(fp, SG_IO, (caddr_t)&io, td->td_ucred, td)) != 0) goto out; CP(io, io32, interface_id); CP(io, io32, dxfer_direction); CP(io, io32, cmd_len); CP(io, io32, mx_sb_len); CP(io, io32, iovec_count); CP(io, io32, dxfer_len); PTROUT_CP(io, io32, dxferp); PTROUT_CP(io, io32, cmdp); PTROUT_CP(io, io32, sbp); CP(io, io32, timeout); CP(io, io32, flags); CP(io, io32, pack_id); PTROUT_CP(io, io32, usr_ptr); CP(io, io32, status); CP(io, io32, masked_status); CP(io, io32, msg_status); CP(io, io32, sb_len_wr); CP(io, io32, host_status); CP(io, io32, driver_status); CP(io, io32, resid); CP(io, io32, duration); CP(io, io32, info); error = copyout(&io32, (void *)args->arg, sizeof(io32)); out: fdrop(fp, td); return (error); } #endif static int linux_ioctl_sg(struct thread *td, struct linux_ioctl_args *args) { switch (args->cmd) { case LINUX_SG_GET_VERSION_NUM: args->cmd = SG_GET_VERSION_NUM; break; case LINUX_SG_SET_TIMEOUT: args->cmd = SG_SET_TIMEOUT; break; case LINUX_SG_GET_TIMEOUT: args->cmd = SG_GET_TIMEOUT; break; case LINUX_SG_IO: args->cmd = SG_IO; #ifdef COMPAT_LINUX32 return (linux_ioctl_sg_io(td, args)); #endif break; case LINUX_SG_GET_RESERVED_SIZE: args->cmd = SG_GET_RESERVED_SIZE; break; case LINUX_SG_GET_SCSI_ID: args->cmd = SG_GET_SCSI_ID; break; case LINUX_SG_GET_SG_TABLESIZE: args->cmd = SG_GET_SG_TABLESIZE; break; default: return (ENODEV); } return (sys_ioctl(td, (struct ioctl_args *)args)); } /* * Video4Linux (V4L) ioctl handler */ static int linux_to_bsd_v4l_tuner(struct l_video_tuner *lvt, struct video_tuner *vt) { vt->tuner = lvt->tuner; strlcpy(vt->name, lvt->name, LINUX_VIDEO_TUNER_NAME_SIZE); vt->rangelow = lvt->rangelow; /* possible long size conversion */ vt->rangehigh = lvt->rangehigh; /* possible long size conversion */ vt->flags = lvt->flags; vt->mode = lvt->mode; vt->signal = lvt->signal; return (0); } static int bsd_to_linux_v4l_tuner(struct video_tuner *vt, struct l_video_tuner *lvt) { lvt->tuner = vt->tuner; strlcpy(lvt->name, vt->name, LINUX_VIDEO_TUNER_NAME_SIZE); lvt->rangelow = vt->rangelow; /* possible long size conversion */ lvt->rangehigh = vt->rangehigh; /* possible long size conversion */ lvt->flags = vt->flags; lvt->mode = vt->mode; lvt->signal = vt->signal; return (0); } #ifdef COMPAT_LINUX_V4L_CLIPLIST static int linux_to_bsd_v4l_clip(struct l_video_clip *lvc, struct video_clip *vc) { vc->x = lvc->x; vc->y = lvc->y; vc->width = lvc->width; vc->height = lvc->height; vc->next = PTRIN(lvc->next); /* possible pointer size conversion */ return (0); } #endif static int linux_to_bsd_v4l_window(struct l_video_window *lvw, struct video_window *vw) { vw->x = lvw->x; vw->y = lvw->y; vw->width = lvw->width; vw->height = lvw->height; vw->chromakey = lvw->chromakey; vw->flags = lvw->flags; vw->clips = PTRIN(lvw->clips); /* possible pointer size conversion */ vw->clipcount = lvw->clipcount; return (0); } static int bsd_to_linux_v4l_window(struct video_window *vw, struct l_video_window *lvw) { memset(lvw, 0, sizeof(*lvw)); lvw->x = vw->x; lvw->y = vw->y; lvw->width = vw->width; lvw->height = vw->height; lvw->chromakey = vw->chromakey; lvw->flags = vw->flags; lvw->clips = PTROUT(vw->clips); /* possible pointer size conversion */ lvw->clipcount = vw->clipcount; return (0); } static int linux_to_bsd_v4l_buffer(struct l_video_buffer *lvb, struct video_buffer *vb) { vb->base = PTRIN(lvb->base); /* possible pointer size conversion */ vb->height = lvb->height; vb->width = lvb->width; vb->depth = lvb->depth; vb->bytesperline = lvb->bytesperline; return (0); } static int bsd_to_linux_v4l_buffer(struct video_buffer *vb, struct l_video_buffer *lvb) { lvb->base = PTROUT(vb->base); /* possible pointer size conversion */ lvb->height = vb->height; lvb->width = vb->width; lvb->depth = vb->depth; lvb->bytesperline = vb->bytesperline; return (0); } static int linux_to_bsd_v4l_code(struct l_video_code *lvc, struct video_code *vc) { strlcpy(vc->loadwhat, lvc->loadwhat, LINUX_VIDEO_CODE_LOADWHAT_SIZE); vc->datasize = lvc->datasize; vc->data = PTRIN(lvc->data); /* possible pointer size conversion */ return (0); } #ifdef COMPAT_LINUX_V4L_CLIPLIST static int linux_v4l_clip_copy(void *lvc, struct video_clip **ppvc) { int error; struct video_clip vclip; struct l_video_clip l_vclip; error = copyin(lvc, &l_vclip, sizeof(l_vclip)); if (error) return (error); linux_to_bsd_v4l_clip(&l_vclip, &vclip); /* XXX: If there can be no concurrency: s/M_NOWAIT/M_WAITOK/ */ if ((*ppvc = malloc(sizeof(**ppvc), M_LINUX, M_NOWAIT)) == NULL) return (ENOMEM); /* XXX: Linux has no ENOMEM here. */ memcpy(*ppvc, &vclip, sizeof(vclip)); (*ppvc)->next = NULL; return (0); } static int linux_v4l_cliplist_free(struct video_window *vw) { struct video_clip **ppvc; struct video_clip **ppvc_next; for (ppvc = &(vw->clips); *ppvc != NULL; ppvc = ppvc_next) { ppvc_next = &((*ppvc)->next); free(*ppvc, M_LINUX); } vw->clips = NULL; return (0); } static int linux_v4l_cliplist_copy(struct l_video_window *lvw, struct video_window *vw) { int error; int clipcount; void *plvc; struct video_clip **ppvc; /* * XXX: The cliplist is used to pass in a list of clipping * rectangles or, if clipcount == VIDEO_CLIP_BITMAP, a * clipping bitmap. Some Linux apps, however, appear to * leave cliplist and clips uninitialized. In any case, * the cliplist is not used by pwc(4), at the time of * writing, FreeBSD's only V4L driver. When a driver * that uses the cliplist is developed, this code may * need re-examiniation. */ error = 0; clipcount = vw->clipcount; if (clipcount == VIDEO_CLIP_BITMAP) { /* * In this case, the pointer (clips) is overloaded * to be a "void *" to a bitmap, therefore there * is no struct video_clip to copy now. */ } else if (clipcount > 0 && clipcount <= 16384) { /* * Clips points to list of clip rectangles, so * copy the list. * * XXX: Upper limit of 16384 was used here to try to * avoid cases when clipcount and clips pointer * are uninitialized and therefore have high random * values, as is the case in the Linux Skype * application. The value 16384 was chosen as that * is what is used in the Linux stradis(4) MPEG * decoder driver, the only place we found an * example of cliplist use. */ plvc = PTRIN(lvw->clips); vw->clips = NULL; ppvc = &(vw->clips); while (clipcount-- > 0) { if (plvc == NULL) { error = EFAULT; break; } else { error = linux_v4l_clip_copy(plvc, ppvc); if (error) { linux_v4l_cliplist_free(vw); break; } } ppvc = &((*ppvc)->next); plvc = PTRIN(((struct l_video_clip *) plvc)->next); } } else { /* * clipcount == 0 or negative (but not VIDEO_CLIP_BITMAP) * Force cliplist to null. */ vw->clipcount = 0; vw->clips = NULL; } return (error); } #endif static int linux_ioctl_v4l(struct thread *td, struct linux_ioctl_args *args) { cap_rights_t rights; struct file *fp; int error; struct video_tuner vtun; struct video_window vwin; struct video_buffer vbuf; struct video_code vcode; struct l_video_tuner l_vtun; struct l_video_window l_vwin; struct l_video_buffer l_vbuf; struct l_video_code l_vcode; switch (args->cmd & 0xffff) { case LINUX_VIDIOCGCAP: args->cmd = VIDIOCGCAP; break; case LINUX_VIDIOCGCHAN: args->cmd = VIDIOCGCHAN; break; case LINUX_VIDIOCSCHAN: args->cmd = VIDIOCSCHAN; break; case LINUX_VIDIOCGTUNER: error = fget(td, args->fd, cap_rights_init(&rights, CAP_IOCTL), &fp); if (error != 0) return (error); error = copyin((void *) args->arg, &l_vtun, sizeof(l_vtun)); if (error) { fdrop(fp, td); return (error); } linux_to_bsd_v4l_tuner(&l_vtun, &vtun); error = fo_ioctl(fp, VIDIOCGTUNER, &vtun, td->td_ucred, td); if (!error) { bsd_to_linux_v4l_tuner(&vtun, &l_vtun); error = copyout(&l_vtun, (void *) args->arg, sizeof(l_vtun)); } fdrop(fp, td); return (error); case LINUX_VIDIOCSTUNER: error = fget(td, args->fd, cap_rights_init(&rights, CAP_IOCTL), &fp); if (error != 0) return (error); error = copyin((void *) args->arg, &l_vtun, sizeof(l_vtun)); if (error) { fdrop(fp, td); return (error); } linux_to_bsd_v4l_tuner(&l_vtun, &vtun); error = fo_ioctl(fp, VIDIOCSTUNER, &vtun, td->td_ucred, td); fdrop(fp, td); return (error); case LINUX_VIDIOCGPICT: args->cmd = VIDIOCGPICT; break; case LINUX_VIDIOCSPICT: args->cmd = VIDIOCSPICT; break; case LINUX_VIDIOCCAPTURE: args->cmd = VIDIOCCAPTURE; break; case LINUX_VIDIOCGWIN: error = fget(td, args->fd, cap_rights_init(&rights, CAP_IOCTL), &fp); if (error != 0) return (error); error = fo_ioctl(fp, VIDIOCGWIN, &vwin, td->td_ucred, td); if (!error) { bsd_to_linux_v4l_window(&vwin, &l_vwin); error = copyout(&l_vwin, (void *) args->arg, sizeof(l_vwin)); } fdrop(fp, td); return (error); case LINUX_VIDIOCSWIN: error = fget(td, args->fd, cap_rights_init(&rights, CAP_IOCTL), &fp); if (error != 0) return (error); error = copyin((void *) args->arg, &l_vwin, sizeof(l_vwin)); if (error) { fdrop(fp, td); return (error); } linux_to_bsd_v4l_window(&l_vwin, &vwin); #ifdef COMPAT_LINUX_V4L_CLIPLIST error = linux_v4l_cliplist_copy(&l_vwin, &vwin); if (error) { fdrop(fp, td); return (error); } #endif error = fo_ioctl(fp, VIDIOCSWIN, &vwin, td->td_ucred, td); fdrop(fp, td); #ifdef COMPAT_LINUX_V4L_CLIPLIST linux_v4l_cliplist_free(&vwin); #endif return (error); case LINUX_VIDIOCGFBUF: error = fget(td, args->fd, cap_rights_init(&rights, CAP_IOCTL), &fp); if (error != 0) return (error); error = fo_ioctl(fp, VIDIOCGFBUF, &vbuf, td->td_ucred, td); if (!error) { bsd_to_linux_v4l_buffer(&vbuf, &l_vbuf); error = copyout(&l_vbuf, (void *) args->arg, sizeof(l_vbuf)); } fdrop(fp, td); return (error); case LINUX_VIDIOCSFBUF: error = fget(td, args->fd, cap_rights_init(&rights, CAP_IOCTL), &fp); if (error != 0) return (error); error = copyin((void *) args->arg, &l_vbuf, sizeof(l_vbuf)); if (error) { fdrop(fp, td); return (error); } linux_to_bsd_v4l_buffer(&l_vbuf, &vbuf); error = fo_ioctl(fp, VIDIOCSFBUF, &vbuf, td->td_ucred, td); fdrop(fp, td); return (error); case LINUX_VIDIOCKEY: args->cmd = VIDIOCKEY; break; case LINUX_VIDIOCGFREQ: args->cmd = VIDIOCGFREQ; break; case LINUX_VIDIOCSFREQ: args->cmd = VIDIOCSFREQ; break; case LINUX_VIDIOCGAUDIO: args->cmd = VIDIOCGAUDIO; break; case LINUX_VIDIOCSAUDIO: args->cmd = VIDIOCSAUDIO; break; case LINUX_VIDIOCSYNC: args->cmd = VIDIOCSYNC; break; case LINUX_VIDIOCMCAPTURE: args->cmd = VIDIOCMCAPTURE; break; case LINUX_VIDIOCGMBUF: args->cmd = VIDIOCGMBUF; break; case LINUX_VIDIOCGUNIT: args->cmd = VIDIOCGUNIT; break; case LINUX_VIDIOCGCAPTURE: args->cmd = VIDIOCGCAPTURE; break; case LINUX_VIDIOCSCAPTURE: args->cmd = VIDIOCSCAPTURE; break; case LINUX_VIDIOCSPLAYMODE: args->cmd = VIDIOCSPLAYMODE; break; case LINUX_VIDIOCSWRITEMODE: args->cmd = VIDIOCSWRITEMODE; break; case LINUX_VIDIOCGPLAYINFO: args->cmd = VIDIOCGPLAYINFO; break; case LINUX_VIDIOCSMICROCODE: error = fget(td, args->fd, cap_rights_init(&rights, CAP_IOCTL), &fp); if (error != 0) return (error); error = copyin((void *) args->arg, &l_vcode, sizeof(l_vcode)); if (error) { fdrop(fp, td); return (error); } linux_to_bsd_v4l_code(&l_vcode, &vcode); error = fo_ioctl(fp, VIDIOCSMICROCODE, &vcode, td->td_ucred, td); fdrop(fp, td); return (error); case LINUX_VIDIOCGVBIFMT: args->cmd = VIDIOCGVBIFMT; break; case LINUX_VIDIOCSVBIFMT: args->cmd = VIDIOCSVBIFMT; break; default: return (ENOIOCTL); } error = sys_ioctl(td, (struct ioctl_args *)args); return (error); } /* * Special ioctl handler */ static int linux_ioctl_special(struct thread *td, struct linux_ioctl_args *args) { int error; switch (args->cmd) { case LINUX_SIOCGIFADDR: args->cmd = SIOCGIFADDR; error = sys_ioctl(td, (struct ioctl_args *)args); break; case LINUX_SIOCSIFADDR: args->cmd = SIOCSIFADDR; error = sys_ioctl(td, (struct ioctl_args *)args); break; case LINUX_SIOCGIFFLAGS: args->cmd = SIOCGIFFLAGS; error = sys_ioctl(td, (struct ioctl_args *)args); break; default: error = ENOIOCTL; } return (error); } static int linux_to_bsd_v4l2_standard(struct l_v4l2_standard *lvstd, struct v4l2_standard *vstd) { vstd->index = lvstd->index; vstd->id = lvstd->id; CTASSERT(sizeof(vstd->name) == sizeof(lvstd->name)); memcpy(vstd->name, lvstd->name, sizeof(vstd->name)); vstd->frameperiod = lvstd->frameperiod; vstd->framelines = lvstd->framelines; CTASSERT(sizeof(vstd->reserved) == sizeof(lvstd->reserved)); memcpy(vstd->reserved, lvstd->reserved, sizeof(vstd->reserved)); return (0); } static int bsd_to_linux_v4l2_standard(struct v4l2_standard *vstd, struct l_v4l2_standard *lvstd) { lvstd->index = vstd->index; lvstd->id = vstd->id; CTASSERT(sizeof(vstd->name) == sizeof(lvstd->name)); memcpy(lvstd->name, vstd->name, sizeof(lvstd->name)); lvstd->frameperiod = vstd->frameperiod; lvstd->framelines = vstd->framelines; CTASSERT(sizeof(vstd->reserved) == sizeof(lvstd->reserved)); memcpy(lvstd->reserved, vstd->reserved, sizeof(lvstd->reserved)); return (0); } static int linux_to_bsd_v4l2_buffer(struct l_v4l2_buffer *lvb, struct v4l2_buffer *vb) { vb->index = lvb->index; vb->type = lvb->type; vb->bytesused = lvb->bytesused; vb->flags = lvb->flags; vb->field = lvb->field; vb->timestamp.tv_sec = lvb->timestamp.tv_sec; vb->timestamp.tv_usec = lvb->timestamp.tv_usec; memcpy(&vb->timecode, &lvb->timecode, sizeof (lvb->timecode)); vb->sequence = lvb->sequence; vb->memory = lvb->memory; if (lvb->memory == V4L2_MEMORY_USERPTR) /* possible pointer size conversion */ vb->m.userptr = (unsigned long)PTRIN(lvb->m.userptr); else vb->m.offset = lvb->m.offset; vb->length = lvb->length; vb->input = lvb->input; vb->reserved = lvb->reserved; return (0); } static int bsd_to_linux_v4l2_buffer(struct v4l2_buffer *vb, struct l_v4l2_buffer *lvb) { lvb->index = vb->index; lvb->type = vb->type; lvb->bytesused = vb->bytesused; lvb->flags = vb->flags; lvb->field = vb->field; lvb->timestamp.tv_sec = vb->timestamp.tv_sec; lvb->timestamp.tv_usec = vb->timestamp.tv_usec; memcpy(&lvb->timecode, &vb->timecode, sizeof (vb->timecode)); lvb->sequence = vb->sequence; lvb->memory = vb->memory; if (vb->memory == V4L2_MEMORY_USERPTR) /* possible pointer size conversion */ lvb->m.userptr = PTROUT(vb->m.userptr); else lvb->m.offset = vb->m.offset; lvb->length = vb->length; lvb->input = vb->input; lvb->reserved = vb->reserved; return (0); } static int linux_to_bsd_v4l2_format(struct l_v4l2_format *lvf, struct v4l2_format *vf) { vf->type = lvf->type; if (lvf->type == V4L2_BUF_TYPE_VIDEO_OVERLAY #ifdef V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY || lvf->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY #endif ) /* * XXX TODO - needs 32 -> 64 bit conversion: * (unused by webcams?) */ return (EINVAL); memcpy(&vf->fmt, &lvf->fmt, sizeof(vf->fmt)); return (0); } static int bsd_to_linux_v4l2_format(struct v4l2_format *vf, struct l_v4l2_format *lvf) { lvf->type = vf->type; if (vf->type == V4L2_BUF_TYPE_VIDEO_OVERLAY #ifdef V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY || vf->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY #endif ) /* * XXX TODO - needs 32 -> 64 bit conversion: * (unused by webcams?) */ return (EINVAL); memcpy(&lvf->fmt, &vf->fmt, sizeof(vf->fmt)); return (0); } static int linux_ioctl_v4l2(struct thread *td, struct linux_ioctl_args *args) { cap_rights_t rights; struct file *fp; int error; struct v4l2_format vformat; struct l_v4l2_format l_vformat; struct v4l2_standard vstd; struct l_v4l2_standard l_vstd; struct l_v4l2_buffer l_vbuf; struct v4l2_buffer vbuf; struct v4l2_input vinp; switch (args->cmd & 0xffff) { case LINUX_VIDIOC_RESERVED: case LINUX_VIDIOC_LOG_STATUS: if ((args->cmd & IOC_DIRMASK) != LINUX_IOC_VOID) return (ENOIOCTL); args->cmd = (args->cmd & 0xffff) | IOC_VOID; break; case LINUX_VIDIOC_OVERLAY: case LINUX_VIDIOC_STREAMON: case LINUX_VIDIOC_STREAMOFF: case LINUX_VIDIOC_S_STD: case LINUX_VIDIOC_S_TUNER: case LINUX_VIDIOC_S_AUDIO: case LINUX_VIDIOC_S_AUDOUT: case LINUX_VIDIOC_S_MODULATOR: case LINUX_VIDIOC_S_FREQUENCY: case LINUX_VIDIOC_S_CROP: case LINUX_VIDIOC_S_JPEGCOMP: case LINUX_VIDIOC_S_PRIORITY: case LINUX_VIDIOC_DBG_S_REGISTER: case LINUX_VIDIOC_S_HW_FREQ_SEEK: case LINUX_VIDIOC_SUBSCRIBE_EVENT: case LINUX_VIDIOC_UNSUBSCRIBE_EVENT: args->cmd = (args->cmd & ~IOC_DIRMASK) | IOC_IN; break; case LINUX_VIDIOC_QUERYCAP: case LINUX_VIDIOC_G_STD: case LINUX_VIDIOC_G_AUDIO: case LINUX_VIDIOC_G_INPUT: case LINUX_VIDIOC_G_OUTPUT: case LINUX_VIDIOC_G_AUDOUT: case LINUX_VIDIOC_G_JPEGCOMP: case LINUX_VIDIOC_QUERYSTD: case LINUX_VIDIOC_G_PRIORITY: case LINUX_VIDIOC_QUERY_DV_PRESET: args->cmd = (args->cmd & ~IOC_DIRMASK) | IOC_OUT; break; case LINUX_VIDIOC_ENUM_FMT: case LINUX_VIDIOC_REQBUFS: case LINUX_VIDIOC_G_PARM: case LINUX_VIDIOC_S_PARM: case LINUX_VIDIOC_G_CTRL: case LINUX_VIDIOC_S_CTRL: case LINUX_VIDIOC_G_TUNER: case LINUX_VIDIOC_QUERYCTRL: case LINUX_VIDIOC_QUERYMENU: case LINUX_VIDIOC_S_INPUT: case LINUX_VIDIOC_S_OUTPUT: case LINUX_VIDIOC_ENUMOUTPUT: case LINUX_VIDIOC_G_MODULATOR: case LINUX_VIDIOC_G_FREQUENCY: case LINUX_VIDIOC_CROPCAP: case LINUX_VIDIOC_G_CROP: case LINUX_VIDIOC_ENUMAUDIO: case LINUX_VIDIOC_ENUMAUDOUT: case LINUX_VIDIOC_G_SLICED_VBI_CAP: #ifdef VIDIOC_ENUM_FRAMESIZES case LINUX_VIDIOC_ENUM_FRAMESIZES: case LINUX_VIDIOC_ENUM_FRAMEINTERVALS: case LINUX_VIDIOC_ENCODER_CMD: case LINUX_VIDIOC_TRY_ENCODER_CMD: #endif case LINUX_VIDIOC_DBG_G_REGISTER: case LINUX_VIDIOC_DBG_G_CHIP_IDENT: case LINUX_VIDIOC_ENUM_DV_PRESETS: case LINUX_VIDIOC_S_DV_PRESET: case LINUX_VIDIOC_G_DV_PRESET: case LINUX_VIDIOC_S_DV_TIMINGS: case LINUX_VIDIOC_G_DV_TIMINGS: args->cmd = (args->cmd & ~IOC_DIRMASK) | IOC_INOUT; break; case LINUX_VIDIOC_G_FMT: case LINUX_VIDIOC_S_FMT: case LINUX_VIDIOC_TRY_FMT: error = copyin((void *)args->arg, &l_vformat, sizeof(l_vformat)); if (error) return (error); error = fget(td, args->fd, cap_rights_init(&rights, CAP_IOCTL), &fp); if (error) return (error); if (linux_to_bsd_v4l2_format(&l_vformat, &vformat) != 0) error = EINVAL; else if ((args->cmd & 0xffff) == LINUX_VIDIOC_G_FMT) error = fo_ioctl(fp, VIDIOC_G_FMT, &vformat, td->td_ucred, td); else if ((args->cmd & 0xffff) == LINUX_VIDIOC_S_FMT) error = fo_ioctl(fp, VIDIOC_S_FMT, &vformat, td->td_ucred, td); else error = fo_ioctl(fp, VIDIOC_TRY_FMT, &vformat, td->td_ucred, td); bsd_to_linux_v4l2_format(&vformat, &l_vformat); copyout(&l_vformat, (void *)args->arg, sizeof(l_vformat)); fdrop(fp, td); return (error); case LINUX_VIDIOC_ENUMSTD: error = copyin((void *)args->arg, &l_vstd, sizeof(l_vstd)); if (error) return (error); linux_to_bsd_v4l2_standard(&l_vstd, &vstd); error = fget(td, args->fd, cap_rights_init(&rights, CAP_IOCTL), &fp); if (error) return (error); error = fo_ioctl(fp, VIDIOC_ENUMSTD, (caddr_t)&vstd, td->td_ucred, td); if (error) { fdrop(fp, td); return (error); } bsd_to_linux_v4l2_standard(&vstd, &l_vstd); error = copyout(&l_vstd, (void *)args->arg, sizeof(l_vstd)); fdrop(fp, td); return (error); case LINUX_VIDIOC_ENUMINPUT: /* * The Linux struct l_v4l2_input differs only in size, * it has no padding at the end. */ error = copyin((void *)args->arg, &vinp, sizeof(struct l_v4l2_input)); if (error != 0) return (error); error = fget(td, args->fd, cap_rights_init(&rights, CAP_IOCTL), &fp); if (error != 0) return (error); error = fo_ioctl(fp, VIDIOC_ENUMINPUT, (caddr_t)&vinp, td->td_ucred, td); if (error) { fdrop(fp, td); return (error); } error = copyout(&vinp, (void *)args->arg, sizeof(struct l_v4l2_input)); fdrop(fp, td); return (error); case LINUX_VIDIOC_QUERYBUF: case LINUX_VIDIOC_QBUF: case LINUX_VIDIOC_DQBUF: error = copyin((void *)args->arg, &l_vbuf, sizeof(l_vbuf)); if (error) return (error); error = fget(td, args->fd, cap_rights_init(&rights, CAP_IOCTL), &fp); if (error) return (error); linux_to_bsd_v4l2_buffer(&l_vbuf, &vbuf); if ((args->cmd & 0xffff) == LINUX_VIDIOC_QUERYBUF) error = fo_ioctl(fp, VIDIOC_QUERYBUF, &vbuf, td->td_ucred, td); else if ((args->cmd & 0xffff) == LINUX_VIDIOC_QBUF) error = fo_ioctl(fp, VIDIOC_QBUF, &vbuf, td->td_ucred, td); else error = fo_ioctl(fp, VIDIOC_DQBUF, &vbuf, td->td_ucred, td); bsd_to_linux_v4l2_buffer(&vbuf, &l_vbuf); copyout(&l_vbuf, (void *)args->arg, sizeof(l_vbuf)); fdrop(fp, td); return (error); /* * XXX TODO - these need 32 -> 64 bit conversion: * (are any of them needed for webcams?) */ case LINUX_VIDIOC_G_FBUF: case LINUX_VIDIOC_S_FBUF: case LINUX_VIDIOC_G_EXT_CTRLS: case LINUX_VIDIOC_S_EXT_CTRLS: case LINUX_VIDIOC_TRY_EXT_CTRLS: case LINUX_VIDIOC_DQEVENT: default: return (ENOIOCTL); } error = sys_ioctl(td, (struct ioctl_args *)args); return (error); } /* * Support for emulators/linux-libusb. This port uses FBSD_LUSB* macros * instead of USB* ones. This lets us to provide correct values for cmd. * 0xffffffe0 -- 0xffffffff range seemed to be the least collision-prone. */ static int linux_ioctl_fbsd_usb(struct thread *td, struct linux_ioctl_args *args) { int error; error = 0; switch (args->cmd) { case FBSD_LUSB_DEVICEENUMERATE: args->cmd = USB_DEVICEENUMERATE; break; case FBSD_LUSB_DEV_QUIRK_ADD: args->cmd = USB_DEV_QUIRK_ADD; break; case FBSD_LUSB_DEV_QUIRK_GET: args->cmd = USB_DEV_QUIRK_GET; break; case FBSD_LUSB_DEV_QUIRK_REMOVE: args->cmd = USB_DEV_QUIRK_REMOVE; break; case FBSD_LUSB_DO_REQUEST: args->cmd = USB_DO_REQUEST; break; case FBSD_LUSB_FS_CLEAR_STALL_SYNC: args->cmd = USB_FS_CLEAR_STALL_SYNC; break; case FBSD_LUSB_FS_CLOSE: args->cmd = USB_FS_CLOSE; break; case FBSD_LUSB_FS_COMPLETE: args->cmd = USB_FS_COMPLETE; break; case FBSD_LUSB_FS_INIT: args->cmd = USB_FS_INIT; break; case FBSD_LUSB_FS_OPEN: args->cmd = USB_FS_OPEN; break; case FBSD_LUSB_FS_START: args->cmd = USB_FS_START; break; case FBSD_LUSB_FS_STOP: args->cmd = USB_FS_STOP; break; case FBSD_LUSB_FS_UNINIT: args->cmd = USB_FS_UNINIT; break; case FBSD_LUSB_GET_CONFIG: args->cmd = USB_GET_CONFIG; break; case FBSD_LUSB_GET_DEVICEINFO: args->cmd = USB_GET_DEVICEINFO; break; case FBSD_LUSB_GET_DEVICE_DESC: args->cmd = USB_GET_DEVICE_DESC; break; case FBSD_LUSB_GET_FULL_DESC: args->cmd = USB_GET_FULL_DESC; break; case FBSD_LUSB_GET_IFACE_DRIVER: args->cmd = USB_GET_IFACE_DRIVER; break; case FBSD_LUSB_GET_PLUGTIME: args->cmd = USB_GET_PLUGTIME; break; case FBSD_LUSB_GET_POWER_MODE: args->cmd = USB_GET_POWER_MODE; break; case FBSD_LUSB_GET_REPORT_DESC: args->cmd = USB_GET_REPORT_DESC; break; case FBSD_LUSB_GET_REPORT_ID: args->cmd = USB_GET_REPORT_ID; break; case FBSD_LUSB_GET_TEMPLATE: args->cmd = USB_GET_TEMPLATE; break; case FBSD_LUSB_IFACE_DRIVER_ACTIVE: args->cmd = USB_IFACE_DRIVER_ACTIVE; break; case FBSD_LUSB_IFACE_DRIVER_DETACH: args->cmd = USB_IFACE_DRIVER_DETACH; break; case FBSD_LUSB_QUIRK_NAME_GET: args->cmd = USB_QUIRK_NAME_GET; break; case FBSD_LUSB_READ_DIR: args->cmd = USB_READ_DIR; break; case FBSD_LUSB_SET_ALTINTERFACE: args->cmd = USB_SET_ALTINTERFACE; break; case FBSD_LUSB_SET_CONFIG: args->cmd = USB_SET_CONFIG; break; case FBSD_LUSB_SET_IMMED: args->cmd = USB_SET_IMMED; break; case FBSD_LUSB_SET_POWER_MODE: args->cmd = USB_SET_POWER_MODE; break; case FBSD_LUSB_SET_TEMPLATE: args->cmd = USB_SET_TEMPLATE; break; case FBSD_LUSB_FS_OPEN_STREAM: args->cmd = USB_FS_OPEN_STREAM; break; case FBSD_LUSB_GET_DEV_PORT_PATH: args->cmd = USB_GET_DEV_PORT_PATH; break; case FBSD_LUSB_GET_POWER_USAGE: args->cmd = USB_GET_POWER_USAGE; break; default: error = ENOIOCTL; } if (error != ENOIOCTL) error = sys_ioctl(td, (struct ioctl_args *)args); return (error); } /* * Some evdev ioctls must be translated. * - EVIOCGMTSLOTS is a IOC_READ ioctl on Linux although it has input data * (must be IOC_INOUT on FreeBSD). * - On Linux, EVIOCGRAB, EVIOCREVOKE and EVIOCRMFF are defined as _IOW with * an int argument. You don't pass an int pointer to the ioctl(), however, * but just the int directly. On FreeBSD, they are defined as _IOWINT for * this to work. */ static int linux_ioctl_evdev(struct thread *td, struct linux_ioctl_args *args) { cap_rights_t rights; struct file *fp; clockid_t clock; int error; args->cmd = SETDIR(args->cmd); switch (args->cmd) { case (EVIOCGRAB & ~IOC_DIRMASK) | IOC_IN: args->cmd = EVIOCGRAB; break; case (EVIOCREVOKE & ~IOC_DIRMASK) | IOC_IN: args->cmd = EVIOCREVOKE; break; case (EVIOCRMFF & ~IOC_DIRMASK) | IOC_IN: args->cmd = EVIOCRMFF; break; case EVIOCSCLOCKID: { error = copyin(PTRIN(args->arg), &clock, sizeof(clock)); if (error != 0) return (error); if (clock & ~(LINUX_IOCTL_EVDEV_CLK)) return (EINVAL); error = linux_to_native_clockid(&clock, clock); if (error != 0) return (error); error = fget(td, args->fd, cap_rights_init(&rights, CAP_IOCTL), &fp); if (error != 0) return (error); error = fo_ioctl(fp, EVIOCSCLOCKID, &clock, td->td_ucred, td); fdrop(fp, td); return (error); } default: break; } if (IOCBASECMD(args->cmd) == ((EVIOCGMTSLOTS(0) & ~IOC_DIRMASK) | IOC_OUT)) args->cmd = (args->cmd & ~IOC_DIRMASK) | IOC_INOUT; return (sys_ioctl(td, (struct ioctl_args *)args)); } /* * main ioctl syscall function */ int linux_ioctl(struct thread *td, struct linux_ioctl_args *args) { cap_rights_t rights; struct file *fp; struct linux_ioctl_handler_element *he; int error, cmd; #ifdef DEBUG if (ldebug(ioctl)) printf(ARGS(ioctl, "%d, %04lx, *"), args->fd, (unsigned long)args->cmd); #endif error = fget(td, args->fd, cap_rights_init(&rights, CAP_IOCTL), &fp); if (error != 0) return (error); if ((fp->f_flag & (FREAD|FWRITE)) == 0) { fdrop(fp, td); return (EBADF); } /* Iterate over the ioctl handlers */ cmd = args->cmd & 0xffff; sx_slock(&linux_ioctl_sx); mtx_lock(&Giant); #ifdef COMPAT_LINUX32 TAILQ_FOREACH(he, &linux32_ioctl_handlers, list) { if (cmd >= he->low && cmd <= he->high) { error = (*he->func)(td, args); if (error != ENOIOCTL) { mtx_unlock(&Giant); sx_sunlock(&linux_ioctl_sx); fdrop(fp, td); return (error); } } } #endif TAILQ_FOREACH(he, &linux_ioctl_handlers, list) { if (cmd >= he->low && cmd <= he->high) { error = (*he->func)(td, args); if (error != ENOIOCTL) { mtx_unlock(&Giant); sx_sunlock(&linux_ioctl_sx); fdrop(fp, td); return (error); } } } mtx_unlock(&Giant); sx_sunlock(&linux_ioctl_sx); fdrop(fp, td); switch (args->cmd & 0xffff) { case LINUX_BTRFS_IOC_CLONE: return (ENOTSUP); default: linux_msg(td, "ioctl fd=%d, cmd=0x%x ('%c',%d) is not implemented", args->fd, (int)(args->cmd & 0xffff), (int)(args->cmd & 0xff00) >> 8, (int)(args->cmd & 0xff)); break; } return (EINVAL); } int linux_ioctl_register_handler(struct linux_ioctl_handler *h) { struct linux_ioctl_handler_element *he, *cur; if (h == NULL || h->func == NULL) return (EINVAL); /* * Reuse the element if the handler is already on the list, otherwise * create a new element. */ sx_xlock(&linux_ioctl_sx); TAILQ_FOREACH(he, &linux_ioctl_handlers, list) { if (he->func == h->func) break; } if (he == NULL) { he = malloc(sizeof(*he), M_LINUX, M_WAITOK); he->func = h->func; } else TAILQ_REMOVE(&linux_ioctl_handlers, he, list); /* Initialize range information. */ he->low = h->low; he->high = h->high; he->span = h->high - h->low + 1; /* Add the element to the list, sorted on span. */ TAILQ_FOREACH(cur, &linux_ioctl_handlers, list) { if (cur->span > he->span) { TAILQ_INSERT_BEFORE(cur, he, list); sx_xunlock(&linux_ioctl_sx); return (0); } } TAILQ_INSERT_TAIL(&linux_ioctl_handlers, he, list); sx_xunlock(&linux_ioctl_sx); return (0); } int linux_ioctl_unregister_handler(struct linux_ioctl_handler *h) { struct linux_ioctl_handler_element *he; if (h == NULL || h->func == NULL) return (EINVAL); sx_xlock(&linux_ioctl_sx); TAILQ_FOREACH(he, &linux_ioctl_handlers, list) { if (he->func == h->func) { TAILQ_REMOVE(&linux_ioctl_handlers, he, list); sx_xunlock(&linux_ioctl_sx); free(he, M_LINUX); return (0); } } sx_xunlock(&linux_ioctl_sx); return (EINVAL); } #ifdef COMPAT_LINUX32 int linux32_ioctl_register_handler(struct linux_ioctl_handler *h) { struct linux_ioctl_handler_element *he, *cur; if (h == NULL || h->func == NULL) return (EINVAL); /* * Reuse the element if the handler is already on the list, otherwise * create a new element. */ sx_xlock(&linux_ioctl_sx); TAILQ_FOREACH(he, &linux32_ioctl_handlers, list) { if (he->func == h->func) break; } if (he == NULL) { he = malloc(sizeof(*he), M_LINUX, M_WAITOK); he->func = h->func; } else TAILQ_REMOVE(&linux32_ioctl_handlers, he, list); /* Initialize range information. */ he->low = h->low; he->high = h->high; he->span = h->high - h->low + 1; /* Add the element to the list, sorted on span. */ TAILQ_FOREACH(cur, &linux32_ioctl_handlers, list) { if (cur->span > he->span) { TAILQ_INSERT_BEFORE(cur, he, list); sx_xunlock(&linux_ioctl_sx); return (0); } } TAILQ_INSERT_TAIL(&linux32_ioctl_handlers, he, list); sx_xunlock(&linux_ioctl_sx); return (0); } int linux32_ioctl_unregister_handler(struct linux_ioctl_handler *h) { struct linux_ioctl_handler_element *he; if (h == NULL || h->func == NULL) return (EINVAL); sx_xlock(&linux_ioctl_sx); TAILQ_FOREACH(he, &linux32_ioctl_handlers, list) { if (he->func == h->func) { TAILQ_REMOVE(&linux32_ioctl_handlers, he, list); sx_xunlock(&linux_ioctl_sx); free(he, M_LINUX); return (0); } } sx_xunlock(&linux_ioctl_sx); return (EINVAL); } #endif Index: releng/11.3/sys/dev/mcd/mcd.c =================================================================== --- releng/11.3/sys/dev/mcd/mcd.c (revision 349624) +++ releng/11.3/sys/dev/mcd/mcd.c (revision 349625) @@ -1,1654 +1,1648 @@ /*- * Copyright 1993 by Holger Veit (data part) * Copyright 1993 by Brian Moore (audio part) * Changes Copyright 1993 by Gary Clark II * Changes Copyright (C) 1994-1995 by Andrey A. Chernov, Moscow, Russia * * Rewrote probe routine to work on newer Mitsumi drives. * Additional changes (C) 1994 by Jordan K. Hubbard * * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This software was developed by Holger Veit and Brian Moore * for use with "386BSD" and similar operating systems. * "Similar operating systems" includes mainly non-profit oriented * systems for research and education, including but not restricted to * "NetBSD", "FreeBSD", "Mach" (by CMU). * 4. Neither the name of the developer(s) nor the name "386BSD" * may be used to endorse or promote products derived from this * software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE DEVELOPER(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 DEVELOPER(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$"); static const char __used COPYRIGHT[] = "mcd-driver (C)1993 by H.Veit & B.Moore"; #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define MCD_TRACE(format, args...) \ { \ if (sc->debug) { \ device_printf(sc->dev, "status=0x%02x: ", \ sc->data.status); \ printf(format, ## args); \ } \ } #define RAW_PART 2 /* flags */ #define MCDVALID 0x0001 /* parameters loaded */ #define MCDINIT 0x0002 /* device is init'd */ #define MCDNEWMODEL 0x0004 /* device is new model */ #define MCDLABEL 0x0008 /* label is read */ #define MCDPROBING 0x0010 /* probing */ #define MCDREADRAW 0x0020 /* read raw mode (2352 bytes) */ #define MCDVOLINFO 0x0040 /* already read volinfo */ #define MCDTOC 0x0080 /* already read toc */ #define MCDMBXBSY 0x0100 /* local mbx is busy */ /* status */ #define MCDAUDIOBSY MCD_ST_AUDIOBSY /* playing audio */ #define MCDDSKCHNG MCD_ST_DSKCHNG /* sensed change of disk */ #define MCDDSKIN MCD_ST_DSKIN /* sensed disk in drive */ #define MCDDOOROPEN MCD_ST_DOOROPEN /* sensed door open */ /* These are apparently the different states a mitsumi can get up to */ #define MCDCDABSENT 0x0030 #define MCDCDPRESENT 0x0020 #define MCDSCLOSED 0x0080 #define MCDSOPEN 0x00a0 #define MCD_MD_UNKNOWN (-1) #define MCD_TYPE_UNKNOWN 0 #define MCD_TYPE_LU002S 1 #define MCD_TYPE_LU005S 2 #define MCD_TYPE_LU006S 3 #define MCD_TYPE_FX001 4 #define MCD_TYPE_FX001D 5 /* reader state machine */ #define MCD_S_BEGIN 0 #define MCD_S_BEGIN1 1 #define MCD_S_WAITSTAT 2 #define MCD_S_WAITMODE 3 #define MCD_S_WAITREAD 4 /* prototypes */ static void mcd_start(struct mcd_softc *); #ifdef NOTYET static void mcd_configure(struct mcd_softc *sc); #endif static int mcd_get(struct mcd_softc *, char *buf, int nmax); static int mcd_setflags(struct mcd_softc *); static int mcd_getstat(struct mcd_softc *,int sflg); static int mcd_send(struct mcd_softc *, int cmd,int nretrys); static void hsg2msf(int hsg, bcd_t *msf); static int msf2hsg(bcd_t *msf, int relative); static int mcd_volinfo(struct mcd_softc *); static int mcd_waitrdy(struct mcd_softc *,int dly); static void mcd_timeout(void *arg); static void mcd_doread(struct mcd_softc *, int state, struct mcd_mbx *mbxin); static void mcd_soft_reset(struct mcd_softc *); static int mcd_hard_reset(struct mcd_softc *); static int mcd_setmode(struct mcd_softc *, int mode); static int mcd_getqchan(struct mcd_softc *, struct mcd_qchninfo *q); -static int mcd_subchan(struct mcd_softc *, struct ioc_read_subchannel *sc, - int nocopyout); +static int mcd_subchan(struct mcd_softc *, struct ioc_read_subchannel *sc); static int mcd_toc_header(struct mcd_softc *, struct ioc_toc_header *th); static int mcd_read_toc(struct mcd_softc *); static int mcd_toc_entrys(struct mcd_softc *, struct ioc_read_toc_entry *te); #if 0 static int mcd_toc_entry(struct mcd_softc *, struct ioc_read_toc_single_entry *te); #endif static int mcd_stop(struct mcd_softc *); static int mcd_eject(struct mcd_softc *); static int mcd_inject(struct mcd_softc *); static int mcd_playtracks(struct mcd_softc *, struct ioc_play_track *pt); static int mcd_play(struct mcd_softc *, struct mcd_read2 *pb); static int mcd_playmsf(struct mcd_softc *, struct ioc_play_msf *pt); static int mcd_playblocks(struct mcd_softc *, struct ioc_play_blocks *); static int mcd_pause(struct mcd_softc *); static int mcd_resume(struct mcd_softc *); static int mcd_lock_door(struct mcd_softc *, int lock); static int mcd_close_tray(struct mcd_softc *); static int mcd_size(struct cdev *dev); static d_open_t mcdopen; static d_close_t mcdclose; static d_ioctl_t mcdioctl; static d_strategy_t mcdstrategy; static struct cdevsw mcd_cdevsw = { .d_version = D_VERSION, .d_open = mcdopen, .d_close = mcdclose, .d_read = physread, .d_ioctl = mcdioctl, .d_strategy = mcdstrategy, .d_name = "mcd", .d_flags = D_DISK, }; #define MCD_RETRYS 5 #define MCD_RDRETRYS 8 #define CLOSE_TRAY_SECS 8 #define DISK_SENSE_SECS 3 #define WAIT_FRAC 4 /* several delays */ #define RDELAY_WAITSTAT 300 #define RDELAY_WAITMODE 300 #define RDELAY_WAITREAD 800 #define MIN_DELAY 15 #define DELAY_GETREPLY 5000000 int mcd_attach(struct mcd_softc *sc) { int unit; unit = device_get_unit(sc->dev); MCD_LOCK(sc); sc->data.flags |= MCDINIT; mcd_soft_reset(sc); bioq_init(&sc->data.head); #ifdef NOTYET /* wire controller for interrupts and dma */ mcd_configure(sc); #endif MCD_UNLOCK(sc); /* name filled in probe */ sc->mcd_dev_t = make_dev(&mcd_cdevsw, 8 * unit, UID_ROOT, GID_OPERATOR, 0640, "mcd%d", unit); sc->mcd_dev_t->si_drv1 = (void *)sc; callout_init_mtx(&sc->timer, &sc->mtx, 0); device_printf(sc->dev, "WARNING: This driver is deprecated and will be removed.\n"); return (0); } static int mcdopen(struct cdev *dev, int flags, int fmt, struct thread *td) { struct mcd_softc *sc; int r,retry; sc = (struct mcd_softc *)dev->si_drv1; /* invalidated in the meantime? mark all open part's invalid */ MCD_LOCK(sc); if (!(sc->data.flags & MCDVALID) && sc->data.openflags) { MCD_UNLOCK(sc); return (ENXIO); } if (mcd_getstat(sc, 1) == -1) { MCD_UNLOCK(sc); return (EIO); } if ( (sc->data.status & (MCDDSKCHNG|MCDDOOROPEN)) || !(sc->data.status & MCDDSKIN)) for (retry = 0; retry < DISK_SENSE_SECS * WAIT_FRAC; retry++) { (void) mtx_sleep(sc, &sc->mtx, PSOCK | PCATCH, "mcdsn1", hz/WAIT_FRAC); if ((r = mcd_getstat(sc, 1)) == -1) { MCD_UNLOCK(sc); return (EIO); } if (r != -2) break; } if (sc->data.status & MCDDOOROPEN) { MCD_UNLOCK(sc); device_printf(sc->dev, "door is open\n"); return (ENXIO); } if (!(sc->data.status & MCDDSKIN)) { MCD_UNLOCK(sc); device_printf(sc->dev, "no CD inside\n"); return (ENXIO); } if (sc->data.status & MCDDSKCHNG) { MCD_UNLOCK(sc); device_printf(sc->dev, "CD not sensed\n"); return (ENXIO); } if (mcd_size(dev) < 0) { MCD_UNLOCK(sc); device_printf(sc->dev, "failed to get disk size\n"); return (ENXIO); } sc->data.openflags = 1; sc->data.partflags |= MCDREADRAW; sc->data.flags |= MCDVALID; (void) mcd_lock_door(sc, MCD_LK_LOCK); if (!(sc->data.flags & MCDVALID)) { MCD_UNLOCK(sc); return (ENXIO); } r = mcd_read_toc(sc); MCD_UNLOCK(sc); return (r); } static int mcdclose(struct cdev *dev, int flags, int fmt, struct thread *td) { struct mcd_softc *sc; sc = (struct mcd_softc *)dev->si_drv1; MCD_LOCK(sc); KASSERT(sc->data.openflags, ("device not open")); (void) mcd_lock_door(sc, MCD_LK_UNLOCK); sc->data.openflags = 0; sc->data.partflags &= ~MCDREADRAW; MCD_UNLOCK(sc); return (0); } static void mcdstrategy(struct bio *bp) { struct mcd_softc *sc; sc = (struct mcd_softc *)bp->bio_dev->si_drv1; /* if device invalidated (e.g. media change, door open), error */ MCD_LOCK(sc); if (!(sc->data.flags & MCDVALID)) { device_printf(sc->dev, "media changed\n"); bp->bio_error = EIO; goto bad; } /* read only */ if (!(bp->bio_cmd == BIO_READ)) { bp->bio_error = EROFS; goto bad; } /* no data to read */ if (bp->bio_bcount == 0) goto done; if (!(sc->data.flags & MCDTOC)) { bp->bio_error = EIO; goto bad; } bp->bio_resid = 0; /* queue it */ bioq_disksort(&sc->data.head, bp); /* now check whether we can perform processing */ mcd_start(sc); MCD_UNLOCK(sc); return; bad: bp->bio_flags |= BIO_ERROR; done: MCD_UNLOCK(sc); bp->bio_resid = bp->bio_bcount; biodone(bp); return; } static void mcd_start(struct mcd_softc *sc) { struct bio *bp; MCD_ASSERT_LOCKED(sc); if (sc->data.flags & MCDMBXBSY) { return; } bp = bioq_takefirst(&sc->data.head); if (bp != 0) { /* block found to process, dequeue */ /*MCD_TRACE("mcd_start: found block bp=0x%x\n",bp,0,0,0);*/ sc->data.flags |= MCDMBXBSY; } else { /* nothing to do */ return; } sc->data.mbx.retry = MCD_RETRYS; sc->data.mbx.bp = bp; mcd_doread(sc, MCD_S_BEGIN,&(sc->data.mbx)); return; } static int mcdioctl(struct cdev *dev, u_long cmd, caddr_t addr, int flags, struct thread *td) { struct mcd_softc *sc; int retry,r; sc = (struct mcd_softc *)dev->si_drv1; MCD_LOCK(sc); if (mcd_getstat(sc, 1) == -1) { /* detect disk change too */ MCD_UNLOCK(sc); return (EIO); } MCD_TRACE("ioctl called 0x%lx\n", cmd); switch (cmd) { case CDIOCSETPATCH: case CDIOCGETVOL: case CDIOCSETVOL: case CDIOCSETMONO: case CDIOCSETSTERIO: case CDIOCSETMUTE: case CDIOCSETLEFT: case CDIOCSETRIGHT: MCD_UNLOCK(sc); return (EINVAL); case CDIOCEJECT: r = mcd_eject(sc); MCD_UNLOCK(sc); return (r); case CDIOCSETDEBUG: sc->data.debug = 1; MCD_UNLOCK(sc); return (0); case CDIOCCLRDEBUG: sc->data.debug = 0; MCD_UNLOCK(sc); return (0); case CDIOCRESET: r = mcd_hard_reset(sc); MCD_UNLOCK(sc); return (r); case CDIOCALLOW: r = mcd_lock_door(sc, MCD_LK_UNLOCK); MCD_UNLOCK(sc); return (r); case CDIOCPREVENT: r = mcd_lock_door(sc, MCD_LK_LOCK); MCD_UNLOCK(sc); return (r); case CDIOCCLOSE: r = mcd_inject(sc); MCD_UNLOCK(sc); return (r); } if (!(sc->data.flags & MCDVALID)) { if ( (sc->data.status & (MCDDSKCHNG|MCDDOOROPEN)) || !(sc->data.status & MCDDSKIN)) for (retry = 0; retry < DISK_SENSE_SECS * WAIT_FRAC; retry++) { (void) mtx_sleep(sc, &sc->mtx, PSOCK | PCATCH, "mcdsn2", hz/WAIT_FRAC); if ((r = mcd_getstat(sc, 1)) == -1) { MCD_UNLOCK(sc); return (EIO); } if (r != -2) break; } if ( (sc->data.status & (MCDDOOROPEN|MCDDSKCHNG)) || !(sc->data.status & MCDDSKIN) || mcd_size(dev) < 0 ) { MCD_UNLOCK(sc); return (ENXIO); } sc->data.flags |= MCDVALID; sc->data.partflags |= MCDREADRAW; (void) mcd_lock_door(sc, MCD_LK_LOCK); if (!(sc->data.flags & MCDVALID)) { MCD_UNLOCK(sc); return (ENXIO); } } switch (cmd) { case DIOCGMEDIASIZE: *(off_t *)addr = (off_t)sc->data.disksize * sc->data.blksize; r = 0; break; case DIOCGSECTORSIZE: *(u_int *)addr = sc->data.blksize; r = 0; break; case CDIOCPLAYTRACKS: r = mcd_playtracks(sc, (struct ioc_play_track *) addr); break; case CDIOCPLAYBLOCKS: r = mcd_playblocks(sc, (struct ioc_play_blocks *) addr); break; case CDIOCPLAYMSF: r = mcd_playmsf(sc, (struct ioc_play_msf *) addr); break; - case CDIOCREADSUBCHANNEL_SYSSPACE: - return mcd_subchan(sc, (struct ioc_read_subchannel *) addr, 1); case CDIOCREADSUBCHANNEL: - return mcd_subchan(sc, (struct ioc_read_subchannel *) addr, 0); + return mcd_subchan(sc, (struct ioc_read_subchannel *) addr); case CDIOREADTOCHEADER: r = mcd_toc_header(sc, (struct ioc_toc_header *) addr); break; case CDIOREADTOCENTRYS: return mcd_toc_entrys(sc, (struct ioc_read_toc_entry *) addr); case CDIOCRESUME: r = mcd_resume(sc); break; case CDIOCPAUSE: r = mcd_pause(sc); break; case CDIOCSTART: if (mcd_setmode(sc, MCD_MD_COOKED) != 0) r = EIO; else r = 0; break; case CDIOCSTOP: r = mcd_stop(sc); break; default: r = ENOTTY; } MCD_UNLOCK(sc); return (r); } static int mcd_size(struct cdev *dev) { struct mcd_softc *sc; int size; sc = (struct mcd_softc *)dev->si_drv1; if (mcd_volinfo(sc) == 0) { sc->data.blksize = MCDBLK; size = msf2hsg(sc->data.volinfo.vol_msf, 0); sc->data.disksize = size * (MCDBLK/DEV_BSIZE); return (0); } return (-1); } /*************************************************************** * lower level of driver starts here **************************************************************/ #ifdef NOTDEF static char irqs[] = { 0x00,0x00,0x10,0x20,0x00,0x30,0x00,0x00, 0x00,0x10,0x40,0x50,0x00,0x00,0x00,0x00 }; static char drqs[] = { 0x00,0x01,0x00,0x03,0x00,0x05,0x06,0x07, }; #endif #ifdef NOT_YET static void mcd_configure(struct mcd_softc *sc) { MCD_WRITE(sc, MCD_REG_CONFIG, sc->data.config); } #endif /* Wait for non-busy - return 0 on timeout */ static int twiddle_thumbs(struct mcd_softc *sc, int count, char *whine) { int i; for (i = 0; i < count; i++) { if (!(MCD_READ(sc, MCD_FLAGS) & MFL_STATUS_NOT_AVAIL)) return (1); } if (bootverbose) device_printf(sc->dev, "timeout %s\n", whine); return (0); } /* check to see if a Mitsumi CD-ROM is attached to the ISA bus */ int mcd_probe(struct mcd_softc *sc) { int i, j; unsigned char stbytes[3]; sc->data.flags = MCDPROBING; #ifdef NOTDEF /* get irq/drq configuration word */ sc->data.config = irqs[dev->id_irq]; /* | drqs[dev->id_drq];*/ #else sc->data.config = 0; #endif /* send a reset */ MCD_WRITE(sc, MCD_FLAGS, M_RESET); /* * delay awhile by getting any pending garbage (old data) and * throwing it away. */ for (i = 1000000; i != 0; i--) (void)MCD_READ(sc, MCD_FLAGS); /* Get status */ MCD_WRITE(sc, MCD_DATA, MCD_CMDGETSTAT); if (!twiddle_thumbs(sc, 1000000, "getting status")) return (ENXIO); /* Timeout */ /* Get version information */ MCD_WRITE(sc, MCD_DATA, MCD_CMDCONTINFO); for (j = 0; j < 3; j++) { if (!twiddle_thumbs(sc, 3000, "getting version info")) return (ENXIO); stbytes[j] = (MCD_READ(sc, MCD_DATA) & 0xFF); } if (stbytes[1] == stbytes[2]) return (ENXIO); if (stbytes[2] >= 4 || stbytes[1] != 'M') { MCD_WRITE(sc, MCD_CTRL, M_PICKLE); sc->data.flags |= MCDNEWMODEL; } sc->data.read_command = MCD_CMDSINGLESPEEDREAD; switch (stbytes[1]) { case 'M': if (stbytes[2] <= 2) { sc->data.type = MCD_TYPE_LU002S; sc->data.name = "Mitsumi LU002S"; } else if (stbytes[2] <= 5) { sc->data.type = MCD_TYPE_LU005S; sc->data.name = "Mitsumi LU005S"; } else { sc->data.type = MCD_TYPE_LU006S; sc->data.name = "Mitsumi LU006S"; } break; case 'F': sc->data.type = MCD_TYPE_FX001; sc->data.name = "Mitsumi FX001"; break; case 'D': sc->data.type = MCD_TYPE_FX001D; sc->data.name = "Mitsumi FX001D"; sc->data.read_command = MCD_CMDDOUBLESPEEDREAD; break; default: sc->data.type = MCD_TYPE_UNKNOWN; sc->data.name = "Mitsumi ???"; break; } if (bootverbose) device_printf(sc->dev, "type %s, version info: %c %x\n", sc->data.name, stbytes[1], stbytes[2]); return (0); } static int mcd_waitrdy(struct mcd_softc *sc, int dly) { int i; /* wait until flag port senses status ready */ for (i=0; idev, "timeout getreply\n"); return (-1); } /* get the data */ return (MCD_READ(sc, MCD_REG_STATUS) & 0xFF); } static int mcd_getstat(struct mcd_softc *sc, int sflg) { int i; /* get the status */ if (sflg) MCD_WRITE(sc, MCD_REG_COMMAND, MCD_CMDGETSTAT); i = mcd_getreply(sc, DELAY_GETREPLY); if (i<0 || (i & MCD_ST_CMDCHECK)) { sc->data.curr_mode = MCD_MD_UNKNOWN; return (-1); } sc->data.status = i; if (mcd_setflags(sc) < 0) return (-2); return (sc->data.status); } static int mcd_setflags(struct mcd_softc *sc) { /* check flags */ if ( (sc->data.status & (MCDDSKCHNG|MCDDOOROPEN)) || !(sc->data.status & MCDDSKIN)) { MCD_TRACE("setflags: sensed DSKCHNG or DOOROPEN or !DSKIN\n"); mcd_soft_reset(sc); return (-1); } if (sc->data.status & MCDAUDIOBSY) sc->data.audio_status = CD_AS_PLAY_IN_PROGRESS; else if (sc->data.audio_status == CD_AS_PLAY_IN_PROGRESS) sc->data.audio_status = CD_AS_PLAY_COMPLETED; return (0); } static int mcd_get(struct mcd_softc *sc, char *buf, int nmax) { int i,k; for (i=0; idev, "timeout mcd_get\n"); return (-1); } buf[i] = k; } return (i); } static int mcd_send(struct mcd_softc *sc, int cmd,int nretrys) { int i,k=0; /*MCD_TRACE("mcd_send: command = 0x%02x\n",cmd,0,0,0);*/ for (i=0; idev, "media changed\n"); return (-1); } if (i == nretrys) { device_printf(sc->dev, "mcd_send retry cnt exceeded\n"); return (-1); } /*MCD_TRACE("mcd_send: done\n",0,0,0,0);*/ return (0); } static void hsg2msf(int hsg, bcd_t *msf) { hsg += 150; F_msf(msf) = bin2bcd(hsg % 75); hsg /= 75; S_msf(msf) = bin2bcd(hsg % 60); hsg /= 60; M_msf(msf) = bin2bcd(hsg); } static int msf2hsg(bcd_t *msf, int relative) { return (bcd2bin(M_msf(msf)) * 60 + bcd2bin(S_msf(msf))) * 75 + bcd2bin(F_msf(msf)) - (!relative) * 150; } static int mcd_volinfo(struct mcd_softc *sc) { /* Just return if we already have it */ if (sc->data.flags & MCDVOLINFO) return (0); /*MCD_TRACE("mcd_volinfo: enter\n",0,0,0,0);*/ /* send volume info command */ if (mcd_send(sc, MCD_CMDGETVOLINFO,MCD_RETRYS) < 0) return (EIO); /* get data */ if (mcd_get(sc, (char*) &sc->data.volinfo,sizeof(struct mcd_volinfo)) < 0) { device_printf(sc->dev, "mcd_volinfo: error read data\n"); return (EIO); } if (sc->data.volinfo.trk_low > 0 && sc->data.volinfo.trk_high >= sc->data.volinfo.trk_low ) { sc->data.flags |= MCDVOLINFO; /* volinfo is OK */ return (0); } return (EINVAL); } /* state machine to process read requests * initialize with MCD_S_BEGIN: calculate sizes, and read status * MCD_S_WAITSTAT: wait for status reply, set mode * MCD_S_WAITMODE: waits for status reply from set mode, set read command * MCD_S_WAITREAD: wait for read ready, read data */ static void mcd_timeout(void *arg) { struct mcd_softc *sc; sc = (struct mcd_softc *)arg; MCD_ASSERT_LOCKED(sc); mcd_doread(sc, sc->ch_state, sc->ch_mbxsave); } static void mcd_doread(struct mcd_softc *sc, int state, struct mcd_mbx *mbxin) { struct mcd_mbx *mbx; struct bio *bp; int rm, i, k; struct mcd_read2 rbuf; int blknum; caddr_t addr; MCD_ASSERT_LOCKED(sc); mbx = (state!=MCD_S_BEGIN) ? sc->ch_mbxsave : mbxin; bp = mbx->bp; loop: switch (state) { case MCD_S_BEGIN: mbx = sc->ch_mbxsave = mbxin; case MCD_S_BEGIN1: retry_status: /* get status */ MCD_WRITE(sc, MCD_REG_COMMAND, MCD_CMDGETSTAT); mbx->count = RDELAY_WAITSTAT; sc->ch_state = MCD_S_WAITSTAT; callout_reset(&sc->timer, hz/100, mcd_timeout, sc); /* XXX */ return; case MCD_S_WAITSTAT: sc->ch_state = MCD_S_WAITSTAT; callout_stop(&sc->timer); if (mbx->count-- >= 0) { if (MCD_READ(sc, MCD_FLAGS) & MFL_STATUS_NOT_AVAIL) { sc->ch_state = MCD_S_WAITSTAT; callout_reset(&sc->timer, hz/100, mcd_timeout, sc); /* XXX */ return; } sc->data.status = MCD_READ(sc, MCD_REG_STATUS) & 0xFF; if (sc->data.status & MCD_ST_CMDCHECK) goto retry_status; if (mcd_setflags(sc) < 0) goto changed; MCD_TRACE("got WAITSTAT delay=%d\n", RDELAY_WAITSTAT-mbx->count); /* reject, if audio active */ if (sc->data.status & MCDAUDIOBSY) { device_printf(sc->dev, "audio is active\n"); goto readerr; } retry_mode: /* to check for raw/cooked mode */ if (sc->data.flags & MCDREADRAW) { rm = MCD_MD_RAW; mbx->sz = MCDRBLK; } else { rm = MCD_MD_COOKED; mbx->sz = sc->data.blksize; } if (rm == sc->data.curr_mode) goto modedone; mbx->count = RDELAY_WAITMODE; sc->data.curr_mode = MCD_MD_UNKNOWN; mbx->mode = rm; MCD_WRITE(sc, MCD_REG_COMMAND, MCD_CMDSETMODE); MCD_WRITE(sc, MCD_REG_COMMAND, rm); sc->ch_state = MCD_S_WAITMODE; callout_reset(&sc->timer, hz / 100, mcd_timeout, sc); /* XXX */ return; } else { device_printf(sc->dev, "timeout getstatus\n"); goto readerr; } case MCD_S_WAITMODE: sc->ch_state = MCD_S_WAITMODE; callout_stop(&sc->timer); if (mbx->count-- < 0) { device_printf(sc->dev, "timeout set mode\n"); goto readerr; } if (MCD_READ(sc, MCD_FLAGS) & MFL_STATUS_NOT_AVAIL) { sc->ch_state = MCD_S_WAITMODE; callout_reset(&sc->timer, hz / 100, mcd_timeout, sc); return; } sc->data.status = MCD_READ(sc, MCD_REG_STATUS) & 0xFF; if (sc->data.status & MCD_ST_CMDCHECK) { sc->data.curr_mode = MCD_MD_UNKNOWN; goto retry_mode; } if (mcd_setflags(sc) < 0) goto changed; sc->data.curr_mode = mbx->mode; MCD_TRACE("got WAITMODE delay=%d\n", RDELAY_WAITMODE-mbx->count); modedone: /* for first block */ mbx->nblk = howmany(bp->bio_bcount, mbx->sz); mbx->skip = 0; nextblock: blknum = bp->bio_offset / mbx->sz + mbx->skip/mbx->sz; MCD_TRACE("mcd_doread: read blknum=%d for bp=%p\n", blknum, bp); /* build parameter block */ hsg2msf(blknum,rbuf.start_msf); retry_read: /* send the read command */ MCD_WRITE(sc, MCD_REG_COMMAND, sc->data.read_command); MCD_WRITE(sc, MCD_REG_COMMAND, rbuf.start_msf[0]); MCD_WRITE(sc, MCD_REG_COMMAND, rbuf.start_msf[1]); MCD_WRITE(sc, MCD_REG_COMMAND, rbuf.start_msf[2]); MCD_WRITE(sc, MCD_REG_COMMAND, 0); MCD_WRITE(sc, MCD_REG_COMMAND, 0); MCD_WRITE(sc, MCD_REG_COMMAND, 1); /* Spin briefly (<= 2ms) to avoid missing next block */ for (i = 0; i < 20; i++) { k = MCD_READ(sc, MCD_FLAGS); if (!(k & MFL_DATA_NOT_AVAIL)) goto got_it; DELAY(100); } mbx->count = RDELAY_WAITREAD; sc->ch_state = MCD_S_WAITREAD; callout_reset(&sc->timer, hz / 100, mcd_timeout, sc); /* XXX */ return; case MCD_S_WAITREAD: sc->ch_state = MCD_S_WAITREAD; callout_stop(&sc->timer); if (mbx->count-- > 0) { k = MCD_READ(sc, MCD_FLAGS); if (!(k & MFL_DATA_NOT_AVAIL)) { /* XXX */ MCD_TRACE("got data delay=%d\n", RDELAY_WAITREAD-mbx->count); got_it: /* data is ready */ addr = bp->bio_data + mbx->skip; MCD_WRITE(sc, MCD_REG_CTL2,0x04); /* XXX */ for (i=0; isz; i++) *addr++ = MCD_READ(sc, MCD_REG_RDATA); MCD_WRITE(sc, MCD_REG_CTL2,0x0c); /* XXX */ k = MCD_READ(sc, MCD_FLAGS); /* If we still have some junk, read it too */ if (!(k & MFL_DATA_NOT_AVAIL)) { MCD_WRITE(sc, MCD_REG_CTL2, 0x04); /* XXX */ (void)MCD_READ(sc, MCD_REG_RDATA); (void)MCD_READ(sc, MCD_REG_RDATA); MCD_WRITE(sc, MCD_REG_CTL2, 0x0c); /* XXX */ } if (--mbx->nblk > 0) { mbx->skip += mbx->sz; goto nextblock; } /* return buffer */ bp->bio_resid = 0; biodone(bp); sc->data.flags &= ~(MCDMBXBSY|MCDREADRAW); mcd_start(sc); return; } if (!(k & MFL_STATUS_NOT_AVAIL)) { sc->data.status = MCD_READ(sc, MCD_REG_STATUS) & 0xFF; if (sc->data.status & MCD_ST_CMDCHECK) goto retry_read; if (mcd_setflags(sc) < 0) goto changed; } sc->ch_state = MCD_S_WAITREAD; callout_reset(&sc->timer, hz / 100, mcd_timeout, sc); /* XXX */ return; } else { device_printf(sc->dev, "timeout read data\n"); goto readerr; } } readerr: if (mbx->retry-- > 0) { device_printf(sc->dev, "retrying\n"); state = MCD_S_BEGIN1; goto loop; } harderr: /* invalidate the buffer */ bp->bio_flags |= BIO_ERROR; bp->bio_resid = bp->bio_bcount; biodone(bp); sc->data.flags &= ~(MCDMBXBSY|MCDREADRAW); mcd_start(sc); return; changed: device_printf(sc->dev, "media changed\n"); goto harderr; #ifdef NOTDEF device_printf(sc->dev, "unit timeout, resetting\n"); MCD_WRITE(sc, MCD_REG_RESET, MCD_CMDRESET); DELAY(300000); (void)mcd_getstat(sc, 1); (void)mcd_getstat(sc, 1); /*sc->data.status &= ~MCDDSKCHNG; */ sc->data.debug = 1; /* preventive set debug mode */ #endif } static int mcd_lock_door(struct mcd_softc *sc, int lock) { MCD_WRITE(sc, MCD_REG_COMMAND, MCD_CMDLOCKDRV); MCD_WRITE(sc, MCD_REG_COMMAND, lock); if (mcd_getstat(sc, 0) == -1) return (EIO); return (0); } static int mcd_close_tray(struct mcd_softc *sc) { int retry, r; if (mcd_getstat(sc, 1) == -1) return (EIO); if (sc->data.status & MCDDOOROPEN) { MCD_WRITE(sc, MCD_REG_COMMAND, MCD_CMDCLOSETRAY); for (retry = 0; retry < CLOSE_TRAY_SECS * WAIT_FRAC; retry++) { if (MCD_READ(sc, MCD_FLAGS) & MFL_STATUS_NOT_AVAIL) (void) mtx_sleep(sc, &sc->mtx, PSOCK | PCATCH, "mcdcls", hz/WAIT_FRAC); else { if ((r = mcd_getstat(sc, 0)) == -1) return (EIO); return (0); } } return (ENXIO); } return (0); } static int mcd_eject(struct mcd_softc *sc) { int r; if (mcd_getstat(sc, 1) == -1) /* detect disk change too */ return (EIO); if (sc->data.status & MCDDOOROPEN) return (0); if ((r = mcd_stop(sc)) == EIO) return (r); MCD_WRITE(sc, MCD_REG_COMMAND, MCD_CMDEJECTDISK); if (mcd_getstat(sc, 0) == -1) return (EIO); return (0); } static int mcd_inject(struct mcd_softc *sc) { if (mcd_getstat(sc, 1) == -1) /* detect disk change too */ return (EIO); if (sc->data.status & MCDDOOROPEN) return mcd_close_tray(sc); return (0); } static int mcd_hard_reset(struct mcd_softc *sc) { MCD_WRITE(sc, MCD_REG_RESET, MCD_CMDRESET); sc->data.curr_mode = MCD_MD_UNKNOWN; sc->data.audio_status = CD_AS_AUDIO_INVALID; return (0); } static void mcd_soft_reset(struct mcd_softc *sc) { sc->data.flags &= (MCDINIT|MCDPROBING|MCDNEWMODEL); sc->data.curr_mode = MCD_MD_UNKNOWN; sc->data.partflags = 0; sc->data.audio_status = CD_AS_AUDIO_INVALID; } static int mcd_setmode(struct mcd_softc *sc, int mode) { int retry, st; if (sc->data.curr_mode == mode) return (0); if (sc->data.debug) device_printf(sc->dev, "setting mode to %d\n", mode); for(retry=0; retrydata.curr_mode = MCD_MD_UNKNOWN; MCD_WRITE(sc, MCD_REG_COMMAND, MCD_CMDSETMODE); MCD_WRITE(sc, MCD_REG_COMMAND, mode); if ((st = mcd_getstat(sc, 0)) >= 0) { sc->data.curr_mode = mode; return (0); } if (st == -2) { device_printf(sc->dev, "media changed\n"); break; } } return (-1); } static int mcd_toc_header(struct mcd_softc *sc, struct ioc_toc_header *th) { int r; if ((r = mcd_volinfo(sc)) != 0) return (r); th->starting_track = bcd2bin(sc->data.volinfo.trk_low); th->ending_track = bcd2bin(sc->data.volinfo.trk_high); th->len = 2 * sizeof(u_char) /* start & end tracks */ + (th->ending_track + 1 - th->starting_track + 1) * sizeof(struct cd_toc_entry); return (0); } static int mcd_read_toc(struct mcd_softc *sc) { struct ioc_toc_header th; struct mcd_qchninfo q; int rc, trk, idx, retry; /* Only read TOC if needed */ if (sc->data.flags & MCDTOC) return (0); if (sc->data.debug) device_printf(sc->dev, "reading toc header\n"); if ((rc = mcd_toc_header(sc, &th)) != 0) return (rc); if (mcd_send(sc, MCD_CMDSTOPAUDIO, MCD_RETRYS) < 0) return (EIO); if (mcd_setmode(sc, MCD_MD_TOC) != 0) return (EIO); if (sc->data.debug) device_printf(sc->dev, "get_toc reading qchannel info\n"); for(trk=th.starting_track; trk<=th.ending_track; trk++) sc->data.toc[trk].idx_no = 0; trk = th.ending_track - th.starting_track + 1; for(retry=0; retry<600 && trk>0; retry++) { if (mcd_getqchan(sc, &q) < 0) break; idx = bcd2bin(q.idx_no); if (idx>=th.starting_track && idx<=th.ending_track && q.trk_no==0) { if (sc->data.toc[idx].idx_no == 0) { sc->data.toc[idx] = q; trk--; } } } if (mcd_setmode(sc, MCD_MD_COOKED) != 0) return (EIO); if (trk != 0) return (ENXIO); /* add a fake last+1 */ idx = th.ending_track + 1; sc->data.toc[idx].control = sc->data.toc[idx-1].control; sc->data.toc[idx].addr_type = sc->data.toc[idx-1].addr_type; sc->data.toc[idx].trk_no = 0; sc->data.toc[idx].idx_no = MCD_LASTPLUS1; sc->data.toc[idx].hd_pos_msf[0] = sc->data.volinfo.vol_msf[0]; sc->data.toc[idx].hd_pos_msf[1] = sc->data.volinfo.vol_msf[1]; sc->data.toc[idx].hd_pos_msf[2] = sc->data.volinfo.vol_msf[2]; if (sc->data.debug) { int i; for (i = th.starting_track; i <= idx; i++) device_printf(sc->dev, "trk %d idx %d pos %d %d %d\n", i, sc->data.toc[i].idx_no > 0x99 ? sc->data.toc[i].idx_no : bcd2bin(sc->data.toc[i].idx_no), bcd2bin(sc->data.toc[i].hd_pos_msf[0]), bcd2bin(sc->data.toc[i].hd_pos_msf[1]), bcd2bin(sc->data.toc[i].hd_pos_msf[2])); } sc->data.flags |= MCDTOC; return (0); } #if 0 static int mcd_toc_entry(struct mcd_softc *sc, struct ioc_read_toc_single_entry *te) { struct ioc_toc_header th; int rc, trk; if (te->address_format != CD_MSF_FORMAT && te->address_format != CD_LBA_FORMAT) return (EINVAL); /* Copy the toc header */ if ((rc = mcd_toc_header(sc, &th)) != 0) return (rc); /* verify starting track */ trk = te->track; if (trk == 0) trk = th.starting_track; else if (trk == MCD_LASTPLUS1) trk = th.ending_track + 1; else if (trk < th.starting_track || trk > th.ending_track + 1) return (EINVAL); /* Make sure we have a valid toc */ if ((rc=mcd_read_toc(sc)) != 0) return (rc); /* Copy the TOC data. */ if (sc->data.toc[trk].idx_no == 0) return (EIO); te->entry.control = sc->data.toc[trk].control; te->entry.addr_type = sc->data.toc[trk].addr_type; te->entry.track = sc->data.toc[trk].idx_no > 0x99 ? sc->data.toc[trk].idx_no : bcd2bin(sc->data.toc[trk].idx_no); switch (te->address_format) { case CD_MSF_FORMAT: te->entry.addr.msf.unused = 0; te->entry.addr.msf.minute = bcd2bin(sc->data.toc[trk].hd_pos_msf[0]); te->entry.addr.msf.second = bcd2bin(sc->data.toc[trk].hd_pos_msf[1]); te->entry.addr.msf.frame = bcd2bin(sc->data.toc[trk].hd_pos_msf[2]); break; case CD_LBA_FORMAT: te->entry.addr.lba = htonl(msf2hsg(sc->data.toc[trk].hd_pos_msf, 0)); break; } return (0); } #endif static int mcd_toc_entrys(struct mcd_softc *sc, struct ioc_read_toc_entry *te) { struct cd_toc_entry entries[MCD_MAXTOCS]; struct ioc_toc_header th; int rc, n, trk, len; if ( te->data_len < sizeof(entries[0]) || (te->data_len % sizeof(entries[0])) != 0 || (te->address_format != CD_MSF_FORMAT && te->address_format != CD_LBA_FORMAT) ) return (EINVAL); /* Copy the toc header */ if ((rc = mcd_toc_header(sc, &th)) != 0) return (rc); /* verify starting track */ trk = te->starting_track; if (trk == 0) trk = th.starting_track; else if (trk == MCD_LASTPLUS1) trk = th.ending_track + 1; else if (trk < th.starting_track || trk > th.ending_track + 1) return (EINVAL); len = ((th.ending_track + 1 - trk) + 1) * sizeof(entries[0]); if (te->data_len < len) len = te->data_len; if (len > sizeof(entries)) return (EINVAL); /* Make sure we have a valid toc */ if ((rc=mcd_read_toc(sc)) != 0) return (rc); /* Copy the TOC data. */ for (n = 0; len > 0 && trk <= th.ending_track + 1; trk++) { if (sc->data.toc[trk].idx_no == 0) continue; entries[n].control = sc->data.toc[trk].control; entries[n].addr_type = sc->data.toc[trk].addr_type; entries[n].track = sc->data.toc[trk].idx_no > 0x99 ? sc->data.toc[trk].idx_no : bcd2bin(sc->data.toc[trk].idx_no); switch (te->address_format) { case CD_MSF_FORMAT: entries[n].addr.msf.unused = 0; entries[n].addr.msf.minute = bcd2bin(sc->data.toc[trk].hd_pos_msf[0]); entries[n].addr.msf.second = bcd2bin(sc->data.toc[trk].hd_pos_msf[1]); entries[n].addr.msf.frame = bcd2bin(sc->data.toc[trk].hd_pos_msf[2]); break; case CD_LBA_FORMAT: entries[n].addr.lba = htonl(msf2hsg(sc->data.toc[trk].hd_pos_msf, 0)); break; } len -= sizeof(struct cd_toc_entry); n++; } /* copy the data back */ MCD_UNLOCK(sc); return copyout(entries, te->data, n * sizeof(struct cd_toc_entry)); } static int mcd_stop(struct mcd_softc *sc) { /* Verify current status */ if (sc->data.audio_status != CD_AS_PLAY_IN_PROGRESS && sc->data.audio_status != CD_AS_PLAY_PAUSED && sc->data.audio_status != CD_AS_PLAY_COMPLETED) { if (sc->data.debug) device_printf(sc->dev, "stop attempted when not playing, audio status %d\n", sc->data.audio_status); return (EINVAL); } if (sc->data.audio_status == CD_AS_PLAY_IN_PROGRESS) if (mcd_send(sc, MCD_CMDSTOPAUDIO, MCD_RETRYS) < 0) return (EIO); sc->data.audio_status = CD_AS_PLAY_COMPLETED; return (0); } static int mcd_getqchan(struct mcd_softc *sc, struct mcd_qchninfo *q) { if (mcd_send(sc, MCD_CMDGETQCHN, MCD_RETRYS) < 0) return (-1); if (mcd_get(sc, (char *) q, sizeof(struct mcd_qchninfo)) < 0) return (-1); if (sc->data.debug) { device_printf(sc->dev, "getqchan control=0x%x addr_type=0x%x trk=%d ind=%d ttm=%d:%d.%d dtm=%d:%d.%d\n", q->control, q->addr_type, bcd2bin(q->trk_no), bcd2bin(q->idx_no), bcd2bin(q->trk_size_msf[0]), bcd2bin(q->trk_size_msf[1]), bcd2bin(q->trk_size_msf[2]), bcd2bin(q->hd_pos_msf[0]), bcd2bin(q->hd_pos_msf[1]), bcd2bin(q->hd_pos_msf[2])); } return (0); } static int -mcd_subchan(struct mcd_softc *sc, struct ioc_read_subchannel *sch, int nocopyout) +mcd_subchan(struct mcd_softc *sc, struct ioc_read_subchannel *sch) { struct mcd_qchninfo q; struct cd_sub_channel_info data; int lba; if (sc->data.debug) device_printf(sc->dev, "subchan af=%d, df=%d\n", sch->address_format, sch->data_format); if (sch->address_format != CD_MSF_FORMAT && sch->address_format != CD_LBA_FORMAT) return (EINVAL); if (sch->data_format != CD_CURRENT_POSITION && sch->data_format != CD_MEDIA_CATALOG) return (EINVAL); if (mcd_setmode(sc, MCD_MD_COOKED) != 0) return (EIO); if (mcd_getqchan(sc, &q) < 0) return (EIO); data.header.audio_status = sc->data.audio_status; data.what.position.data_format = sch->data_format; switch (sch->data_format) { case CD_MEDIA_CATALOG: data.what.media_catalog.mc_valid = 1; data.what.media_catalog.mc_number[0] = '\0'; break; case CD_CURRENT_POSITION: data.what.position.control = q.control; data.what.position.addr_type = q.addr_type; data.what.position.track_number = bcd2bin(q.trk_no); data.what.position.index_number = bcd2bin(q.idx_no); switch (sch->address_format) { case CD_MSF_FORMAT: data.what.position.reladdr.msf.unused = 0; data.what.position.reladdr.msf.minute = bcd2bin(q.trk_size_msf[0]); data.what.position.reladdr.msf.second = bcd2bin(q.trk_size_msf[1]); data.what.position.reladdr.msf.frame = bcd2bin(q.trk_size_msf[2]); data.what.position.absaddr.msf.unused = 0; data.what.position.absaddr.msf.minute = bcd2bin(q.hd_pos_msf[0]); data.what.position.absaddr.msf.second = bcd2bin(q.hd_pos_msf[1]); data.what.position.absaddr.msf.frame = bcd2bin(q.hd_pos_msf[2]); break; case CD_LBA_FORMAT: lba = msf2hsg(q.trk_size_msf, 1); /* * Pre-gap has index number of 0, and decreasing MSF * address. Must be converted to negative LBA, per * SCSI spec. */ if (data.what.position.index_number == 0) lba = -lba; data.what.position.reladdr.lba = htonl(lba); data.what.position.absaddr.lba = htonl(msf2hsg(q.hd_pos_msf, 0)); break; } break; } MCD_UNLOCK(sc); - if (nocopyout == 0) - return copyout(&data, sch->data, min(sizeof(struct cd_sub_channel_info), sch->data_len)); - bcopy(&data, sch->data, min(sizeof(struct cd_sub_channel_info), sch->data_len)); - return (0); + return (copyout(&data, sch->data, min(sizeof(struct cd_sub_channel_info), sch->data_len))); } static int mcd_playmsf(struct mcd_softc *sc, struct ioc_play_msf *p) { struct mcd_read2 pb; if (sc->data.debug) device_printf(sc->dev, "playmsf: from %d:%d.%d to %d:%d.%d\n", p->start_m, p->start_s, p->start_f, p->end_m, p->end_s, p->end_f); if ((p->start_m * 60 * 75 + p->start_s * 75 + p->start_f) >= (p->end_m * 60 * 75 + p->end_s * 75 + p->end_f) || (p->end_m * 60 * 75 + p->end_s * 75 + p->end_f) > M_msf(sc->data.volinfo.vol_msf) * 60 * 75 + S_msf(sc->data.volinfo.vol_msf) * 75 + F_msf(sc->data.volinfo.vol_msf)) return (EINVAL); pb.start_msf[0] = bin2bcd(p->start_m); pb.start_msf[1] = bin2bcd(p->start_s); pb.start_msf[2] = bin2bcd(p->start_f); pb.end_msf[0] = bin2bcd(p->end_m); pb.end_msf[1] = bin2bcd(p->end_s); pb.end_msf[2] = bin2bcd(p->end_f); if (mcd_setmode(sc, MCD_MD_COOKED) != 0) return (EIO); return mcd_play(sc, &pb); } static int mcd_playtracks(struct mcd_softc *sc, struct ioc_play_track *pt) { struct mcd_read2 pb; int a = pt->start_track; int z = pt->end_track; int rc, i; if ((rc = mcd_read_toc(sc)) != 0) return (rc); if (sc->data.debug) device_printf(sc->dev, "playtracks from %d:%d to %d:%d\n", a, pt->start_index, z, pt->end_index); if ( a < bcd2bin(sc->data.volinfo.trk_low) || a > bcd2bin(sc->data.volinfo.trk_high) || a > z || z < bcd2bin(sc->data.volinfo.trk_low) || z > bcd2bin(sc->data.volinfo.trk_high)) return (EINVAL); for (i = 0; i < 3; i++) { pb.start_msf[i] = sc->data.toc[a].hd_pos_msf[i]; pb.end_msf[i] = sc->data.toc[z+1].hd_pos_msf[i]; } if (mcd_setmode(sc, MCD_MD_COOKED) != 0) return (EIO); return mcd_play(sc, &pb); } static int mcd_playblocks(struct mcd_softc *sc, struct ioc_play_blocks *p) { struct mcd_read2 pb; if (sc->data.debug) device_printf(sc->dev, "playblocks: blkno %d length %d\n", p->blk, p->len); if (p->blk > sc->data.disksize || p->len > sc->data.disksize || p->blk < 0 || p->len < 0 || (p->blk + p->len) > sc->data.disksize) return (EINVAL); hsg2msf(p->blk, pb.start_msf); hsg2msf(p->blk + p->len, pb.end_msf); if (mcd_setmode(sc, MCD_MD_COOKED) != 0) return (EIO); return mcd_play(sc, &pb); } static int mcd_play(struct mcd_softc *sc, struct mcd_read2 *pb) { int retry, st = -1, status; sc->data.lastpb = *pb; for(retry=0; retrystart_msf[0]); MCD_WRITE(sc, MCD_REG_COMMAND, pb->start_msf[1]); MCD_WRITE(sc, MCD_REG_COMMAND, pb->start_msf[2]); MCD_WRITE(sc, MCD_REG_COMMAND, pb->end_msf[0]); MCD_WRITE(sc, MCD_REG_COMMAND, pb->end_msf[1]); MCD_WRITE(sc, MCD_REG_COMMAND, pb->end_msf[2]); critical_exit(); status=mcd_getstat(sc, 0); if (status == -1) continue; else if (status != -2) st = 0; break; } if (status == -2) { device_printf(sc->dev, "media changed\n"); return (ENXIO); } if (sc->data.debug) device_printf(sc->dev, "mcd_play retry=%d, status=0x%02x\n", retry, status); if (st < 0) return (ENXIO); sc->data.audio_status = CD_AS_PLAY_IN_PROGRESS; return (0); } static int mcd_pause(struct mcd_softc *sc) { struct mcd_qchninfo q; int rc; /* Verify current status */ if (sc->data.audio_status != CD_AS_PLAY_IN_PROGRESS && sc->data.audio_status != CD_AS_PLAY_PAUSED) { if (sc->data.debug) device_printf(sc->dev, "pause attempted when not playing, audio status %d\n", sc->data.audio_status); return (EINVAL); } /* Get the current position */ if (mcd_getqchan(sc, &q) < 0) return (EIO); /* Copy it into lastpb */ sc->data.lastpb.start_msf[0] = q.hd_pos_msf[0]; sc->data.lastpb.start_msf[1] = q.hd_pos_msf[1]; sc->data.lastpb.start_msf[2] = q.hd_pos_msf[2]; /* Stop playing */ if ((rc=mcd_stop(sc)) != 0) return (rc); /* Set the proper status and exit */ sc->data.audio_status = CD_AS_PLAY_PAUSED; return (0); } static int mcd_resume(struct mcd_softc *sc) { if (sc->data.audio_status != CD_AS_PLAY_PAUSED) return (EINVAL); return mcd_play(sc, &sc->data.lastpb); } Index: releng/11.3/sys/dev/scd/scd.c =================================================================== --- releng/11.3/sys/dev/scd/scd.c (revision 349624) +++ releng/11.3/sys/dev/scd/scd.c (revision 349625) @@ -1,1433 +1,1427 @@ /*- * Copyright (c) 1995 Mikael Hybsch * All rights reserved. * * Portions of this file are copied from mcd.c * which has the following copyrights: * * Copyright 1993 by Holger Veit (data part) * Copyright 1993 by Brian Moore (audio part) * Changes Copyright 1993 by Gary Clark II * Changes Copyright (C) 1994 by Andrew A. Chernov * * Rewrote probe routine to work on newer Mitsumi drives. * Additional changes (C) 1994 by Jordan K. Hubbard * * 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 * in this position and unchanged. * 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 ``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$"); #undef SCD_DEBUG #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* flags */ #define SCDOPEN 0x0001 /* device opened */ #define SCDVALID 0x0002 /* parameters loaded */ #define SCDINIT 0x0004 /* device is init'd */ #define SCDPROBING 0x0020 /* probing */ #define SCDTOC 0x0100 /* already read toc */ #define SCDMBXBSY 0x0200 /* local mbx is busy */ #define SCDSPINNING 0x0400 /* drive is spun up */ #define SCD_S_BEGIN 0 #define SCD_S_BEGIN1 1 #define SCD_S_WAITSTAT 2 #define SCD_S_WAITFIFO 3 #define SCD_S_WAITSPIN 4 #define SCD_S_WAITREAD 5 #define SCD_S_WAITPARAM 6 #define RDELAY_WAIT 300 #define RDELAY_WAITREAD 300 #define SCDBLKSIZE 2048 #ifdef SCD_DEBUG static int scd_debuglevel = SCD_DEBUG; # define XDEBUG(sc, level, fmt, args...) \ do { \ if (scd_debuglevel >= level) \ device_printf(sc->dev, fmt, ## args); \ } while (0) #else # define XDEBUG(sc, level, fmt, args...) #endif #define IS_ATTENTION(sc) ((SCD_READ(sc, IREG_STATUS) & SBIT_ATTENTION) != 0) #define IS_BUSY(sc) ((SCD_READ(sc, IREG_STATUS) & SBIT_BUSY) != 0) #define IS_DATA_RDY(sc) ((SCD_READ(sc, IREG_STATUS) & SBIT_DATA_READY) != 0) #define STATUS_BIT(sc, bit) ((SCD_READ(sc, IREG_STATUS) & (bit)) != 0) #define FSTATUS_BIT(sc, bit) ((SCD_READ(sc, IREG_FSTATUS) & (bit)) != 0) /* prototypes */ static void hsg2msf(int hsg, bcd_t *msf); static int msf2hsg(bcd_t *msf); static void process_attention(struct scd_softc *); static int waitfor_status_bits(struct scd_softc *, int bits_set, int bits_clear); static int send_cmd(struct scd_softc *, u_char cmd, u_int nargs, ...); static void init_drive(struct scd_softc *); static int spin_up(struct scd_softc *); static int read_toc(struct scd_softc *); static int get_result(struct scd_softc *, int result_len, u_char *result); static void print_error(struct scd_softc *, int errcode); static void scd_start(struct scd_softc *); static void scd_timeout(void *); static void scd_doread(struct scd_softc *, int state, struct scd_mbx *mbxin); static int scd_eject(struct scd_softc *); static int scd_stop(struct scd_softc *); static int scd_pause(struct scd_softc *); static int scd_resume(struct scd_softc *); static int scd_playtracks(struct scd_softc *, struct ioc_play_track *pt); static int scd_playmsf(struct scd_softc *, struct ioc_play_msf *msf); static int scd_play(struct scd_softc *, struct ioc_play_msf *msf); -static int scd_subchan(struct scd_softc *, struct ioc_read_subchannel *sch, int nocopyout); +static int scd_subchan(struct scd_softc *, struct ioc_read_subchannel *sch); static int read_subcode(struct scd_softc *, struct sony_subchannel_position_data *sch); /* for xcdplayer */ static int scd_toc_header(struct scd_softc *, struct ioc_toc_header *th); static int scd_toc_entrys(struct scd_softc *, struct ioc_read_toc_entry *te); static int scd_toc_entry(struct scd_softc *, struct ioc_read_toc_single_entry *te); #define SCD_LASTPLUS1 170 /* don't ask, xcdplayer passes this in */ static d_open_t scdopen; static d_close_t scdclose; static d_ioctl_t scdioctl; static d_strategy_t scdstrategy; static struct cdevsw scd_cdevsw = { .d_version = D_VERSION, .d_open = scdopen, .d_close = scdclose, .d_read = physread, .d_ioctl = scdioctl, .d_strategy = scdstrategy, .d_name = "scd", .d_flags = D_DISK, }; int scd_attach(struct scd_softc *sc) { int unit; unit = device_get_unit(sc->dev); SCD_LOCK(sc); init_drive(sc); sc->data.flags = SCDINIT; sc->data.audio_status = CD_AS_AUDIO_INVALID; bioq_init(&sc->data.head); SCD_UNLOCK(sc); sc->scd_dev_t = make_dev(&scd_cdevsw, 8 * unit, UID_ROOT, GID_OPERATOR, 0640, "scd%d", unit); sc->scd_dev_t->si_drv1 = (void *)sc; device_printf(sc->dev, "WARNING: This driver is deprecated and will be removed.\n"); return (0); } static int scdopen(struct cdev *dev, int flags, int fmt, struct thread *td) { struct scd_softc *sc; int rc; sc = (struct scd_softc *)dev->si_drv1; /* mark all open part's invalid */ SCD_LOCK(sc); if (sc->data.openflag) { SCD_UNLOCK(sc); return (ENXIO); } XDEBUG(sc, 1, "DEBUG: status = 0x%x\n", SCD_READ(sc, IREG_STATUS)); if ((rc = spin_up(sc)) != 0) { print_error(sc, rc); SCD_UNLOCK(sc); return (EIO); } if (!(sc->data.flags & SCDTOC)) { int loop_count = 3; while (loop_count-- > 0 && (rc = read_toc(sc)) != 0) { if (rc == ERR_NOT_SPINNING) { rc = spin_up(sc); if (rc) { print_error(sc, rc); SCD_UNLOCK(sc); return (EIO); } continue; } device_printf(sc->dev, "TOC read error 0x%x\n", rc); SCD_UNLOCK(sc); return (EIO); } } sc->data.openflag = 1; sc->data.flags |= SCDVALID; SCD_UNLOCK(sc); return (0); } static int scdclose(struct cdev *dev, int flags, int fmt, struct thread *td) { struct scd_softc *sc; sc = (struct scd_softc *)dev->si_drv1; SCD_LOCK(sc); KASSERT(sc->data.openflag, ("device not open")); if (sc->data.audio_status != CD_AS_PLAY_IN_PROGRESS) { (void)send_cmd(sc, CMD_SPIN_DOWN, 0); sc->data.flags &= ~SCDSPINNING; } /* close channel */ sc->data.openflag = 0; SCD_UNLOCK(sc); return (0); } static void scdstrategy(struct bio *bp) { struct scd_softc *sc; sc = (struct scd_softc *)bp->bio_dev->si_drv1; /* if device invalidated (e.g. media change, door open), error */ SCD_LOCK(sc); if (!(sc->data.flags & SCDVALID)) { device_printf(sc->dev, "media changed\n"); bp->bio_error = EIO; goto bad; } /* read only */ if (!(bp->bio_cmd == BIO_READ)) { bp->bio_error = EROFS; goto bad; } /* no data to read */ if (bp->bio_bcount == 0) goto done; if (!(sc->data.flags & SCDTOC)) { bp->bio_error = EIO; goto bad; } bp->bio_resid = 0; /* queue it */ bioq_disksort(&sc->data.head, bp); /* now check whether we can perform processing */ scd_start(sc); SCD_UNLOCK(sc); return; bad: bp->bio_flags |= BIO_ERROR; done: SCD_UNLOCK(sc); bp->bio_resid = bp->bio_bcount; biodone(bp); return; } static void scd_start(struct scd_softc *sc) { struct bio *bp; SCD_ASSERT_LOCKED(sc); if (sc->data.flags & SCDMBXBSY) return; bp = bioq_takefirst(&sc->data.head); if (bp != 0) { /* block found to process, dequeue */ sc->data.flags |= SCDMBXBSY; } else { /* nothing to do */ return; } sc->data.mbx.retry = 3; sc->data.mbx.bp = bp; scd_doread(sc, SCD_S_BEGIN, &(sc->data.mbx)); return; } static int scdioctl(struct cdev *dev, u_long cmd, caddr_t addr, int flags, struct thread *td) { struct scd_softc *sc; int error; sc = (struct scd_softc *)dev->si_drv1; XDEBUG(sc, 1, "ioctl: cmd=0x%lx\n", cmd); SCD_LOCK(sc); if (!(sc->data.flags & SCDVALID)) { SCD_UNLOCK(sc); return (EIO); } error = 0; switch (cmd) { case DIOCGMEDIASIZE: *(off_t *)addr = (off_t)sc->data.disksize * sc->data.blksize; break; case DIOCGSECTORSIZE: *(u_int *)addr = sc->data.blksize; break; case CDIOCPLAYTRACKS: error = scd_playtracks(sc, (struct ioc_play_track *) addr); break; case CDIOCPLAYBLOCKS: error = EINVAL; break; case CDIOCPLAYMSF: error = scd_playmsf(sc, (struct ioc_play_msf *) addr); break; - case CDIOCREADSUBCHANNEL_SYSSPACE: - return scd_subchan(sc, (struct ioc_read_subchannel *) addr, 1); case CDIOCREADSUBCHANNEL: - return scd_subchan(sc, (struct ioc_read_subchannel *) addr, 0); + return scd_subchan(sc, (struct ioc_read_subchannel *) addr); case CDIOREADTOCHEADER: error = scd_toc_header (sc, (struct ioc_toc_header *) addr); break; case CDIOREADTOCENTRYS: return scd_toc_entrys (sc, (struct ioc_read_toc_entry*) addr); case CDIOREADTOCENTRY: error = scd_toc_entry (sc, (struct ioc_read_toc_single_entry*) addr); break; case CDIOCSETPATCH: case CDIOCGETVOL: case CDIOCSETVOL: case CDIOCSETMONO: case CDIOCSETSTERIO: case CDIOCSETMUTE: case CDIOCSETLEFT: case CDIOCSETRIGHT: error = EINVAL; break; case CDIOCRESUME: error = scd_resume(sc); break; case CDIOCPAUSE: error = scd_pause(sc); break; case CDIOCSTART: error = EINVAL; break; case CDIOCSTOP: error = scd_stop(sc); break; case CDIOCEJECT: error = scd_eject(sc); break; case CDIOCALLOW: break; case CDIOCSETDEBUG: #ifdef SCD_DEBUG scd_debuglevel++; #endif break; case CDIOCCLRDEBUG: #ifdef SCD_DEBUG scd_debuglevel = 0; #endif break; default: device_printf(sc->dev, "unsupported ioctl (cmd=0x%lx)\n", cmd); error = ENOTTY; break; } SCD_UNLOCK(sc); return (error); } /*************************************************************** * lower level of driver starts here **************************************************************/ static int scd_playtracks(struct scd_softc *sc, struct ioc_play_track *pt) { struct ioc_play_msf msf; int a = pt->start_track; int z = pt->end_track; int rc; if (!(sc->data.flags & SCDTOC) && (rc = read_toc(sc)) != 0) { if (rc == -ERR_NOT_SPINNING) { if (spin_up(sc) != 0) return (EIO); rc = read_toc(sc); } if (rc != 0) { print_error(sc, rc); return (EIO); } } XDEBUG(sc, 1, "playtracks from %d:%d to %d:%d\n", a, pt->start_index, z, pt->end_index); if ( a < sc->data.first_track || a > sc->data.last_track || a > z || z > sc->data.last_track) return (EINVAL); bcopy(sc->data.toc[a].start_msf, &msf.start_m, 3); hsg2msf(msf2hsg(sc->data.toc[z+1].start_msf)-1, &msf.end_m); return scd_play(sc, &msf); } /* The start/end msf is expected to be in bin format */ static int scd_playmsf(struct scd_softc *sc, struct ioc_play_msf *msfin) { struct ioc_play_msf msf; msf.start_m = bin2bcd(msfin->start_m); msf.start_s = bin2bcd(msfin->start_s); msf.start_f = bin2bcd(msfin->start_f); msf.end_m = bin2bcd(msfin->end_m); msf.end_s = bin2bcd(msfin->end_s); msf.end_f = bin2bcd(msfin->end_f); return scd_play(sc, &msf); } /* The start/end msf is expected to be in bcd format */ static int scd_play(struct scd_softc *sc, struct ioc_play_msf *msf) { int i, rc; XDEBUG(sc, 1, "playing: %02x:%02x:%02x -> %02x:%02x:%02x\n", msf->start_m, msf->start_s, msf->start_f, msf->end_m, msf->end_s, msf->end_f); for (i = 0; i < 2; i++) { rc = send_cmd(sc, CMD_PLAY_AUDIO, 7, 0x03, msf->start_m, msf->start_s, msf->start_f, msf->end_m, msf->end_s, msf->end_f); if (rc == -ERR_NOT_SPINNING) { sc->data.flags &= ~SCDSPINNING; if (spin_up(sc) != 0) return (EIO); } else if (rc < 0) { print_error(sc, rc); return (EIO); } else { break; } } sc->data.audio_status = CD_AS_PLAY_IN_PROGRESS; bcopy((char *)msf, (char *)&sc->data.last_play, sizeof(struct ioc_play_msf)); return (0); } static int scd_stop(struct scd_softc *sc) { (void)send_cmd(sc, CMD_STOP_AUDIO, 0); sc->data.audio_status = CD_AS_PLAY_COMPLETED; return (0); } static int scd_pause(struct scd_softc *sc) { struct sony_subchannel_position_data subpos; if (sc->data.audio_status != CD_AS_PLAY_IN_PROGRESS) return (EINVAL); if (read_subcode(sc, &subpos) != 0) return (EIO); if (send_cmd(sc, CMD_STOP_AUDIO, 0) != 0) return (EIO); sc->data.last_play.start_m = subpos.abs_msf[0]; sc->data.last_play.start_s = subpos.abs_msf[1]; sc->data.last_play.start_f = subpos.abs_msf[2]; sc->data.audio_status = CD_AS_PLAY_PAUSED; XDEBUG(sc, 1, "pause @ %02x:%02x:%02x\n", sc->data.last_play.start_m, sc->data.last_play.start_s, sc->data.last_play.start_f); return (0); } static int scd_resume(struct scd_softc *sc) { if (sc->data.audio_status != CD_AS_PLAY_PAUSED) return (EINVAL); return scd_play(sc, &sc->data.last_play); } static int scd_eject(struct scd_softc *sc) { sc->data.audio_status = CD_AS_AUDIO_INVALID; sc->data.flags &= ~(SCDSPINNING|SCDTOC); if (send_cmd(sc, CMD_STOP_AUDIO, 0) != 0 || send_cmd(sc, CMD_SPIN_DOWN, 0) != 0 || send_cmd(sc, CMD_EJECT, 0) != 0) { return (EIO); } return (0); } static int -scd_subchan(struct scd_softc *sc, struct ioc_read_subchannel *sch, int nocopyout) +scd_subchan(struct scd_softc *sc, struct ioc_read_subchannel *sch) { struct sony_subchannel_position_data q; struct cd_sub_channel_info data; XDEBUG(sc, 1, "subchan af=%d, df=%d\n", sch->address_format, sch->data_format); if (sch->address_format != CD_MSF_FORMAT) return (EINVAL); if (sch->data_format != CD_CURRENT_POSITION) return (EINVAL); if (read_subcode(sc, &q) != 0) return (EIO); data.header.audio_status = sc->data.audio_status; data.what.position.data_format = CD_MSF_FORMAT; data.what.position.track_number = bcd2bin(q.track_number); data.what.position.reladdr.msf.unused = 0; data.what.position.reladdr.msf.minute = bcd2bin(q.rel_msf[0]); data.what.position.reladdr.msf.second = bcd2bin(q.rel_msf[1]); data.what.position.reladdr.msf.frame = bcd2bin(q.rel_msf[2]); data.what.position.absaddr.msf.unused = 0; data.what.position.absaddr.msf.minute = bcd2bin(q.abs_msf[0]); data.what.position.absaddr.msf.second = bcd2bin(q.abs_msf[1]); data.what.position.absaddr.msf.frame = bcd2bin(q.abs_msf[2]); SCD_UNLOCK(sc); - if (nocopyout == 0) { - if (copyout(&data, sch->data, min(sizeof(struct cd_sub_channel_info), sch->data_len))!=0) - return (EFAULT); - } else { - bcopy(&data, sch->data, min(sizeof(struct cd_sub_channel_info), sch->data_len)); - } + if (copyout(&data, sch->data, min(sizeof(struct cd_sub_channel_info), sch->data_len))!=0) + return (EFAULT); return (0); } int scd_probe(struct scd_softc *sc) { struct sony_drive_configuration drive_config; int rc; static char namebuf[8+16+8+3]; char *s = namebuf; int loop_count = 0; sc->data.flags = SCDPROBING; bzero(&drive_config, sizeof(drive_config)); again: /* Reset drive */ SCD_WRITE(sc, OREG_CONTROL, CBIT_RESET_DRIVE); /* Calm down */ DELAY(300000); /* Only the ATTENTION bit may be set */ if ((SCD_READ(sc, IREG_STATUS) & ~1) != 0) { XDEBUG(sc, 1, "too many bits set. probe failed.\n"); return (ENXIO); } rc = send_cmd(sc, CMD_GET_DRIVE_CONFIG, 0); if (rc != sizeof(drive_config)) { /* Sometimes if the drive is playing audio I get */ /* the bad result 82. Fix by repeating the reset */ if (rc > 0 && loop_count++ == 0) goto again; return (ENXIO); } if (get_result(sc, rc, (u_char *)&drive_config) != 0) return (ENXIO); bcopy(drive_config.vendor, namebuf, 8); s = namebuf+8; while (*(s-1) == ' ') /* Strip trailing spaces */ s--; *s++ = ' '; bcopy(drive_config.product, s, 16); s += 16; while (*(s-1) == ' ') s--; *s++ = ' '; bcopy(drive_config.revision, s, 8); s += 8; while (*(s-1) == ' ') s--; *s = 0; sc->data.name = namebuf; if (drive_config.config & 0x10) sc->data.double_speed = 1; else sc->data.double_speed = 0; return (0); } static int read_subcode(struct scd_softc *sc, struct sony_subchannel_position_data *scp) { int rc; rc = send_cmd(sc, CMD_GET_SUBCHANNEL_DATA, 0); if (rc < 0 || rc < sizeof(*scp)) return (EIO); if (get_result(sc, rc, (u_char *)scp) != 0) return (EIO); return (0); } /* State machine copied from mcd.c */ /* This (and the code in mcd.c) will not work with more than one drive */ /* because there is only one sc->ch_mbxsave below. Should fix that some day. */ /* (sc->ch_mbxsave & state should probably be included in the scd_data struct and */ /* the unit number used as first argument to scd_doread().) /Micke */ /* state machine to process read requests * initialize with SCD_S_BEGIN: reset state machine * SCD_S_WAITSTAT: wait for ready (!busy) * SCD_S_WAITSPIN: wait for drive to spin up (if not spinning) * SCD_S_WAITFIFO: wait for param fifo to get ready, them exec. command. * SCD_S_WAITREAD: wait for data ready, read data * SCD_S_WAITPARAM: wait for command result params, read them, error if bad data read. */ static void scd_timeout(void *arg) { struct scd_softc *sc; sc = (struct scd_softc *)arg; SCD_ASSERT_LOCKED(sc); scd_doread(sc, sc->ch_state, sc->ch_mbxsave); } static void scd_doread(struct scd_softc *sc, int state, struct scd_mbx *mbxin) { struct scd_mbx *mbx = (state!=SCD_S_BEGIN) ? sc->ch_mbxsave : mbxin; struct bio *bp = mbx->bp; int i; int blknum; caddr_t addr; static char sdata[3]; /* Must be preserved between calls to this function */ SCD_ASSERT_LOCKED(sc); loop: switch (state) { case SCD_S_BEGIN: mbx = sc->ch_mbxsave = mbxin; case SCD_S_BEGIN1: /* get status */ mbx->count = RDELAY_WAIT; process_attention(sc); goto trystat; case SCD_S_WAITSTAT: sc->ch_state = SCD_S_WAITSTAT; callout_stop(&sc->timer); if (mbx->count-- <= 0) { device_printf(sc->dev, "timeout. drive busy.\n"); goto harderr; } trystat: if (IS_BUSY(sc)) { sc->ch_state = SCD_S_WAITSTAT; callout_reset(&sc->timer, hz / 100, scd_timeout, sc); /* XXX */ return; } process_attention(sc); /* reject, if audio active */ if (sc->data.audio_status & CD_AS_PLAY_IN_PROGRESS) { device_printf(sc->dev, "audio is active\n"); goto harderr; } mbx->sz = sc->data.blksize; /* for first block */ mbx->nblk = howmany(bp->bio_bcount, mbx->sz); mbx->skip = 0; nextblock: if (!(sc->data.flags & SCDVALID)) goto changed; blknum = bp->bio_offset / mbx->sz + mbx->skip/mbx->sz; XDEBUG(sc, 2, "scd_doread: read blknum=%d\n", blknum); /* build parameter block */ hsg2msf(blknum, sdata); SCD_WRITE(sc, OREG_CONTROL, CBIT_RESULT_READY_CLEAR); SCD_WRITE(sc, OREG_CONTROL, CBIT_RPARAM_CLEAR); SCD_WRITE(sc, OREG_CONTROL, CBIT_DATA_READY_CLEAR); if (FSTATUS_BIT(sc, FBIT_WPARAM_READY)) goto writeparam; mbx->count = 100; sc->ch_state = SCD_S_WAITFIFO; callout_reset(&sc->timer, hz / 100, scd_timeout, sc); /* XXX */ return; case SCD_S_WAITSPIN: sc->ch_state = SCD_S_WAITSPIN; callout_stop(&sc->timer); if (mbx->count-- <= 0) { device_printf(sc->dev, "timeout waiting for drive to spin up.\n"); goto harderr; } if (!STATUS_BIT(sc, SBIT_RESULT_READY)) { sc->ch_state = SCD_S_WAITSPIN; callout_reset(&sc->timer, hz / 100, scd_timeout, sc); /* XXX */ return; } SCD_WRITE(sc, OREG_CONTROL, CBIT_RESULT_READY_CLEAR); switch ((i = SCD_READ(sc, IREG_RESULT)) & 0xf0) { case 0x20: i = SCD_READ(sc, IREG_RESULT); print_error(sc, i); goto harderr; case 0x00: (void)SCD_READ(sc, IREG_RESULT); sc->data.flags |= SCDSPINNING; break; } XDEBUG(sc, 1, "DEBUG: spin up complete\n"); state = SCD_S_BEGIN1; goto loop; case SCD_S_WAITFIFO: sc->ch_state = SCD_S_WAITFIFO; callout_stop(&sc->timer); if (mbx->count-- <= 0) { device_printf(sc->dev, "timeout. write param not ready.\n"); goto harderr; } if (!FSTATUS_BIT(sc, FBIT_WPARAM_READY)) { sc->ch_state = SCD_S_WAITFIFO; callout_reset(&sc->timer, hz / 100, scd_timeout, sc); /* XXX */ return; } XDEBUG(sc, 1, "mbx->count (writeparamwait) = %d(%d)\n", mbx->count, 100); writeparam: /* The reason this test isn't done 'till now is to make sure */ /* that it is ok to send the SPIN_UP cmd below. */ if (!(sc->data.flags & SCDSPINNING)) { XDEBUG(sc, 1, "spinning up drive ...\n"); SCD_WRITE(sc, OREG_COMMAND, CMD_SPIN_UP); mbx->count = 300; sc->ch_state = SCD_S_WAITSPIN; callout_reset(&sc->timer, hz / 100, scd_timeout, sc); /* XXX */ return; } /* send the read command */ SCD_WRITE(sc, OREG_WPARAMS, sdata[0]); SCD_WRITE(sc, OREG_WPARAMS, sdata[1]); SCD_WRITE(sc, OREG_WPARAMS, sdata[2]); SCD_WRITE(sc, OREG_WPARAMS, 0); SCD_WRITE(sc, OREG_WPARAMS, 0); SCD_WRITE(sc, OREG_WPARAMS, 1); SCD_WRITE(sc, OREG_COMMAND, CMD_READ); mbx->count = RDELAY_WAITREAD; for (i = 0; i < 50; i++) { if (STATUS_BIT(sc, SBIT_DATA_READY)) goto got_data; DELAY(100); } sc->ch_state = SCD_S_WAITREAD; callout_reset(&sc->timer, hz / 100, scd_timeout, sc); /* XXX */ return; case SCD_S_WAITREAD: sc->ch_state = SCD_S_WAITREAD; callout_stop(&sc->timer); if (mbx->count-- <= 0) { if (STATUS_BIT(sc, SBIT_RESULT_READY)) goto got_param; device_printf(sc->dev, "timeout while reading data\n"); goto readerr; } if (!STATUS_BIT(sc, SBIT_DATA_READY)) { process_attention(sc); if (!(sc->data.flags & SCDVALID)) goto changed; sc->ch_state = SCD_S_WAITREAD; callout_reset(&sc->timer, hz / 100, scd_timeout, sc); /* XXX */ return; } XDEBUG(sc, 2, "mbx->count (after RDY_BIT) = %d(%d)\n", mbx->count, RDELAY_WAITREAD); got_data: /* data is ready */ addr = bp->bio_data + mbx->skip; SCD_WRITE(sc, OREG_CONTROL, CBIT_DATA_READY_CLEAR); SCD_READ_MULTI(sc, IREG_DATA, addr, mbx->sz); mbx->count = 100; for (i = 0; i < 20; i++) { if (STATUS_BIT(sc, SBIT_RESULT_READY)) goto waitfor_param; DELAY(100); } goto waitfor_param; case SCD_S_WAITPARAM: sc->ch_state = SCD_S_WAITPARAM; callout_stop(&sc->timer); if (mbx->count-- <= 0) { device_printf(sc->dev, "timeout waiting for params\n"); goto readerr; } waitfor_param: if (!STATUS_BIT(sc, SBIT_RESULT_READY)) { sc->ch_state = SCD_S_WAITPARAM; callout_reset(&sc->timer, hz / 100, scd_timeout, sc); /* XXX */ return; } #ifdef SCD_DEBUG if (mbx->count < 100 && scd_debuglevel > 0) device_printf(sc->dev, "mbx->count (paramwait) = %d(%d)\n", mbx->count, 100); #endif got_param: SCD_WRITE(sc, OREG_CONTROL, CBIT_RESULT_READY_CLEAR); switch ((i = SCD_READ(sc, IREG_RESULT)) & 0xf0) { case 0x50: switch (i) { case ERR_FATAL_READ_ERROR1: case ERR_FATAL_READ_ERROR2: device_printf(sc->dev, "unrecoverable read error 0x%x\n", i); goto harderr; } break; case 0x20: i = SCD_READ(sc, IREG_RESULT); switch (i) { case ERR_NOT_SPINNING: XDEBUG(sc, 1, "read error: drive not spinning\n"); if (mbx->retry-- > 0) { state = SCD_S_BEGIN1; sc->data.flags &= ~SCDSPINNING; goto loop; } goto harderr; default: print_error(sc, i); goto readerr; } case 0x00: i = SCD_READ(sc, IREG_RESULT); break; } if (--mbx->nblk > 0) { mbx->skip += mbx->sz; goto nextblock; } /* return buffer */ bp->bio_resid = 0; biodone(bp); sc->data.flags &= ~SCDMBXBSY; scd_start(sc); return; } readerr: if (mbx->retry-- > 0) { device_printf(sc->dev, "retrying ...\n"); state = SCD_S_BEGIN1; goto loop; } harderr: /* invalidate the buffer */ bp->bio_error = EIO; bp->bio_flags |= BIO_ERROR; bp->bio_resid = bp->bio_bcount; biodone(bp); sc->data.flags &= ~SCDMBXBSY; scd_start(sc); return; changed: device_printf(sc->dev, "media changed\n"); goto harderr; } static void hsg2msf(int hsg, bcd_t *msf) { hsg += 150; M_msf(msf) = bin2bcd(hsg / 4500); hsg %= 4500; S_msf(msf) = bin2bcd(hsg / 75); F_msf(msf) = bin2bcd(hsg % 75); } static int msf2hsg(bcd_t *msf) { return (bcd2bin(M_msf(msf)) * 60 + bcd2bin(S_msf(msf))) * 75 + bcd2bin(F_msf(msf)) - 150; } static void process_attention(struct scd_softc *sc) { unsigned char code; int count = 0; while (IS_ATTENTION(sc) && count++ < 30) { SCD_WRITE(sc, OREG_CONTROL, CBIT_ATTENTION_CLEAR); code = SCD_READ(sc, IREG_RESULT); #ifdef SCD_DEBUG if (scd_debuglevel > 0) { if (count == 1) device_printf(sc->dev, "DEBUG: ATTENTIONS = 0x%x", code); else printf(",0x%x", code); } #endif switch (code) { case ATTEN_SPIN_DOWN: sc->data.flags &= ~SCDSPINNING; break; case ATTEN_SPIN_UP_DONE: sc->data.flags |= SCDSPINNING; break; case ATTEN_AUDIO_DONE: sc->data.audio_status = CD_AS_PLAY_COMPLETED; break; case ATTEN_DRIVE_LOADED: sc->data.flags &= ~(SCDTOC|SCDSPINNING|SCDVALID); sc->data.audio_status = CD_AS_AUDIO_INVALID; break; case ATTEN_EJECT_PUSHED: sc->data.flags &= ~SCDVALID; break; } DELAY(100); } #ifdef SCD_DEBUG if (scd_debuglevel > 0 && count > 0) printf("\n"); #endif } /* Returns 0 OR sony error code */ static int spin_up(struct scd_softc *sc) { unsigned char res_reg[12]; unsigned int res_size; int rc; int loop_count = 0; again: rc = send_cmd(sc, CMD_SPIN_UP, 0, 0, res_reg, &res_size); if (rc != 0) { XDEBUG(sc, 2, "CMD_SPIN_UP error 0x%x\n", rc); return (rc); } if (!(sc->data.flags & SCDTOC)) { rc = send_cmd(sc, CMD_READ_TOC, 0); if (rc == ERR_NOT_SPINNING) { if (loop_count++ < 3) goto again; return (rc); } if (rc != 0) return (rc); } sc->data.flags |= SCDSPINNING; return (0); } static struct sony_tracklist * get_tl(struct sony_toc *toc, int size) { struct sony_tracklist *tl = &toc->tracks[0]; if (tl->track != 0xb0) return (tl); if (tl->track != 0xb1) return (tl); tl = (struct sony_tracklist *)((char *)tl + 9); if (tl->track != 0xb2) return (tl); tl = (struct sony_tracklist *)((char *)tl + 9); if (tl->track != 0xb3) return (tl); tl = (struct sony_tracklist *)((char *)tl + 9); if (tl->track != 0xb4) return (tl); tl = (struct sony_tracklist *)((char *)tl + 9); if (tl->track != 0xc0) return (tl); tl = (struct sony_tracklist *)((char *)tl + 9); return (tl); } static int read_toc(struct scd_softc *sc) { struct sony_toc toc; struct sony_tracklist *tl; int rc, i, j; u_long first, last; rc = send_cmd(sc, CMD_GET_TOC, 1, 1); if (rc < 0) return (rc); if (rc > sizeof(toc)) { device_printf(sc->dev, "program error: toc too large (%d)\n", rc); return (EIO); } if (get_result(sc, rc, (u_char *)&toc) != 0) return (EIO); XDEBUG(sc, 1, "toc read. len = %d, sizeof(toc) = %d\n", rc, sizeof(toc)); tl = get_tl(&toc, rc); first = msf2hsg(tl->start_msf); last = msf2hsg(toc.lead_out_start_msf); sc->data.blksize = SCDBLKSIZE; sc->data.disksize = last*sc->data.blksize/DEV_BSIZE; XDEBUG(sc, 1, "firstsector = %ld, lastsector = %ld", first, last); sc->data.first_track = bcd2bin(toc.first_track); sc->data.last_track = bcd2bin(toc.last_track); if (sc->data.last_track > (MAX_TRACKS-2)) sc->data.last_track = MAX_TRACKS-2; for (j = 0, i = sc->data.first_track; i <= sc->data.last_track; i++, j++) { sc->data.toc[i].adr = tl[j].adr; sc->data.toc[i].ctl = tl[j].ctl; /* for xcdplayer */ bcopy(tl[j].start_msf, sc->data.toc[i].start_msf, 3); #ifdef SCD_DEBUG if (scd_debuglevel > 0) { if ((j % 3) == 0) { printf("\n"); device_printf(sc->dev, "tracks "); } printf("[%03d: %2d %2d %2d] ", i, bcd2bin(sc->data.toc[i].start_msf[0]), bcd2bin(sc->data.toc[i].start_msf[1]), bcd2bin(sc->data.toc[i].start_msf[2])); } #endif } bcopy(toc.lead_out_start_msf, sc->data.toc[sc->data.last_track+1].start_msf, 3); #ifdef SCD_DEBUG if (scd_debuglevel > 0) { i = sc->data.last_track+1; printf("[END: %2d %2d %2d]\n", bcd2bin(sc->data.toc[i].start_msf[0]), bcd2bin(sc->data.toc[i].start_msf[1]), bcd2bin(sc->data.toc[i].start_msf[2])); } #endif sc->data.flags |= SCDTOC; return (0); } static void init_drive(struct scd_softc *sc) { int rc; rc = send_cmd(sc, CMD_SET_DRIVE_PARAM, 2, 0x05, 0x03 | ((sc->data.double_speed) ? 0x04: 0)); if (rc != 0) device_printf(sc->dev, "Unable to set parameters. Errcode = 0x%x\n", rc); } /* Returns 0 or errno */ static int get_result(struct scd_softc *sc, int result_len, u_char *result) { int loop_index = 2; /* send_cmd() reads two bytes ... */ XDEBUG(sc, 1, "DEBUG: get_result: bytes=%d\n", result_len); while (result_len-- > 0) { if (loop_index++ >= 10) { loop_index = 1; if (waitfor_status_bits(sc, SBIT_RESULT_READY, 0)) return (EIO); SCD_WRITE(sc, OREG_CONTROL, CBIT_RESULT_READY_CLEAR); } if (result) *result++ = SCD_READ(sc, IREG_RESULT); else (void)SCD_READ(sc, IREG_RESULT); } return (0); } /* Returns -0x100 for timeout, -(drive error code) OR number of result bytes */ static int send_cmd(struct scd_softc *sc, u_char cmd, u_int nargs, ...) { va_list ap; u_char c; int rc; int i; if (waitfor_status_bits(sc, 0, SBIT_BUSY)) { device_printf(sc->dev, "drive busy\n"); return (-0x100); } XDEBUG(sc, 1, "DEBUG: send_cmd: cmd=0x%x nargs=%d", cmd, nargs); SCD_WRITE(sc, OREG_CONTROL, CBIT_RESULT_READY_CLEAR); SCD_WRITE(sc, OREG_CONTROL, CBIT_RPARAM_CLEAR); for (i = 0; i < 100; i++) if (FSTATUS_BIT(sc, FBIT_WPARAM_READY)) break; if (!FSTATUS_BIT(sc, FBIT_WPARAM_READY)) { XDEBUG(sc, 1, "\nwparam timeout\n"); return (-EIO); } va_start(ap, nargs); for (i = 0; i < nargs; i++) { c = (u_char)va_arg(ap, int); SCD_WRITE(sc, OREG_WPARAMS, c); XDEBUG(sc, 1, ",{0x%x}", c); } va_end(ap); XDEBUG(sc, 1, "\n"); SCD_WRITE(sc, OREG_COMMAND, cmd); rc = waitfor_status_bits(sc, SBIT_RESULT_READY, SBIT_BUSY); if (rc) return (-0x100); SCD_WRITE(sc, OREG_CONTROL, CBIT_RESULT_READY_CLEAR); switch ((rc = SCD_READ(sc, IREG_RESULT)) & 0xf0) { case 0x20: rc = SCD_READ(sc, IREG_RESULT); /* FALLTHROUGH */ case 0x50: XDEBUG(sc, 1, "DEBUG: send_cmd: drive_error=0x%x\n", rc); return (-rc); case 0x00: default: rc = SCD_READ(sc, IREG_RESULT); XDEBUG(sc, 1, "DEBUG: send_cmd: result_len=%d\n", rc); return (rc); } } static void print_error(struct scd_softc *sc, int errcode) { switch (errcode) { case -ERR_CD_NOT_LOADED: device_printf(sc->dev, "door is open\n"); break; case -ERR_NO_CD_INSIDE: device_printf(sc->dev, "no cd inside\n"); break; default: if (errcode == -0x100 || errcode > 0) device_printf(sc->dev, "device timeout\n"); else device_printf(sc->dev, "unexpected error 0x%x\n", -errcode); break; } } /* Returns 0 or errno value */ static int waitfor_status_bits(struct scd_softc *sc, int bits_set, int bits_clear) { u_int flags = sc->data.flags; u_int max_loop; u_char c = 0; if (flags & SCDPROBING) { max_loop = 0; while (max_loop++ < 1000) { c = SCD_READ(sc, IREG_STATUS); if (c == 0xff) return (EIO); if (c & SBIT_ATTENTION) { process_attention(sc); continue; } if ((c & bits_set) == bits_set && (c & bits_clear) == 0) { break; } DELAY(10000); } } else { max_loop = 100; while (max_loop-- > 0) { c = SCD_READ(sc, IREG_STATUS); if (c & SBIT_ATTENTION) { process_attention(sc); continue; } if ((c & bits_set) == bits_set && (c & bits_clear) == 0) { break; } SCD_UNLOCK(sc); pause("waitfor", hz/10); SCD_LOCK(sc); } } if ((c & bits_set) == bits_set && (c & bits_clear) == 0) { return (0); } #ifdef SCD_DEBUG if (scd_debuglevel > 0) device_printf(sc->dev, "DEBUG: waitfor: TIMEOUT (0x%x,(0x%x,0x%x))\n", c, bits_set, bits_clear); else #endif device_printf(sc->dev, "timeout.\n"); return (EIO); } /* these two routines for xcdplayer - "borrowed" from mcd.c */ static int scd_toc_header (struct scd_softc *sc, struct ioc_toc_header* th) { int rc; if (!(sc->data.flags & SCDTOC) && (rc = read_toc(sc)) != 0) { print_error(sc, rc); return (EIO); } th->starting_track = sc->data.first_track; th->ending_track = sc->data.last_track; th->len = 0; /* not used */ return (0); } static int scd_toc_entrys (struct scd_softc *sc, struct ioc_read_toc_entry *te) { struct cd_toc_entry toc_entry; int rc, i, len = te->data_len; if (!(sc->data.flags & SCDTOC) && (rc = read_toc(sc)) != 0) { print_error(sc, rc); return (EIO); } /* find the toc to copy*/ i = te->starting_track; if (i == SCD_LASTPLUS1) i = sc->data.last_track + 1; /* verify starting track */ if (i < sc->data.first_track || i > sc->data.last_track+1) return (EINVAL); /* valid length ? */ if (len < sizeof(struct cd_toc_entry) || (len % sizeof(struct cd_toc_entry)) != 0) return (EINVAL); /* copy the toc data */ toc_entry.control = sc->data.toc[i].ctl; toc_entry.addr_type = te->address_format; toc_entry.track = i; if (te->address_format == CD_MSF_FORMAT) { toc_entry.addr.msf.unused = 0; toc_entry.addr.msf.minute = bcd2bin(sc->data.toc[i].start_msf[0]); toc_entry.addr.msf.second = bcd2bin(sc->data.toc[i].start_msf[1]); toc_entry.addr.msf.frame = bcd2bin(sc->data.toc[i].start_msf[2]); } SCD_UNLOCK(sc); /* copy the data back */ if (copyout(&toc_entry, te->data, sizeof(struct cd_toc_entry)) != 0) return (EFAULT); return (0); } static int scd_toc_entry (struct scd_softc *sc, struct ioc_read_toc_single_entry *te) { struct cd_toc_entry toc_entry; int rc, i; if (!(sc->data.flags & SCDTOC) && (rc = read_toc(sc)) != 0) { print_error(sc, rc); return (EIO); } /* find the toc to copy*/ i = te->track; if (i == SCD_LASTPLUS1) i = sc->data.last_track + 1; /* verify starting track */ if (i < sc->data.first_track || i > sc->data.last_track+1) return (EINVAL); /* copy the toc data */ toc_entry.control = sc->data.toc[i].ctl; toc_entry.addr_type = te->address_format; toc_entry.track = i; if (te->address_format == CD_MSF_FORMAT) { toc_entry.addr.msf.unused = 0; toc_entry.addr.msf.minute = bcd2bin(sc->data.toc[i].start_msf[0]); toc_entry.addr.msf.second = bcd2bin(sc->data.toc[i].start_msf[1]); toc_entry.addr.msf.frame = bcd2bin(sc->data.toc[i].start_msf[2]); } /* copy the data back */ bcopy(&toc_entry, &te->entry, sizeof(struct cd_toc_entry)); return (0); } Index: releng/11.3/sys/sys/cdio.h =================================================================== --- releng/11.3/sys/sys/cdio.h (revision 349624) +++ releng/11.3/sys/sys/cdio.h (revision 349625) @@ -1,284 +1,277 @@ /* * 16 Feb 93 Julian Elischer (julian@dialix.oz.au) * * $FreeBSD$ */ /* <1> Fixed a conflict with ioctl usage. There were two different functions using code #25. Made file formatting consistent. Added two new ioctl codes: door closing and audio pitch playback. Added a STEREO union called STEREO. 5-Mar-95 Frank Durda IV bsdmail@nemesis.lonestar.org <2> Added a new ioctl that allows you to find out what capabilities a drive has and what commands it will accept. This allows a user application to only offer controls (buttons, sliders, etc) for functions that drive can actually do. Things it can't do can disappear or be greyed-out (like some other system). If the driver doesn't respond to this call, well, handle it the way you used to do it. 2-Apr-95 Frank Durda IV bsdmail@nemesis.lonestar.org */ /* Shared between kernel & process */ #ifndef _SYS_CDIO_H_ #define _SYS_CDIO_H_ #ifndef _KERNEL #include #endif #include union msf_lba { struct { unsigned char unused; unsigned char minute; unsigned char second; unsigned char frame; } msf; int lba; /* network byte order */ u_char addr[4]; }; struct cd_toc_entry { #if BYTE_ORDER == LITTLE_ENDIAN u_int :8; u_int control:4; u_int addr_type:4; #else u_int :8; u_int addr_type:4; u_int control:4; #endif u_char track; u_int :8; union msf_lba addr; }; struct cd_sub_channel_header { u_int :8; u_char audio_status; #define CD_AS_AUDIO_INVALID 0x00 #define CD_AS_PLAY_IN_PROGRESS 0x11 #define CD_AS_PLAY_PAUSED 0x12 #define CD_AS_PLAY_COMPLETED 0x13 #define CD_AS_PLAY_ERROR 0x14 #define CD_AS_NO_STATUS 0x15 u_char data_len[2]; }; struct cd_sub_channel_position_data { u_char data_format; u_int control:4; u_int addr_type:4; u_char track_number; u_char index_number; union msf_lba absaddr; union msf_lba reladdr; }; struct cd_sub_channel_media_catalog { u_char data_format; u_int :8; u_int :8; u_int :8; u_int :7; u_int mc_valid:1; u_char mc_number[15]; }; struct cd_sub_channel_track_info { u_char data_format; u_int :8; u_char track_number; u_int :8; u_int :7; u_int ti_valid:1; u_char ti_number[15]; }; struct cd_sub_channel_info { struct cd_sub_channel_header header; union { struct cd_sub_channel_position_data position; struct cd_sub_channel_media_catalog media_catalog; struct cd_sub_channel_track_info track_info; } what; }; /***************************************************************\ * Ioctls for the CD drive * \***************************************************************/ struct ioc_play_track { u_char start_track; u_char start_index; u_char end_track; u_char end_index; }; #define CDIOCPLAYTRACKS _IOW('c',1,struct ioc_play_track) struct ioc_play_blocks { int blk; int len; }; #define CDIOCPLAYBLOCKS _IOW('c',2,struct ioc_play_blocks) struct ioc_read_subchannel { u_char address_format; #define CD_LBA_FORMAT 1 #define CD_MSF_FORMAT 2 u_char data_format; #define CD_SUBQ_DATA 0 #define CD_CURRENT_POSITION 1 #define CD_MEDIA_CATALOG 2 #define CD_TRACK_INFO 3 u_char track; int data_len; struct cd_sub_channel_info *data; }; #define CDIOCREADSUBCHANNEL _IOWR('c', 3 , struct ioc_read_subchannel ) struct ioc_toc_header { u_short len; u_char starting_track; u_char ending_track; }; #define CDIOREADTOCHEADER _IOR('c',4,struct ioc_toc_header) struct ioc_read_toc_entry { u_char address_format; u_char starting_track; u_short data_len; struct cd_toc_entry *data; }; #define CDIOREADTOCENTRYS _IOWR('c',5,struct ioc_read_toc_entry) struct ioc_read_toc_single_entry { u_char address_format; u_char track; struct cd_toc_entry entry; }; #define CDIOREADTOCENTRY _IOWR('c',6,struct ioc_read_toc_single_entry) struct ioc_patch { u_char patch[4]; /* one for each channel */ }; #define CDIOCSETPATCH _IOW('c',9,struct ioc_patch) struct ioc_vol { u_char vol[4]; /* one for each channel */ }; #define CDIOCGETVOL _IOR('c',10,struct ioc_vol) #define CDIOCSETVOL _IOW('c',11,struct ioc_vol) #define CDIOCSETMONO _IO('c',12) #define CDIOCSETSTERIO _IO('c',13) #define CDIOCSETSTEREO _IO('c',13) #define CDIOCSETMUTE _IO('c',14) #define CDIOCSETLEFT _IO('c',15) #define CDIOCSETRIGHT _IO('c',16) #define CDIOCSETDEBUG _IO('c',17) #define CDIOCCLRDEBUG _IO('c',18) #define CDIOCPAUSE _IO('c',19) #define CDIOCRESUME _IO('c',20) #define CDIOCRESET _IO('c',21) #define CDIOCSTART _IO('c',22) #define CDIOCSTOP _IO('c',23) #define CDIOCEJECT _IO('c',24) struct ioc_play_msf { u_char start_m; u_char start_s; u_char start_f; u_char end_m; u_char end_s; u_char end_f; }; #define CDIOCPLAYMSF _IOW('c',25,struct ioc_play_msf) #define CDIOCALLOW _IO('c',26) #define CDIOCPREVENT _IO('c',27) /*<1>For drives that support it, this*/ /*<1>causes the drive to close its door*/ /*<1>and make the media (if any) ready*/ #define CDIOCCLOSE _IO('c',28) /*<1>*/ struct ioc_pitch { /*<1>For drives that support it, this*/ /*<1>call instructs the drive to play the*/ short speed; /*<1>audio at a faster or slower-than-normal*/ }; /*<1>rate. -32767 to -1 is slower, 0==normal,*/ /*<1>and 1 to 32767 is faster. LSB bits are*/ /*<1>discarded first by drives with less res.*/ #define CDIOCPITCH _IOW('c',29,struct ioc_pitch) /*<1>*/ struct ioc_capability { /*<2>*/ u_long play_function; /*<2>*/ #define CDDOPLAYTRK 0x00000001 /*<2>Can Play tracks/index*/ #define CDDOPLAYMSF 0x00000002 /*<2>Can Play msf to msf*/ #define CDDOPLAYBLOCKS 0x00000004 /*<2>Can Play range of blocks*/ #define CDDOPAUSE 0x00000100 /*<2>Output can be paused*/ #define CDDORESUME 0x00000200 /*<2>Output can be resumed*/ #define CDDORESET 0x00000400 /*<2>Drive can be completely reset*/ #define CDDOSTART 0x00000800 /*<2>Audio can be started*/ #define CDDOSTOP 0x00001000 /*<2>Audio can be stopped*/ #define CDDOPITCH 0x00002000 /*<2>Audio pitch */ u_long routing_function; /*<2>*/ #define CDREADVOLUME 0x00000001 /*<2>Volume settings can be read*/ #define CDSETVOLUME 0x00000002 /*<2>Volume settings can be set*/ #define CDSETMONO 0x00000100 /*<2>Output can be set to mono*/ #define CDSETSTEREO 0x00000200 /*<2>Output can be set to stereo (def)*/ #define CDSETLEFT 0x00000400 /*<2>Output can be set to left only*/ #define CDSETRIGHT 0x00000800 /*<2>Output can be set to right only*/ #define CDSETMUTE 0x00001000 /*<2>Output can be muted*/ #define CDSETPATCH 0x00008000 /*<2>Direct routing control allowed*/ u_long special_function; /*<2>*/ #define CDDOEJECT 0x00000001 /*<2>The tray can be opened*/ #define CDDOCLOSE 0x00000002 /*<2>The tray can be closed*/ #define CDDOLOCK 0x00000004 /*<2>The tray can be locked*/ #define CDREADHEADER 0x00000100 /*<2>Can read Table of Contents*/ #define CDREADENTRIES 0x00000200 /*<2>Can read TOC Entries*/ #define CDREADSUBQ 0x00000200 /*<2>Can read Subchannel info*/ #define CDREADRW 0x00000400 /*<2>Can read subcodes R-W*/ #define CDHASDEBUG 0x00004000 /*<2>The tray has dynamic debugging*/ }; /*<2>*/ #define CDIOCCAPABILITY _IOR('c',30,struct ioc_capability) /*<2>*/ -/* - * Special version of CDIOCREADSUBCHANNEL which assumes that - * ioc_read_subchannel->data points to the kernel memory. For - * use in compatibility layers. - */ -#define CDIOCREADSUBCHANNEL_SYSSPACE _IOWR('c', 31, struct ioc_read_subchannel) - #endif /* !_SYS_CDIO_H_ */ Index: releng/12.0/sys/cam/scsi/scsi_cd.c =================================================================== --- releng/12.0/sys/cam/scsi/scsi_cd.c (revision 349624) +++ releng/12.0/sys/cam/scsi/scsi_cd.c (revision 349625) @@ -1,3755 +1,3745 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * 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 #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; #define CD_ANNOUNCETMP_SZ 120 char announce_temp[CD_ANNOUNCETMP_SZ]; #define CD_ANNOUNCE_SZ 400 char announce_buf[CD_ANNOUNCE_SZ]; }; 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 } }; #ifdef COMPAT_FREEBSD32 struct ioc_read_toc_entry32 { u_char address_format; u_char starting_track; u_short data_len; uint32_t data; /* (struct cd_toc_entry *) */ }; #define CDIOREADTOCENTRYS_32 \ _IOC_NEWTYPE(CDIOREADTOCENTRYS, struct ioc_read_toc_entry32) #endif 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) == 0) { 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[32], tmpstr2[16]; periph = (struct cam_periph *)context; if (cam_periph_acquire(periph) != 0) 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_WITH_LABEL(&softc->sysctl_ctx, SYSCTL_STATIC_CHILDREN(_kern_cam_cd), OID_AUTO, tmpstr2, CTLFLAG_RD, 0, tmpstr, "device_index"); 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, nitems(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 */ xpt_path_inq(&cpi, periph->path); 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) != 0) { 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) != 0) 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; struct cd_params *cdp; int error; cdp = &softc->params; announce_buf = softc->announce_temp; bzero(announce_buf, CD_ANNOUNCETMP_SZ); 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, CD_ANNOUNCETMP_SZ, "%juMB (%ju %u byte sectors)", ((uintmax_t)cdp->disksize * cdp->blksize) / (1024 * 1024), (uintmax_t)cdp->disksize, 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 || error_code == SSD_DESC_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, CD_ANNOUNCETMP_SZ, "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, CD_ANNOUNCETMP_SZ, "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 = NULL; } else { /* * Invalidate this peripheral. */ cam_periph_invalidate(periph); announce_buf = NULL; } } } free(rdcap, M_SCSICD); if (announce_buf != NULL) { struct sbuf sb; sbuf_new(&sb, softc->announce_buf, CD_ANNOUNCE_SZ, SBUF_FIXEDLEN); xpt_announce_periph_sbuf(periph, &sb, announce_buf); xpt_announce_quirks_sbuf(periph, &sb, softc->quirks, CD_Q_BIT_STRING); sbuf_finish(&sb); sbuf_putbuf(&sb); /* * 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) { u_int i; for (i = 0; i < nitems(cd_page_size_table); i++) { if (cd_page_size_table[i].page == page_num) return (cd_page_size_table[i].page_size); } return (-1); } static struct cd_toc_entry * te_data_get_ptr(void *irtep, u_long cmd) { union { struct ioc_read_toc_entry irte; #ifdef COMPAT_FREEBSD32 struct ioc_read_toc_entry32 irte32; #endif } *irteup; irteup = irtep; switch (IOCPARM_LEN(cmd)) { case sizeof(irteup->irte): return (irteup->irte.data); #ifdef COMPAT_FREEBSD32 case sizeof(irteup->irte32): return ((struct cd_toc_entry *)(uintptr_t)irteup->irte32.data); #endif default: panic("Unhandled ioctl command %ld", cmd); } } 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; + int 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); - } + error = copyout(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: #ifdef COMPAT_FREEBSD32 case CDIOREADTOCENTRYS_32: #endif { 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_get_ptr(te, cmd), 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, /*cbfcnp*/NULL, 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, /*cbfcnp*/NULL, 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)); } 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) == 0) { 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 */ NULL, /* 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 */ NULL, /* 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 */ NULL, /* 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 */ NULL, /* 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, /*cbfcnp*/NULL, /*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 */ NULL, /* 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 */ NULL, /* 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 */ NULL, /* 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 */ NULL, /* 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 */ NULL, /* 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 */ NULL, /* 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 */ NULL, /* 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 */ NULL, /* 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 */ NULL, /* 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); } Index: releng/12.0/sys/compat/linux/linux_ioctl.c =================================================================== --- releng/12.0/sys/compat/linux/linux_ioctl.c (revision 349624) +++ releng/12.0/sys/compat/linux/linux_ioctl.c (revision 349625) @@ -1,3881 +1,3891 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 1994-1995 Søren Schmidt * 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. * * 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 "opt_compat.h" #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 #include #include #include #include #include #include #include #include #include #include #ifdef COMPAT_LINUX32 #include #include #else #include #include #endif #include #include #include #include #include #include #include #include #include #include CTASSERT(LINUX_IFNAMSIZ == IFNAMSIZ); static linux_ioctl_function_t linux_ioctl_cdrom; static linux_ioctl_function_t linux_ioctl_vfat; static linux_ioctl_function_t linux_ioctl_console; static linux_ioctl_function_t linux_ioctl_hdio; static linux_ioctl_function_t linux_ioctl_disk; static linux_ioctl_function_t linux_ioctl_socket; static linux_ioctl_function_t linux_ioctl_sound; static linux_ioctl_function_t linux_ioctl_termio; static linux_ioctl_function_t linux_ioctl_private; static linux_ioctl_function_t linux_ioctl_drm; static linux_ioctl_function_t linux_ioctl_sg; static linux_ioctl_function_t linux_ioctl_v4l; static linux_ioctl_function_t linux_ioctl_v4l2; static linux_ioctl_function_t linux_ioctl_special; static linux_ioctl_function_t linux_ioctl_fbsd_usb; static linux_ioctl_function_t linux_ioctl_evdev; static struct linux_ioctl_handler cdrom_handler = { linux_ioctl_cdrom, LINUX_IOCTL_CDROM_MIN, LINUX_IOCTL_CDROM_MAX }; static struct linux_ioctl_handler vfat_handler = { linux_ioctl_vfat, LINUX_IOCTL_VFAT_MIN, LINUX_IOCTL_VFAT_MAX }; static struct linux_ioctl_handler console_handler = { linux_ioctl_console, LINUX_IOCTL_CONSOLE_MIN, LINUX_IOCTL_CONSOLE_MAX }; static struct linux_ioctl_handler hdio_handler = { linux_ioctl_hdio, LINUX_IOCTL_HDIO_MIN, LINUX_IOCTL_HDIO_MAX }; static struct linux_ioctl_handler disk_handler = { linux_ioctl_disk, LINUX_IOCTL_DISK_MIN, LINUX_IOCTL_DISK_MAX }; static struct linux_ioctl_handler socket_handler = { linux_ioctl_socket, LINUX_IOCTL_SOCKET_MIN, LINUX_IOCTL_SOCKET_MAX }; static struct linux_ioctl_handler sound_handler = { linux_ioctl_sound, LINUX_IOCTL_SOUND_MIN, LINUX_IOCTL_SOUND_MAX }; static struct linux_ioctl_handler termio_handler = { linux_ioctl_termio, LINUX_IOCTL_TERMIO_MIN, LINUX_IOCTL_TERMIO_MAX }; static struct linux_ioctl_handler private_handler = { linux_ioctl_private, LINUX_IOCTL_PRIVATE_MIN, LINUX_IOCTL_PRIVATE_MAX }; static struct linux_ioctl_handler drm_handler = { linux_ioctl_drm, LINUX_IOCTL_DRM_MIN, LINUX_IOCTL_DRM_MAX }; static struct linux_ioctl_handler sg_handler = { linux_ioctl_sg, LINUX_IOCTL_SG_MIN, LINUX_IOCTL_SG_MAX }; static struct linux_ioctl_handler video_handler = { linux_ioctl_v4l, LINUX_IOCTL_VIDEO_MIN, LINUX_IOCTL_VIDEO_MAX }; static struct linux_ioctl_handler video2_handler = { linux_ioctl_v4l2, LINUX_IOCTL_VIDEO2_MIN, LINUX_IOCTL_VIDEO2_MAX }; static struct linux_ioctl_handler fbsd_usb = { linux_ioctl_fbsd_usb, FBSD_LUSB_MIN, FBSD_LUSB_MAX }; static struct linux_ioctl_handler evdev_handler = { linux_ioctl_evdev, LINUX_IOCTL_EVDEV_MIN, LINUX_IOCTL_EVDEV_MAX }; DATA_SET(linux_ioctl_handler_set, cdrom_handler); DATA_SET(linux_ioctl_handler_set, vfat_handler); DATA_SET(linux_ioctl_handler_set, console_handler); DATA_SET(linux_ioctl_handler_set, hdio_handler); DATA_SET(linux_ioctl_handler_set, disk_handler); DATA_SET(linux_ioctl_handler_set, socket_handler); DATA_SET(linux_ioctl_handler_set, sound_handler); DATA_SET(linux_ioctl_handler_set, termio_handler); DATA_SET(linux_ioctl_handler_set, private_handler); DATA_SET(linux_ioctl_handler_set, drm_handler); DATA_SET(linux_ioctl_handler_set, sg_handler); DATA_SET(linux_ioctl_handler_set, video_handler); DATA_SET(linux_ioctl_handler_set, video2_handler); DATA_SET(linux_ioctl_handler_set, fbsd_usb); DATA_SET(linux_ioctl_handler_set, evdev_handler); #ifdef __i386__ static TAILQ_HEAD(, linux_ioctl_handler_element) linux_ioctl_handlers = TAILQ_HEAD_INITIALIZER(linux_ioctl_handlers); static struct sx linux_ioctl_sx; SX_SYSINIT(linux_ioctl, &linux_ioctl_sx, "Linux ioctl handlers"); #else extern TAILQ_HEAD(, linux_ioctl_handler_element) linux_ioctl_handlers; extern struct sx linux_ioctl_sx; #endif #ifdef COMPAT_LINUX32 static TAILQ_HEAD(, linux_ioctl_handler_element) linux32_ioctl_handlers = TAILQ_HEAD_INITIALIZER(linux32_ioctl_handlers); #endif /* * hdio related ioctls for VMWare support */ struct linux_hd_geometry { u_int8_t heads; u_int8_t sectors; u_int16_t cylinders; u_int32_t start; }; struct linux_hd_big_geometry { u_int8_t heads; u_int8_t sectors; u_int32_t cylinders; u_int32_t start; }; static int linux_ioctl_hdio(struct thread *td, struct linux_ioctl_args *args) { struct file *fp; int error; u_int sectorsize, fwcylinders, fwheads, fwsectors; off_t mediasize, bytespercyl; error = fget(td, args->fd, &cap_ioctl_rights, &fp); if (error != 0) return (error); switch (args->cmd & 0xffff) { case LINUX_HDIO_GET_GEO: case LINUX_HDIO_GET_GEO_BIG: error = fo_ioctl(fp, DIOCGMEDIASIZE, (caddr_t)&mediasize, td->td_ucred, td); if (!error) error = fo_ioctl(fp, DIOCGSECTORSIZE, (caddr_t)§orsize, td->td_ucred, td); if (!error) error = fo_ioctl(fp, DIOCGFWHEADS, (caddr_t)&fwheads, td->td_ucred, td); if (!error) error = fo_ioctl(fp, DIOCGFWSECTORS, (caddr_t)&fwsectors, td->td_ucred, td); /* * XXX: DIOCGFIRSTOFFSET is not yet implemented, so * so pretend that GEOM always says 0. This is NOT VALID * for slices or partitions, only the per-disk raw devices. */ fdrop(fp, td); if (error) return (error); /* * 1. Calculate the number of bytes in a cylinder, * given the firmware's notion of heads and sectors * per cylinder. * 2. Calculate the number of cylinders, given the total * size of the media. * All internal calculations should have 64-bit precision. */ bytespercyl = (off_t) sectorsize * fwheads * fwsectors; fwcylinders = mediasize / bytespercyl; #if defined(DEBUG) linux_msg(td, "HDIO_GET_GEO: mediasize %jd, c/h/s %d/%d/%d, " "bpc %jd", (intmax_t)mediasize, fwcylinders, fwheads, fwsectors, (intmax_t)bytespercyl); #endif if ((args->cmd & 0xffff) == LINUX_HDIO_GET_GEO) { struct linux_hd_geometry hdg; hdg.cylinders = fwcylinders; hdg.heads = fwheads; hdg.sectors = fwsectors; hdg.start = 0; error = copyout(&hdg, (void *)args->arg, sizeof(hdg)); } else if ((args->cmd & 0xffff) == LINUX_HDIO_GET_GEO_BIG) { struct linux_hd_big_geometry hdbg; memset(&hdbg, 0, sizeof(hdbg)); hdbg.cylinders = fwcylinders; hdbg.heads = fwheads; hdbg.sectors = fwsectors; hdbg.start = 0; error = copyout(&hdbg, (void *)args->arg, sizeof(hdbg)); } return (error); break; default: /* XXX */ linux_msg(td, "ioctl fd=%d, cmd=0x%x ('%c',%d) is not implemented", args->fd, (int)(args->cmd & 0xffff), (int)(args->cmd & 0xff00) >> 8, (int)(args->cmd & 0xff)); break; } fdrop(fp, td); return (ENOIOCTL); } static int linux_ioctl_disk(struct thread *td, struct linux_ioctl_args *args) { struct file *fp; int error; u_int sectorsize; off_t mediasize; error = fget(td, args->fd, &cap_ioctl_rights, &fp); if (error != 0) return (error); switch (args->cmd & 0xffff) { case LINUX_BLKGETSIZE: error = fo_ioctl(fp, DIOCGSECTORSIZE, (caddr_t)§orsize, td->td_ucred, td); if (!error) error = fo_ioctl(fp, DIOCGMEDIASIZE, (caddr_t)&mediasize, td->td_ucred, td); fdrop(fp, td); if (error) return (error); sectorsize = mediasize / sectorsize; /* * XXX: How do we know we return the right size of integer ? */ return (copyout(§orsize, (void *)args->arg, sizeof(sectorsize))); break; case LINUX_BLKSSZGET: error = fo_ioctl(fp, DIOCGSECTORSIZE, (caddr_t)§orsize, td->td_ucred, td); fdrop(fp, td); if (error) return (error); return (copyout(§orsize, (void *)args->arg, sizeof(sectorsize))); break; } fdrop(fp, td); return (ENOIOCTL); } /* * termio related ioctls */ struct linux_termio { unsigned short c_iflag; unsigned short c_oflag; unsigned short c_cflag; unsigned short c_lflag; unsigned char c_line; unsigned char c_cc[LINUX_NCC]; }; struct linux_termios { unsigned int c_iflag; unsigned int c_oflag; unsigned int c_cflag; unsigned int c_lflag; unsigned char c_line; unsigned char c_cc[LINUX_NCCS]; }; struct linux_winsize { unsigned short ws_row, ws_col; unsigned short ws_xpixel, ws_ypixel; }; struct speedtab { int sp_speed; /* Speed. */ int sp_code; /* Code. */ }; static struct speedtab sptab[] = { { B0, LINUX_B0 }, { B50, LINUX_B50 }, { B75, LINUX_B75 }, { B110, LINUX_B110 }, { B134, LINUX_B134 }, { B150, LINUX_B150 }, { B200, LINUX_B200 }, { B300, LINUX_B300 }, { B600, LINUX_B600 }, { B1200, LINUX_B1200 }, { B1800, LINUX_B1800 }, { B2400, LINUX_B2400 }, { B4800, LINUX_B4800 }, { B9600, LINUX_B9600 }, { B19200, LINUX_B19200 }, { B38400, LINUX_B38400 }, { B57600, LINUX_B57600 }, { B115200, LINUX_B115200 }, {-1, -1 } }; struct linux_serial_struct { int type; int line; int port; int irq; int flags; int xmit_fifo_size; int custom_divisor; int baud_base; unsigned short close_delay; char reserved_char[2]; int hub6; unsigned short closing_wait; unsigned short closing_wait2; int reserved[4]; }; static int linux_to_bsd_speed(int code, struct speedtab *table) { for ( ; table->sp_code != -1; table++) if (table->sp_code == code) return (table->sp_speed); return (-1); } static int bsd_to_linux_speed(int speed, struct speedtab *table) { for ( ; table->sp_speed != -1; table++) if (table->sp_speed == speed) return (table->sp_code); return (-1); } static void bsd_to_linux_termios(struct termios *bios, struct linux_termios *lios) { int i; #ifdef DEBUG if (ldebug(ioctl)) { printf("LINUX: BSD termios structure (input):\n"); printf("i=%08x o=%08x c=%08x l=%08x ispeed=%d ospeed=%d\n", bios->c_iflag, bios->c_oflag, bios->c_cflag, bios->c_lflag, bios->c_ispeed, bios->c_ospeed); printf("c_cc "); for (i=0; ic_cc[i]); printf("\n"); } #endif lios->c_iflag = 0; if (bios->c_iflag & IGNBRK) lios->c_iflag |= LINUX_IGNBRK; if (bios->c_iflag & BRKINT) lios->c_iflag |= LINUX_BRKINT; if (bios->c_iflag & IGNPAR) lios->c_iflag |= LINUX_IGNPAR; if (bios->c_iflag & PARMRK) lios->c_iflag |= LINUX_PARMRK; if (bios->c_iflag & INPCK) lios->c_iflag |= LINUX_INPCK; if (bios->c_iflag & ISTRIP) lios->c_iflag |= LINUX_ISTRIP; if (bios->c_iflag & INLCR) lios->c_iflag |= LINUX_INLCR; if (bios->c_iflag & IGNCR) lios->c_iflag |= LINUX_IGNCR; if (bios->c_iflag & ICRNL) lios->c_iflag |= LINUX_ICRNL; if (bios->c_iflag & IXON) lios->c_iflag |= LINUX_IXON; if (bios->c_iflag & IXANY) lios->c_iflag |= LINUX_IXANY; if (bios->c_iflag & IXOFF) lios->c_iflag |= LINUX_IXOFF; if (bios->c_iflag & IMAXBEL) lios->c_iflag |= LINUX_IMAXBEL; lios->c_oflag = 0; if (bios->c_oflag & OPOST) lios->c_oflag |= LINUX_OPOST; if (bios->c_oflag & ONLCR) lios->c_oflag |= LINUX_ONLCR; if (bios->c_oflag & TAB3) lios->c_oflag |= LINUX_XTABS; lios->c_cflag = bsd_to_linux_speed(bios->c_ispeed, sptab); lios->c_cflag |= (bios->c_cflag & CSIZE) >> 4; if (bios->c_cflag & CSTOPB) lios->c_cflag |= LINUX_CSTOPB; if (bios->c_cflag & CREAD) lios->c_cflag |= LINUX_CREAD; if (bios->c_cflag & PARENB) lios->c_cflag |= LINUX_PARENB; if (bios->c_cflag & PARODD) lios->c_cflag |= LINUX_PARODD; if (bios->c_cflag & HUPCL) lios->c_cflag |= LINUX_HUPCL; if (bios->c_cflag & CLOCAL) lios->c_cflag |= LINUX_CLOCAL; if (bios->c_cflag & CRTSCTS) lios->c_cflag |= LINUX_CRTSCTS; lios->c_lflag = 0; if (bios->c_lflag & ISIG) lios->c_lflag |= LINUX_ISIG; if (bios->c_lflag & ICANON) lios->c_lflag |= LINUX_ICANON; if (bios->c_lflag & ECHO) lios->c_lflag |= LINUX_ECHO; if (bios->c_lflag & ECHOE) lios->c_lflag |= LINUX_ECHOE; if (bios->c_lflag & ECHOK) lios->c_lflag |= LINUX_ECHOK; if (bios->c_lflag & ECHONL) lios->c_lflag |= LINUX_ECHONL; if (bios->c_lflag & NOFLSH) lios->c_lflag |= LINUX_NOFLSH; if (bios->c_lflag & TOSTOP) lios->c_lflag |= LINUX_TOSTOP; if (bios->c_lflag & ECHOCTL) lios->c_lflag |= LINUX_ECHOCTL; if (bios->c_lflag & ECHOPRT) lios->c_lflag |= LINUX_ECHOPRT; if (bios->c_lflag & ECHOKE) lios->c_lflag |= LINUX_ECHOKE; if (bios->c_lflag & FLUSHO) lios->c_lflag |= LINUX_FLUSHO; if (bios->c_lflag & PENDIN) lios->c_lflag |= LINUX_PENDIN; if (bios->c_lflag & IEXTEN) lios->c_lflag |= LINUX_IEXTEN; for (i=0; ic_cc[i] = LINUX_POSIX_VDISABLE; lios->c_cc[LINUX_VINTR] = bios->c_cc[VINTR]; lios->c_cc[LINUX_VQUIT] = bios->c_cc[VQUIT]; lios->c_cc[LINUX_VERASE] = bios->c_cc[VERASE]; lios->c_cc[LINUX_VKILL] = bios->c_cc[VKILL]; lios->c_cc[LINUX_VEOF] = bios->c_cc[VEOF]; lios->c_cc[LINUX_VEOL] = bios->c_cc[VEOL]; lios->c_cc[LINUX_VMIN] = bios->c_cc[VMIN]; lios->c_cc[LINUX_VTIME] = bios->c_cc[VTIME]; lios->c_cc[LINUX_VEOL2] = bios->c_cc[VEOL2]; lios->c_cc[LINUX_VSUSP] = bios->c_cc[VSUSP]; lios->c_cc[LINUX_VSTART] = bios->c_cc[VSTART]; lios->c_cc[LINUX_VSTOP] = bios->c_cc[VSTOP]; lios->c_cc[LINUX_VREPRINT] = bios->c_cc[VREPRINT]; lios->c_cc[LINUX_VDISCARD] = bios->c_cc[VDISCARD]; lios->c_cc[LINUX_VWERASE] = bios->c_cc[VWERASE]; lios->c_cc[LINUX_VLNEXT] = bios->c_cc[VLNEXT]; for (i=0; ic_cc[i] == _POSIX_VDISABLE) lios->c_cc[i] = LINUX_POSIX_VDISABLE; } lios->c_line = 0; #ifdef DEBUG if (ldebug(ioctl)) { printf("LINUX: LINUX termios structure (output):\n"); printf("i=%08x o=%08x c=%08x l=%08x line=%d\n", lios->c_iflag, lios->c_oflag, lios->c_cflag, lios->c_lflag, (int)lios->c_line); printf("c_cc "); for (i=0; ic_cc[i]); printf("\n"); } #endif } static void linux_to_bsd_termios(struct linux_termios *lios, struct termios *bios) { int i; #ifdef DEBUG if (ldebug(ioctl)) { printf("LINUX: LINUX termios structure (input):\n"); printf("i=%08x o=%08x c=%08x l=%08x line=%d\n", lios->c_iflag, lios->c_oflag, lios->c_cflag, lios->c_lflag, (int)lios->c_line); printf("c_cc "); for (i=0; ic_cc[i]); printf("\n"); } #endif bios->c_iflag = 0; if (lios->c_iflag & LINUX_IGNBRK) bios->c_iflag |= IGNBRK; if (lios->c_iflag & LINUX_BRKINT) bios->c_iflag |= BRKINT; if (lios->c_iflag & LINUX_IGNPAR) bios->c_iflag |= IGNPAR; if (lios->c_iflag & LINUX_PARMRK) bios->c_iflag |= PARMRK; if (lios->c_iflag & LINUX_INPCK) bios->c_iflag |= INPCK; if (lios->c_iflag & LINUX_ISTRIP) bios->c_iflag |= ISTRIP; if (lios->c_iflag & LINUX_INLCR) bios->c_iflag |= INLCR; if (lios->c_iflag & LINUX_IGNCR) bios->c_iflag |= IGNCR; if (lios->c_iflag & LINUX_ICRNL) bios->c_iflag |= ICRNL; if (lios->c_iflag & LINUX_IXON) bios->c_iflag |= IXON; if (lios->c_iflag & LINUX_IXANY) bios->c_iflag |= IXANY; if (lios->c_iflag & LINUX_IXOFF) bios->c_iflag |= IXOFF; if (lios->c_iflag & LINUX_IMAXBEL) bios->c_iflag |= IMAXBEL; bios->c_oflag = 0; if (lios->c_oflag & LINUX_OPOST) bios->c_oflag |= OPOST; if (lios->c_oflag & LINUX_ONLCR) bios->c_oflag |= ONLCR; if (lios->c_oflag & LINUX_XTABS) bios->c_oflag |= TAB3; bios->c_cflag = (lios->c_cflag & LINUX_CSIZE) << 4; if (lios->c_cflag & LINUX_CSTOPB) bios->c_cflag |= CSTOPB; if (lios->c_cflag & LINUX_CREAD) bios->c_cflag |= CREAD; if (lios->c_cflag & LINUX_PARENB) bios->c_cflag |= PARENB; if (lios->c_cflag & LINUX_PARODD) bios->c_cflag |= PARODD; if (lios->c_cflag & LINUX_HUPCL) bios->c_cflag |= HUPCL; if (lios->c_cflag & LINUX_CLOCAL) bios->c_cflag |= CLOCAL; if (lios->c_cflag & LINUX_CRTSCTS) bios->c_cflag |= CRTSCTS; bios->c_lflag = 0; if (lios->c_lflag & LINUX_ISIG) bios->c_lflag |= ISIG; if (lios->c_lflag & LINUX_ICANON) bios->c_lflag |= ICANON; if (lios->c_lflag & LINUX_ECHO) bios->c_lflag |= ECHO; if (lios->c_lflag & LINUX_ECHOE) bios->c_lflag |= ECHOE; if (lios->c_lflag & LINUX_ECHOK) bios->c_lflag |= ECHOK; if (lios->c_lflag & LINUX_ECHONL) bios->c_lflag |= ECHONL; if (lios->c_lflag & LINUX_NOFLSH) bios->c_lflag |= NOFLSH; if (lios->c_lflag & LINUX_TOSTOP) bios->c_lflag |= TOSTOP; if (lios->c_lflag & LINUX_ECHOCTL) bios->c_lflag |= ECHOCTL; if (lios->c_lflag & LINUX_ECHOPRT) bios->c_lflag |= ECHOPRT; if (lios->c_lflag & LINUX_ECHOKE) bios->c_lflag |= ECHOKE; if (lios->c_lflag & LINUX_FLUSHO) bios->c_lflag |= FLUSHO; if (lios->c_lflag & LINUX_PENDIN) bios->c_lflag |= PENDIN; if (lios->c_lflag & LINUX_IEXTEN) bios->c_lflag |= IEXTEN; for (i=0; ic_cc[i] = _POSIX_VDISABLE; bios->c_cc[VINTR] = lios->c_cc[LINUX_VINTR]; bios->c_cc[VQUIT] = lios->c_cc[LINUX_VQUIT]; bios->c_cc[VERASE] = lios->c_cc[LINUX_VERASE]; bios->c_cc[VKILL] = lios->c_cc[LINUX_VKILL]; bios->c_cc[VEOF] = lios->c_cc[LINUX_VEOF]; bios->c_cc[VEOL] = lios->c_cc[LINUX_VEOL]; bios->c_cc[VMIN] = lios->c_cc[LINUX_VMIN]; bios->c_cc[VTIME] = lios->c_cc[LINUX_VTIME]; bios->c_cc[VEOL2] = lios->c_cc[LINUX_VEOL2]; bios->c_cc[VSUSP] = lios->c_cc[LINUX_VSUSP]; bios->c_cc[VSTART] = lios->c_cc[LINUX_VSTART]; bios->c_cc[VSTOP] = lios->c_cc[LINUX_VSTOP]; bios->c_cc[VREPRINT] = lios->c_cc[LINUX_VREPRINT]; bios->c_cc[VDISCARD] = lios->c_cc[LINUX_VDISCARD]; bios->c_cc[VWERASE] = lios->c_cc[LINUX_VWERASE]; bios->c_cc[VLNEXT] = lios->c_cc[LINUX_VLNEXT]; for (i=0; ic_cc[i] == LINUX_POSIX_VDISABLE) bios->c_cc[i] = _POSIX_VDISABLE; } bios->c_ispeed = bios->c_ospeed = linux_to_bsd_speed(lios->c_cflag & LINUX_CBAUD, sptab); #ifdef DEBUG if (ldebug(ioctl)) { printf("LINUX: BSD termios structure (output):\n"); printf("i=%08x o=%08x c=%08x l=%08x ispeed=%d ospeed=%d\n", bios->c_iflag, bios->c_oflag, bios->c_cflag, bios->c_lflag, bios->c_ispeed, bios->c_ospeed); printf("c_cc "); for (i=0; ic_cc[i]); printf("\n"); } #endif } static void bsd_to_linux_termio(struct termios *bios, struct linux_termio *lio) { struct linux_termios lios; bsd_to_linux_termios(bios, &lios); lio->c_iflag = lios.c_iflag; lio->c_oflag = lios.c_oflag; lio->c_cflag = lios.c_cflag; lio->c_lflag = lios.c_lflag; lio->c_line = lios.c_line; memcpy(lio->c_cc, lios.c_cc, LINUX_NCC); } static void linux_to_bsd_termio(struct linux_termio *lio, struct termios *bios) { struct linux_termios lios; int i; lios.c_iflag = lio->c_iflag; lios.c_oflag = lio->c_oflag; lios.c_cflag = lio->c_cflag; lios.c_lflag = lio->c_lflag; for (i=LINUX_NCC; ic_cc, LINUX_NCC); linux_to_bsd_termios(&lios, bios); } static int linux_ioctl_termio(struct thread *td, struct linux_ioctl_args *args) { struct termios bios; struct linux_termios lios; struct linux_termio lio; struct file *fp; int error; error = fget(td, args->fd, &cap_ioctl_rights, &fp); if (error != 0) return (error); switch (args->cmd & 0xffff) { case LINUX_TCGETS: error = fo_ioctl(fp, TIOCGETA, (caddr_t)&bios, td->td_ucred, td); if (error) break; bsd_to_linux_termios(&bios, &lios); error = copyout(&lios, (void *)args->arg, sizeof(lios)); break; case LINUX_TCSETS: error = copyin((void *)args->arg, &lios, sizeof(lios)); if (error) break; linux_to_bsd_termios(&lios, &bios); error = (fo_ioctl(fp, TIOCSETA, (caddr_t)&bios, td->td_ucred, td)); break; case LINUX_TCSETSW: error = copyin((void *)args->arg, &lios, sizeof(lios)); if (error) break; linux_to_bsd_termios(&lios, &bios); error = (fo_ioctl(fp, TIOCSETAW, (caddr_t)&bios, td->td_ucred, td)); break; case LINUX_TCSETSF: error = copyin((void *)args->arg, &lios, sizeof(lios)); if (error) break; linux_to_bsd_termios(&lios, &bios); error = (fo_ioctl(fp, TIOCSETAF, (caddr_t)&bios, td->td_ucred, td)); break; case LINUX_TCGETA: error = fo_ioctl(fp, TIOCGETA, (caddr_t)&bios, td->td_ucred, td); if (error) break; bsd_to_linux_termio(&bios, &lio); error = (copyout(&lio, (void *)args->arg, sizeof(lio))); break; case LINUX_TCSETA: error = copyin((void *)args->arg, &lio, sizeof(lio)); if (error) break; linux_to_bsd_termio(&lio, &bios); error = (fo_ioctl(fp, TIOCSETA, (caddr_t)&bios, td->td_ucred, td)); break; case LINUX_TCSETAW: error = copyin((void *)args->arg, &lio, sizeof(lio)); if (error) break; linux_to_bsd_termio(&lio, &bios); error = (fo_ioctl(fp, TIOCSETAW, (caddr_t)&bios, td->td_ucred, td)); break; case LINUX_TCSETAF: error = copyin((void *)args->arg, &lio, sizeof(lio)); if (error) break; linux_to_bsd_termio(&lio, &bios); error = (fo_ioctl(fp, TIOCSETAF, (caddr_t)&bios, td->td_ucred, td)); break; /* LINUX_TCSBRK */ case LINUX_TCXONC: { switch (args->arg) { case LINUX_TCOOFF: args->cmd = TIOCSTOP; break; case LINUX_TCOON: args->cmd = TIOCSTART; break; case LINUX_TCIOFF: case LINUX_TCION: { int c; struct write_args wr; error = fo_ioctl(fp, TIOCGETA, (caddr_t)&bios, td->td_ucred, td); if (error) break; fdrop(fp, td); c = (args->arg == LINUX_TCIOFF) ? VSTOP : VSTART; c = bios.c_cc[c]; if (c != _POSIX_VDISABLE) { wr.fd = args->fd; wr.buf = &c; wr.nbyte = sizeof(c); return (sys_write(td, &wr)); } else return (0); } default: fdrop(fp, td); return (EINVAL); } args->arg = 0; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; } case LINUX_TCFLSH: { int val; switch (args->arg) { case LINUX_TCIFLUSH: val = FREAD; break; case LINUX_TCOFLUSH: val = FWRITE; break; case LINUX_TCIOFLUSH: val = FREAD | FWRITE; break; default: fdrop(fp, td); return (EINVAL); } error = (fo_ioctl(fp,TIOCFLUSH,(caddr_t)&val,td->td_ucred,td)); break; } case LINUX_TIOCEXCL: args->cmd = TIOCEXCL; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; case LINUX_TIOCNXCL: args->cmd = TIOCNXCL; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; case LINUX_TIOCSCTTY: args->cmd = TIOCSCTTY; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; case LINUX_TIOCGPGRP: args->cmd = TIOCGPGRP; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; case LINUX_TIOCSPGRP: args->cmd = TIOCSPGRP; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; /* LINUX_TIOCOUTQ */ /* LINUX_TIOCSTI */ case LINUX_TIOCGWINSZ: args->cmd = TIOCGWINSZ; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; case LINUX_TIOCSWINSZ: args->cmd = TIOCSWINSZ; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; case LINUX_TIOCMGET: args->cmd = TIOCMGET; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; case LINUX_TIOCMBIS: args->cmd = TIOCMBIS; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; case LINUX_TIOCMBIC: args->cmd = TIOCMBIC; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; case LINUX_TIOCMSET: args->cmd = TIOCMSET; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; /* TIOCGSOFTCAR */ /* TIOCSSOFTCAR */ case LINUX_FIONREAD: /* LINUX_TIOCINQ */ args->cmd = FIONREAD; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; /* LINUX_TIOCLINUX */ case LINUX_TIOCCONS: args->cmd = TIOCCONS; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; case LINUX_TIOCGSERIAL: { struct linux_serial_struct lss; bzero(&lss, sizeof(lss)); lss.type = LINUX_PORT_16550A; lss.flags = 0; lss.close_delay = 0; error = copyout(&lss, (void *)args->arg, sizeof(lss)); break; } case LINUX_TIOCSSERIAL: { struct linux_serial_struct lss; error = copyin((void *)args->arg, &lss, sizeof(lss)); if (error) break; /* XXX - It really helps to have an implementation that * does nothing. NOT! */ error = 0; break; } case LINUX_TIOCPKT: args->cmd = TIOCPKT; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; case LINUX_FIONBIO: args->cmd = FIONBIO; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; case LINUX_TIOCNOTTY: args->cmd = TIOCNOTTY; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; case LINUX_TIOCSETD: { int line; switch (args->arg) { case LINUX_N_TTY: line = TTYDISC; break; case LINUX_N_SLIP: line = SLIPDISC; break; case LINUX_N_PPP: line = PPPDISC; break; default: fdrop(fp, td); return (EINVAL); } error = (fo_ioctl(fp, TIOCSETD, (caddr_t)&line, td->td_ucred, td)); break; } case LINUX_TIOCGETD: { int linux_line; int bsd_line = TTYDISC; error = fo_ioctl(fp, TIOCGETD, (caddr_t)&bsd_line, td->td_ucred, td); if (error) break; switch (bsd_line) { case TTYDISC: linux_line = LINUX_N_TTY; break; case SLIPDISC: linux_line = LINUX_N_SLIP; break; case PPPDISC: linux_line = LINUX_N_PPP; break; default: fdrop(fp, td); return (EINVAL); } error = (copyout(&linux_line, (void *)args->arg, sizeof(int))); break; } /* LINUX_TCSBRKP */ /* LINUX_TIOCTTYGSTRUCT */ case LINUX_FIONCLEX: args->cmd = FIONCLEX; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; case LINUX_FIOCLEX: args->cmd = FIOCLEX; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; case LINUX_FIOASYNC: args->cmd = FIOASYNC; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; /* LINUX_TIOCSERCONFIG */ /* LINUX_TIOCSERGWILD */ /* LINUX_TIOCSERSWILD */ /* LINUX_TIOCGLCKTRMIOS */ /* LINUX_TIOCSLCKTRMIOS */ case LINUX_TIOCSBRK: args->cmd = TIOCSBRK; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; case LINUX_TIOCCBRK: args->cmd = TIOCCBRK; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; case LINUX_TIOCGPTN: { int nb; error = fo_ioctl(fp, TIOCGPTN, (caddr_t)&nb, td->td_ucred, td); if (!error) error = copyout(&nb, (void *)args->arg, sizeof(int)); break; } case LINUX_TIOCSPTLCK: /* Our unlockpt() does nothing. */ error = 0; break; default: error = ENOIOCTL; break; } fdrop(fp, td); return (error); } /* * CDROM related ioctls */ struct linux_cdrom_msf { u_char cdmsf_min0; u_char cdmsf_sec0; u_char cdmsf_frame0; u_char cdmsf_min1; u_char cdmsf_sec1; u_char cdmsf_frame1; }; struct linux_cdrom_tochdr { u_char cdth_trk0; u_char cdth_trk1; }; union linux_cdrom_addr { struct { u_char minute; u_char second; u_char frame; } msf; int lba; }; struct linux_cdrom_tocentry { u_char cdte_track; u_char cdte_adr:4; u_char cdte_ctrl:4; u_char cdte_format; union linux_cdrom_addr cdte_addr; u_char cdte_datamode; }; struct linux_cdrom_subchnl { u_char cdsc_format; u_char cdsc_audiostatus; u_char cdsc_adr:4; u_char cdsc_ctrl:4; u_char cdsc_trk; u_char cdsc_ind; union linux_cdrom_addr cdsc_absaddr; union linux_cdrom_addr cdsc_reladdr; }; struct l_cdrom_read_audio { union linux_cdrom_addr addr; u_char addr_format; l_int nframes; u_char *buf; }; struct l_dvd_layer { u_char book_version:4; u_char book_type:4; u_char min_rate:4; u_char disc_size:4; u_char layer_type:4; u_char track_path:1; u_char nlayers:2; u_char track_density:4; u_char linear_density:4; u_char bca:1; u_int32_t start_sector; u_int32_t end_sector; u_int32_t end_sector_l0; }; struct l_dvd_physical { u_char type; u_char layer_num; struct l_dvd_layer layer[4]; }; struct l_dvd_copyright { u_char type; u_char layer_num; u_char cpst; u_char rmi; }; struct l_dvd_disckey { u_char type; l_uint agid:2; u_char value[2048]; }; struct l_dvd_bca { u_char type; l_int len; u_char value[188]; }; struct l_dvd_manufact { u_char type; u_char layer_num; l_int len; u_char value[2048]; }; typedef union { u_char type; struct l_dvd_physical physical; struct l_dvd_copyright copyright; struct l_dvd_disckey disckey; struct l_dvd_bca bca; struct l_dvd_manufact manufact; } l_dvd_struct; typedef u_char l_dvd_key[5]; typedef u_char l_dvd_challenge[10]; struct l_dvd_lu_send_agid { u_char type; l_uint agid:2; }; struct l_dvd_host_send_challenge { u_char type; l_uint agid:2; l_dvd_challenge chal; }; struct l_dvd_send_key { u_char type; l_uint agid:2; l_dvd_key key; }; struct l_dvd_lu_send_challenge { u_char type; l_uint agid:2; l_dvd_challenge chal; }; struct l_dvd_lu_send_title_key { u_char type; l_uint agid:2; l_dvd_key title_key; l_int lba; l_uint cpm:1; l_uint cp_sec:1; l_uint cgms:2; }; struct l_dvd_lu_send_asf { u_char type; l_uint agid:2; l_uint asf:1; }; struct l_dvd_host_send_rpcstate { u_char type; u_char pdrc; }; struct l_dvd_lu_send_rpcstate { u_char type:2; u_char vra:3; u_char ucca:3; u_char region_mask; u_char rpc_scheme; }; typedef union { u_char type; struct l_dvd_lu_send_agid lsa; struct l_dvd_host_send_challenge hsc; struct l_dvd_send_key lsk; struct l_dvd_lu_send_challenge lsc; struct l_dvd_send_key hsk; struct l_dvd_lu_send_title_key lstk; struct l_dvd_lu_send_asf lsasf; struct l_dvd_host_send_rpcstate hrpcs; struct l_dvd_lu_send_rpcstate lrpcs; } l_dvd_authinfo; static void bsd_to_linux_msf_lba(u_char af, union msf_lba *bp, union linux_cdrom_addr *lp) { if (af == CD_LBA_FORMAT) lp->lba = bp->lba; else { lp->msf.minute = bp->msf.minute; lp->msf.second = bp->msf.second; lp->msf.frame = bp->msf.frame; } } static void set_linux_cdrom_addr(union linux_cdrom_addr *addr, int format, int lba) { if (format == LINUX_CDROM_MSF) { addr->msf.frame = lba % 75; lba /= 75; lba += 2; addr->msf.second = lba % 60; addr->msf.minute = lba / 60; } else addr->lba = lba; } static int linux_to_bsd_dvd_struct(l_dvd_struct *lp, struct dvd_struct *bp) { bp->format = lp->type; switch (bp->format) { case DVD_STRUCT_PHYSICAL: if (bp->layer_num >= 4) return (EINVAL); bp->layer_num = lp->physical.layer_num; break; case DVD_STRUCT_COPYRIGHT: bp->layer_num = lp->copyright.layer_num; break; case DVD_STRUCT_DISCKEY: bp->agid = lp->disckey.agid; break; case DVD_STRUCT_BCA: case DVD_STRUCT_MANUFACT: break; default: return (EINVAL); } return (0); } static int bsd_to_linux_dvd_struct(struct dvd_struct *bp, l_dvd_struct *lp) { switch (bp->format) { case DVD_STRUCT_PHYSICAL: { struct dvd_layer *blp = (struct dvd_layer *)bp->data; struct l_dvd_layer *llp = &lp->physical.layer[bp->layer_num]; memset(llp, 0, sizeof(*llp)); llp->book_version = blp->book_version; llp->book_type = blp->book_type; llp->min_rate = blp->max_rate; llp->disc_size = blp->disc_size; llp->layer_type = blp->layer_type; llp->track_path = blp->track_path; llp->nlayers = blp->nlayers; llp->track_density = blp->track_density; llp->linear_density = blp->linear_density; llp->bca = blp->bca; llp->start_sector = blp->start_sector; llp->end_sector = blp->end_sector; llp->end_sector_l0 = blp->end_sector_l0; break; } case DVD_STRUCT_COPYRIGHT: lp->copyright.cpst = bp->cpst; lp->copyright.rmi = bp->rmi; break; case DVD_STRUCT_DISCKEY: memcpy(lp->disckey.value, bp->data, sizeof(lp->disckey.value)); break; case DVD_STRUCT_BCA: lp->bca.len = bp->length; memcpy(lp->bca.value, bp->data, sizeof(lp->bca.value)); break; case DVD_STRUCT_MANUFACT: lp->manufact.len = bp->length; memcpy(lp->manufact.value, bp->data, sizeof(lp->manufact.value)); /* lp->manufact.layer_num is unused in Linux (redhat 7.0). */ break; default: return (EINVAL); } return (0); } static int linux_to_bsd_dvd_authinfo(l_dvd_authinfo *lp, int *bcode, struct dvd_authinfo *bp) { switch (lp->type) { case LINUX_DVD_LU_SEND_AGID: *bcode = DVDIOCREPORTKEY; bp->format = DVD_REPORT_AGID; bp->agid = lp->lsa.agid; break; case LINUX_DVD_HOST_SEND_CHALLENGE: *bcode = DVDIOCSENDKEY; bp->format = DVD_SEND_CHALLENGE; bp->agid = lp->hsc.agid; memcpy(bp->keychal, lp->hsc.chal, 10); break; case LINUX_DVD_LU_SEND_KEY1: *bcode = DVDIOCREPORTKEY; bp->format = DVD_REPORT_KEY1; bp->agid = lp->lsk.agid; break; case LINUX_DVD_LU_SEND_CHALLENGE: *bcode = DVDIOCREPORTKEY; bp->format = DVD_REPORT_CHALLENGE; bp->agid = lp->lsc.agid; break; case LINUX_DVD_HOST_SEND_KEY2: *bcode = DVDIOCSENDKEY; bp->format = DVD_SEND_KEY2; bp->agid = lp->hsk.agid; memcpy(bp->keychal, lp->hsk.key, 5); break; case LINUX_DVD_LU_SEND_TITLE_KEY: *bcode = DVDIOCREPORTKEY; bp->format = DVD_REPORT_TITLE_KEY; bp->agid = lp->lstk.agid; bp->lba = lp->lstk.lba; break; case LINUX_DVD_LU_SEND_ASF: *bcode = DVDIOCREPORTKEY; bp->format = DVD_REPORT_ASF; bp->agid = lp->lsasf.agid; break; case LINUX_DVD_INVALIDATE_AGID: *bcode = DVDIOCREPORTKEY; bp->format = DVD_INVALIDATE_AGID; bp->agid = lp->lsa.agid; break; case LINUX_DVD_LU_SEND_RPC_STATE: *bcode = DVDIOCREPORTKEY; bp->format = DVD_REPORT_RPC; break; case LINUX_DVD_HOST_SEND_RPC_STATE: *bcode = DVDIOCSENDKEY; bp->format = DVD_SEND_RPC; bp->region = lp->hrpcs.pdrc; break; default: return (EINVAL); } return (0); } static int bsd_to_linux_dvd_authinfo(struct dvd_authinfo *bp, l_dvd_authinfo *lp) { switch (lp->type) { case LINUX_DVD_LU_SEND_AGID: lp->lsa.agid = bp->agid; break; case LINUX_DVD_HOST_SEND_CHALLENGE: lp->type = LINUX_DVD_LU_SEND_KEY1; break; case LINUX_DVD_LU_SEND_KEY1: memcpy(lp->lsk.key, bp->keychal, sizeof(lp->lsk.key)); break; case LINUX_DVD_LU_SEND_CHALLENGE: memcpy(lp->lsc.chal, bp->keychal, sizeof(lp->lsc.chal)); break; case LINUX_DVD_HOST_SEND_KEY2: lp->type = LINUX_DVD_AUTH_ESTABLISHED; break; case LINUX_DVD_LU_SEND_TITLE_KEY: memcpy(lp->lstk.title_key, bp->keychal, sizeof(lp->lstk.title_key)); lp->lstk.cpm = bp->cpm; lp->lstk.cp_sec = bp->cp_sec; lp->lstk.cgms = bp->cgms; break; case LINUX_DVD_LU_SEND_ASF: lp->lsasf.asf = bp->asf; break; case LINUX_DVD_INVALIDATE_AGID: break; case LINUX_DVD_LU_SEND_RPC_STATE: lp->lrpcs.type = bp->reg_type; lp->lrpcs.vra = bp->vend_rsts; lp->lrpcs.ucca = bp->user_rsts; lp->lrpcs.region_mask = bp->region; lp->lrpcs.rpc_scheme = bp->rpc_scheme; break; case LINUX_DVD_HOST_SEND_RPC_STATE: break; default: return (EINVAL); } return (0); } static int linux_ioctl_cdrom(struct thread *td, struct linux_ioctl_args *args) { struct file *fp; int error; error = fget(td, args->fd, &cap_ioctl_rights, &fp); if (error != 0) return (error); switch (args->cmd & 0xffff) { case LINUX_CDROMPAUSE: args->cmd = CDIOCPAUSE; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; case LINUX_CDROMRESUME: args->cmd = CDIOCRESUME; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; case LINUX_CDROMPLAYMSF: args->cmd = CDIOCPLAYMSF; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; case LINUX_CDROMPLAYTRKIND: args->cmd = CDIOCPLAYTRACKS; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; case LINUX_CDROMREADTOCHDR: { struct ioc_toc_header th; struct linux_cdrom_tochdr lth; error = fo_ioctl(fp, CDIOREADTOCHEADER, (caddr_t)&th, td->td_ucred, td); if (!error) { lth.cdth_trk0 = th.starting_track; lth.cdth_trk1 = th.ending_track; copyout(<h, (void *)args->arg, sizeof(lth)); } break; } case LINUX_CDROMREADTOCENTRY: { struct linux_cdrom_tocentry lte; struct ioc_read_toc_single_entry irtse; error = copyin((void *)args->arg, <e, sizeof(lte)); if (error) break; irtse.address_format = lte.cdte_format; irtse.track = lte.cdte_track; error = fo_ioctl(fp, CDIOREADTOCENTRY, (caddr_t)&irtse, td->td_ucred, td); if (!error) { lte.cdte_ctrl = irtse.entry.control; lte.cdte_adr = irtse.entry.addr_type; bsd_to_linux_msf_lba(irtse.address_format, &irtse.entry.addr, <e.cdte_addr); error = copyout(<e, (void *)args->arg, sizeof(lte)); } break; } case LINUX_CDROMSTOP: args->cmd = CDIOCSTOP; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; case LINUX_CDROMSTART: args->cmd = CDIOCSTART; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; case LINUX_CDROMEJECT: args->cmd = CDIOCEJECT; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; /* LINUX_CDROMVOLCTRL */ case LINUX_CDROMSUBCHNL: { struct linux_cdrom_subchnl sc; struct ioc_read_subchannel bsdsc; struct cd_sub_channel_info bsdinfo; + error = copyin((void *)args->arg, &sc, sizeof(sc)); + if (error) + break; + + /* + * Invoke the native ioctl and bounce the returned data through + * the userspace buffer. This works because the Linux structure + * is the same size as our structures for the subchannel header + * and position data. + */ bsdsc.address_format = CD_LBA_FORMAT; bsdsc.data_format = CD_CURRENT_POSITION; bsdsc.track = 0; - bsdsc.data_len = sizeof(bsdinfo); - bsdsc.data = &bsdinfo; - error = fo_ioctl(fp, CDIOCREADSUBCHANNEL_SYSSPACE, - (caddr_t)&bsdsc, td->td_ucred, td); + bsdsc.data_len = sizeof(sc); + bsdsc.data = (void *)args->arg; + error = fo_ioctl(fp, CDIOCREADSUBCHANNEL, (caddr_t)&bsdsc, + td->td_ucred, td); if (error) break; - error = copyin((void *)args->arg, &sc, sizeof(sc)); + error = copyin((void *)args->arg, &bsdinfo, sizeof(bsdinfo)); if (error) break; sc.cdsc_audiostatus = bsdinfo.header.audio_status; sc.cdsc_adr = bsdinfo.what.position.addr_type; sc.cdsc_ctrl = bsdinfo.what.position.control; sc.cdsc_trk = bsdinfo.what.position.track_number; sc.cdsc_ind = bsdinfo.what.position.index_number; set_linux_cdrom_addr(&sc.cdsc_absaddr, sc.cdsc_format, bsdinfo.what.position.absaddr.lba); set_linux_cdrom_addr(&sc.cdsc_reladdr, sc.cdsc_format, bsdinfo.what.position.reladdr.lba); error = copyout(&sc, (void *)args->arg, sizeof(sc)); break; } /* LINUX_CDROMREADMODE2 */ /* LINUX_CDROMREADMODE1 */ /* LINUX_CDROMREADAUDIO */ /* LINUX_CDROMEJECT_SW */ /* LINUX_CDROMMULTISESSION */ /* LINUX_CDROM_GET_UPC */ case LINUX_CDROMRESET: args->cmd = CDIOCRESET; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; /* LINUX_CDROMVOLREAD */ /* LINUX_CDROMREADRAW */ /* LINUX_CDROMREADCOOKED */ /* LINUX_CDROMSEEK */ /* LINUX_CDROMPLAYBLK */ /* LINUX_CDROMREADALL */ /* LINUX_CDROMCLOSETRAY */ /* LINUX_CDROMLOADFROMSLOT */ /* LINUX_CDROMGETSPINDOWN */ /* LINUX_CDROMSETSPINDOWN */ /* LINUX_CDROM_SET_OPTIONS */ /* LINUX_CDROM_CLEAR_OPTIONS */ /* LINUX_CDROM_SELECT_SPEED */ /* LINUX_CDROM_SELECT_DISC */ /* LINUX_CDROM_MEDIA_CHANGED */ /* LINUX_CDROM_DRIVE_STATUS */ /* LINUX_CDROM_DISC_STATUS */ /* LINUX_CDROM_CHANGER_NSLOTS */ /* LINUX_CDROM_LOCKDOOR */ /* LINUX_CDROM_DEBUG */ /* LINUX_CDROM_GET_CAPABILITY */ /* LINUX_CDROMAUDIOBUFSIZ */ case LINUX_DVD_READ_STRUCT: { l_dvd_struct *lds; struct dvd_struct *bds; lds = malloc(sizeof(*lds), M_LINUX, M_WAITOK); bds = malloc(sizeof(*bds), M_LINUX, M_WAITOK); error = copyin((void *)args->arg, lds, sizeof(*lds)); if (error) goto out; error = linux_to_bsd_dvd_struct(lds, bds); if (error) goto out; error = fo_ioctl(fp, DVDIOCREADSTRUCTURE, (caddr_t)bds, td->td_ucred, td); if (error) goto out; error = bsd_to_linux_dvd_struct(bds, lds); if (error) goto out; error = copyout(lds, (void *)args->arg, sizeof(*lds)); out: free(bds, M_LINUX); free(lds, M_LINUX); break; } /* LINUX_DVD_WRITE_STRUCT */ case LINUX_DVD_AUTH: { l_dvd_authinfo lda; struct dvd_authinfo bda; int bcode; error = copyin((void *)args->arg, &lda, sizeof(lda)); if (error) break; error = linux_to_bsd_dvd_authinfo(&lda, &bcode, &bda); if (error) break; error = fo_ioctl(fp, bcode, (caddr_t)&bda, td->td_ucred, td); if (error) { if (lda.type == LINUX_DVD_HOST_SEND_KEY2) { lda.type = LINUX_DVD_AUTH_FAILURE; copyout(&lda, (void *)args->arg, sizeof(lda)); } break; } error = bsd_to_linux_dvd_authinfo(&bda, &lda); if (error) break; error = copyout(&lda, (void *)args->arg, sizeof(lda)); break; } case LINUX_SCSI_GET_BUS_NUMBER: { struct sg_scsi_id id; error = fo_ioctl(fp, SG_GET_SCSI_ID, (caddr_t)&id, td->td_ucred, td); if (error) break; error = copyout(&id.channel, (void *)args->arg, sizeof(int)); break; } case LINUX_SCSI_GET_IDLUN: { struct sg_scsi_id id; struct scsi_idlun idl; error = fo_ioctl(fp, SG_GET_SCSI_ID, (caddr_t)&id, td->td_ucred, td); if (error) break; idl.dev_id = (id.scsi_id & 0xff) + ((id.lun & 0xff) << 8) + ((id.channel & 0xff) << 16) + ((id.host_no & 0xff) << 24); idl.host_unique_id = id.host_no; error = copyout(&idl, (void *)args->arg, sizeof(idl)); break; } /* LINUX_CDROM_SEND_PACKET */ /* LINUX_CDROM_NEXT_WRITABLE */ /* LINUX_CDROM_LAST_WRITTEN */ default: error = ENOIOCTL; break; } fdrop(fp, td); return (error); } static int linux_ioctl_vfat(struct thread *td, struct linux_ioctl_args *args) { return (ENOTTY); } /* * Sound related ioctls */ struct linux_old_mixer_info { char id[16]; char name[32]; }; static u_int32_t dirbits[4] = { IOC_VOID, IOC_IN, IOC_OUT, IOC_INOUT }; #define SETDIR(c) (((c) & ~IOC_DIRMASK) | dirbits[args->cmd >> 30]) static int linux_ioctl_sound(struct thread *td, struct linux_ioctl_args *args) { switch (args->cmd & 0xffff) { case LINUX_SOUND_MIXER_WRITE_VOLUME: args->cmd = SETDIR(SOUND_MIXER_WRITE_VOLUME); return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SOUND_MIXER_WRITE_BASS: args->cmd = SETDIR(SOUND_MIXER_WRITE_BASS); return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SOUND_MIXER_WRITE_TREBLE: args->cmd = SETDIR(SOUND_MIXER_WRITE_TREBLE); return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SOUND_MIXER_WRITE_SYNTH: args->cmd = SETDIR(SOUND_MIXER_WRITE_SYNTH); return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SOUND_MIXER_WRITE_PCM: args->cmd = SETDIR(SOUND_MIXER_WRITE_PCM); return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SOUND_MIXER_WRITE_SPEAKER: args->cmd = SETDIR(SOUND_MIXER_WRITE_SPEAKER); return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SOUND_MIXER_WRITE_LINE: args->cmd = SETDIR(SOUND_MIXER_WRITE_LINE); return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SOUND_MIXER_WRITE_MIC: args->cmd = SETDIR(SOUND_MIXER_WRITE_MIC); return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SOUND_MIXER_WRITE_CD: args->cmd = SETDIR(SOUND_MIXER_WRITE_CD); return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SOUND_MIXER_WRITE_IMIX: args->cmd = SETDIR(SOUND_MIXER_WRITE_IMIX); return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SOUND_MIXER_WRITE_ALTPCM: args->cmd = SETDIR(SOUND_MIXER_WRITE_ALTPCM); return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SOUND_MIXER_WRITE_RECLEV: args->cmd = SETDIR(SOUND_MIXER_WRITE_RECLEV); return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SOUND_MIXER_WRITE_IGAIN: args->cmd = SETDIR(SOUND_MIXER_WRITE_IGAIN); return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SOUND_MIXER_WRITE_OGAIN: args->cmd = SETDIR(SOUND_MIXER_WRITE_OGAIN); return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SOUND_MIXER_WRITE_LINE1: args->cmd = SETDIR(SOUND_MIXER_WRITE_LINE1); return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SOUND_MIXER_WRITE_LINE2: args->cmd = SETDIR(SOUND_MIXER_WRITE_LINE2); return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SOUND_MIXER_WRITE_LINE3: args->cmd = SETDIR(SOUND_MIXER_WRITE_LINE3); return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SOUND_MIXER_INFO: { /* Key on encoded length */ switch ((args->cmd >> 16) & 0x1fff) { case 0x005c: { /* SOUND_MIXER_INFO */ args->cmd = SOUND_MIXER_INFO; return (sys_ioctl(td, (struct ioctl_args *)args)); } case 0x0030: { /* SOUND_OLD_MIXER_INFO */ struct linux_old_mixer_info info; bzero(&info, sizeof(info)); strncpy(info.id, "OSS", sizeof(info.id) - 1); strncpy(info.name, "FreeBSD OSS Mixer", sizeof(info.name) - 1); copyout(&info, (void *)args->arg, sizeof(info)); return (0); } default: return (ENOIOCTL); } break; } case LINUX_OSS_GETVERSION: { int version = linux_get_oss_version(td); return (copyout(&version, (void *)args->arg, sizeof(int))); } case LINUX_SOUND_MIXER_READ_STEREODEVS: args->cmd = SOUND_MIXER_READ_STEREODEVS; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SOUND_MIXER_READ_CAPS: args->cmd = SOUND_MIXER_READ_CAPS; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SOUND_MIXER_READ_RECMASK: args->cmd = SOUND_MIXER_READ_RECMASK; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SOUND_MIXER_READ_DEVMASK: args->cmd = SOUND_MIXER_READ_DEVMASK; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SOUND_MIXER_WRITE_RECSRC: args->cmd = SETDIR(SOUND_MIXER_WRITE_RECSRC); return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SNDCTL_DSP_RESET: args->cmd = SNDCTL_DSP_RESET; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SNDCTL_DSP_SYNC: args->cmd = SNDCTL_DSP_SYNC; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SNDCTL_DSP_SPEED: args->cmd = SNDCTL_DSP_SPEED; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SNDCTL_DSP_STEREO: args->cmd = SNDCTL_DSP_STEREO; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SNDCTL_DSP_GETBLKSIZE: /* LINUX_SNDCTL_DSP_SETBLKSIZE */ args->cmd = SNDCTL_DSP_GETBLKSIZE; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SNDCTL_DSP_SETFMT: args->cmd = SNDCTL_DSP_SETFMT; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SOUND_PCM_WRITE_CHANNELS: args->cmd = SOUND_PCM_WRITE_CHANNELS; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SOUND_PCM_WRITE_FILTER: args->cmd = SOUND_PCM_WRITE_FILTER; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SNDCTL_DSP_POST: args->cmd = SNDCTL_DSP_POST; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SNDCTL_DSP_SUBDIVIDE: args->cmd = SNDCTL_DSP_SUBDIVIDE; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SNDCTL_DSP_SETFRAGMENT: args->cmd = SNDCTL_DSP_SETFRAGMENT; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SNDCTL_DSP_GETFMTS: args->cmd = SNDCTL_DSP_GETFMTS; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SNDCTL_DSP_GETOSPACE: args->cmd = SNDCTL_DSP_GETOSPACE; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SNDCTL_DSP_GETISPACE: args->cmd = SNDCTL_DSP_GETISPACE; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SNDCTL_DSP_NONBLOCK: args->cmd = SNDCTL_DSP_NONBLOCK; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SNDCTL_DSP_GETCAPS: args->cmd = SNDCTL_DSP_GETCAPS; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SNDCTL_DSP_SETTRIGGER: /* LINUX_SNDCTL_GETTRIGGER */ args->cmd = SNDCTL_DSP_SETTRIGGER; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SNDCTL_DSP_GETIPTR: args->cmd = SNDCTL_DSP_GETIPTR; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SNDCTL_DSP_GETOPTR: args->cmd = SNDCTL_DSP_GETOPTR; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SNDCTL_DSP_SETDUPLEX: args->cmd = SNDCTL_DSP_SETDUPLEX; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SNDCTL_DSP_GETODELAY: args->cmd = SNDCTL_DSP_GETODELAY; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SNDCTL_SEQ_RESET: args->cmd = SNDCTL_SEQ_RESET; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SNDCTL_SEQ_SYNC: args->cmd = SNDCTL_SEQ_SYNC; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SNDCTL_SYNTH_INFO: args->cmd = SNDCTL_SYNTH_INFO; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SNDCTL_SEQ_CTRLRATE: args->cmd = SNDCTL_SEQ_CTRLRATE; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SNDCTL_SEQ_GETOUTCOUNT: args->cmd = SNDCTL_SEQ_GETOUTCOUNT; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SNDCTL_SEQ_GETINCOUNT: args->cmd = SNDCTL_SEQ_GETINCOUNT; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SNDCTL_SEQ_PERCMODE: args->cmd = SNDCTL_SEQ_PERCMODE; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SNDCTL_FM_LOAD_INSTR: args->cmd = SNDCTL_FM_LOAD_INSTR; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SNDCTL_SEQ_TESTMIDI: args->cmd = SNDCTL_SEQ_TESTMIDI; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SNDCTL_SEQ_RESETSAMPLES: args->cmd = SNDCTL_SEQ_RESETSAMPLES; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SNDCTL_SEQ_NRSYNTHS: args->cmd = SNDCTL_SEQ_NRSYNTHS; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SNDCTL_SEQ_NRMIDIS: args->cmd = SNDCTL_SEQ_NRMIDIS; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SNDCTL_MIDI_INFO: args->cmd = SNDCTL_MIDI_INFO; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SNDCTL_SEQ_TRESHOLD: args->cmd = SNDCTL_SEQ_TRESHOLD; return (sys_ioctl(td, (struct ioctl_args *)args)); case LINUX_SNDCTL_SYNTH_MEMAVL: args->cmd = SNDCTL_SYNTH_MEMAVL; return (sys_ioctl(td, (struct ioctl_args *)args)); } return (ENOIOCTL); } /* * Console related ioctls */ static int linux_ioctl_console(struct thread *td, struct linux_ioctl_args *args) { struct file *fp; int error; error = fget(td, args->fd, &cap_ioctl_rights, &fp); if (error != 0) return (error); switch (args->cmd & 0xffff) { case LINUX_KIOCSOUND: args->cmd = KIOCSOUND; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; case LINUX_KDMKTONE: args->cmd = KDMKTONE; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; case LINUX_KDGETLED: args->cmd = KDGETLED; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; case LINUX_KDSETLED: args->cmd = KDSETLED; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; case LINUX_KDSETMODE: args->cmd = KDSETMODE; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; case LINUX_KDGETMODE: args->cmd = KDGETMODE; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; case LINUX_KDGKBMODE: args->cmd = KDGKBMODE; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; case LINUX_KDSKBMODE: { int kbdmode; switch (args->arg) { case LINUX_KBD_RAW: kbdmode = K_RAW; break; case LINUX_KBD_XLATE: kbdmode = K_XLATE; break; case LINUX_KBD_MEDIUMRAW: kbdmode = K_RAW; break; default: fdrop(fp, td); return (EINVAL); } error = (fo_ioctl(fp, KDSKBMODE, (caddr_t)&kbdmode, td->td_ucred, td)); break; } case LINUX_VT_OPENQRY: args->cmd = VT_OPENQRY; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; case LINUX_VT_GETMODE: args->cmd = VT_GETMODE; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; case LINUX_VT_SETMODE: { struct vt_mode mode; if ((error = copyin((void *)args->arg, &mode, sizeof(mode)))) break; if (LINUX_SIG_VALID(mode.relsig)) mode.relsig = linux_to_bsd_signal(mode.relsig); else mode.relsig = 0; if (LINUX_SIG_VALID(mode.acqsig)) mode.acqsig = linux_to_bsd_signal(mode.acqsig); else mode.acqsig = 0; /* XXX. Linux ignores frsig and set it to 0. */ mode.frsig = 0; if ((error = copyout(&mode, (void *)args->arg, sizeof(mode)))) break; args->cmd = VT_SETMODE; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; } case LINUX_VT_GETSTATE: args->cmd = VT_GETACTIVE; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; case LINUX_VT_RELDISP: args->cmd = VT_RELDISP; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; case LINUX_VT_ACTIVATE: args->cmd = VT_ACTIVATE; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; case LINUX_VT_WAITACTIVE: args->cmd = VT_WAITACTIVE; error = (sys_ioctl(td, (struct ioctl_args *)args)); break; default: error = ENOIOCTL; break; } fdrop(fp, td); return (error); } /* * Criteria for interface name translation */ #define IFP_IS_ETH(ifp) (ifp->if_type == IFT_ETHER) /* * Translate a Linux interface name to a FreeBSD interface name, * and return the associated ifnet structure * bsdname and lxname need to be least IFNAMSIZ bytes long, but * can point to the same buffer. */ static struct ifnet * ifname_linux_to_bsd(struct thread *td, const char *lxname, char *bsdname) { struct ifnet *ifp; int len, unit; char *ep; int is_eth, index; for (len = 0; len < LINUX_IFNAMSIZ; ++len) if (!isalpha(lxname[len])) break; if (len == 0 || len == LINUX_IFNAMSIZ) return (NULL); unit = (int)strtoul(lxname + len, &ep, 10); if (ep == NULL || ep == lxname + len || ep >= lxname + LINUX_IFNAMSIZ) return (NULL); index = 0; is_eth = (len == 3 && !strncmp(lxname, "eth", len)) ? 1 : 0; CURVNET_SET(TD_TO_VNET(td)); IFNET_RLOCK(); CK_STAILQ_FOREACH(ifp, &V_ifnet, if_link) { /* * Allow Linux programs to use FreeBSD names. Don't presume * we never have an interface named "eth", so don't make * the test optional based on is_eth. */ if (strncmp(ifp->if_xname, lxname, LINUX_IFNAMSIZ) == 0) break; if (is_eth && IFP_IS_ETH(ifp) && unit == index++) break; } IFNET_RUNLOCK(); CURVNET_RESTORE(); if (ifp != NULL) strlcpy(bsdname, ifp->if_xname, IFNAMSIZ); return (ifp); } /* * Implement the SIOCGIFNAME ioctl */ static int linux_ioctl_ifname(struct thread *td, struct l_ifreq *uifr) { struct l_ifreq ifr; struct ifnet *ifp; int error, ethno, index; error = copyin(uifr, &ifr, sizeof(ifr)); if (error != 0) return (error); CURVNET_SET(TD_TO_VNET(curthread)); IFNET_RLOCK(); index = 1; /* ifr.ifr_ifindex starts from 1 */ ethno = 0; error = ENODEV; CK_STAILQ_FOREACH(ifp, &V_ifnet, if_link) { if (ifr.ifr_ifindex == index) { if (IFP_IS_ETH(ifp)) snprintf(ifr.ifr_name, LINUX_IFNAMSIZ, "eth%d", ethno); else strlcpy(ifr.ifr_name, ifp->if_xname, LINUX_IFNAMSIZ); error = 0; break; } if (IFP_IS_ETH(ifp)) ethno++; index++; } IFNET_RUNLOCK(); if (error == 0) error = copyout(&ifr, uifr, sizeof(ifr)); CURVNET_RESTORE(); return (error); } /* * Implement the SIOCGIFCONF ioctl */ static int linux_ifconf(struct thread *td, struct ifconf *uifc) { #ifdef COMPAT_LINUX32 struct l_ifconf ifc; #else struct ifconf ifc; #endif struct l_ifreq ifr; struct ifnet *ifp; struct ifaddr *ifa; struct sbuf *sb; int error, ethno, full = 0, valid_len, max_len; error = copyin(uifc, &ifc, sizeof(ifc)); if (error != 0) return (error); max_len = MAXPHYS - 1; CURVNET_SET(TD_TO_VNET(td)); /* handle the 'request buffer size' case */ if ((l_uintptr_t)ifc.ifc_buf == PTROUT(NULL)) { ifc.ifc_len = 0; IFNET_RLOCK(); CK_STAILQ_FOREACH(ifp, &V_ifnet, if_link) { CK_STAILQ_FOREACH(ifa, &ifp->if_addrhead, ifa_link) { struct sockaddr *sa = ifa->ifa_addr; if (sa->sa_family == AF_INET) ifc.ifc_len += sizeof(ifr); } } IFNET_RUNLOCK(); error = copyout(&ifc, uifc, sizeof(ifc)); CURVNET_RESTORE(); return (error); } if (ifc.ifc_len <= 0) { CURVNET_RESTORE(); return (EINVAL); } again: /* Keep track of eth interfaces */ ethno = 0; if (ifc.ifc_len <= max_len) { max_len = ifc.ifc_len; full = 1; } sb = sbuf_new(NULL, NULL, max_len + 1, SBUF_FIXEDLEN); max_len = 0; valid_len = 0; /* Return all AF_INET addresses of all interfaces */ IFNET_RLOCK(); CK_STAILQ_FOREACH(ifp, &V_ifnet, if_link) { int addrs = 0; bzero(&ifr, sizeof(ifr)); if (IFP_IS_ETH(ifp)) snprintf(ifr.ifr_name, LINUX_IFNAMSIZ, "eth%d", ethno++); else strlcpy(ifr.ifr_name, ifp->if_xname, LINUX_IFNAMSIZ); /* Walk the address list */ CK_STAILQ_FOREACH(ifa, &ifp->if_addrhead, ifa_link) { struct sockaddr *sa = ifa->ifa_addr; if (sa->sa_family == AF_INET) { ifr.ifr_addr.sa_family = LINUX_AF_INET; memcpy(ifr.ifr_addr.sa_data, sa->sa_data, sizeof(ifr.ifr_addr.sa_data)); sbuf_bcat(sb, &ifr, sizeof(ifr)); max_len += sizeof(ifr); addrs++; } if (sbuf_error(sb) == 0) valid_len = sbuf_len(sb); } if (addrs == 0) { bzero((caddr_t)&ifr.ifr_addr, sizeof(ifr.ifr_addr)); sbuf_bcat(sb, &ifr, sizeof(ifr)); max_len += sizeof(ifr); if (sbuf_error(sb) == 0) valid_len = sbuf_len(sb); } } IFNET_RUNLOCK(); if (valid_len != max_len && !full) { sbuf_delete(sb); goto again; } ifc.ifc_len = valid_len; sbuf_finish(sb); error = copyout(sbuf_data(sb), PTRIN(ifc.ifc_buf), ifc.ifc_len); if (error == 0) error = copyout(&ifc, uifc, sizeof(ifc)); sbuf_delete(sb); CURVNET_RESTORE(); return (error); } static int linux_gifflags(struct thread *td, struct ifnet *ifp, struct l_ifreq *ifr) { l_short flags; flags = (ifp->if_flags | ifp->if_drv_flags) & 0xffff; /* these flags have no Linux equivalent */ flags &= ~(IFF_DRV_OACTIVE|IFF_SIMPLEX| IFF_LINK0|IFF_LINK1|IFF_LINK2); /* Linux' multicast flag is in a different bit */ if (flags & IFF_MULTICAST) { flags &= ~IFF_MULTICAST; flags |= 0x1000; } return (copyout(&flags, &ifr->ifr_flags, sizeof(flags))); } #define ARPHRD_ETHER 1 #define ARPHRD_LOOPBACK 772 static int linux_gifhwaddr(struct ifnet *ifp, struct l_ifreq *ifr) { struct ifaddr *ifa; struct sockaddr_dl *sdl; struct l_sockaddr lsa; if (ifp->if_type == IFT_LOOP) { bzero(&lsa, sizeof(lsa)); lsa.sa_family = ARPHRD_LOOPBACK; return (copyout(&lsa, &ifr->ifr_hwaddr, sizeof(lsa))); } if (ifp->if_type != IFT_ETHER) return (ENOENT); CK_STAILQ_FOREACH(ifa, &ifp->if_addrhead, ifa_link) { sdl = (struct sockaddr_dl*)ifa->ifa_addr; if (sdl != NULL && (sdl->sdl_family == AF_LINK) && (sdl->sdl_type == IFT_ETHER)) { bzero(&lsa, sizeof(lsa)); lsa.sa_family = ARPHRD_ETHER; bcopy(LLADDR(sdl), lsa.sa_data, LINUX_IFHWADDRLEN); return (copyout(&lsa, &ifr->ifr_hwaddr, sizeof(lsa))); } } return (ENOENT); } /* * If we fault in bsd_to_linux_ifreq() then we will fault when we call * the native ioctl(). Thus, we don't really need to check the return * value of this function. */ static int bsd_to_linux_ifreq(struct ifreq *arg) { struct ifreq ifr; size_t ifr_len = sizeof(struct ifreq); int error; if ((error = copyin(arg, &ifr, ifr_len))) return (error); *(u_short *)&ifr.ifr_addr = ifr.ifr_addr.sa_family; error = copyout(&ifr, arg, ifr_len); return (error); } /* * Socket related ioctls */ static int linux_ioctl_socket(struct thread *td, struct linux_ioctl_args *args) { char lifname[LINUX_IFNAMSIZ], ifname[IFNAMSIZ]; struct ifnet *ifp; struct file *fp; int error, type; ifp = NULL; error = 0; error = fget(td, args->fd, &cap_ioctl_rights, &fp); if (error != 0) return (error); type = fp->f_type; fdrop(fp, td); if (type != DTYPE_SOCKET) { /* not a socket - probably a tap / vmnet device */ switch (args->cmd) { case LINUX_SIOCGIFADDR: case LINUX_SIOCSIFADDR: case LINUX_SIOCGIFFLAGS: return (linux_ioctl_special(td, args)); default: return (ENOIOCTL); } } switch (args->cmd & 0xffff) { case LINUX_FIOGETOWN: case LINUX_FIOSETOWN: case LINUX_SIOCADDMULTI: case LINUX_SIOCATMARK: case LINUX_SIOCDELMULTI: case LINUX_SIOCGIFNAME: case LINUX_SIOCGIFCONF: case LINUX_SIOCGPGRP: case LINUX_SIOCSPGRP: case LINUX_SIOCGIFCOUNT: /* these ioctls don't take an interface name */ #ifdef DEBUG printf("%s(): ioctl %d\n", __func__, args->cmd & 0xffff); #endif break; case LINUX_SIOCGIFFLAGS: case LINUX_SIOCGIFADDR: case LINUX_SIOCSIFADDR: case LINUX_SIOCGIFDSTADDR: case LINUX_SIOCGIFBRDADDR: case LINUX_SIOCGIFNETMASK: case LINUX_SIOCSIFNETMASK: case LINUX_SIOCGIFMTU: case LINUX_SIOCSIFMTU: case LINUX_SIOCSIFNAME: case LINUX_SIOCGIFHWADDR: case LINUX_SIOCSIFHWADDR: case LINUX_SIOCDEVPRIVATE: case LINUX_SIOCDEVPRIVATE+1: case LINUX_SIOCGIFINDEX: /* copy in the interface name and translate it. */ error = copyin((void *)args->arg, lifname, LINUX_IFNAMSIZ); if (error != 0) return (error); #ifdef DEBUG printf("%s(): ioctl %d on %.*s\n", __func__, args->cmd & 0xffff, LINUX_IFNAMSIZ, lifname); #endif memset(ifname, 0, sizeof(ifname)); ifp = ifname_linux_to_bsd(td, lifname, ifname); if (ifp == NULL) return (EINVAL); /* * We need to copy it back out in case we pass the * request on to our native ioctl(), which will expect * the ifreq to be in user space and have the correct * interface name. */ error = copyout(ifname, (void *)args->arg, IFNAMSIZ); if (error != 0) return (error); #ifdef DEBUG printf("%s(): %s translated to %s\n", __func__, lifname, ifname); #endif break; default: return (ENOIOCTL); } switch (args->cmd & 0xffff) { case LINUX_FIOSETOWN: args->cmd = FIOSETOWN; error = sys_ioctl(td, (struct ioctl_args *)args); break; case LINUX_SIOCSPGRP: args->cmd = SIOCSPGRP; error = sys_ioctl(td, (struct ioctl_args *)args); break; case LINUX_FIOGETOWN: args->cmd = FIOGETOWN; error = sys_ioctl(td, (struct ioctl_args *)args); break; case LINUX_SIOCGPGRP: args->cmd = SIOCGPGRP; error = sys_ioctl(td, (struct ioctl_args *)args); break; case LINUX_SIOCATMARK: args->cmd = SIOCATMARK; error = sys_ioctl(td, (struct ioctl_args *)args); break; /* LINUX_SIOCGSTAMP */ case LINUX_SIOCGIFNAME: error = linux_ioctl_ifname(td, (struct l_ifreq *)args->arg); break; case LINUX_SIOCGIFCONF: error = linux_ifconf(td, (struct ifconf *)args->arg); break; case LINUX_SIOCGIFFLAGS: args->cmd = SIOCGIFFLAGS; error = linux_gifflags(td, ifp, (struct l_ifreq *)args->arg); break; case LINUX_SIOCGIFADDR: args->cmd = SIOCGIFADDR; error = sys_ioctl(td, (struct ioctl_args *)args); bsd_to_linux_ifreq((struct ifreq *)args->arg); break; case LINUX_SIOCSIFADDR: /* XXX probably doesn't work, included for completeness */ args->cmd = SIOCSIFADDR; error = sys_ioctl(td, (struct ioctl_args *)args); break; case LINUX_SIOCGIFDSTADDR: args->cmd = SIOCGIFDSTADDR; error = sys_ioctl(td, (struct ioctl_args *)args); bsd_to_linux_ifreq((struct ifreq *)args->arg); break; case LINUX_SIOCGIFBRDADDR: args->cmd = SIOCGIFBRDADDR; error = sys_ioctl(td, (struct ioctl_args *)args); bsd_to_linux_ifreq((struct ifreq *)args->arg); break; case LINUX_SIOCGIFNETMASK: args->cmd = SIOCGIFNETMASK; error = sys_ioctl(td, (struct ioctl_args *)args); bsd_to_linux_ifreq((struct ifreq *)args->arg); break; case LINUX_SIOCSIFNETMASK: error = ENOIOCTL; break; case LINUX_SIOCGIFMTU: args->cmd = SIOCGIFMTU; error = sys_ioctl(td, (struct ioctl_args *)args); break; case LINUX_SIOCSIFMTU: args->cmd = SIOCSIFMTU; error = sys_ioctl(td, (struct ioctl_args *)args); break; case LINUX_SIOCSIFNAME: error = ENOIOCTL; break; case LINUX_SIOCGIFHWADDR: error = linux_gifhwaddr(ifp, (struct l_ifreq *)args->arg); break; case LINUX_SIOCSIFHWADDR: error = ENOIOCTL; break; case LINUX_SIOCADDMULTI: args->cmd = SIOCADDMULTI; error = sys_ioctl(td, (struct ioctl_args *)args); break; case LINUX_SIOCDELMULTI: args->cmd = SIOCDELMULTI; error = sys_ioctl(td, (struct ioctl_args *)args); break; case LINUX_SIOCGIFINDEX: args->cmd = SIOCGIFINDEX; error = sys_ioctl(td, (struct ioctl_args *)args); break; case LINUX_SIOCGIFCOUNT: error = 0; break; /* * XXX This is slightly bogus, but these ioctls are currently * XXX only used by the aironet (if_an) network driver. */ case LINUX_SIOCDEVPRIVATE: args->cmd = SIOCGPRIVATE_0; error = sys_ioctl(td, (struct ioctl_args *)args); break; case LINUX_SIOCDEVPRIVATE+1: args->cmd = SIOCGPRIVATE_1; error = sys_ioctl(td, (struct ioctl_args *)args); break; } if (ifp != NULL) /* restore the original interface name */ copyout(lifname, (void *)args->arg, LINUX_IFNAMSIZ); #ifdef DEBUG printf("%s(): returning %d\n", __func__, error); #endif return (error); } /* * Device private ioctl handler */ static int linux_ioctl_private(struct thread *td, struct linux_ioctl_args *args) { struct file *fp; int error, type; error = fget(td, args->fd, &cap_ioctl_rights, &fp); if (error != 0) return (error); type = fp->f_type; fdrop(fp, td); if (type == DTYPE_SOCKET) return (linux_ioctl_socket(td, args)); return (ENOIOCTL); } /* * DRM ioctl handler (sys/dev/drm) */ static int linux_ioctl_drm(struct thread *td, struct linux_ioctl_args *args) { args->cmd = SETDIR(args->cmd); return (sys_ioctl(td, (struct ioctl_args *)args)); } #ifdef COMPAT_LINUX32 #define CP(src,dst,fld) do { (dst).fld = (src).fld; } while (0) #define PTRIN_CP(src,dst,fld) \ do { (dst).fld = PTRIN((src).fld); } while (0) #define PTROUT_CP(src,dst,fld) \ do { (dst).fld = PTROUT((src).fld); } while (0) static int linux_ioctl_sg_io(struct thread *td, struct linux_ioctl_args *args) { struct sg_io_hdr io; struct sg_io_hdr32 io32; struct file *fp; int error; error = fget(td, args->fd, &cap_ioctl_rights, &fp); if (error != 0) { printf("sg_linux_ioctl: fget returned %d\n", error); return (error); } if ((error = copyin((void *)args->arg, &io32, sizeof(io32))) != 0) goto out; CP(io32, io, interface_id); CP(io32, io, dxfer_direction); CP(io32, io, cmd_len); CP(io32, io, mx_sb_len); CP(io32, io, iovec_count); CP(io32, io, dxfer_len); PTRIN_CP(io32, io, dxferp); PTRIN_CP(io32, io, cmdp); PTRIN_CP(io32, io, sbp); CP(io32, io, timeout); CP(io32, io, flags); CP(io32, io, pack_id); PTRIN_CP(io32, io, usr_ptr); CP(io32, io, status); CP(io32, io, masked_status); CP(io32, io, msg_status); CP(io32, io, sb_len_wr); CP(io32, io, host_status); CP(io32, io, driver_status); CP(io32, io, resid); CP(io32, io, duration); CP(io32, io, info); if ((error = fo_ioctl(fp, SG_IO, (caddr_t)&io, td->td_ucred, td)) != 0) goto out; CP(io, io32, interface_id); CP(io, io32, dxfer_direction); CP(io, io32, cmd_len); CP(io, io32, mx_sb_len); CP(io, io32, iovec_count); CP(io, io32, dxfer_len); PTROUT_CP(io, io32, dxferp); PTROUT_CP(io, io32, cmdp); PTROUT_CP(io, io32, sbp); CP(io, io32, timeout); CP(io, io32, flags); CP(io, io32, pack_id); PTROUT_CP(io, io32, usr_ptr); CP(io, io32, status); CP(io, io32, masked_status); CP(io, io32, msg_status); CP(io, io32, sb_len_wr); CP(io, io32, host_status); CP(io, io32, driver_status); CP(io, io32, resid); CP(io, io32, duration); CP(io, io32, info); error = copyout(&io32, (void *)args->arg, sizeof(io32)); out: fdrop(fp, td); return (error); } #endif static int linux_ioctl_sg(struct thread *td, struct linux_ioctl_args *args) { switch (args->cmd) { case LINUX_SG_GET_VERSION_NUM: args->cmd = SG_GET_VERSION_NUM; break; case LINUX_SG_SET_TIMEOUT: args->cmd = SG_SET_TIMEOUT; break; case LINUX_SG_GET_TIMEOUT: args->cmd = SG_GET_TIMEOUT; break; case LINUX_SG_IO: args->cmd = SG_IO; #ifdef COMPAT_LINUX32 return (linux_ioctl_sg_io(td, args)); #endif break; case LINUX_SG_GET_RESERVED_SIZE: args->cmd = SG_GET_RESERVED_SIZE; break; case LINUX_SG_GET_SCSI_ID: args->cmd = SG_GET_SCSI_ID; break; case LINUX_SG_GET_SG_TABLESIZE: args->cmd = SG_GET_SG_TABLESIZE; break; default: return (ENODEV); } return (sys_ioctl(td, (struct ioctl_args *)args)); } /* * Video4Linux (V4L) ioctl handler */ static int linux_to_bsd_v4l_tuner(struct l_video_tuner *lvt, struct video_tuner *vt) { vt->tuner = lvt->tuner; strlcpy(vt->name, lvt->name, LINUX_VIDEO_TUNER_NAME_SIZE); vt->rangelow = lvt->rangelow; /* possible long size conversion */ vt->rangehigh = lvt->rangehigh; /* possible long size conversion */ vt->flags = lvt->flags; vt->mode = lvt->mode; vt->signal = lvt->signal; return (0); } static int bsd_to_linux_v4l_tuner(struct video_tuner *vt, struct l_video_tuner *lvt) { lvt->tuner = vt->tuner; strlcpy(lvt->name, vt->name, LINUX_VIDEO_TUNER_NAME_SIZE); lvt->rangelow = vt->rangelow; /* possible long size conversion */ lvt->rangehigh = vt->rangehigh; /* possible long size conversion */ lvt->flags = vt->flags; lvt->mode = vt->mode; lvt->signal = vt->signal; return (0); } #ifdef COMPAT_LINUX_V4L_CLIPLIST static int linux_to_bsd_v4l_clip(struct l_video_clip *lvc, struct video_clip *vc) { vc->x = lvc->x; vc->y = lvc->y; vc->width = lvc->width; vc->height = lvc->height; vc->next = PTRIN(lvc->next); /* possible pointer size conversion */ return (0); } #endif static int linux_to_bsd_v4l_window(struct l_video_window *lvw, struct video_window *vw) { vw->x = lvw->x; vw->y = lvw->y; vw->width = lvw->width; vw->height = lvw->height; vw->chromakey = lvw->chromakey; vw->flags = lvw->flags; vw->clips = PTRIN(lvw->clips); /* possible pointer size conversion */ vw->clipcount = lvw->clipcount; return (0); } static int bsd_to_linux_v4l_window(struct video_window *vw, struct l_video_window *lvw) { lvw->x = vw->x; lvw->y = vw->y; lvw->width = vw->width; lvw->height = vw->height; lvw->chromakey = vw->chromakey; lvw->flags = vw->flags; lvw->clips = PTROUT(vw->clips); /* possible pointer size conversion */ lvw->clipcount = vw->clipcount; return (0); } static int linux_to_bsd_v4l_buffer(struct l_video_buffer *lvb, struct video_buffer *vb) { vb->base = PTRIN(lvb->base); /* possible pointer size conversion */ vb->height = lvb->height; vb->width = lvb->width; vb->depth = lvb->depth; vb->bytesperline = lvb->bytesperline; return (0); } static int bsd_to_linux_v4l_buffer(struct video_buffer *vb, struct l_video_buffer *lvb) { lvb->base = PTROUT(vb->base); /* possible pointer size conversion */ lvb->height = vb->height; lvb->width = vb->width; lvb->depth = vb->depth; lvb->bytesperline = vb->bytesperline; return (0); } static int linux_to_bsd_v4l_code(struct l_video_code *lvc, struct video_code *vc) { strlcpy(vc->loadwhat, lvc->loadwhat, LINUX_VIDEO_CODE_LOADWHAT_SIZE); vc->datasize = lvc->datasize; vc->data = PTRIN(lvc->data); /* possible pointer size conversion */ return (0); } #ifdef COMPAT_LINUX_V4L_CLIPLIST static int linux_v4l_clip_copy(void *lvc, struct video_clip **ppvc) { int error; struct video_clip vclip; struct l_video_clip l_vclip; error = copyin(lvc, &l_vclip, sizeof(l_vclip)); if (error) return (error); linux_to_bsd_v4l_clip(&l_vclip, &vclip); /* XXX: If there can be no concurrency: s/M_NOWAIT/M_WAITOK/ */ if ((*ppvc = malloc(sizeof(**ppvc), M_LINUX, M_NOWAIT)) == NULL) return (ENOMEM); /* XXX: Linux has no ENOMEM here. */ memcpy(*ppvc, &vclip, sizeof(vclip)); (*ppvc)->next = NULL; return (0); } static int linux_v4l_cliplist_free(struct video_window *vw) { struct video_clip **ppvc; struct video_clip **ppvc_next; for (ppvc = &(vw->clips); *ppvc != NULL; ppvc = ppvc_next) { ppvc_next = &((*ppvc)->next); free(*ppvc, M_LINUX); } vw->clips = NULL; return (0); } static int linux_v4l_cliplist_copy(struct l_video_window *lvw, struct video_window *vw) { int error; int clipcount; void *plvc; struct video_clip **ppvc; /* * XXX: The cliplist is used to pass in a list of clipping * rectangles or, if clipcount == VIDEO_CLIP_BITMAP, a * clipping bitmap. Some Linux apps, however, appear to * leave cliplist and clips uninitialized. In any case, * the cliplist is not used by pwc(4), at the time of * writing, FreeBSD's only V4L driver. When a driver * that uses the cliplist is developed, this code may * need re-examiniation. */ error = 0; clipcount = vw->clipcount; if (clipcount == VIDEO_CLIP_BITMAP) { /* * In this case, the pointer (clips) is overloaded * to be a "void *" to a bitmap, therefore there * is no struct video_clip to copy now. */ } else if (clipcount > 0 && clipcount <= 16384) { /* * Clips points to list of clip rectangles, so * copy the list. * * XXX: Upper limit of 16384 was used here to try to * avoid cases when clipcount and clips pointer * are uninitialized and therefore have high random * values, as is the case in the Linux Skype * application. The value 16384 was chosen as that * is what is used in the Linux stradis(4) MPEG * decoder driver, the only place we found an * example of cliplist use. */ plvc = PTRIN(lvw->clips); vw->clips = NULL; ppvc = &(vw->clips); while (clipcount-- > 0) { if (plvc == NULL) { error = EFAULT; break; } else { error = linux_v4l_clip_copy(plvc, ppvc); if (error) { linux_v4l_cliplist_free(vw); break; } } ppvc = &((*ppvc)->next); plvc = PTRIN(((struct l_video_clip *) plvc)->next); } } else { /* * clipcount == 0 or negative (but not VIDEO_CLIP_BITMAP) * Force cliplist to null. */ vw->clipcount = 0; vw->clips = NULL; } return (error); } #endif static int linux_ioctl_v4l(struct thread *td, struct linux_ioctl_args *args) { struct file *fp; int error; struct video_tuner vtun; struct video_window vwin; struct video_buffer vbuf; struct video_code vcode; struct l_video_tuner l_vtun; struct l_video_window l_vwin; struct l_video_buffer l_vbuf; struct l_video_code l_vcode; switch (args->cmd & 0xffff) { case LINUX_VIDIOCGCAP: args->cmd = VIDIOCGCAP; break; case LINUX_VIDIOCGCHAN: args->cmd = VIDIOCGCHAN; break; case LINUX_VIDIOCSCHAN: args->cmd = VIDIOCSCHAN; break; case LINUX_VIDIOCGTUNER: error = fget(td, args->fd, &cap_ioctl_rights, &fp); if (error != 0) return (error); error = copyin((void *) args->arg, &l_vtun, sizeof(l_vtun)); if (error) { fdrop(fp, td); return (error); } linux_to_bsd_v4l_tuner(&l_vtun, &vtun); error = fo_ioctl(fp, VIDIOCGTUNER, &vtun, td->td_ucred, td); if (!error) { bsd_to_linux_v4l_tuner(&vtun, &l_vtun); error = copyout(&l_vtun, (void *) args->arg, sizeof(l_vtun)); } fdrop(fp, td); return (error); case LINUX_VIDIOCSTUNER: error = fget(td, args->fd, &cap_ioctl_rights, &fp); if (error != 0) return (error); error = copyin((void *) args->arg, &l_vtun, sizeof(l_vtun)); if (error) { fdrop(fp, td); return (error); } linux_to_bsd_v4l_tuner(&l_vtun, &vtun); error = fo_ioctl(fp, VIDIOCSTUNER, &vtun, td->td_ucred, td); fdrop(fp, td); return (error); case LINUX_VIDIOCGPICT: args->cmd = VIDIOCGPICT; break; case LINUX_VIDIOCSPICT: args->cmd = VIDIOCSPICT; break; case LINUX_VIDIOCCAPTURE: args->cmd = VIDIOCCAPTURE; break; case LINUX_VIDIOCGWIN: error = fget(td, args->fd, &cap_ioctl_rights, &fp); if (error != 0) return (error); error = fo_ioctl(fp, VIDIOCGWIN, &vwin, td->td_ucred, td); if (!error) { bsd_to_linux_v4l_window(&vwin, &l_vwin); error = copyout(&l_vwin, (void *) args->arg, sizeof(l_vwin)); } fdrop(fp, td); return (error); case LINUX_VIDIOCSWIN: error = fget(td, args->fd, &cap_ioctl_rights, &fp); if (error != 0) return (error); error = copyin((void *) args->arg, &l_vwin, sizeof(l_vwin)); if (error) { fdrop(fp, td); return (error); } linux_to_bsd_v4l_window(&l_vwin, &vwin); #ifdef COMPAT_LINUX_V4L_CLIPLIST error = linux_v4l_cliplist_copy(&l_vwin, &vwin); if (error) { fdrop(fp, td); return (error); } #endif error = fo_ioctl(fp, VIDIOCSWIN, &vwin, td->td_ucred, td); fdrop(fp, td); #ifdef COMPAT_LINUX_V4L_CLIPLIST linux_v4l_cliplist_free(&vwin); #endif return (error); case LINUX_VIDIOCGFBUF: error = fget(td, args->fd, &cap_ioctl_rights, &fp); if (error != 0) return (error); error = fo_ioctl(fp, VIDIOCGFBUF, &vbuf, td->td_ucred, td); if (!error) { bsd_to_linux_v4l_buffer(&vbuf, &l_vbuf); error = copyout(&l_vbuf, (void *) args->arg, sizeof(l_vbuf)); } fdrop(fp, td); return (error); case LINUX_VIDIOCSFBUF: error = fget(td, args->fd, &cap_ioctl_rights, &fp); if (error != 0) return (error); error = copyin((void *) args->arg, &l_vbuf, sizeof(l_vbuf)); if (error) { fdrop(fp, td); return (error); } linux_to_bsd_v4l_buffer(&l_vbuf, &vbuf); error = fo_ioctl(fp, VIDIOCSFBUF, &vbuf, td->td_ucred, td); fdrop(fp, td); return (error); case LINUX_VIDIOCKEY: args->cmd = VIDIOCKEY; break; case LINUX_VIDIOCGFREQ: args->cmd = VIDIOCGFREQ; break; case LINUX_VIDIOCSFREQ: args->cmd = VIDIOCSFREQ; break; case LINUX_VIDIOCGAUDIO: args->cmd = VIDIOCGAUDIO; break; case LINUX_VIDIOCSAUDIO: args->cmd = VIDIOCSAUDIO; break; case LINUX_VIDIOCSYNC: args->cmd = VIDIOCSYNC; break; case LINUX_VIDIOCMCAPTURE: args->cmd = VIDIOCMCAPTURE; break; case LINUX_VIDIOCGMBUF: args->cmd = VIDIOCGMBUF; break; case LINUX_VIDIOCGUNIT: args->cmd = VIDIOCGUNIT; break; case LINUX_VIDIOCGCAPTURE: args->cmd = VIDIOCGCAPTURE; break; case LINUX_VIDIOCSCAPTURE: args->cmd = VIDIOCSCAPTURE; break; case LINUX_VIDIOCSPLAYMODE: args->cmd = VIDIOCSPLAYMODE; break; case LINUX_VIDIOCSWRITEMODE: args->cmd = VIDIOCSWRITEMODE; break; case LINUX_VIDIOCGPLAYINFO: args->cmd = VIDIOCGPLAYINFO; break; case LINUX_VIDIOCSMICROCODE: error = fget(td, args->fd, &cap_ioctl_rights, &fp); if (error != 0) return (error); error = copyin((void *) args->arg, &l_vcode, sizeof(l_vcode)); if (error) { fdrop(fp, td); return (error); } linux_to_bsd_v4l_code(&l_vcode, &vcode); error = fo_ioctl(fp, VIDIOCSMICROCODE, &vcode, td->td_ucred, td); fdrop(fp, td); return (error); case LINUX_VIDIOCGVBIFMT: args->cmd = VIDIOCGVBIFMT; break; case LINUX_VIDIOCSVBIFMT: args->cmd = VIDIOCSVBIFMT; break; default: return (ENOIOCTL); } error = sys_ioctl(td, (struct ioctl_args *)args); return (error); } /* * Special ioctl handler */ static int linux_ioctl_special(struct thread *td, struct linux_ioctl_args *args) { int error; switch (args->cmd) { case LINUX_SIOCGIFADDR: args->cmd = SIOCGIFADDR; error = sys_ioctl(td, (struct ioctl_args *)args); break; case LINUX_SIOCSIFADDR: args->cmd = SIOCSIFADDR; error = sys_ioctl(td, (struct ioctl_args *)args); break; case LINUX_SIOCGIFFLAGS: args->cmd = SIOCGIFFLAGS; error = sys_ioctl(td, (struct ioctl_args *)args); break; default: error = ENOIOCTL; } return (error); } static int linux_to_bsd_v4l2_standard(struct l_v4l2_standard *lvstd, struct v4l2_standard *vstd) { vstd->index = lvstd->index; vstd->id = lvstd->id; CTASSERT(sizeof(vstd->name) == sizeof(lvstd->name)); memcpy(vstd->name, lvstd->name, sizeof(vstd->name)); vstd->frameperiod = lvstd->frameperiod; vstd->framelines = lvstd->framelines; CTASSERT(sizeof(vstd->reserved) == sizeof(lvstd->reserved)); memcpy(vstd->reserved, lvstd->reserved, sizeof(vstd->reserved)); return (0); } static int bsd_to_linux_v4l2_standard(struct v4l2_standard *vstd, struct l_v4l2_standard *lvstd) { lvstd->index = vstd->index; lvstd->id = vstd->id; CTASSERT(sizeof(vstd->name) == sizeof(lvstd->name)); memcpy(lvstd->name, vstd->name, sizeof(lvstd->name)); lvstd->frameperiod = vstd->frameperiod; lvstd->framelines = vstd->framelines; CTASSERT(sizeof(vstd->reserved) == sizeof(lvstd->reserved)); memcpy(lvstd->reserved, vstd->reserved, sizeof(lvstd->reserved)); return (0); } static int linux_to_bsd_v4l2_buffer(struct l_v4l2_buffer *lvb, struct v4l2_buffer *vb) { vb->index = lvb->index; vb->type = lvb->type; vb->bytesused = lvb->bytesused; vb->flags = lvb->flags; vb->field = lvb->field; vb->timestamp.tv_sec = lvb->timestamp.tv_sec; vb->timestamp.tv_usec = lvb->timestamp.tv_usec; memcpy(&vb->timecode, &lvb->timecode, sizeof (lvb->timecode)); vb->sequence = lvb->sequence; vb->memory = lvb->memory; if (lvb->memory == V4L2_MEMORY_USERPTR) /* possible pointer size conversion */ vb->m.userptr = (unsigned long)PTRIN(lvb->m.userptr); else vb->m.offset = lvb->m.offset; vb->length = lvb->length; vb->input = lvb->input; vb->reserved = lvb->reserved; return (0); } static int bsd_to_linux_v4l2_buffer(struct v4l2_buffer *vb, struct l_v4l2_buffer *lvb) { lvb->index = vb->index; lvb->type = vb->type; lvb->bytesused = vb->bytesused; lvb->flags = vb->flags; lvb->field = vb->field; lvb->timestamp.tv_sec = vb->timestamp.tv_sec; lvb->timestamp.tv_usec = vb->timestamp.tv_usec; memcpy(&lvb->timecode, &vb->timecode, sizeof (vb->timecode)); lvb->sequence = vb->sequence; lvb->memory = vb->memory; if (vb->memory == V4L2_MEMORY_USERPTR) /* possible pointer size conversion */ lvb->m.userptr = PTROUT(vb->m.userptr); else lvb->m.offset = vb->m.offset; lvb->length = vb->length; lvb->input = vb->input; lvb->reserved = vb->reserved; return (0); } static int linux_to_bsd_v4l2_format(struct l_v4l2_format *lvf, struct v4l2_format *vf) { vf->type = lvf->type; if (lvf->type == V4L2_BUF_TYPE_VIDEO_OVERLAY #ifdef V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY || lvf->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY #endif ) /* * XXX TODO - needs 32 -> 64 bit conversion: * (unused by webcams?) */ return (EINVAL); memcpy(&vf->fmt, &lvf->fmt, sizeof(vf->fmt)); return (0); } static int bsd_to_linux_v4l2_format(struct v4l2_format *vf, struct l_v4l2_format *lvf) { lvf->type = vf->type; if (vf->type == V4L2_BUF_TYPE_VIDEO_OVERLAY #ifdef V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY || vf->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY #endif ) /* * XXX TODO - needs 32 -> 64 bit conversion: * (unused by webcams?) */ return (EINVAL); memcpy(&lvf->fmt, &vf->fmt, sizeof(vf->fmt)); return (0); } static int linux_ioctl_v4l2(struct thread *td, struct linux_ioctl_args *args) { struct file *fp; int error; struct v4l2_format vformat; struct l_v4l2_format l_vformat; struct v4l2_standard vstd; struct l_v4l2_standard l_vstd; struct l_v4l2_buffer l_vbuf; struct v4l2_buffer vbuf; struct v4l2_input vinp; switch (args->cmd & 0xffff) { case LINUX_VIDIOC_RESERVED: case LINUX_VIDIOC_LOG_STATUS: if ((args->cmd & IOC_DIRMASK) != LINUX_IOC_VOID) return (ENOIOCTL); args->cmd = (args->cmd & 0xffff) | IOC_VOID; break; case LINUX_VIDIOC_OVERLAY: case LINUX_VIDIOC_STREAMON: case LINUX_VIDIOC_STREAMOFF: case LINUX_VIDIOC_S_STD: case LINUX_VIDIOC_S_TUNER: case LINUX_VIDIOC_S_AUDIO: case LINUX_VIDIOC_S_AUDOUT: case LINUX_VIDIOC_S_MODULATOR: case LINUX_VIDIOC_S_FREQUENCY: case LINUX_VIDIOC_S_CROP: case LINUX_VIDIOC_S_JPEGCOMP: case LINUX_VIDIOC_S_PRIORITY: case LINUX_VIDIOC_DBG_S_REGISTER: case LINUX_VIDIOC_S_HW_FREQ_SEEK: case LINUX_VIDIOC_SUBSCRIBE_EVENT: case LINUX_VIDIOC_UNSUBSCRIBE_EVENT: args->cmd = (args->cmd & ~IOC_DIRMASK) | IOC_IN; break; case LINUX_VIDIOC_QUERYCAP: case LINUX_VIDIOC_G_STD: case LINUX_VIDIOC_G_AUDIO: case LINUX_VIDIOC_G_INPUT: case LINUX_VIDIOC_G_OUTPUT: case LINUX_VIDIOC_G_AUDOUT: case LINUX_VIDIOC_G_JPEGCOMP: case LINUX_VIDIOC_QUERYSTD: case LINUX_VIDIOC_G_PRIORITY: case LINUX_VIDIOC_QUERY_DV_PRESET: args->cmd = (args->cmd & ~IOC_DIRMASK) | IOC_OUT; break; case LINUX_VIDIOC_ENUM_FMT: case LINUX_VIDIOC_REQBUFS: case LINUX_VIDIOC_G_PARM: case LINUX_VIDIOC_S_PARM: case LINUX_VIDIOC_G_CTRL: case LINUX_VIDIOC_S_CTRL: case LINUX_VIDIOC_G_TUNER: case LINUX_VIDIOC_QUERYCTRL: case LINUX_VIDIOC_QUERYMENU: case LINUX_VIDIOC_S_INPUT: case LINUX_VIDIOC_S_OUTPUT: case LINUX_VIDIOC_ENUMOUTPUT: case LINUX_VIDIOC_G_MODULATOR: case LINUX_VIDIOC_G_FREQUENCY: case LINUX_VIDIOC_CROPCAP: case LINUX_VIDIOC_G_CROP: case LINUX_VIDIOC_ENUMAUDIO: case LINUX_VIDIOC_ENUMAUDOUT: case LINUX_VIDIOC_G_SLICED_VBI_CAP: #ifdef VIDIOC_ENUM_FRAMESIZES case LINUX_VIDIOC_ENUM_FRAMESIZES: case LINUX_VIDIOC_ENUM_FRAMEINTERVALS: case LINUX_VIDIOC_ENCODER_CMD: case LINUX_VIDIOC_TRY_ENCODER_CMD: #endif case LINUX_VIDIOC_DBG_G_REGISTER: case LINUX_VIDIOC_DBG_G_CHIP_IDENT: case LINUX_VIDIOC_ENUM_DV_PRESETS: case LINUX_VIDIOC_S_DV_PRESET: case LINUX_VIDIOC_G_DV_PRESET: case LINUX_VIDIOC_S_DV_TIMINGS: case LINUX_VIDIOC_G_DV_TIMINGS: args->cmd = (args->cmd & ~IOC_DIRMASK) | IOC_INOUT; break; case LINUX_VIDIOC_G_FMT: case LINUX_VIDIOC_S_FMT: case LINUX_VIDIOC_TRY_FMT: error = copyin((void *)args->arg, &l_vformat, sizeof(l_vformat)); if (error) return (error); error = fget(td, args->fd, &cap_ioctl_rights, &fp); if (error) return (error); if (linux_to_bsd_v4l2_format(&l_vformat, &vformat) != 0) error = EINVAL; else if ((args->cmd & 0xffff) == LINUX_VIDIOC_G_FMT) error = fo_ioctl(fp, VIDIOC_G_FMT, &vformat, td->td_ucred, td); else if ((args->cmd & 0xffff) == LINUX_VIDIOC_S_FMT) error = fo_ioctl(fp, VIDIOC_S_FMT, &vformat, td->td_ucred, td); else error = fo_ioctl(fp, VIDIOC_TRY_FMT, &vformat, td->td_ucred, td); bsd_to_linux_v4l2_format(&vformat, &l_vformat); copyout(&l_vformat, (void *)args->arg, sizeof(l_vformat)); fdrop(fp, td); return (error); case LINUX_VIDIOC_ENUMSTD: error = copyin((void *)args->arg, &l_vstd, sizeof(l_vstd)); if (error) return (error); linux_to_bsd_v4l2_standard(&l_vstd, &vstd); error = fget(td, args->fd, &cap_ioctl_rights, &fp); if (error) return (error); error = fo_ioctl(fp, VIDIOC_ENUMSTD, (caddr_t)&vstd, td->td_ucred, td); if (error) { fdrop(fp, td); return (error); } bsd_to_linux_v4l2_standard(&vstd, &l_vstd); error = copyout(&l_vstd, (void *)args->arg, sizeof(l_vstd)); fdrop(fp, td); return (error); case LINUX_VIDIOC_ENUMINPUT: /* * The Linux struct l_v4l2_input differs only in size, * it has no padding at the end. */ error = copyin((void *)args->arg, &vinp, sizeof(struct l_v4l2_input)); if (error != 0) return (error); error = fget(td, args->fd, &cap_ioctl_rights, &fp); if (error != 0) return (error); error = fo_ioctl(fp, VIDIOC_ENUMINPUT, (caddr_t)&vinp, td->td_ucred, td); if (error) { fdrop(fp, td); return (error); } error = copyout(&vinp, (void *)args->arg, sizeof(struct l_v4l2_input)); fdrop(fp, td); return (error); case LINUX_VIDIOC_QUERYBUF: case LINUX_VIDIOC_QBUF: case LINUX_VIDIOC_DQBUF: error = copyin((void *)args->arg, &l_vbuf, sizeof(l_vbuf)); if (error) return (error); error = fget(td, args->fd, &cap_ioctl_rights, &fp); if (error) return (error); linux_to_bsd_v4l2_buffer(&l_vbuf, &vbuf); if ((args->cmd & 0xffff) == LINUX_VIDIOC_QUERYBUF) error = fo_ioctl(fp, VIDIOC_QUERYBUF, &vbuf, td->td_ucred, td); else if ((args->cmd & 0xffff) == LINUX_VIDIOC_QBUF) error = fo_ioctl(fp, VIDIOC_QBUF, &vbuf, td->td_ucred, td); else error = fo_ioctl(fp, VIDIOC_DQBUF, &vbuf, td->td_ucred, td); bsd_to_linux_v4l2_buffer(&vbuf, &l_vbuf); copyout(&l_vbuf, (void *)args->arg, sizeof(l_vbuf)); fdrop(fp, td); return (error); /* * XXX TODO - these need 32 -> 64 bit conversion: * (are any of them needed for webcams?) */ case LINUX_VIDIOC_G_FBUF: case LINUX_VIDIOC_S_FBUF: case LINUX_VIDIOC_G_EXT_CTRLS: case LINUX_VIDIOC_S_EXT_CTRLS: case LINUX_VIDIOC_TRY_EXT_CTRLS: case LINUX_VIDIOC_DQEVENT: default: return (ENOIOCTL); } error = sys_ioctl(td, (struct ioctl_args *)args); return (error); } /* * Support for emulators/linux-libusb. This port uses FBSD_LUSB* macros * instead of USB* ones. This lets us to provide correct values for cmd. * 0xffffffe0 -- 0xffffffff range seemed to be the least collision-prone. */ static int linux_ioctl_fbsd_usb(struct thread *td, struct linux_ioctl_args *args) { int error; error = 0; switch (args->cmd) { case FBSD_LUSB_DEVICEENUMERATE: args->cmd = USB_DEVICEENUMERATE; break; case FBSD_LUSB_DEV_QUIRK_ADD: args->cmd = USB_DEV_QUIRK_ADD; break; case FBSD_LUSB_DEV_QUIRK_GET: args->cmd = USB_DEV_QUIRK_GET; break; case FBSD_LUSB_DEV_QUIRK_REMOVE: args->cmd = USB_DEV_QUIRK_REMOVE; break; case FBSD_LUSB_DO_REQUEST: args->cmd = USB_DO_REQUEST; break; case FBSD_LUSB_FS_CLEAR_STALL_SYNC: args->cmd = USB_FS_CLEAR_STALL_SYNC; break; case FBSD_LUSB_FS_CLOSE: args->cmd = USB_FS_CLOSE; break; case FBSD_LUSB_FS_COMPLETE: args->cmd = USB_FS_COMPLETE; break; case FBSD_LUSB_FS_INIT: args->cmd = USB_FS_INIT; break; case FBSD_LUSB_FS_OPEN: args->cmd = USB_FS_OPEN; break; case FBSD_LUSB_FS_START: args->cmd = USB_FS_START; break; case FBSD_LUSB_FS_STOP: args->cmd = USB_FS_STOP; break; case FBSD_LUSB_FS_UNINIT: args->cmd = USB_FS_UNINIT; break; case FBSD_LUSB_GET_CONFIG: args->cmd = USB_GET_CONFIG; break; case FBSD_LUSB_GET_DEVICEINFO: args->cmd = USB_GET_DEVICEINFO; break; case FBSD_LUSB_GET_DEVICE_DESC: args->cmd = USB_GET_DEVICE_DESC; break; case FBSD_LUSB_GET_FULL_DESC: args->cmd = USB_GET_FULL_DESC; break; case FBSD_LUSB_GET_IFACE_DRIVER: args->cmd = USB_GET_IFACE_DRIVER; break; case FBSD_LUSB_GET_PLUGTIME: args->cmd = USB_GET_PLUGTIME; break; case FBSD_LUSB_GET_POWER_MODE: args->cmd = USB_GET_POWER_MODE; break; case FBSD_LUSB_GET_REPORT_DESC: args->cmd = USB_GET_REPORT_DESC; break; case FBSD_LUSB_GET_REPORT_ID: args->cmd = USB_GET_REPORT_ID; break; case FBSD_LUSB_GET_TEMPLATE: args->cmd = USB_GET_TEMPLATE; break; case FBSD_LUSB_IFACE_DRIVER_ACTIVE: args->cmd = USB_IFACE_DRIVER_ACTIVE; break; case FBSD_LUSB_IFACE_DRIVER_DETACH: args->cmd = USB_IFACE_DRIVER_DETACH; break; case FBSD_LUSB_QUIRK_NAME_GET: args->cmd = USB_QUIRK_NAME_GET; break; case FBSD_LUSB_READ_DIR: args->cmd = USB_READ_DIR; break; case FBSD_LUSB_SET_ALTINTERFACE: args->cmd = USB_SET_ALTINTERFACE; break; case FBSD_LUSB_SET_CONFIG: args->cmd = USB_SET_CONFIG; break; case FBSD_LUSB_SET_IMMED: args->cmd = USB_SET_IMMED; break; case FBSD_LUSB_SET_POWER_MODE: args->cmd = USB_SET_POWER_MODE; break; case FBSD_LUSB_SET_TEMPLATE: args->cmd = USB_SET_TEMPLATE; break; case FBSD_LUSB_FS_OPEN_STREAM: args->cmd = USB_FS_OPEN_STREAM; break; case FBSD_LUSB_GET_DEV_PORT_PATH: args->cmd = USB_GET_DEV_PORT_PATH; break; case FBSD_LUSB_GET_POWER_USAGE: args->cmd = USB_GET_POWER_USAGE; break; default: error = ENOIOCTL; } if (error != ENOIOCTL) error = sys_ioctl(td, (struct ioctl_args *)args); return (error); } /* * Some evdev ioctls must be translated. * - EVIOCGMTSLOTS is a IOC_READ ioctl on Linux although it has input data * (must be IOC_INOUT on FreeBSD). * - On Linux, EVIOCGRAB, EVIOCREVOKE and EVIOCRMFF are defined as _IOW with * an int argument. You don't pass an int pointer to the ioctl(), however, * but just the int directly. On FreeBSD, they are defined as _IOWINT for * this to work. */ static int linux_ioctl_evdev(struct thread *td, struct linux_ioctl_args *args) { struct file *fp; clockid_t clock; int error; args->cmd = SETDIR(args->cmd); switch (args->cmd) { case (EVIOCGRAB & ~IOC_DIRMASK) | IOC_IN: args->cmd = EVIOCGRAB; break; case (EVIOCREVOKE & ~IOC_DIRMASK) | IOC_IN: args->cmd = EVIOCREVOKE; break; case (EVIOCRMFF & ~IOC_DIRMASK) | IOC_IN: args->cmd = EVIOCRMFF; break; case EVIOCSCLOCKID: { error = copyin(PTRIN(args->arg), &clock, sizeof(clock)); if (error != 0) return (error); if (clock & ~(LINUX_IOCTL_EVDEV_CLK)) return (EINVAL); error = linux_to_native_clockid(&clock, clock); if (error != 0) return (error); error = fget(td, args->fd, &cap_ioctl_rights, &fp); if (error != 0) return (error); error = fo_ioctl(fp, EVIOCSCLOCKID, &clock, td->td_ucred, td); fdrop(fp, td); return (error); } default: break; } if (IOCBASECMD(args->cmd) == ((EVIOCGMTSLOTS(0) & ~IOC_DIRMASK) | IOC_OUT)) args->cmd = (args->cmd & ~IOC_DIRMASK) | IOC_INOUT; return (sys_ioctl(td, (struct ioctl_args *)args)); } /* * main ioctl syscall function */ int linux_ioctl(struct thread *td, struct linux_ioctl_args *args) { struct file *fp; struct linux_ioctl_handler_element *he; int error, cmd; #ifdef DEBUG if (ldebug(ioctl)) printf(ARGS(ioctl, "%d, %04lx, *"), args->fd, (unsigned long)args->cmd); #endif error = fget(td, args->fd, &cap_ioctl_rights, &fp); if (error != 0) return (error); if ((fp->f_flag & (FREAD|FWRITE)) == 0) { fdrop(fp, td); return (EBADF); } /* Iterate over the ioctl handlers */ cmd = args->cmd & 0xffff; sx_slock(&linux_ioctl_sx); mtx_lock(&Giant); #ifdef COMPAT_LINUX32 TAILQ_FOREACH(he, &linux32_ioctl_handlers, list) { if (cmd >= he->low && cmd <= he->high) { error = (*he->func)(td, args); if (error != ENOIOCTL) { mtx_unlock(&Giant); sx_sunlock(&linux_ioctl_sx); fdrop(fp, td); return (error); } } } #endif TAILQ_FOREACH(he, &linux_ioctl_handlers, list) { if (cmd >= he->low && cmd <= he->high) { error = (*he->func)(td, args); if (error != ENOIOCTL) { mtx_unlock(&Giant); sx_sunlock(&linux_ioctl_sx); fdrop(fp, td); return (error); } } } mtx_unlock(&Giant); sx_sunlock(&linux_ioctl_sx); fdrop(fp, td); switch (args->cmd & 0xffff) { case LINUX_BTRFS_IOC_CLONE: return (ENOTSUP); default: linux_msg(td, "ioctl fd=%d, cmd=0x%x ('%c',%d) is not implemented", args->fd, (int)(args->cmd & 0xffff), (int)(args->cmd & 0xff00) >> 8, (int)(args->cmd & 0xff)); break; } return (EINVAL); } int linux_ioctl_register_handler(struct linux_ioctl_handler *h) { struct linux_ioctl_handler_element *he, *cur; if (h == NULL || h->func == NULL) return (EINVAL); /* * Reuse the element if the handler is already on the list, otherwise * create a new element. */ sx_xlock(&linux_ioctl_sx); TAILQ_FOREACH(he, &linux_ioctl_handlers, list) { if (he->func == h->func) break; } if (he == NULL) { he = malloc(sizeof(*he), M_LINUX, M_WAITOK); he->func = h->func; } else TAILQ_REMOVE(&linux_ioctl_handlers, he, list); /* Initialize range information. */ he->low = h->low; he->high = h->high; he->span = h->high - h->low + 1; /* Add the element to the list, sorted on span. */ TAILQ_FOREACH(cur, &linux_ioctl_handlers, list) { if (cur->span > he->span) { TAILQ_INSERT_BEFORE(cur, he, list); sx_xunlock(&linux_ioctl_sx); return (0); } } TAILQ_INSERT_TAIL(&linux_ioctl_handlers, he, list); sx_xunlock(&linux_ioctl_sx); return (0); } int linux_ioctl_unregister_handler(struct linux_ioctl_handler *h) { struct linux_ioctl_handler_element *he; if (h == NULL || h->func == NULL) return (EINVAL); sx_xlock(&linux_ioctl_sx); TAILQ_FOREACH(he, &linux_ioctl_handlers, list) { if (he->func == h->func) { TAILQ_REMOVE(&linux_ioctl_handlers, he, list); sx_xunlock(&linux_ioctl_sx); free(he, M_LINUX); return (0); } } sx_xunlock(&linux_ioctl_sx); return (EINVAL); } #ifdef COMPAT_LINUX32 int linux32_ioctl_register_handler(struct linux_ioctl_handler *h) { struct linux_ioctl_handler_element *he, *cur; if (h == NULL || h->func == NULL) return (EINVAL); /* * Reuse the element if the handler is already on the list, otherwise * create a new element. */ sx_xlock(&linux_ioctl_sx); TAILQ_FOREACH(he, &linux32_ioctl_handlers, list) { if (he->func == h->func) break; } if (he == NULL) { he = malloc(sizeof(*he), M_LINUX, M_WAITOK); he->func = h->func; } else TAILQ_REMOVE(&linux32_ioctl_handlers, he, list); /* Initialize range information. */ he->low = h->low; he->high = h->high; he->span = h->high - h->low + 1; /* Add the element to the list, sorted on span. */ TAILQ_FOREACH(cur, &linux32_ioctl_handlers, list) { if (cur->span > he->span) { TAILQ_INSERT_BEFORE(cur, he, list); sx_xunlock(&linux_ioctl_sx); return (0); } } TAILQ_INSERT_TAIL(&linux32_ioctl_handlers, he, list); sx_xunlock(&linux_ioctl_sx); return (0); } int linux32_ioctl_unregister_handler(struct linux_ioctl_handler *h) { struct linux_ioctl_handler_element *he; if (h == NULL || h->func == NULL) return (EINVAL); sx_xlock(&linux_ioctl_sx); TAILQ_FOREACH(he, &linux32_ioctl_handlers, list) { if (he->func == h->func) { TAILQ_REMOVE(&linux32_ioctl_handlers, he, list); sx_xunlock(&linux_ioctl_sx); free(he, M_LINUX); return (0); } } sx_xunlock(&linux_ioctl_sx); return (EINVAL); } #endif Index: releng/12.0/sys/sys/cdio.h =================================================================== --- releng/12.0/sys/sys/cdio.h (revision 349624) +++ releng/12.0/sys/sys/cdio.h (revision 349625) @@ -1,284 +1,277 @@ /* * 16 Feb 93 Julian Elischer (julian@dialix.oz.au) * * $FreeBSD$ */ /* <1> Fixed a conflict with ioctl usage. There were two different functions using code #25. Made file formatting consistent. Added two new ioctl codes: door closing and audio pitch playback. Added a STEREO union called STEREO. 5-Mar-95 Frank Durda IV bsdmail@nemesis.lonestar.org <2> Added a new ioctl that allows you to find out what capabilities a drive has and what commands it will accept. This allows a user application to only offer controls (buttons, sliders, etc) for functions that drive can actually do. Things it can't do can disappear or be greyed-out (like some other system). If the driver doesn't respond to this call, well, handle it the way you used to do it. 2-Apr-95 Frank Durda IV bsdmail@nemesis.lonestar.org */ /* Shared between kernel & process */ #ifndef _SYS_CDIO_H_ #define _SYS_CDIO_H_ #ifndef _KERNEL #include #endif #include union msf_lba { struct { unsigned char unused; unsigned char minute; unsigned char second; unsigned char frame; } msf; int lba; /* network byte order */ u_char addr[4]; }; struct cd_toc_entry { #if BYTE_ORDER == LITTLE_ENDIAN u_int :8; u_int control:4; u_int addr_type:4; #else u_int :8; u_int addr_type:4; u_int control:4; #endif u_char track; u_int :8; union msf_lba addr; }; struct cd_sub_channel_header { u_int :8; u_char audio_status; #define CD_AS_AUDIO_INVALID 0x00 #define CD_AS_PLAY_IN_PROGRESS 0x11 #define CD_AS_PLAY_PAUSED 0x12 #define CD_AS_PLAY_COMPLETED 0x13 #define CD_AS_PLAY_ERROR 0x14 #define CD_AS_NO_STATUS 0x15 u_char data_len[2]; }; struct cd_sub_channel_position_data { u_char data_format; u_int control:4; u_int addr_type:4; u_char track_number; u_char index_number; union msf_lba absaddr; union msf_lba reladdr; }; struct cd_sub_channel_media_catalog { u_char data_format; u_int :8; u_int :8; u_int :8; u_int :7; u_int mc_valid:1; u_char mc_number[15]; }; struct cd_sub_channel_track_info { u_char data_format; u_int :8; u_char track_number; u_int :8; u_int :7; u_int ti_valid:1; u_char ti_number[15]; }; struct cd_sub_channel_info { struct cd_sub_channel_header header; union { struct cd_sub_channel_position_data position; struct cd_sub_channel_media_catalog media_catalog; struct cd_sub_channel_track_info track_info; } what; }; /***************************************************************\ * Ioctls for the CD drive * \***************************************************************/ struct ioc_play_track { u_char start_track; u_char start_index; u_char end_track; u_char end_index; }; #define CDIOCPLAYTRACKS _IOW('c',1,struct ioc_play_track) struct ioc_play_blocks { int blk; int len; }; #define CDIOCPLAYBLOCKS _IOW('c',2,struct ioc_play_blocks) struct ioc_read_subchannel { u_char address_format; #define CD_LBA_FORMAT 1 #define CD_MSF_FORMAT 2 u_char data_format; #define CD_SUBQ_DATA 0 #define CD_CURRENT_POSITION 1 #define CD_MEDIA_CATALOG 2 #define CD_TRACK_INFO 3 u_char track; int data_len; struct cd_sub_channel_info *data; }; #define CDIOCREADSUBCHANNEL _IOWR('c', 3 , struct ioc_read_subchannel ) struct ioc_toc_header { u_short len; u_char starting_track; u_char ending_track; }; #define CDIOREADTOCHEADER _IOR('c',4,struct ioc_toc_header) struct ioc_read_toc_entry { u_char address_format; u_char starting_track; u_short data_len; struct cd_toc_entry *data; }; #define CDIOREADTOCENTRYS _IOWR('c',5,struct ioc_read_toc_entry) struct ioc_read_toc_single_entry { u_char address_format; u_char track; struct cd_toc_entry entry; }; #define CDIOREADTOCENTRY _IOWR('c',6,struct ioc_read_toc_single_entry) struct ioc_patch { u_char patch[4]; /* one for each channel */ }; #define CDIOCSETPATCH _IOW('c',9,struct ioc_patch) struct ioc_vol { u_char vol[4]; /* one for each channel */ }; #define CDIOCGETVOL _IOR('c',10,struct ioc_vol) #define CDIOCSETVOL _IOW('c',11,struct ioc_vol) #define CDIOCSETMONO _IO('c',12) #define CDIOCSETSTERIO _IO('c',13) #define CDIOCSETSTEREO _IO('c',13) #define CDIOCSETMUTE _IO('c',14) #define CDIOCSETLEFT _IO('c',15) #define CDIOCSETRIGHT _IO('c',16) #define CDIOCSETDEBUG _IO('c',17) #define CDIOCCLRDEBUG _IO('c',18) #define CDIOCPAUSE _IO('c',19) #define CDIOCRESUME _IO('c',20) #define CDIOCRESET _IO('c',21) #define CDIOCSTART _IO('c',22) #define CDIOCSTOP _IO('c',23) #define CDIOCEJECT _IO('c',24) struct ioc_play_msf { u_char start_m; u_char start_s; u_char start_f; u_char end_m; u_char end_s; u_char end_f; }; #define CDIOCPLAYMSF _IOW('c',25,struct ioc_play_msf) #define CDIOCALLOW _IO('c',26) #define CDIOCPREVENT _IO('c',27) /*<1>For drives that support it, this*/ /*<1>causes the drive to close its door*/ /*<1>and make the media (if any) ready*/ #define CDIOCCLOSE _IO('c',28) /*<1>*/ struct ioc_pitch { /*<1>For drives that support it, this*/ /*<1>call instructs the drive to play the*/ short speed; /*<1>audio at a faster or slower-than-normal*/ }; /*<1>rate. -32767 to -1 is slower, 0==normal,*/ /*<1>and 1 to 32767 is faster. LSB bits are*/ /*<1>discarded first by drives with less res.*/ #define CDIOCPITCH _IOW('c',29,struct ioc_pitch) /*<1>*/ struct ioc_capability { /*<2>*/ u_long play_function; /*<2>*/ #define CDDOPLAYTRK 0x00000001 /*<2>Can Play tracks/index*/ #define CDDOPLAYMSF 0x00000002 /*<2>Can Play msf to msf*/ #define CDDOPLAYBLOCKS 0x00000004 /*<2>Can Play range of blocks*/ #define CDDOPAUSE 0x00000100 /*<2>Output can be paused*/ #define CDDORESUME 0x00000200 /*<2>Output can be resumed*/ #define CDDORESET 0x00000400 /*<2>Drive can be completely reset*/ #define CDDOSTART 0x00000800 /*<2>Audio can be started*/ #define CDDOSTOP 0x00001000 /*<2>Audio can be stopped*/ #define CDDOPITCH 0x00002000 /*<2>Audio pitch */ u_long routing_function; /*<2>*/ #define CDREADVOLUME 0x00000001 /*<2>Volume settings can be read*/ #define CDSETVOLUME 0x00000002 /*<2>Volume settings can be set*/ #define CDSETMONO 0x00000100 /*<2>Output can be set to mono*/ #define CDSETSTEREO 0x00000200 /*<2>Output can be set to stereo (def)*/ #define CDSETLEFT 0x00000400 /*<2>Output can be set to left only*/ #define CDSETRIGHT 0x00000800 /*<2>Output can be set to right only*/ #define CDSETMUTE 0x00001000 /*<2>Output can be muted*/ #define CDSETPATCH 0x00008000 /*<2>Direct routing control allowed*/ u_long special_function; /*<2>*/ #define CDDOEJECT 0x00000001 /*<2>The tray can be opened*/ #define CDDOCLOSE 0x00000002 /*<2>The tray can be closed*/ #define CDDOLOCK 0x00000004 /*<2>The tray can be locked*/ #define CDREADHEADER 0x00000100 /*<2>Can read Table of Contents*/ #define CDREADENTRIES 0x00000200 /*<2>Can read TOC Entries*/ #define CDREADSUBQ 0x00000200 /*<2>Can read Subchannel info*/ #define CDREADRW 0x00000400 /*<2>Can read subcodes R-W*/ #define CDHASDEBUG 0x00004000 /*<2>The tray has dynamic debugging*/ }; /*<2>*/ #define CDIOCCAPABILITY _IOR('c',30,struct ioc_capability) /*<2>*/ -/* - * Special version of CDIOCREADSUBCHANNEL which assumes that - * ioc_read_subchannel->data points to the kernel memory. For - * use in compatibility layers. - */ -#define CDIOCREADSUBCHANNEL_SYSSPACE _IOWR('c', 31, struct ioc_read_subchannel) - #endif /* !_SYS_CDIO_H_ */