/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include /* * We're emulating (and possibly layering on top of) cmdk devices, so xdf * disk unit mappings must match up with cmdk disk unit mappings'. */ #if !defined(XDF_PSHIFT) #error "can't find definition for xdf unit mappings - XDF_PSHIFT" #endif /* XDF_PSHIFT */ #if !defined(CMDK_UNITSHF) #error "can't find definition for cmdk unit mappings - CMDK_UNITSHF" #endif /* CMDK_UNITSHF */ #if ((XDF_PSHIFT - CMDK_UNITSHF) != 0) #error "cmdk and xdf unit mappings don't match." #endif /* ((XDF_PSHIFT - CMDK_UNITSHF) != 0) */ extern const struct dev_ops cmdk_ops; extern void *cmdk_state; /* * Globals required by xdf_shell.c */ const char *xdfs_c_name = "cmdk"; const char *xdfs_c_linkinfo = "PV Common Direct Access Disk"; void **xdfs_c_hvm_ss = &cmdk_state; const size_t xdfs_c_hvm_ss_size = sizeof (struct cmdk); const struct dev_ops *xdfs_c_hvm_dev_ops = &cmdk_ops; const xdfs_h2p_map_t xdfs_c_h2p_map[] = { /* * The paths mapping here are very specific to xen and qemu. When a * domU is booted under xen in HVM mode, qemu is normally used to * emulate up to four ide disks. These disks always have the four * path listed below. To configure an emulated ide device, the * xen domain configuration file normally has an entry that looks * like this: * disk = [ 'file:/foo.img,hda,w' ] * * The part we're interested in is the 'hda', which we'll call the * xen disk device name here. The xen management tools (which parse * the xen domain configuration file and launch qemu) makes the * following assumptions about this value: * hda == emulated ide disk 0 (ide bus 0, master) * hdb == emulated ide disk 1 (ide bus 0, slave) * hdc == emulated ide disk 2 (ide bus 1, master) * hdd == emulated ide disk 3 (ide bus 1, slave) * * (Uncoincidentally, these xen disk device names actually map to * the /dev filesystem names of ide disk devices in Linux. So in * Linux /dev/hda is the first ide disk.) So for the first part of * our mapping we've just hardcoded the cmdk paths that we know * qemu will use. * * To understand the second half of the mapping (ie, the xdf device * that each emulated cmdk device should be mapped two) we need to * know the solaris device node address that will be assigned to * each xdf device. (The device node address is the decimal * number that comes after the "xdf@" in the device path.) * * So the question becomes, how do we know what the xenstore device * id for emulated disk will be? Well, it turns out that since the * xen management tools expect the disk device names to be Linux * device names, those same management tools assign each disk a * device id that matches the dev_t of the corresponding device * under Linux. (Big shocker.) This xen device name-to-id mapping * is currently all hard coded here: * xen.hg/tools/python/xen/util/blkif.py`blkdev_name_to_number() * * So looking at the code above we can see the following xen disk * device name to xenstore device id mappings: * 'hda' == 0t768 == ((3 * 256) + (0 * 64)) * 'hdb' == 0t832 == ((3 * 256) + (1 * 64)) * 'hdc' == 0t5632 == ((22 * 256) + (0 * 64)) * 'hdd' == 0t5696 == ((22 * 256) + (1 * 64)) */ { "/pci@0,0/pci-ide@1,1/ide@0/cmdk@0,0", "/xpvd/xdf@768" }, { "/pci@0,0/pci-ide@1,1/ide@0/cmdk@1,0", "/xpvd/xdf@832" }, { "/pci@0,0/pci-ide@1,1/ide@1/cmdk@0,0", "/xpvd/xdf@5632" }, { "/pci@0,0/pci-ide@1,1/ide@1/cmdk@1,0", "/xpvd/xdf@5696" }, { NULL, 0 } }; /* * Private functions */ /* * xdfs_get_modser() is basically a local copy of * cmdk_get_modser() modified to work without the dadk layer. * (which the non-pv version of the cmdk driver uses.) */ static int xdfs_get_modser(xdfs_state_t *xsp, int ioccmd, char *buf, int len) { struct scsi_device *scsi_device; opaque_t ctlobjp; dadk_ioc_string_t strarg; char *s; char ch; boolean_t ret; int i; int tb; strarg.is_buf = buf; strarg.is_size = len; scsi_device = ddi_get_driver_private(xsp->xdfss_dip); ctlobjp = scsi_device->sd_address.a_hba_tran; if (CTL_IOCTL(ctlobjp, ioccmd, (uintptr_t)&strarg, FNATIVE | FKIOCTL) != 0) return (0); /* * valid model/serial string must contain a non-zero non-space * trim trailing spaces/NULL */ ret = B_FALSE; s = buf; for (i = 0; i < strarg.is_size; i++) { ch = *s++; if (ch != ' ' && ch != '\0') tb = i + 1; if (ch != ' ' && ch != '\0' && ch != '0') ret = B_TRUE; } if (ret == B_FALSE) return (0); return (tb); } /* * xdfs_devid_modser() is basically a copy of cmdk_devid_modser() * that has been modified to use local pv cmdk driver functions. * * Build a devid from the model and serial number * Return DDI_SUCCESS or DDI_FAILURE. */ static int xdfs_devid_modser(xdfs_state_t *xsp) { int rc = DDI_FAILURE; char *hwid; int modlen; int serlen; /* * device ID is a concatenation of model number, '=', serial number. */ hwid = kmem_alloc(CMDK_HWIDLEN, KM_SLEEP); modlen = xdfs_get_modser(xsp, DIOCTL_GETMODEL, hwid, CMDK_HWIDLEN); if (modlen == 0) goto err; hwid[modlen++] = '='; serlen = xdfs_get_modser(xsp, DIOCTL_GETSERIAL, hwid + modlen, CMDK_HWIDLEN - modlen); if (serlen == 0) goto err; hwid[modlen + serlen] = 0; /* Initialize the device ID, trailing NULL not included */ rc = ddi_devid_init(xsp->xdfss_dip, DEVID_ATA_SERIAL, modlen + serlen, hwid, (ddi_devid_t *)&xsp->xdfss_tgt_devid); if (rc != DDI_SUCCESS) goto err; kmem_free(hwid, CMDK_HWIDLEN); return (DDI_SUCCESS); err: kmem_free(hwid, CMDK_HWIDLEN); return (DDI_FAILURE); } /* * xdfs_devid_read() is basically a local copy of * cmdk_devid_read() modified to work without the dadk layer. * (which the non-pv version of the cmdk driver uses.) * * Read a devid from on the first block of the last track of * the last cylinder. Make sure what we read is a valid devid. * Return DDI_SUCCESS or DDI_FAILURE. */ static int xdfs_devid_read(xdfs_state_t *xsp) { diskaddr_t blk; struct dk_devid *dkdevidp; uint_t *ip, chksum; int i; if (cmlb_get_devid_block(xsp->xdfss_cmlbhandle, &blk, 0) != 0) return (DDI_FAILURE); dkdevidp = kmem_zalloc(NBPSCTR, KM_SLEEP); if (xdfs_lb_rdwr(xsp->xdfss_dip, TG_READ, dkdevidp, blk, NBPSCTR, NULL) != 0) goto err; /* Validate the revision */ if ((dkdevidp->dkd_rev_hi != DK_DEVID_REV_MSB) || (dkdevidp->dkd_rev_lo != DK_DEVID_REV_LSB)) goto err; /* Calculate the checksum */ chksum = 0; ip = (uint_t *)dkdevidp; for (i = 0; i < ((NBPSCTR - sizeof (int))/sizeof (int)); i++) chksum ^= ip[i]; if (DKD_GETCHKSUM(dkdevidp) != chksum) goto err; /* Validate the device id */ if (ddi_devid_valid((ddi_devid_t)dkdevidp->dkd_devid) != DDI_SUCCESS) goto err; /* keep a copy of the device id */ i = ddi_devid_sizeof((ddi_devid_t)dkdevidp->dkd_devid); xsp->xdfss_tgt_devid = kmem_alloc(i, KM_SLEEP); bcopy(dkdevidp->dkd_devid, xsp->xdfss_tgt_devid, i); kmem_free(dkdevidp, NBPSCTR); return (DDI_SUCCESS); err: kmem_free(dkdevidp, NBPSCTR); return (DDI_FAILURE); } /* * xdfs_devid_fabricate() is basically a local copy of * cmdk_devid_fabricate() modified to work without the dadk layer. * (which the non-pv version of the cmdk driver uses.) * * Create a devid and write it on the first block of the last track of * the last cylinder. * Return DDI_SUCCESS or DDI_FAILURE. */ static int xdfs_devid_fabricate(xdfs_state_t *xsp) { ddi_devid_t devid = NULL; /* devid made by ddi_devid_init */ struct dk_devid *dkdevidp = NULL; /* devid struct stored on disk */ diskaddr_t blk; uint_t *ip, chksum; int i; if (cmlb_get_devid_block(xsp->xdfss_cmlbhandle, &blk, 0) != 0) return (DDI_FAILURE); if (ddi_devid_init(xsp->xdfss_dip, DEVID_FAB, 0, NULL, &devid) != DDI_SUCCESS) return (DDI_FAILURE); /* allocate a buffer */ dkdevidp = (struct dk_devid *)kmem_zalloc(NBPSCTR, KM_SLEEP); /* Fill in the revision */ dkdevidp->dkd_rev_hi = DK_DEVID_REV_MSB; dkdevidp->dkd_rev_lo = DK_DEVID_REV_LSB; /* Copy in the device id */ i = ddi_devid_sizeof(devid); if (i > DK_DEVID_SIZE) goto err; bcopy(devid, dkdevidp->dkd_devid, i); /* Calculate the chksum */ chksum = 0; ip = (uint_t *)dkdevidp; for (i = 0; i < ((NBPSCTR - sizeof (int))/sizeof (int)); i++) chksum ^= ip[i]; /* Fill in the checksum */ DKD_FORMCHKSUM(chksum, dkdevidp); if (xdfs_lb_rdwr(xsp->xdfss_dip, TG_WRITE, dkdevidp, blk, NBPSCTR, NULL) != 0) goto err; kmem_free(dkdevidp, NBPSCTR); xsp->xdfss_tgt_devid = devid; return (DDI_SUCCESS); err: if (dkdevidp != NULL) kmem_free(dkdevidp, NBPSCTR); if (devid != NULL) ddi_devid_free(devid); return (DDI_FAILURE); } /* * xdfs_rwcmd_copyin() is a duplicate of rwcmd_copyin(). */ static int xdfs_rwcmd_copyin(struct dadkio_rwcmd *rwcmdp, caddr_t inaddr, int flag) { switch (ddi_model_convert_from(flag)) { case DDI_MODEL_ILP32: { struct dadkio_rwcmd32 cmd32; if (ddi_copyin(inaddr, &cmd32, sizeof (struct dadkio_rwcmd32), flag)) { return (EFAULT); } rwcmdp->cmd = cmd32.cmd; rwcmdp->flags = cmd32.flags; rwcmdp->blkaddr = (blkaddr_t)cmd32.blkaddr; rwcmdp->buflen = cmd32.buflen; rwcmdp->bufaddr = (caddr_t)(intptr_t)cmd32.bufaddr; /* * Note: we do not convert the 'status' field, * as it should not contain valid data at this * point. */ bzero(&rwcmdp->status, sizeof (rwcmdp->status)); break; } case DDI_MODEL_NONE: { if (ddi_copyin(inaddr, rwcmdp, sizeof (struct dadkio_rwcmd), flag)) { return (EFAULT); } } } return (0); } /* * xdfs_rwcmd_copyout() is a duplicate of rwcmd_copyout(). */ static int xdfs_rwcmd_copyout(struct dadkio_rwcmd *rwcmdp, caddr_t outaddr, int flag) { switch (ddi_model_convert_from(flag)) { case DDI_MODEL_ILP32: { struct dadkio_rwcmd32 cmd32; cmd32.cmd = rwcmdp->cmd; cmd32.flags = rwcmdp->flags; cmd32.blkaddr = rwcmdp->blkaddr; cmd32.buflen = rwcmdp->buflen; ASSERT64(((uintptr_t)rwcmdp->bufaddr >> 32) == 0); cmd32.bufaddr = (caddr32_t)(uintptr_t)rwcmdp->bufaddr; cmd32.status.status = rwcmdp->status.status; cmd32.status.resid = rwcmdp->status.resid; cmd32.status.failed_blk_is_valid = rwcmdp->status.failed_blk_is_valid; cmd32.status.failed_blk = rwcmdp->status.failed_blk; cmd32.status.fru_code_is_valid = rwcmdp->status.fru_code_is_valid; cmd32.status.fru_code = rwcmdp->status.fru_code; bcopy(rwcmdp->status.add_error_info, cmd32.status.add_error_info, DADKIO_ERROR_INFO_LEN); if (ddi_copyout(&cmd32, outaddr, sizeof (struct dadkio_rwcmd32), flag)) return (EFAULT); break; } case DDI_MODEL_NONE: { if (ddi_copyout(rwcmdp, outaddr, sizeof (struct dadkio_rwcmd), flag)) return (EFAULT); } } return (0); } static int xdfs_dioctl_rwcmd(dev_t dev, intptr_t arg, int flag) { struct dadkio_rwcmd *rwcmdp; struct iovec aiov; struct uio auio; struct buf *bp; int rw, status; rwcmdp = kmem_alloc(sizeof (struct dadkio_rwcmd), KM_SLEEP); status = xdfs_rwcmd_copyin(rwcmdp, (caddr_t)arg, flag); if (status != 0) goto out; switch (rwcmdp->cmd) { case DADKIO_RWCMD_READ: case DADKIO_RWCMD_WRITE: break; default: status = EINVAL; goto out; } bzero((caddr_t)&aiov, sizeof (struct iovec)); aiov.iov_base = rwcmdp->bufaddr; aiov.iov_len = rwcmdp->buflen; bzero((caddr_t)&auio, sizeof (struct uio)); auio.uio_iov = &aiov; auio.uio_iovcnt = 1; auio.uio_loffset = (offset_t)rwcmdp->blkaddr * (offset_t)XB_BSIZE; auio.uio_resid = rwcmdp->buflen; auio.uio_segflg = (flag & FKIOCTL) ? UIO_SYSSPACE : UIO_USERSPACE; /* * Tell the xdf driver that this I/O request is using an absolute * offset. */ bp = getrbuf(KM_SLEEP); bp->b_private = (void *)XB_SLICE_NONE; rw = ((rwcmdp->cmd == DADKIO_RWCMD_WRITE) ? B_WRITE : B_READ); status = physio(xdfs_strategy, bp, dev, rw, xdfs_minphys, &auio); biofini(bp); kmem_free(bp, sizeof (buf_t)); if (status == 0) status = xdfs_rwcmd_copyout(rwcmdp, (caddr_t)arg, flag); out: kmem_free(rwcmdp, sizeof (struct dadkio_rwcmd)); return (status); } /* * xdf_shell callback functions */ /*ARGSUSED*/ int xdfs_c_ioctl(xdfs_state_t *xsp, dev_t dev, int part, int cmd, intptr_t arg, int flag, cred_t *credp, int *rvalp, boolean_t *done) { *done = B_TRUE; switch (cmd) { default: *done = B_FALSE; return (0); case DKIOCLOCK: case DKIOCUNLOCK: case FDEJECT: case DKIOCEJECT: case CDROMEJECT: { /* we don't support ejectable devices */ return (ENOTTY); } case DKIOCGETWCE: case DKIOCSETWCE: { /* we don't support write cache get/set */ return (EIO); } case DKIOCADDBAD: { /* * This is for ata/ide bad block handling. It is supposed * to cause the driver to re-read the bad block list and * alternate map after it has been updated. Our driver * will refuse to attach to any disk which has a bad blocks * list defined, so there really isn't much to do here. */ return (0); } case DKIOCGETDEF: { /* * I can't actually find any code that utilizes this ioctl, * hence we're leaving it explicitly unimplemented. */ ASSERT("ioctl cmd unsupported by xdf shell: DKIOCGETDEF"); return (EIO); } case DIOCTL_RWCMD: { /* * This just seems to just be an alternate interface for * reading and writing the disk. Great, another way to * do the same thing... */ return (xdfs_dioctl_rwcmd(dev, arg, flag)); } case DKIOCINFO: { int instance = ddi_get_instance(xsp->xdfss_dip); dev_info_t *dip = xsp->xdfss_dip; struct dk_cinfo info; int rv; /* Pass on the ioctl request, save the response */ if ((rv = ldi_ioctl(xsp->xdfss_tgt_lh[part], cmd, (intptr_t)&info, FKIOCTL, credp, rvalp)) != 0) return (rv); /* Update controller info */ info.dki_cnum = ddi_get_instance(ddi_get_parent(dip)); (void) strlcpy(info.dki_cname, ddi_get_name(ddi_get_parent(dip)), sizeof (info.dki_cname)); /* Update unit info. */ if (info.dki_ctype == DKC_VBD) info.dki_ctype = DKC_DIRECT; info.dki_unit = instance; (void) strlcpy(info.dki_dname, ddi_driver_name(dip), sizeof (info.dki_dname)); info.dki_addr = 1; if (ddi_copyout(&info, (void *)arg, sizeof (info), flag)) return (EFAULT); return (0); } } /* switch (cmd) */ /*NOTREACHED*/ } /* * xdfs_c_devid_setup() is a slightly modified copy of cmdk_devid_setup(). * * Create and register the devid. * There are 4 different ways we can get a device id: * 1. Already have one - nothing to do * 2. Build one from the drive's model and serial numbers * 3. Read one from the disk (first sector of last track) * 4. Fabricate one and write it on the disk. * If any of these succeeds, register the deviceid */ void xdfs_c_devid_setup(xdfs_state_t *xsp) { int rc; /* Try options until one succeeds, or all have failed */ /* 1. All done if already registered */ if (xsp->xdfss_tgt_devid != NULL) return; /* 2. Build a devid from the model and serial number */ rc = xdfs_devid_modser(xsp); if (rc != DDI_SUCCESS) { /* 3. Read devid from the disk, if present */ rc = xdfs_devid_read(xsp); /* 4. otherwise make one up and write it on the disk */ if (rc != DDI_SUCCESS) rc = xdfs_devid_fabricate(xsp); } /* If we managed to get a devid any of the above ways, register it */ if (rc == DDI_SUCCESS) (void) ddi_devid_register(xsp->xdfss_dip, xsp->xdfss_tgt_devid); } int xdfs_c_getpgeom(dev_info_t *dip, cmlb_geom_t *pgeom) { struct scsi_device *scsi_device; struct tgdk_geom tgdk_geom; opaque_t ctlobjp; int err; scsi_device = ddi_get_driver_private(dip); ctlobjp = scsi_device->sd_address.a_hba_tran; if ((err = CTL_IOCTL(ctlobjp, DIOCTL_GETPHYGEOM, (uintptr_t)&tgdk_geom, FKIOCTL)) != 0) return (err); /* This driver won't work if this isn't true */ ASSERT(tgdk_geom.g_secsiz == XB_BSIZE); pgeom->g_ncyl = tgdk_geom.g_cyl; pgeom->g_acyl = tgdk_geom.g_acyl; pgeom->g_nhead = tgdk_geom.g_head; pgeom->g_nsect = tgdk_geom.g_sec; pgeom->g_secsize = tgdk_geom.g_secsiz; pgeom->g_capacity = tgdk_geom.g_cap; pgeom->g_intrlv = 1; pgeom->g_rpm = 3600; return (0); } boolean_t xdfs_c_bb_check(xdfs_state_t *xsp) { struct alts_parttbl *ap; diskaddr_t nblocks, blk; uint32_t altused, altbase, altlast; uint16_t vtoctag; int alts; /* find slice with V_ALTSCTR tag */ for (alts = 0; alts < NDKMAP; alts++) { if (cmlb_partinfo(xsp->xdfss_cmlbhandle, alts, &nblocks, &blk, NULL, &vtoctag, 0) != 0) { /* no partition table exists */ return (B_FALSE); } if ((vtoctag == V_ALTSCTR) && (nblocks > 1)) break; } if (alts >= NDKMAP) return (B_FALSE); /* no V_ALTSCTR slice defined */ /* read in ALTS label block */ ap = (struct alts_parttbl *)kmem_zalloc(NBPSCTR, KM_SLEEP); if (xdfs_lb_rdwr(xsp->xdfss_dip, TG_READ, ap, blk, NBPSCTR, NULL) != 0) goto err; altused = ap->alts_ent_used; /* number of BB entries */ altbase = ap->alts_ent_base; /* blk offset from begin slice */ altlast = ap->alts_ent_end; /* blk offset to last block */ if ((altused == 0) || (altbase < 1) || (altbase > altlast) || (altlast >= nblocks)) goto err; /* we found bad block mappins */ kmem_free(ap, NBPSCTR); return (B_TRUE); err: kmem_free(ap, NBPSCTR); return (B_FALSE); } char * xdfs_c_cmlb_node_type(xdfs_state_t *xsp) { return (xsp->xdfss_tgt_is_cd ? DDI_NT_CD : DDI_NT_BLOCK); } /*ARGSUSED*/ int xdfs_c_cmlb_alter_behavior(xdfs_state_t *xsp) { return (xsp->xdfss_tgt_is_cd ? 0 : CMLB_CREATE_ALTSLICE_VTOC_16_DTYPE_DIRECT); } /*ARGSUSED*/ void xdfs_c_attach(xdfs_state_t *xsp) { }