cmdk.c revision b9ccdc5a0f0a722ae408b257a831b90011369316
/*
* 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
* 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 2008 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
/*
* Local Static Data
*/
#ifdef CMDK_DEBUG
#define DENT 0x0001
#define DIO 0x0002
static int cmdk_debug = DIO;
#endif
#ifndef TRUE
#define TRUE 1
#endif
#ifndef FALSE
#define FALSE 0
#endif
/*
* NDKMAP is the base number for accessing the fdisk partitions.
* c?d?p0 --> cmdk@?,?:q
*/
#define PARTITION0_INDEX (NDKMAP + 0)
static void *cmdk_state;
/*
* the cmdk_attach_mutex protects cmdk_max_instance in multi-threaded
* attach situations
*/
static kmutex_t cmdk_attach_mutex;
static int cmdk_max_instance = 0;
/*
* Panic dumpsys state
* There is only a single flag that is not mutex locked since
* the system is prevented from thread switching and cmdk_dump
* will only be called in a single threaded operation.
*/
static int cmdk_indump;
/*
* Local Function Prototypes
*/
/*
* Bad Block Handling Functions Prototypes
*/
static struct bbh_objops cmdk_bbh_ops = {
0, 0
};
/*
* Device driver ops vector
*/
static struct cb_ops cmdk_cb_ops = {
cmdkopen, /* open */
cmdkclose, /* close */
cmdkstrategy, /* strategy */
nodev, /* print */
cmdkdump, /* dump */
cmdkread, /* read */
cmdkwrite, /* write */
cmdkioctl, /* ioctl */
nodev, /* devmap */
nodev, /* mmap */
nodev, /* segmap */
nochpoll, /* poll */
cmdk_prop_op, /* cb_prop_op */
0, /* streamtab */
CB_REV, /* cb_rev */
cmdkaread, /* async read */
cmdkawrite /* async write */
};
void **result);
DEVO_REV, /* devo_rev, */
0, /* refcnt */
cmdkinfo, /* info */
nulldev, /* identify */
cmdkprobe, /* probe */
cmdkattach, /* attach */
cmdkdetach, /* detach */
nodev, /* reset */
&cmdk_cb_ops, /* driver operations */
(struct bus_ops *)0, /* bus operations */
cmdkpower /* power */
};
/*
* This is the loadable module wrapper.
*/
extern struct mod_ops mod_driverops;
&mod_driverops, /* Type of module. This one is a driver */
"Common Direct Access Disk %I%",
&cmdk_ops, /* driver ops */
};
static struct modlinkage modlinkage = {
};
/* Function prototypes for cmlb callbacks */
void *tg_cookie);
static cmlb_tg_ops_t cmdk_lb_ops = {
};
static boolean_t
{
/* account for close */
return (B_TRUE);
return (B_TRUE);
return (B_FALSE);
}
int
_init(void)
{
int rval;
return (rval);
}
return (rval);
}
int
_fini(void)
{
return (EBUSY);
/*
* This has been commented out until cmdk is a true
* unloadable module. Right now x86's are panicking on
* a diskless reconfig boot.
*/
#if 0 /* bugid 1186679 */
int rval;
if (rval != 0)
return (rval);
return (0);
#endif
}
int
{
}
/*
* Autoconfiguration Routines
*/
static int
{
int instance;
int status;
return (DDI_PROBE_PARTIAL);
return (DDI_PROBE_PARTIAL);
/* linkage to dadk and strategy */
return (DDI_PROBE_PARTIAL);
}
if (status != DDI_PROBE_SUCCESS) {
return (status);
}
#ifdef CMDK_DEBUG
if (cmdk_debug & DENT)
PRF("cmdkprobe: instance= %d name= `%s`\n",
#endif
return (status);
}
static int
{
int instance;
char *node_type;
switch (cmd) {
case DDI_ATTACH:
break;
case DDI_RESUME:
return (cmdkresume(dip));
default:
return (DDI_FAILURE);
}
return (DDI_FAILURE);
/* dadk_attach is an empty function that only returns SUCCESS */
(void) dadk_attach(DKTP_DATA);
/*
* this open allows cmlb to read the device
* and determine the label types
* so that cmlb can create minor nodes for device
*/
/* open the target disk */
goto fail2;
/* mark as having opened target */
if (cmlb_attach(dip,
DTYPE_DIRECT, /* device_type */
0, /* removable */
0, /* hot pluggable XXX */
CMLB_CREATE_ALTSLICE_VTOC_16_DTYPE_DIRECT, /* alter_behaviour */
0) != 0)
goto fail1;
/* Calling validate will create minor nodes according to disk label */
/* set bbh (Bad Block Handling) */
/* setup devid string */
if (instance > cmdk_max_instance)
/*
* Add a zero-length attribute to tell the world we support
* kernel ioctls (for layered drivers)
*/
DDI_KERNEL_IOCTL, NULL, 0);
/*
* Initialize power management
*/
return (DDI_SUCCESS);
(void) dadk_close(DKTP_DATA);
return (DDI_FAILURE);
}
static int
{
int instance;
int max_instance;
switch (cmd) {
case DDI_DETACH:
/* return (DDI_FAILURE); */
break;
case DDI_SUSPEND:
return (cmdksuspend(dip));
default:
#ifdef CMDK_DEBUG
if (cmdk_debug & DIO) {
}
#endif
return (DDI_FAILURE);
}
/* check if any instance of driver is open */
if (!dkp)
continue;
return (DDI_FAILURE);
}
return (DDI_SUCCESS);
/*
* The cmdk_part_info call at the end of cmdkattach may have
* caused cmdk_reopen to do a TGDK_OPEN, make sure we close on
*/
(void) dadk_close(DKTP_DATA);
}
return (DDI_SUCCESS);
}
static int
{
int instance;
#ifdef lint
#endif
#ifdef CMDK_DEBUG
if (cmdk_debug & DENT)
PRF("cmdkinfo: call\n");
#endif
switch (infocmd) {
case DDI_INFO_DEVT2DEVINFO:
return (DDI_FAILURE);
break;
case DDI_INFO_DEVT2INSTANCE:
break;
default:
return (DDI_FAILURE);
}
return (DDI_SUCCESS);
}
/*
* Initialize the power management components
*/
static void
{
/*
* Since the cmdk device does not the 'reg' property,
* cpr will not call its DDI_SUSPEND/DDI_RESUME entries.
* The following code is to tell cpr that this device
* DOES need to be suspended and resumed.
*/
"pm-hardware-state", "needs-suspend-resume");
} else {
dkp->dk_pm_is_enabled = 0;
}
} else {
dkp->dk_pm_is_enabled = 0;
}
}
/*
* suspend routine, it will be run when get the command
* DDI_SUSPEND at detach(9E) from system power management
*/
static int
{
int instance;
return (DDI_FAILURE);
return (DDI_SUCCESS);
}
/* need to wait a while */
while (dadk_getcmds(DKTP_DATA) != 0) {
if (count > 60) {
return (DDI_FAILURE);
}
count++;
}
return (DDI_SUCCESS);
}
/*
* resume routine, it will be run when get the command
* DDI_RESUME at attach(9E) from system power management
*/
static int
{
int instance;
return (DDI_FAILURE);
return (DDI_FAILURE);
}
return (DDI_SUCCESS);
}
/*
* power management entry point, it was used to
* change power management component.
* was handled in ata, so this function is not
* doing any real work other than verifying that
* the disk is idle.
*/
static int
{
int instance;
level < CMDK_SPINDLE_OFF) {
return (DDI_FAILURE);
}
return (DDI_SUCCESS);
}
if ((level == CMDK_SPINDLE_OFF) &&
(dadk_getcmds(DKTP_DATA) != 0)) {
return (DDI_FAILURE);
}
return (DDI_SUCCESS);
}
static int
{
#ifdef CMDK_DEBUG
if (cmdk_debug & DENT)
PRF("cmdk_prop_op: call\n");
#endif
}
/*
* dump routine
*/
static int
{
int instance;
#ifdef CMDK_DEBUG
if (cmdk_debug & DENT)
PRF("cmdkdump: call\n");
#endif
return (ENXIO);
if (cmlb_partinfo(
NULL,
NULL,
0)) {
return (ENXIO);
}
return (EINVAL);
}
/*
* Copy in the dadkio_rwcmd according to the user's data model. If needed,
* convert it for our internal use.
*/
static int
{
switch (ddi_model_convert_from(flag)) {
case DDI_MODEL_ILP32: {
struct dadkio_rwcmd32 cmd32;
sizeof (struct dadkio_rwcmd32), flag)) {
return (EFAULT);
}
/*
* Note: we do not convert the 'status' field,
* as it should not contain valid data at this
* point.
*/
break;
}
case DDI_MODEL_NONE: {
sizeof (struct dadkio_rwcmd), flag)) {
return (EFAULT);
}
}
}
return (0);
}
/*
* If necessary, convert the internal rwcmdp and status to the appropriate
* data model and copy it out to the user.
*/
static int
{
switch (ddi_model_convert_from(flag)) {
case DDI_MODEL_ILP32: {
struct dadkio_rwcmd32 cmd32;
sizeof (struct dadkio_rwcmd32), flag))
return (EFAULT);
break;
}
case DDI_MODEL_NONE: {
sizeof (struct dadkio_rwcmd), flag))
return (EFAULT);
}
}
return (0);
}
/*
* ioctl routine
*/
static int
{
int instance;
struct scsi_device *devp;
return (ENXIO);
}
switch (cmd) {
case DKIOCGMEDIAINFO: {
struct dk_minfo media_info;
/* dadk_getphygeom always returns success */
return (EFAULT);
} else {
return (0);
}
}
case DKIOCINFO: {
/* controller information */
/* Unit Information */
return (EFAULT);
else
return (0);
}
case DKIOCSTATE: {
int state;
int rval;
return (EFAULT);
/* dadk_check_media blocks until state changes */
return (rval);
if (state == DKIO_INSERTED) {
return (ENXIO);
return (ENXIO);
if (p_lblkcnt <= 0)
return (ENXIO);
}
return (EFAULT);
return (0);
}
/*
* is media removable?
*/
case DKIOCREMOVABLE: {
int i;
return (EFAULT);
return (0);
}
case DKIOCADDBAD:
/*
* This is not an update mechanism to add bad blocks
* to the bad block structures stored on disk.
*
* addbadsec(1M) will update the bad block data on disk
* and use this ioctl to force the driver to re-initialize
* the list of bad blocks in the driver.
*/
/* start BBH */
return (0);
case DKIOCG_PHYGEOM:
case DKIOCG_VIRTGEOM:
case DKIOCGGEOM:
case DKIOCSGEOM:
case DKIOCGAPART:
case DKIOCSAPART:
case DKIOCGVTOC:
case DKIOCSVTOC:
case DKIOCPARTINFO:
case DKIOCGMBOOT:
case DKIOCSMBOOT:
case DKIOCGETEFI:
case DKIOCSETEFI:
case DKIOCPARTITION:
{
int rc;
if (cmd == DKIOCSVTOC)
return (rc);
}
case DIOCTL_RWCMD: {
struct dadkio_rwcmd *rwcmdp;
int status;
if (status == 0) {
dev,
cmd,
flag,
rvalp);
}
if (status == 0)
return (status);
}
default:
return (dadk_ioctl(DKTP_DATA,
dev,
cmd,
arg,
flag,
rvalp));
}
}
/*ARGSUSED1*/
static int
{
int part;
int instance;
int lastclose = 1;
int i;
return (ENXIO);
/* check if device has been opened */
return (ENXIO);
}
}
/* account for close */
} else {
}
for (i = 0; i < CMDK_MAXPART; i++)
if (dkp->dk_open_lyr[i] != 0) {
lastclose = 0;
break;
}
if (lastclose)
for (i = 0; i < OTYPCNT; i++)
if (dkp->dk_open_reg[i] != 0) {
lastclose = 0;
break;
}
if (lastclose)
return (DDI_SUCCESS);
}
/*ARGSUSED3*/
static int
{
int part;
int instance;
int i;
int nodelay;
return (ENXIO);
return (EINVAL);
}
/* fail if not doing non block open */
if (!nodelay) {
return (ENXIO);
}
return (ENXIO);
}
} else {
/* fail if not doing non block open */
if (!nodelay) {
return (ENXIO);
}
}
return (EROFS);
}
/* check for part already opend exclusively */
goto excl_open_fail;
/* check if we can establish exclusive open */
goto excl_open_fail;
for (i = 0; i < OTYPCNT; i++) {
goto excl_open_fail;
}
}
/* open will succeed, account for open */
else
return (DDI_SUCCESS);
return (EBUSY);
}
/*
* read routine
*/
/*ARGSUSED2*/
static int
{
}
/*
* async read routine
*/
/*ARGSUSED2*/
static int
{
}
/*
* write routine
*/
/*ARGSUSED2*/
static int
{
}
/*
* async write routine
*/
/*ARGSUSED2*/
static int
{
}
static void
{
}
static int
{
int instance;
return (ENXIO);
}
}
static int
{
int instance;
return (ENXIO);
}
}
/*
* strategy routine
*/
static int
{
int instance;
long d_cnt;
return (0);
}
}
/*
* only re-read the vtoc if necessary (force == FALSE)
*/
}
return (0);
}
}
}
return (0);
}
static int
{
struct scsi_device *devp;
char que_keyvalp[64];
int que_keylen;
char flc_keyvalp[64];
int flc_keylen;
/* Create linkage to queueing routines based on property */
que_keylen = sizeof (que_keyvalp);
return (DDI_FAILURE);
}
que_keyvalp[que_keylen] = (char)0;
} else {
return (DDI_FAILURE);
}
/* Create linkage to dequeueing routines based on property */
flc_keylen = sizeof (flc_keyvalp);
"cmdk_create_obj: flow-control property undefined");
return (DDI_FAILURE);
}
flc_keyvalp[flc_keylen] = (char)0;
} else {
return (DDI_FAILURE);
}
/* populate bbh_obj object stored in dkp */
/* create linkage to dadk */
NULL);
return (DDI_SUCCESS);
}
static void
{
char que_keyvalp[64];
int que_keylen;
char flc_keyvalp[64];
int flc_keylen;
que_keylen = sizeof (que_keyvalp);
return;
}
que_keyvalp[que_keylen] = (char)0;
flc_keylen = sizeof (flc_keyvalp);
"cmdk_destroy_obj: flow-control property undefined");
return;
}
flc_keyvalp[flc_keylen] = (char)0;
}
/*ARGSUSED5*/
static int
{
int rc = 0;
char *bufa;
return (ENXIO);
return (EINVAL);
/* count must be multiple of 512 */
if (!handle)
return (ENOMEM);
if (!bufa)
else
} else {
if (!bufa)
}
return (rc);
}
/*ARGSUSED3*/
static int
{
return (ENXIO);
switch (cmd) {
case TG_GETPHYGEOM: {
/* dadk_getphygeom always returns success */
return (0);
}
case TG_GETVIRTGEOM: {
/*
* If the controller returned us something that doesn't
* really fit into an Int 13/function 8 geometry
* result, just fail the ioctl. See PSARC 1998/313.
*/
return (EINVAL);
return (0);
}
case TG_GETCAPACITY:
case TG_GETBLOCKSIZE:
{
/* dadk_getphygeom always returns success */
if (cmd == TG_GETCAPACITY)
else
return (0);
}
case TG_GETATTR: {
else
return (0);
}
default:
return (ENOTTY);
}
}
/*
* 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
*/
static void
{
int rc;
/* Try options until one succeeds, or all have failed */
/* 1. All done if already registered */
return;
/* 2. Build a devid from the model and serial number */
if (rc != DDI_SUCCESS) {
/* 3. Read devid from the disk, if present */
/* 4. otherwise make one up and write it on the disk */
if (rc != DDI_SUCCESS)
}
/* If we managed to get a devid any of the above ways, register it */
if (rc == DDI_SUCCESS)
}
/*
* Build a devid from the model and serial number
* Return DDI_SUCCESS or DDI_FAILURE.
*/
static int
{
int rc = DDI_FAILURE;
char *hwid;
int modlen;
int serlen;
/*
* device ID is a concatenation of model number, '=', serial number.
*/
if (modlen == 0) {
rc = DDI_FAILURE;
goto err;
}
if (serlen == 0) {
rc = DDI_FAILURE;
goto err;
}
/* Initialize the device ID, trailing NULL not included */
if (rc != DDI_SUCCESS) {
rc = DDI_FAILURE;
goto err;
}
rc = DDI_SUCCESS;
err:
return (rc);
}
static int
{
int rval;
char *s;
char ch;
int i;
int tb;
if (dadk_ioctl(DKTP_DATA,
NULL,
&rval) != 0)
return (0);
/*
*/
s = buf;
ch = *s++;
tb = i + 1;
}
return (0);
return (tb);
}
/*
* 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
{
int chksum;
int i, sz;
int rc = DDI_FAILURE;
goto err;
/* read the devid */
goto err;
goto err;
/* Validate the revision */
goto err;
/* Calculate the checksum */
chksum = 0;
for (i = 0; i < ((NBPSCTR - sizeof (int))/sizeof (int)); i++)
goto err;
/* Validate the device id */
goto err;
/* keep a copy of the device id */
rc = DDI_SUCCESS;
err:
return (rc);
}
/*
* 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
{
int i;
int rc;
if (rc != DDI_SUCCESS)
goto err;
/* no device id block address */
return (DDI_FAILURE);
}
if (!handle)
goto err;
/* Locate the buffer */
/* Fill in the revision */
/* Copy in the device id */
i = ddi_devid_sizeof(devid);
if (i > DK_DEVID_SIZE)
goto err;
/* Calculate the chksum */
chksum = 0;
for (i = 0; i < ((NBPSCTR - sizeof (int))/sizeof (int)); i++)
/* Fill in the checksum */
/* write the devid */
rc = DDI_SUCCESS;
err:
return (rc);
}
static void
{
if (dkp->dk_alts_hdl) {
}
}
static void
{
struct alts_parttbl *ap;
int alts;
int i, j;
/* find slice with V_ALTSCTR tag */
if (cmlb_partinfo(
alts,
&slcn,
&slcb,
NULL,
&vtoctag,
0)) {
goto empty; /* no partition table exists */
}
break;
}
goto empty; /* no V_ALTSCTR slice defined */
}
/* read in ALTS label block */
if (!handle) {
goto empty;
}
goto empty;
}
/* ((altused * sizeof (struct alts_ent) + NBPSCTR - 1) & ~NBPSCTR) */
if (altused == 0 ||
altbase < 1 ||
goto empty;
}
/* read in ALTS remapping table */
if (!handle) {
goto empty;
}
if (!enttblp) {
goto empty;
}
/* allocate space for dk_slc_cnt and dk_slc_ent tables */
}
/* free previous BB table (if any) */
if (dkp->dk_alts_hdl) {
dkp->dk_altused = 0;
}
/* save linkage to new BB table */
/*
* build indexes to BB table by slice
* effectively we have
* struct alts_ent *enttblp[altused];
*
* uint32_t dk_slc_cnt[NDKMAP];
* struct alts_ent *dk_slc_ent[NDKMAP];
*/
for (i = 0; i < NDKMAP; i++) {
if (cmlb_partinfo(
i,
&slcn,
&slcb,
NULL,
NULL,
0)) {
goto empty1;
}
dkp->dk_slc_cnt[i] = 0;
if (slcn == 0)
continue; /* slice is not allocated */
/* last block in slice */
/* find first remap entry in after beginnning of slice */
for (j = 0; j < altused; j++) {
break;
}
/* count remap entrys until end of slice */
}
}
return;
if (dkp->dk_alts_hdl) {
}
}
/*ARGSUSED*/
static bbh_cookie_t
{
struct bbh_handle *hp;
return (ckp);
}
/*ARGSUSED*/
static void
{
struct bbh_handle *hp;
}
/*
* cmdk_bbh_gethandle remaps the bad sectors to alternates.
* There are 7 different cases when the comparison is made
* between the bad sector cluster and the disk section.
*
* bad sector cluster gggggggggggbbbbbbbggggggggggg
* case 1: ddddd
* case 2: -d-----
* case 3: ddddd
* case 4: dddddddddddd
* case 5: ddddddd-----
* case 6: ---ddddddd
* case 7: ddddddd
*
* where: g = good sector, b = bad sector
* d = sector in disk section
* - = disk section may be extended to cover those disk area
*/
static opaque_t
{
struct bbh_handle *hp;
struct bbh_cookie *ckp;
long d_count;
int i;
int idx;
int cnt;
return (NULL);
/*
* This if statement is atomic and it will succeed
* if there are no bad blocks (almost always)
*
* so this if is performed outside of the rw_enter for speed
* and then repeated inside the rw_enter for safety
*/
if (!dkp->dk_alts_hdl) {
return (NULL);
}
return (NULL);
}
if (alts_used == 0) {
return (NULL);
}
/*
* binary search for the largest bad sector index in the alternate
* entry table which overlaps or larger than the starting d_sec
*/
/* if starting sector is > the largest bad sector, return */
if (i == -1) {
return (NULL);
}
/* i is the starting index. Set altp to the starting entry addr */
altp += i;
/* calculate the number of bad sectors */
break;
}
if (!cnt) {
return (NULL);
}
/* calculate the maximum number of reserved cookies */
cnt <<= 1;
cnt++;
/* allocate the handle */
altp += i;
/* CASE 1: */
break;
/* CASE 3: */
continue;
/* CASE 2 and 7: */
break;
}
/* at least one bad sector in our section. break it. */
/* CASE 5: */
break;
}
/* CASE 6: */
idx++;
continue; /* check rest of section */
}
/* CASE 4: */
idx += 2;
}
}
static int
{
int i;
int ind;
int interval;
int mystatus = -1;
if (!cnt)
return (mystatus);
ind = i;
return (ind-1);
} else {
interval >>= 1;
/* record the largest bad sector index */
if (!interval)
break;
} else {
/*
* if key is larger than the last element
* then break
*/
break;
}
}
}
return (mystatus);
}