cmdk.c revision 7c478bd95313f5f23a4c958a745db2134aa03244
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (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 2005 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
#include <sys/scsi/scsi.h>
#include <sys/dktp/cm.h>
#include <sys/dktp/quetypes.h>
#include <sys/dktp/queue.h>
#include <sys/dktp/fctypes.h>
#include <sys/dktp/flowctrl.h>
#include <sys/dktp/objmgr.h>
#include <sys/dktp/cmdev.h>
#include <sys/dktp/cmdk.h>
#include <sys/dkio.h>
#include <sys/dktp/tgdk.h>
#include <sys/dktp/bbh.h>
#include <sys/stat.h>
#include <sys/vtoc.h>
#include <sys/file.h>
#include <sys/dktp/dadkio.h>
#include <sys/dktp/dklb.h>
#include <sys/aio_req.h>
/*
* 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;
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;
/*
* the cmdk_attach_mutex protects cmdk_max_instance in multi-threaded
* attach situations
*/
static kmutex_t cmdk_attach_mutex;
static struct driver_minor_data {
char *name;
int minor;
int type;
} cmdk_minor_data[] = {
{"a", 0, S_IFBLK},
{"b", 1, S_IFBLK},
{"c", 2, S_IFBLK},
{"d", 3, S_IFBLK},
{"e", 4, S_IFBLK},
{"f", 5, S_IFBLK},
{"g", 6, S_IFBLK},
{"h", 7, S_IFBLK},
{"i", 8, S_IFBLK},
{"j", 9, S_IFBLK},
{"k", 10, S_IFBLK},
{"l", 11, S_IFBLK},
{"m", 12, S_IFBLK},
{"n", 13, S_IFBLK},
{"o", 14, S_IFBLK},
{"p", 15, S_IFBLK},
{"q", 16, S_IFBLK},
{"r", 17, S_IFBLK},
{"s", 18, S_IFBLK},
{"t", 19, S_IFBLK},
{"u", 20, S_IFBLK},
{"a,raw", 0, S_IFCHR},
{"b,raw", 1, S_IFCHR},
{"c,raw", 2, S_IFCHR},
{"d,raw", 3, S_IFCHR},
{"e,raw", 4, S_IFCHR},
{"f,raw", 5, S_IFCHR},
{"g,raw", 6, S_IFCHR},
{"h,raw", 7, S_IFCHR},
{"i,raw", 8, S_IFCHR},
{"j,raw", 9, S_IFCHR},
{"k,raw", 10, S_IFCHR},
{"l,raw", 11, S_IFCHR},
{"m,raw", 12, S_IFCHR},
{"n,raw", 13, S_IFCHR},
{"o,raw", 14, S_IFCHR},
{"p,raw", 15, S_IFCHR},
{"q,raw", 16, S_IFCHR},
{"r,raw", 17, S_IFCHR},
{"s,raw", 18, S_IFCHR},
{"t,raw", 19, S_IFCHR},
{"u,raw", 20, S_IFCHR},
{0}
};
/*
* Local Function Prototypes
*/
static int cmdk_reopen(struct cmdk *dkp);
static int cmdk_create_obj(dev_info_t *devi, struct cmdk *dkp);
static void cmdk_destroy_obj(dev_info_t *devi, struct cmdk *dkp, int unload);
static int cmdk_create_lbobj(dev_info_t *devi, struct cmdk *dkp);
static void cmdk_destroy_lbobj(dev_info_t *devi, struct cmdk *dkp, int unload);
static void cmdkmin(struct buf *bp);
static int cmdkrw(dev_t dev, struct uio *uio, int flag);
static int cmdkarw(dev_t dev, struct aio_req *aio, int flag);
static int cmdk_part_info(struct cmdk *dkp, int force, daddr_t *startp,
long *countp, int part);
static void cmdk_part_info_init(struct cmdk *dkp);
static void cmdk_part_info_fini(struct cmdk *dkp);
#ifdef NOT_USED
static void cmdk_devstatus(struct cmdk *dkp);
#endif /* NOT_USED */
/*
* Bad Block Handling Functions Prototypes
*/
static opaque_t cmdk_bbh_gethandle(opaque_t bbh_data, struct buf *bp);
static bbh_cookie_t cmdk_bbh_htoc(opaque_t bbh_data, opaque_t handle);
static void cmdk_bbh_freehandle(opaque_t bbh_data, opaque_t handle);
static struct bbh_objops cmdk_bbh_ops = {
nulldev,
nulldev,
cmdk_bbh_gethandle,
cmdk_bbh_htoc,
cmdk_bbh_freehandle,
0, 0
};
static struct bbh_obj cmdk_bbh_obj = {
NULL,
&cmdk_bbh_ops
};
static int cmdkopen(dev_t *dev_p, int flag, int otyp, cred_t *cred_p);
static int cmdkclose(dev_t dev, int flag, int otyp, cred_t *cred_p);
static int cmdkstrategy(struct buf *bp);
static int cmdkdump(dev_t dev, caddr_t addr, daddr_t blkno, int nblk);
static int cmdkioctl(dev_t, int, intptr_t, int, cred_t *, int *);
static int cmdkread(dev_t dev, struct uio *uio, cred_t *cred_p);
static int cmdkwrite(dev_t dev, struct uio *uio, cred_t *cred_p);
static int cmdk_prop_op(dev_t dev, dev_info_t *dip, ddi_prop_op_t prop_op,
int mod_flags, char *name, caddr_t valuep, int *lengthp);
static int cmdkaread(dev_t dev, struct aio_req *aio, cred_t *cred_p);
static int cmdkawrite(dev_t dev, struct aio_req *aio, cred_t *cred_p);
/*
* Configuration Data
*/
/*
* 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 */
D_64BIT | D_MP | D_NEW, /* Driver comaptibility flag */
CB_REV, /* cb_rev */
cmdkaread, /* async read */
cmdkawrite /* async write */
};
static int cmdkinfo(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg,
void **result);
static int cmdkprobe(dev_info_t *devi);
static int cmdkattach(dev_info_t *devi, ddi_attach_cmd_t cmd);
static int cmdkdetach(dev_info_t *devi, ddi_detach_cmd_t cmd);
struct dev_ops cmdk_ops = {
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 */
};
/*
* This is the loadable module wrapper.
*/
#include <sys/modctl.h>
extern struct mod_ops mod_driverops;
static struct modldrv modldrv = {
&mod_driverops, /* Type of module. This one is a driver */
"Common Direct Access Disk Driver %I%",
&cmdk_ops, /* driver ops */
};
static struct modlinkage modlinkage = {
MODREV_1, (void *)&modldrv, NULL
};
char _depends_on[] =
"drv/objmgr misc/snlb misc/dadk misc/strategy";
int
_init(void)
{
int rval;
if (rval = ddi_soft_state_init(&cmdk_state, sizeof (struct cmdk), 7))
return (rval);
mutex_init(&cmdk_attach_mutex, NULL, MUTEX_DRIVER, NULL);
if ((rval = mod_install(&modlinkage)) != 0) {
mutex_destroy(&cmdk_attach_mutex);
ddi_soft_state_fini(&cmdk_state);
}
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;
rval = mod_remove(&modlinkage);
if (rval != 0)
return (rval);
mutex_destroy(&cmdk_attach_mutex);
ddi_soft_state_fini(&cmdk_state);
return (0);
#endif
}
int
_info(struct modinfo *modinfop)
{
return (mod_info(&modlinkage, modinfop));
}
/* pseudo BBH functions */
/*ARGSUSED*/
static opaque_t
cmdk_bbh_gethandle(opaque_t bbh_data, struct buf *bp)
{
return (NULL);
}
/*ARGSUSED*/
static bbh_cookie_t
cmdk_bbh_htoc(opaque_t bbh_data, opaque_t handle)
{
return (NULL);
}
/*ARGSUSED*/
static void
cmdk_bbh_freehandle(opaque_t bbh_data, opaque_t handle)
{
}
/*
* Autoconfiguration Routines
*/
static int
cmdkprobe(dev_info_t *devi)
{
int instance;
int status;
struct cmdk *dkp;
instance = ddi_get_instance(devi);
if (ddi_get_soft_state(cmdk_state, instance))
return (DDI_PROBE_PARTIAL);
if ((ddi_soft_state_zalloc(cmdk_state, instance) != DDI_SUCCESS) ||
((dkp = ddi_get_soft_state(cmdk_state, instance)) == NULL))
return (DDI_PROBE_PARTIAL);
dkp->dk_dip = devi;
/* for property create inside DKLB_*() */
dkp->dk_dev = makedevice(ddi_driver_major(devi),
ddi_get_instance(devi) << CMDK_UNITSHF);
if (cmdk_create_obj(devi, dkp) != DDI_SUCCESS) {
ddi_soft_state_free(cmdk_state, instance);
return (DDI_PROBE_PARTIAL);
}
status = TGDK_PROBE(CMDK_TGOBJP(dkp), KM_NOSLEEP);
if (status != DDI_PROBE_SUCCESS) {
cmdk_destroy_obj(devi, dkp, 0);
ddi_soft_state_free(cmdk_state, instance);
return (status);
}
sema_init(&dkp->dk_semoclose, 1, NULL, SEMA_DRIVER, NULL);
#ifdef CMDK_DEBUG
if (cmdk_debug & DENT)
PRF("cmdkprobe: instance= %d name= `%s`\n",
instance, ddi_get_name_addr(devi));
#endif
return (status);
}
static int
cmdkattach(dev_info_t *devi, ddi_attach_cmd_t cmd)
{
int instance;
long start, count;
struct driver_minor_data *dmdp;
struct cmdk *dkp;
char *node_type;
char name[48];
if (cmd != DDI_ATTACH)
return (DDI_FAILURE);
instance = ddi_get_instance(devi);
if (!(dkp = ddi_get_soft_state(cmdk_state, instance)))
return (DDI_FAILURE);
if (TGDK_ATTACH(CMDK_TGOBJP(dkp)) != DDI_SUCCESS) {
ddi_soft_state_free(cmdk_state, instance);
return (DDI_FAILURE);
}
node_type = TGDK_GETNODETYPE(CMDK_TGOBJP(dkp));
for (dmdp = cmdk_minor_data; dmdp->name != NULL; dmdp++) {
minor_t minor_num = (instance << CMDK_UNITSHF) | dmdp->minor;
(void) sprintf(name, "%s", dmdp->name);
if (ddi_create_minor_node(devi, name, dmdp->type, minor_num,
node_type, NULL) == DDI_FAILURE) {
cmdk_destroy_obj(devi, dkp, 0);
sema_destroy(&dkp->dk_semoclose);
ddi_soft_state_free(cmdk_state, instance);
ddi_remove_minor_node(devi, NULL);
ddi_prop_remove_all(devi);
return (DDI_FAILURE);
}
}
mutex_enter(&cmdk_attach_mutex);
if (instance > cmdk_max_instance)
cmdk_max_instance = instance;
mutex_exit(&cmdk_attach_mutex);
/*
* Add a zero-length attribute to tell the world we support
* kernel ioctls (for layered drivers)
*/
(void) ddi_prop_create(DDI_DEV_T_NONE, devi, DDI_PROP_CANSLEEP,
DDI_KERNEL_IOCTL, NULL, 0);
ddi_report_dev(devi);
cmdk_part_info_init(dkp);
/* Need to open the label and register devid early */
(void) cmdk_part_info(dkp, TRUE, &start, &count, PARTITION0_INDEX);
return (DDI_SUCCESS);
}
static int
cmdkdetach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
struct cmdk *dkp;
int instance;
int max_instance;
switch (cmd) {
case DDI_DETACH:
mutex_enter(&cmdk_attach_mutex);
max_instance = cmdk_max_instance;
mutex_exit(&cmdk_attach_mutex);
for (instance = 0; instance < max_instance; instance++) {
dkp = ddi_get_soft_state(cmdk_state, instance);
if (!dkp)
continue;
if (dkp->dk_flag & CMDK_OPEN)
return (DDI_FAILURE);
}
instance = ddi_get_instance(dip);
if (!(dkp = ddi_get_soft_state(cmdk_state, instance)))
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
* detach for case when cmdkopen/cmdkclose never occurs.
*/
if (dkp->dk_flag & CMDK_TGDK_OPEN) {
dkp->dk_flag &= ~CMDK_TGDK_OPEN;
TGDK_CLOSE(CMDK_TGOBJP(dkp));
}
cmdk_part_info_fini(dkp);
cmdk_destroy_lbobj(dip, dkp, 1);
cmdk_destroy_obj(dip, dkp, 1);
sema_destroy(&dkp->dk_semoclose);
ddi_soft_state_free(cmdk_state, instance);
ddi_prop_remove_all(dip);
ddi_remove_minor_node(dip, NULL);
return (DDI_SUCCESS);
default:
#ifdef CMDK_DEBUG
if (cmdk_debug & DIO) {
PRF("cmdkdetach: cmd = %d unknown\n", cmd);
}
#endif
return (DDI_FAILURE);
}
}
static int
cmdkinfo(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result)
{
dev_t dev = (dev_t)arg;
int instance;
struct cmdk *dkp;
#ifdef lint
dip = dip; /* no one ever uses this */
#endif
#ifdef CMDK_DEBUG
if (cmdk_debug & DENT)
PRF("cmdkinfo: call\n");
#endif
instance = CMDKUNIT(dev);
switch (infocmd) {
case DDI_INFO_DEVT2DEVINFO:
if (!(dkp = ddi_get_soft_state(cmdk_state, instance)))
return (DDI_FAILURE);
*result = (void *) dkp->dk_dip;
break;
case DDI_INFO_DEVT2INSTANCE:
*result = (void *)(intptr_t)instance;
break;
default:
return (DDI_FAILURE);
}
return (DDI_SUCCESS);
}
static int
cmdk_prop_op(dev_t dev, dev_info_t *dip, ddi_prop_op_t prop_op, int mod_flags,
char *name, caddr_t valuep, int *lengthp)
{
int instance = ddi_get_instance(dip);
struct cmdk *dkp;
long lblocks;
uint64_t nblocks64;
daddr_t p_lblksrt;
#ifdef CMDK_DEBUG
if (cmdk_debug & DENT)
PRF("cmdk_prop_op: call\n");
#endif
/*
* Our dynamic properties are all device specific and size oriented.
* Requests issued under conditions where size is valid are passed
* to ddi_prop_op_nblocks with the size information, otherwise the
* request is passed to ddi_prop_op. Size depends on valid label.
*/
dkp = ddi_get_soft_state(cmdk_state, instance);
if ((dev == DDI_DEV_T_ANY) || (dkp == NULL) ||
!(dkp->dk_flag & CMDK_VALID_LABEL)) {
pass: return (ddi_prop_op(dev, dip, prop_op, mod_flags,
name, valuep, lengthp));
} else {
/* force re-read of MBR and label and partition info */
if (!cmdk_part_info(dkp, TRUE, &p_lblksrt,
&lblocks, CMDKPART(dev)))
goto pass;
/* get nblocks value */
nblocks64 = (ulong_t)lblocks;
return (ddi_prop_op_nblocks(dev, dip, prop_op, mod_flags,
name, valuep, lengthp, nblocks64));
}
}
/*
* dump routine
*/
static int
cmdkdump(dev_t dev, caddr_t addr, daddr_t blkno, int nblk)
{
int instance;
struct cmdk *dkp;
daddr_t p_lblksrt;
long p_lblkcnt;
struct buf local;
struct buf *bp;
#ifdef CMDK_DEBUG
if (cmdk_debug & DENT)
PRF("cmdkdump: call\n");
#endif
instance = CMDKUNIT(dev);
if (!(dkp = ddi_get_soft_state(cmdk_state, instance)) || (blkno < 0))
return (ENXIO);
DKLB_PARTINFO(dkp->dk_lbobjp, &p_lblksrt, &p_lblkcnt, CMDKPART(dev));
if ((blkno+nblk) > p_lblkcnt)
return (EINVAL);
cmdk_indump = 1; /* Tell disk targets we are panic dumpping */
bp = &local;
bzero(bp, sizeof (*bp));
bp->b_flags = B_BUSY;
bp->b_un.b_addr = addr;
bp->b_bcount = nblk << SCTRSHFT;
SET_BP_SEC(bp, (p_lblksrt + blkno));
TGDK_DUMP(CMDK_TGOBJP(dkp), bp);
return (bp->b_error);
}
/*
* Copy in the dadkio_rwcmd according to the user's data model. If needed,
* convert it for our internal use.
*/
static int
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 = (daddr_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);
}
/*
* If necessary, convert the internal rwcmdp and status to the appropriate
* data model and copy it out to the user.
*/
static int
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);
}
/*
* ioctl routine
*/
static int
cmdkioctl(dev_t dev, int cmd, intptr_t arg, int flag, cred_t *cred_p,
int *rval_p)
{
int instance;
struct scsi_device *devp;
struct cmdk *dkp;
char data[NBPSCTR];
instance = CMDKUNIT(dev);
if (!(dkp = ddi_get_soft_state(cmdk_state, instance)))
return (ENXIO);
bzero(data, sizeof (data));
switch (cmd) {
case DKIOCGMEDIAINFO: {
struct dk_minfo media_info;
struct tgdk_geom phyg;
TGDK_GETPHYGEOM(CMDK_TGOBJP(dkp), &phyg);
media_info.dki_lbsize = phyg.g_secsiz;
media_info.dki_capacity = phyg.g_cap;
media_info.dki_media_type = DK_FIXED_DISK;
if (ddi_copyout(&media_info, (void *)arg,
sizeof (struct dk_minfo), flag)) {
return (EFAULT);
} else {
return (0);
}
}
case DKIOCINFO: {
struct dk_cinfo *info = (struct dk_cinfo *)data;
/* controller information */
info->dki_ctype = TGDK_GETCTYPE(CMDK_TGOBJP(dkp));
info->dki_cnum = ddi_get_instance(ddi_get_parent(dkp->dk_dip));
(void) strcpy(info->dki_cname,
ddi_get_name(ddi_get_parent(dkp->dk_dip)));
/* Unit Information */
info->dki_unit = ddi_get_instance(dkp->dk_dip);
devp = ddi_get_driver_private(dkp->dk_dip);
info->dki_slave = (CMDEV_TARG(devp)<<3) | CMDEV_LUN(devp);
(void) strcpy(info->dki_dname, ddi_driver_name(dkp->dk_dip));
info->dki_flags = DKI_FMTVOL;
info->dki_partition = CMDKPART(dev);
info->dki_maxtransfer = maxphys / DEV_BSIZE;
info->dki_addr = 1;
info->dki_space = 0;
info->dki_prio = 0;
info->dki_vec = 0;
if (ddi_copyout(data, (void *)arg, sizeof (*info), flag))
return (EFAULT);
else
return (0);
}
case DKIOCPARTINFO: {
daddr_t start;
long len;
STRUCT_DECL(part_info, p);
/*
* force re-read of MBR and label and partition info
*/
if (!cmdk_part_info(dkp, TRUE, &start, &len, CMDKPART(dev)))
return (ENXIO);
if (len > INT_MAX)
return (EOVERFLOW);
STRUCT_INIT(p, flag & FMODELS);
STRUCT_FSET(p, p_start, start);
STRUCT_FSET(p, p_length, (int)len);
if (ddi_copyout(STRUCT_BUF(p), (caddr_t)arg, STRUCT_SIZE(p),
flag))
return (EFAULT);
return (0);
}
case DKIOCSTATE: {
int state;
int rval;
int part;
daddr_t p_lblksrt;
long p_lblkcnt;
if (ddi_copyin((void *)arg, &state, sizeof (int), flag))
return (EFAULT);
if (rval = TGDK_CHECK_MEDIA(CMDK_TGOBJP(dkp), &state))
return (rval);
if (state == DKIO_INSERTED) {
part = CMDKPART(dev);
/*
* force re-read of MBR and label and partition info
*/
if (!cmdk_part_info(dkp, TRUE, &p_lblksrt, &p_lblkcnt,
part))
return (ENXIO);
if (part < 0 || p_lblkcnt <= 0)
return (ENXIO);
}
if (ddi_copyout(&state, (caddr_t)arg, sizeof (int), flag))
return (EFAULT);
return (0);
}
/*
* is media removable?
*/
case DKIOCREMOVABLE: {
int i;
i = TGDK_RMB(CMDK_TGOBJP(dkp)) ? 1 : 0;
if (ddi_copyout(&i, (caddr_t)arg, sizeof (int), flag))
return (EFAULT);
return (0);
}
case DKIOCG_VIRTGEOM:
case DKIOCG_PHYGEOM:
case DKIOCGGEOM:
case DKIOCSGEOM:
case DKIOCSVTOC:
case DKIOCGVTOC:
case DKIOCGAPART:
case DKIOCSAPART:
case DKIOCADDBAD:
/* If we don't have a label obj we can't call its ioctl */
if (!dkp->dk_lbobjp)
return (EIO);
return (DKLB_IOCTL(dkp->dk_lbobjp, cmd, arg, flag,
cred_p, rval_p));
case DIOCTL_RWCMD: {
struct dadkio_rwcmd *rwcmdp;
int status;
rwcmdp = kmem_alloc(sizeof (struct dadkio_rwcmd), KM_SLEEP);
status = rwcmd_copyin(rwcmdp, (caddr_t)arg, flag);
if (status == 0) {
bzero(&(rwcmdp->status), sizeof (struct dadkio_status));
status = TGDK_IOCTL(CMDK_TGOBJP(dkp), dev, cmd,
(uintptr_t)rwcmdp, flag, cred_p, rval_p);
}
if (status == 0)
status = rwcmd_copyout(rwcmdp, (caddr_t)arg, flag);
kmem_free(rwcmdp, sizeof (struct dadkio_rwcmd));
return (status);
}
default:
return (TGDK_IOCTL(CMDK_TGOBJP(dkp), dev, cmd, arg, flag,
cred_p, rval_p));
}
}
/*ARGSUSED1*/
static int
cmdkclose(dev_t dev, int flag, int otyp, cred_t *cred_p)
{
int part;
ulong_t partbit;
int instance;
struct cmdk *dkp;
int i;
instance = CMDKUNIT(dev);
if (!(dkp = ddi_get_soft_state(cmdk_state, instance)) ||
(otyp >= OTYPCNT))
return (ENXIO);
sema_p(&dkp->dk_semoclose);
/* check for device has been opened */
if (!(dkp->dk_flag & CMDK_OPEN)) {
sema_v(&dkp->dk_semoclose);
return (ENXIO);
}
part = CMDKPART(dev);
if (part < 0) {
sema_v(&dkp->dk_semoclose);
return (ENXIO);
}
partbit = 1 << part;
/* account for close */
if (otyp == OTYP_LYR) {
if (dkp->dk_open.dk_lyr[part])
dkp->dk_open.dk_lyr[part]--;
} else
dkp->dk_open.dk_reg[otyp] &= ~partbit;
dkp->dk_open.dk_exl &= ~partbit;
/* check for last close */
for (i = 0; i < OTYPCNT; i++) {
if (dkp->dk_open.dk_reg[i])
break;
}
if (i >= OTYPCNT) {
for (i = 0; i < CMDK_MAXPART; i++) {
if (dkp->dk_open.dk_lyr[i])
break;
}
if (i >= CMDK_MAXPART) {
/* OK, last close */
TGDK_CLOSE(CMDK_TGOBJP(dkp));
dkp->dk_flag &=
~(CMDK_OPEN | CMDK_TGDK_OPEN | CMDK_VALID_LABEL);
}
}
sema_v(&dkp->dk_semoclose);
return (DDI_SUCCESS);
}
/*ARGSUSED3*/
static int
cmdkopen(dev_t *dev_p, int flag, int otyp, cred_t *cred_p)
{
dev_t dev = *dev_p;
int part;
ulong_t partbit;
int instance;
struct cmdk *dkp;
daddr_t p_lblksrt;
long p_lblkcnt;
int i;
instance = CMDKUNIT(dev);
if (!(dkp = ddi_get_soft_state(cmdk_state, instance)))
return (ENXIO);
if (otyp >= OTYPCNT)
return (EINVAL);
if ((part = CMDKPART(dev)) < 0)
return (ENXIO);
sema_p(&dkp->dk_semoclose);
/* re-do the target open */
if (cmdk_part_info(dkp, TRUE, &p_lblksrt, &p_lblkcnt, part)) {
if (p_lblkcnt <= 0 &&
((flag & (FNDELAY|FNONBLOCK)) == 0 || otyp != OTYP_CHR)) {
sema_v(&dkp->dk_semoclose);
return (ENXIO);
}
} else {
/* fail if not doing non block open */
if ((flag & (FNONBLOCK|FNDELAY)) == 0) {
sema_v(&dkp->dk_semoclose);
return (ENXIO);
}
}
if (TGDK_RDONLY(CMDK_TGOBJP(dkp)) && (flag & FWRITE)) {
sema_v(&dkp->dk_semoclose);
return (EROFS);
}
partbit = 1 << part;
/* check for part already opend exclusively */
if (dkp->dk_open.dk_exl & partbit)
goto excl_open_fail;
/* check if we can establish exclusive open */
if (flag & FEXCL) {
if (dkp->dk_open.dk_lyr[part])
goto excl_open_fail;
for (i = 0; i < OTYPCNT; i++) {
if (dkp->dk_open.dk_reg[i] & partbit)
goto excl_open_fail;
}
}
/* open will succeed, acount for open */
dkp->dk_flag |= CMDK_OPEN;
if (otyp == OTYP_LYR)
dkp->dk_open.dk_lyr[part]++;
else
dkp->dk_open.dk_reg[otyp] |= partbit;
if (flag & FEXCL)
dkp->dk_open.dk_exl |= partbit;
sema_v(&dkp->dk_semoclose);
return (DDI_SUCCESS);
excl_open_fail:
sema_v(&dkp->dk_semoclose);
return (EBUSY);
}
static int
cmdk_reopen(struct cmdk *dkp)
{
struct cmdk_label *dklbp;
int i;
/* open the target disk */
if (TGDK_OPEN(CMDK_TGOBJP(dkp), 0) != DDI_SUCCESS)
return (FALSE);
/* mark as having opened target */
dkp->dk_flag |= CMDK_TGDK_OPEN;
/* check for valid label object */
if (!dkp->dk_lbobjp) {
if (cmdk_create_lbobj(dkp->dk_dip, dkp) != DDI_SUCCESS)
return (FALSE);
} else {
/* reset back to pseudo bbh */
TGDK_SET_BBHOBJ(CMDK_TGOBJP(dkp), &cmdk_bbh_obj);
}
/* search for proper disk label object */
for (i = 0, dklbp = dkp->dk_lb; i < CMDK_LABEL_MAX; i++, dklbp++) {
if (!dklbp->dkl_objp)
continue;
dkp->dk_lbobjp = dklbp->dkl_objp;
if (DKLB_OPEN(dkp->dk_lbobjp, dkp->dk_dev, dkp->dk_dip)
== DDI_SUCCESS)
break;
}
/*
* the last opened label object will become the installed label
* this allows whole raw disk access for disk preparations
*/
dkp->dk_flag |= CMDK_VALID_LABEL;
return (TRUE);
}
/*
* read routine
*/
/*ARGSUSED2*/
static int
cmdkread(dev_t dev, struct uio *uio, cred_t *cred_p)
{
return (cmdkrw(dev, uio, B_READ));
}
/*
* async read routine
*/
/*ARGSUSED2*/
static int
cmdkaread(dev_t dev, struct aio_req *aio, cred_t *cred_p)
{
return (cmdkarw(dev, aio, B_READ));
}
/*
* write routine
*/
/*ARGSUSED2*/
static int
cmdkwrite(dev_t dev, struct uio *uio, cred_t *cred_p)
{
return (cmdkrw(dev, uio, B_WRITE));
}
/*
* async write routine
*/
/*ARGSUSED2*/
static int
cmdkawrite(dev_t dev, struct aio_req *aio, cred_t *cred_p)
{
return (cmdkarw(dev, aio, B_WRITE));
}
static void
cmdkmin(struct buf *bp)
{
if (bp->b_bcount > DK_MAXRECSIZE)
bp->b_bcount = DK_MAXRECSIZE;
}
static int
cmdkrw(dev_t dev, struct uio *uio, int flag)
{
return (physio(cmdkstrategy, (struct buf *)0, dev, flag, cmdkmin, uio));
}
static int
cmdkarw(dev_t dev, struct aio_req *aio, int flag)
{
return (aphysio(cmdkstrategy, anocancel, dev, flag, cmdkmin, aio));
}
/*
* strategy routine
*/
static int
cmdkstrategy(struct buf *bp)
{
int instance;
struct cmdk *dkp;
long d_cnt;
daddr_t p_lblksrt;
long p_lblkcnt;
instance = CMDKUNIT(bp->b_edev);
if (cmdk_indump || !(dkp = ddi_get_soft_state(cmdk_state, instance)) ||
(dkblock(bp) < 0)) {
bp->b_resid = bp->b_bcount;
SETBPERR(bp, ENXIO);
biodone(bp);
return (0);
}
bp->b_flags &= ~(B_DONE|B_ERROR);
bp->b_resid = 0;
bp->av_back = NULL;
/*
* only re-read the vtoc if necessary (force == FALSE)
*/
if (!cmdk_part_info(dkp, FALSE, &p_lblksrt, &p_lblkcnt,
CMDKPART(bp->b_edev))) {
SETBPERR(bp, ENXIO);
}
if ((bp->b_bcount & (NBPSCTR-1)) || (dkblock(bp) > p_lblkcnt))
SETBPERR(bp, ENXIO);
if ((bp->b_flags & B_ERROR) || (dkblock(bp) == p_lblkcnt)) {
bp->b_resid = bp->b_bcount;
biodone(bp);
return (0);
}
d_cnt = bp->b_bcount >> SCTRSHFT;
if ((dkblock(bp) + d_cnt) > p_lblkcnt) {
bp->b_resid = ((dkblock(bp) + d_cnt) - p_lblkcnt) << SCTRSHFT;
bp->b_bcount -= bp->b_resid;
}
SET_BP_SEC(bp, (p_lblksrt + dkblock(bp)));
if (TGDK_STRATEGY(CMDK_TGOBJP(dkp), bp) != DDI_SUCCESS) {
bp->b_resid += bp->b_bcount;
biodone(bp);
}
return (0);
}
static int
cmdk_create_obj(dev_info_t *devi, struct cmdk *dkp)
{
struct scsi_device *devp;
opaque_t queobjp = NULL;
opaque_t flcobjp = NULL;
char que_keyvalp[OBJNAMELEN];
int que_keylen;
char flc_keyvalp[OBJNAMELEN];
int flc_keylen;
char dsk_keyvalp[OBJNAMELEN];
int dsk_keylen;
que_keylen = sizeof (que_keyvalp);
if (ddi_prop_op(DDI_DEV_T_NONE, devi, PROP_LEN_AND_VAL_BUF,
DDI_PROP_CANSLEEP, "queue", que_keyvalp, &que_keylen) !=
DDI_PROP_SUCCESS) {
cmn_err(CE_WARN, "cmdk_create_obj: queue property undefined");
return (DDI_FAILURE);
}
que_keyvalp[que_keylen] = (char)0;
flc_keylen = sizeof (flc_keyvalp);
if (ddi_prop_op(DDI_DEV_T_NONE, devi, PROP_LEN_AND_VAL_BUF,
DDI_PROP_CANSLEEP, "flow_control", flc_keyvalp, &flc_keylen) !=
DDI_PROP_SUCCESS) {
cmn_err(CE_WARN,
"cmdk_create_obj: flow-control property undefined");
return (DDI_FAILURE);
}
flc_keyvalp[flc_keylen] = (char)0;
dsk_keylen = sizeof (dsk_keyvalp);
if (ddi_prop_op(DDI_DEV_T_NONE, devi, PROP_LEN_AND_VAL_BUF,
DDI_PROP_CANSLEEP, "disk", dsk_keyvalp, &dsk_keylen) !=
DDI_PROP_SUCCESS) {
cmn_err(CE_WARN,
"cmdk_create_obj: target disk property undefined");
return (DDI_FAILURE);
}
dsk_keyvalp[dsk_keylen] = (char)0;
if ((objmgr_load_obj(que_keyvalp) != DDI_SUCCESS) ||
!(queobjp = objmgr_create_obj(que_keyvalp))) {
cmn_err(CE_WARN,
"cmdk_create_obj: ERROR creating queue method %s\n",
que_keyvalp);
return (DDI_FAILURE);
}
if ((objmgr_load_obj(flc_keyvalp) != DDI_SUCCESS) ||
!(flcobjp = objmgr_create_obj(flc_keyvalp))) {
QUE_FREE(queobjp);
(void) objmgr_destroy_obj(que_keyvalp);
cmn_err(CE_WARN,
"cmdk_create_obj: ERROR creating flow control %s\n",
flc_keyvalp);
return (DDI_FAILURE);
}
if ((objmgr_load_obj(dsk_keyvalp) != DDI_SUCCESS) ||
!(dkp->dk_tgobjp = objmgr_create_obj(dsk_keyvalp))) {
QUE_FREE(queobjp);
(void) objmgr_destroy_obj(que_keyvalp);
FLC_FREE(flcobjp);
(void) objmgr_destroy_obj(flc_keyvalp);
cmn_err(CE_WARN,
"cmdk_create_obj: ERROR creating target disk %s\n",
dsk_keyvalp);
return (DDI_FAILURE);
}
devp = ddi_get_driver_private(devi);
TGDK_INIT(CMDK_TGOBJP(dkp), devp, flcobjp, queobjp, &cmdk_bbh_obj,
NULL);
return (DDI_SUCCESS);
}
static void
cmdk_destroy_obj(dev_info_t *devi, struct cmdk *dkp, int unload)
{
char que_keyvalp[OBJNAMELEN];
int que_keylen;
char flc_keyvalp[OBJNAMELEN];
int flc_keylen;
char dsk_keyvalp[OBJNAMELEN];
int dsk_keylen;
TGDK_FREE(CMDK_TGOBJP(dkp));
CMDK_TGOBJP(dkp) = NULL;
que_keylen = sizeof (que_keyvalp);
if (ddi_prop_op(DDI_DEV_T_NONE, devi, PROP_LEN_AND_VAL_BUF,
DDI_PROP_CANSLEEP, "queue", que_keyvalp, &que_keylen) !=
DDI_PROP_SUCCESS) {
cmn_err(CE_WARN, "cmdk_destroy_obj: queue property undefined");
return;
}
que_keyvalp[que_keylen] = (char)0;
flc_keylen = sizeof (flc_keyvalp);
if (ddi_prop_op(DDI_DEV_T_NONE, devi, PROP_LEN_AND_VAL_BUF,
DDI_PROP_CANSLEEP, "flow_control", flc_keyvalp, &flc_keylen) !=
DDI_PROP_SUCCESS) {
cmn_err(CE_WARN,
"cmdk_destroy_obj: flow-control property undefined");
return;
}
flc_keyvalp[flc_keylen] = (char)0;
dsk_keylen = sizeof (dsk_keyvalp);
if (ddi_prop_op(DDI_DEV_T_NONE, devi, PROP_LEN_AND_VAL_BUF,
DDI_PROP_CANSLEEP, "disk", dsk_keyvalp, &dsk_keylen) !=
DDI_PROP_SUCCESS) {
cmn_err(CE_WARN,
"cmdk_destroy_obj: target disk property undefined");
return;
}
dsk_keyvalp[dsk_keylen] = (char)0;
(void) objmgr_destroy_obj(que_keyvalp);
(void) objmgr_destroy_obj(flc_keyvalp);
(void) objmgr_destroy_obj(dsk_keyvalp);
if (unload) {
objmgr_unload_obj(que_keyvalp);
objmgr_unload_obj(flc_keyvalp);
objmgr_unload_obj(dsk_keyvalp);
}
}
static int
cmdk_create_lbobj(dev_info_t *devi, struct cmdk *dkp)
{
struct cmdk_label *dklbp;
char **contents;
uint_t numelem;
int i;
int mystatus;
dklbp = dkp->dk_lb;
if (ddi_prop_lookup_string_array(DDI_DEV_T_ANY, devi, 0, "disklabel",
&contents, &numelem) != DDI_PROP_SUCCESS) {
(void) strcpy(dklbp->dkl_name, "snlb");
} else {
for (i = 0; (i < CMDK_LABEL_MAX) && (i < numelem);
i++, dklbp++) {
(void) strncpy(dklbp->dkl_name, contents[i],
sizeof (dklbp->dkl_name));
dklbp->dkl_name[sizeof (dklbp->dkl_name) - 1] = '\0';
}
ddi_prop_free(contents);
}
dklbp = dkp->dk_lb;
for (i = 0, mystatus = DDI_FAILURE; i < CMDK_LABEL_MAX; i++, dklbp++) {
if (dklbp->dkl_name[0] == 0)
break;
if ((objmgr_load_obj(dklbp->dkl_name) != DDI_SUCCESS) ||
!(dklbp->dkl_objp = objmgr_create_obj(dklbp->dkl_name))) {
cmn_err(CE_WARN,
"cmdk_create_lbobj: ERROR creating disklabel %s",
dklbp->dkl_name);
dklbp->dkl_name[0] = 0;
} else {
DKLB_INIT(dklbp->dkl_objp, CMDK_TGOBJP(dkp), NULL);
mystatus = DDI_SUCCESS;
}
}
return (mystatus);
}
/*ARGSUSED*/
static void
cmdk_destroy_lbobj(dev_info_t *devi, struct cmdk *dkp, int unload)
{
struct cmdk_label *dklbp;
int i;
if (!dkp->dk_lbobjp)
return;
dklbp = dkp->dk_lb;
for (i = 0; i < CMDK_LABEL_MAX; i++, dklbp++) {
if (dklbp->dkl_name[0] == 0)
continue;
DKLB_FREE(dklbp->dkl_objp);
(void) objmgr_destroy_obj(dklbp->dkl_name);
if (unload)
objmgr_unload_obj(dklbp->dkl_name);
dklbp->dkl_name[0] = 0;
}
dkp->dk_lbobjp = 0;
}
/*
* cmdk_part_info()
*
* Make the device valid if possible. The dk_pinfo_lock is only
* held for very short periods so that there's very little
* contention with the cmdk_devstatus() function which can
* be called from interrupt context.
*
* This function implements a simple state machine which looks
* like this:
*
*
* +---------------------------------+
* | |
* +--> invalid --> busy --> valid --+
* ^|
* |v
* busy2
*
* This function can change the state from invalid to busy, or from
* busy2 to busy, or from busy to valid.
*
* The cmdk_devstatus() function can change the state from valid
* to invalid or from busy to busy2.
*
*/
static int
cmdk_part_info(struct cmdk *dkp, int force, daddr_t *startp, long *countp,
int part)
{
/*
* The dk_pinfo_state variable (and by implication the partition
* info) is always protected by the dk_pinfo_lock mutex.
*/
mutex_enter(&dkp->dk_pinfo_lock);
for (;;) {
switch (dkp->dk_pinfo_state) {
case CMDK_PARTINFO_VALID:
/* it's already valid */
if (!force) {
goto done;
}
/*FALLTHROUGH*/
case CMDK_PARTINFO_INVALID:
/*
* It's invalid or we're being forced to reread
*/
goto reopen;
case CMDK_PARTINFO_BUSY:
case CMDK_PARTINFO_BUSY2:
/*
* Some other thread has already called
* cmdk_reopen(), wait for it to complete and then
* start over from the top.
*/
cv_wait(&dkp->dk_pinfo_cv, &dkp->dk_pinfo_lock);
}
}
reopen:
/*
* ASSERT: only one thread at a time can possibly reach this point
* and invoke cmdk_reopen()
*/
dkp->dk_pinfo_state = CMDK_PARTINFO_BUSY;
for (;;) {
int rc;
/*
* drop the mutex while in cmdk_reopen() because
* it may take a long time to return
*/
mutex_exit(&dkp->dk_pinfo_lock);
rc = cmdk_reopen(dkp);
mutex_enter(&dkp->dk_pinfo_lock);
if (rc == FALSE) {
/*
* bailout, probably due to no device,
* or invalid label
*/
goto error;
}
switch (dkp->dk_pinfo_state) {
case CMDK_PARTINFO_BUSY:
dkp->dk_pinfo_state = CMDK_PARTINFO_VALID;
cv_broadcast(&dkp->dk_pinfo_cv);
goto done;
case CMDK_PARTINFO_BUSY2:
/*
* device status changed by cmdk_devstatus(),
* redo the reopen
*/
dkp->dk_pinfo_state = CMDK_PARTINFO_BUSY;
break;
}
}
done:
/*
* finished cmdk_reopen() without any device status change
*/
DKLB_PARTINFO(dkp->dk_lbobjp, startp, countp, part);
mutex_exit(&dkp->dk_pinfo_lock);
return (TRUE);
error:
dkp->dk_pinfo_state = CMDK_PARTINFO_INVALID;
cv_broadcast(&dkp->dk_pinfo_cv);
mutex_exit(&dkp->dk_pinfo_lock);
return (FALSE);
}
#ifdef NOT_USED
static void
cmdk_devstatus(struct cmdk *dkp)
{
mutex_enter(&dkp->dk_pinfo_lock);
switch (dkp->dk_pinfo_state) {
case CMDK_PARTINFO_VALID:
dkp->dk_pinfo_state = CMDK_PARTINFO_INVALID;
break;
case CMDK_PARTINFO_INVALID:
break;
case CMDK_PARTINFO_BUSY:
dkp->dk_pinfo_state = CMDK_PARTINFO_BUSY2;
break;
case CMDK_PARTINFO_BUSY2:
break;
}
mutex_exit(&dkp->dk_pinfo_lock);
}
#endif /* NOT_USED */
/*
* initialize the state for cmdk_part_info()
*/
static void
cmdk_part_info_init(struct cmdk *dkp)
{
mutex_init(&dkp->dk_pinfo_lock, NULL, MUTEX_DRIVER, NULL);
cv_init(&dkp->dk_pinfo_cv, NULL, CV_DRIVER, NULL);
dkp->dk_pinfo_state = CMDK_PARTINFO_INVALID;
}
static void
cmdk_part_info_fini(struct cmdk *dkp)
{
mutex_destroy(&dkp->dk_pinfo_lock);
cv_destroy(&dkp->dk_pinfo_cv);
}