diff options
Diffstat (limited to 'usr/src/lib/libdiskmgt/common/drive.c')
| -rw-r--r-- | usr/src/lib/libdiskmgt/common/drive.c | 1575 | 
1 files changed, 1575 insertions, 0 deletions
| diff --git a/usr/src/lib/libdiskmgt/common/drive.c b/usr/src/lib/libdiskmgt/common/drive.c new file mode 100644 index 0000000000..00fd979ae3 --- /dev/null +++ b/usr/src/lib/libdiskmgt/common/drive.c @@ -0,0 +1,1575 @@ +/* + * 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 2004 Sun Microsystems, Inc.  All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident	"%Z%%M%	%I%	%E% SMI" + +#include <fcntl.h> +#include <libdevinfo.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stropts.h> +#include <sys/dkio.h> +#include <sys/sunddi.h> +#include <sys/types.h> +#include <unistd.h> +#include <kstat.h> +#include <errno.h> +#include <devid.h> +#include <dirent.h> + +/* included for uscsi */ +#include <strings.h> +#include <sys/stat.h> +#include <sys/scsi/impl/types.h> +#include <sys/scsi/impl/uscsi.h> +#include <sys/scsi/generic/commands.h> +#include <sys/scsi/impl/commands.h> +#include <sys/scsi/generic/mode.h> +#include <sys/byteorder.h> + +#include "libdiskmgt.h" +#include "disks_private.h" + +#define	KSTAT_CLASS_DISK	"disk" +#define	KSTAT_CLASS_ERROR	"device_error" + +#define	SCSIBUFLEN		0xffff + +/* byte get macros */ +#define	b3(a)			(((a)>>24) & 0xFF) +#define	b2(a)			(((a)>>16) & 0xFF) +#define	b1(a)			(((a)>>8) & 0xFF) +#define	b0(a)			(((a)>>0) & 0xFF) + +static char *kstat_err_names[] = { +	"Soft Errors", +	"Hard Errors", +	"Transport Errors", +	"Media Error", +	"Device Not Ready", +	"No Device", +	"Recoverable", +	"Illegal Request", +	"Predictive Failure Analysis", +	NULL +}; + +static char *err_attr_names[] = { +	DM_NSOFTERRS, +	DM_NHARDERRS, +	DM_NTRANSERRS, +	DM_NMEDIAERRS, +	DM_NDNRERRS, +	DM_NNODEVERRS, +	DM_NRECOVERRS, +	DM_NILLREQERRS, +	DM_FAILING, +	NULL +}; + +/* + *	**************** begin uscsi stuff **************** + */ + +#if defined(_BIT_FIELDS_LTOH) +#elif defined(_BIT_FIELDS_HTOL) +#else +#error	One of _BIT_FIELDS_LTOH or _BIT_FIELDS_HTOL must be defined +#endif + +struct conf_feature { +	uchar_t feature[2]; /* common to all */ +#if defined(_BIT_FIELDS_LTOH) +	uchar_t current : 1; +	uchar_t persist : 1; +	uchar_t version : 4; +	uchar_t reserved: 2; +#else +	uchar_t reserved: 2; +	uchar_t version : 4; +	uchar_t persist : 1; +	uchar_t current : 1; +#endif	/* _BIT_FIELDS_LTOH */ +	uchar_t len; +	union features { +		struct generic { +			uchar_t data[1]; +		} gen; +		uchar_t data[1]; +		struct profile_list { +			uchar_t profile[2]; +#if defined(_BIT_FIELDS_LTOH) +			uchar_t current_p : 1; +			uchar_t reserved1 : 7; +#else +			uchar_t reserved1 : 7; +			uchar_t current_p : 1; +#endif	/* _BIT_FIELDS_LTOH */ +			uchar_t reserved2; +		} plist[1]; +		struct core { +			uchar_t phys[4]; +		} core; +		struct morphing { +#if defined(_BIT_FIELDS_LTOH) +			uchar_t async		: 1; +			uchar_t reserved1	: 7; +#else +			uchar_t reserved1	: 7; +			uchar_t async		: 1; +#endif	/* _BIT_FIELDS_LTOH */ +			uchar_t reserved[3]; +		} morphing; +		struct removable { +#if defined(_BIT_FIELDS_LTOH) +			uchar_t lock	: 1; +			uchar_t	resv1	: 1; +			uchar_t	pvnt	: 1; +			uchar_t eject	: 1; +			uchar_t resv2	: 1; +			uchar_t loading : 3; +#else +			uchar_t loading : 3; +			uchar_t resv2	: 1; +			uchar_t eject	: 1; +			uchar_t	pvnt	: 1; +			uchar_t	resv1	: 1; +			uchar_t lock	: 1; +#endif	/* _BIT_FIELDS_LTOH */ +			uchar_t reserved[3]; +		} removable; +		struct random_readable { +			uchar_t lbsize[4]; +			uchar_t blocking[2]; +#if defined(_BIT_FIELDS_LTOH) +			uchar_t pp		: 1; +			uchar_t reserved1	: 7; +#else +			uchar_t reserved1	: 7; +			uchar_t pp		: 1; +#endif	/* _BIT_FIELDS_LTOH */ +			uchar_t reserved; +		} rread; +		struct cd_read { +#if defined(_BIT_FIELDS_LTOH) +			uchar_t cdtext		: 1; +			uchar_t c2flag		: 1; +			uchar_t reserved1	: 6; +#else +			uchar_t reserved1	: 6; +			uchar_t c2flag		: 1; +			uchar_t cdtext		: 1; +#endif	/* _BIT_FIELDS_LTOH */ +		} cdread; +		struct cd_audio { +#if defined(_BIT_FIELDS_LTOH) +			uchar_t sv	: 1; +			uchar_t scm	: 1; +			uchar_t scan	: 1; +			uchar_t resv	: 5; +#else +			uchar_t resv	: 5; +			uchar_t scan	: 1; +			uchar_t scm	: 1; +			uchar_t sv	: 1; +#endif	/* _BIT_FIELDS_LTOH */ +			uchar_t reserved; +			uchar_t numlevels[2]; +		} audio; +		struct dvd_css { +			uchar_t reserved[3]; +			uchar_t version; +		} dvdcss; +	} features; +}; + +#define	PROF_NON_REMOVABLE	0x0001 +#define	PROF_REMOVABLE		0x0002 +#define	PROF_MAGNETO_OPTICAL	0x0003 +#define	PROF_OPTICAL_WO		0x0004 +#define	PROF_OPTICAL_ASMO	0x0005 +#define	PROF_CDROM		0x0008 +#define	PROF_CDR		0x0009 +#define	PROF_CDRW		0x000a +#define	PROF_DVDROM		0x0010 +#define	PROF_DVDR		0x0011 +#define	PROF_DVDRAM		0x0012 +#define	PROF_DVDRW_REST		0x0013 +#define	PROF_DVDRW_SEQ		0x0014 +#define	PROF_DVDRW		0x001a +#define	PROF_DDCD_ROM		0x0020 +#define	PROF_DDCD_R		0x0021 +#define	PROF_DDCD_RW		0x0022 +#define	PROF_NON_CONFORMING	0xffff + +struct get_configuration { +	uchar_t len[4]; +	uchar_t reserved[2]; +	uchar_t curprof[2]; +	struct conf_feature feature; +}; + +struct capabilities { +#if defined(_BIT_FIELDS_LTOH) +	uchar_t pagecode	: 6; +	uchar_t resv1		: 1; +	uchar_t ps		: 1; +#else +	uchar_t ps		: 1; +	uchar_t resv1		: 1; +	uchar_t pagecode	: 6; +#endif	/* _BIT_FIELDS_LTOH */ +	uchar_t pagelen; +#if defined(_BIT_FIELDS_LTOH) +	/* read capabilities */ +	uchar_t	cdr_read	: 1; +	uchar_t cdrw_read	: 1; +	uchar_t method2		: 1; +	uchar_t dvdrom_read	: 1; +	uchar_t dvdr_read	: 1; +	uchar_t dvdram_read	: 1; +	uchar_t resv2		: 2; +#else +	uchar_t resv2		: 2; +	uchar_t dvdram_read	: 1; +	uchar_t dvdr_read	: 1; +	uchar_t dvdrom_read	: 1; +	uchar_t method2		: 1; +	uchar_t cdrw_read	: 1; +	uchar_t	cdr_read	: 1; +#endif	/* _BIT_FIELDS_LTOH */ +#if defined(_BIT_FIELDS_LTOH) +	/* write capabilities */ +	uchar_t cdr_write	: 1; +	uchar_t cdrw_write	: 1; +	uchar_t testwrite	: 1; +	uchar_t resv3		: 1; +	uchar_t dvdr_write	: 1; +	uchar_t dvdram_write	: 1; +	uchar_t resv4		: 2; +#else +	/* write capabilities */ +	uchar_t resv4		: 2; +	uchar_t dvdram_write	: 1; +	uchar_t dvdr_write	: 1; +	uchar_t resv3		: 1; +	uchar_t testwrite	: 1; +	uchar_t cdrw_write	: 1; +	uchar_t cdr_write	: 1; +#endif	/* _BIT_FIELDS_LTOH */ +	uchar_t misc0; +	uchar_t misc1; +	uchar_t misc2; +	uchar_t misc3; +	uchar_t obsolete0[2]; +	uchar_t numvlevels[2]; +	uchar_t bufsize[2]; +	uchar_t obsolete1[4]; +	uchar_t resv5; +	uchar_t misc4; +	uchar_t obsolete2; +	uchar_t copymgt[2]; +	/* there is more to this page, but nothing we care about */ +}; + +struct mode_header_g2 { +	uchar_t modelen[2]; +	uchar_t obsolete; +	uchar_t reserved[3]; +	uchar_t desclen[2]; +}; + +/* + * Mode sense/select page header information + */ +struct scsi_ms_header { +	struct mode_header	mode_header; +	struct block_descriptor	block_descriptor; +}; + +#define	MODESENSE_PAGE_LEN(p)	(((int)((struct mode_page *)p)->length) + \ +				    sizeof (struct mode_page)) + +#define	MODE_SENSE_PC_CURRENT	(0 << 6) +#define	MODE_SENSE_PC_DEFAULT	(2 << 6) +#define	MODE_SENSE_PC_SAVED	(3 << 6) + +#define	MAX_MODE_SENSE_SIZE	255 +#define	IMPOSSIBLE_SCSI_STATUS	0xff + +/* + *	********** end of uscsi stuff ************ + */ + +static descriptor_t	**apply_filter(descriptor_t **drives, int filter[], +			    int *errp); +static int		check_atapi(int fd); +static int		conv_drive_type(uint_t drive_type); +static uint64_t		convnum(uchar_t *nptr, int len); +static void		fill_command_g1(struct uscsi_cmd *cmd, +			    union scsi_cdb *cdb, caddr_t buff, int blen); +static void		fill_general_page_cdb_g1(union scsi_cdb *cdb, +			    int command, int lun, uchar_t c0, uchar_t c1); +static void		fill_mode_page_cdb(union scsi_cdb *cdb, int page); +static descriptor_t	**get_assoc_alias(disk_t *diskp, int *errp); +static descriptor_t	**get_assoc_controllers(descriptor_t *dp, int *errp); +static descriptor_t	**get_assoc_paths(descriptor_t *dp, int *errp); +static int		get_attrs(disk_t *diskp, int fd, char *opath, +			    nvlist_t *nvp); +static int		get_cdrom_drvtype(int fd); +static int		get_disk_kstats(kstat_ctl_t *kc, char *diskname, +			    char *classname, nvlist_t *stats); +static void		get_drive_type(disk_t *dp, int fd); +static int		get_err_kstats(kstat_ctl_t *kc, char *diskname, +			    nvlist_t *stats); +static int		get_io_kstats(kstat_ctl_t *kc, char *diskname, +			    nvlist_t *stats); +static int		get_kstat_vals(kstat_t *ksp, nvlist_t *stats); +static char		*get_err_attr_name(char *kstat_name); +static int		get_rpm(disk_t *dp, int fd); +static int		update_stat64(nvlist_t *stats, char *attr, +			    uint64_t value); +static int		update_stat32(nvlist_t *stats, char *attr, +			    uint32_t value); +static int		uscsi_mode_sense(int fd, int page_code, +			    int page_control, caddr_t page_data, int page_size, +			    struct  scsi_ms_header *header); + +descriptor_t ** +drive_get_assoc_descriptors(descriptor_t *dp, dm_desc_type_t type, +    int *errp) +{ +	switch (type) { +	case DM_CONTROLLER: +	    return (get_assoc_controllers(dp, errp)); +	case DM_PATH: +	    return (get_assoc_paths(dp, errp)); +	case DM_ALIAS: +	    return (get_assoc_alias(dp->p.disk, errp)); +	case DM_MEDIA: +	    return (media_get_assocs(dp, errp)); +	} + +	*errp = EINVAL; +	return (NULL); +} + +/* + * Get the drive descriptors for the given media/alias/devpath. + */ +descriptor_t ** +drive_get_assocs(descriptor_t *desc, int *errp) +{ +	descriptor_t	**drives; + +	/* at most one drive is associated with these descriptors */ + +	drives = (descriptor_t **)calloc(2, sizeof (descriptor_t *)); +	if (drives == NULL) { +	    *errp = ENOMEM; +	    return (NULL); +	} + +	drives[0] = cache_get_desc(DM_DRIVE, desc->p.disk, NULL, NULL, errp); +	if (*errp != 0) { +	    cache_free_descriptors(drives); +	    return (NULL); +	} + +	drives[1] = NULL; + +	return (drives); +} + +nvlist_t * +drive_get_attributes(descriptor_t *dp, int *errp) +{ +	nvlist_t	*attrs = NULL; +	int		fd; +	char		opath[MAXPATHLEN]; + +	if (nvlist_alloc(&attrs, NVATTRS, 0) != 0) { +	    *errp = ENOMEM; +	    return (NULL); +	} + +	opath[0] = 0; +	fd = drive_open_disk(dp->p.disk, opath, sizeof (opath)); + +	if ((*errp = get_attrs(dp->p.disk, fd, opath, attrs)) != 0) { +	    nvlist_free(attrs); +	    attrs = NULL; +	} + +	if (fd >= 0) { +	    (void) close(fd); +	} + +	return (attrs); +} + +/* + * Check if we have the drive in our list, based upon the device id. + * We got the device id from the dev tree walk.  This is encoded + * using devid_str_encode(3DEVID).   In order to check the device ids we need + * to use the devid_compare(3DEVID) function, so we need to decode the + * string representation of the device id. + */ +descriptor_t * +drive_get_descriptor_by_name(char *name, int *errp) +{ +	ddi_devid_t	devid; +	descriptor_t	**drives; +	descriptor_t	*drive = NULL; +	int		i; + +	if (name == NULL || devid_str_decode(name, &devid, NULL) != 0) { +	    *errp = EINVAL; +	    return (NULL); +	} + +	drives = cache_get_descriptors(DM_DRIVE, errp); +	if (*errp != 0) { +	    devid_free(devid); +	    return (NULL); +	} + +	/* +	 * We have to loop through all of them, freeing the ones we don't +	 * want.  Once drive is set, we don't need to compare any more. +	 */ +	for (i = 0; drives[i]; i++) { +	    if (drive == NULL && drives[i]->p.disk->devid != NULL && +		devid_compare(devid, drives[i]->p.disk->devid) == 0) { +		drive = drives[i]; + +	    } else { +		/* clean up the unused descriptor */ +		cache_free_descriptor(drives[i]); +	    } +	} +	free(drives); +	devid_free(devid); + +	if (drive == NULL) { +	    *errp = ENODEV; +	} + +	return (drive); +} + +descriptor_t ** +drive_get_descriptors(int filter[], int *errp) +{ +	descriptor_t	**drives; + +	drives = cache_get_descriptors(DM_DRIVE, errp); +	if (*errp != 0) { +	    return (NULL); +	} + +	if (filter != NULL && filter[0] != DM_FILTER_END) { +	    descriptor_t	**found; +	    found = apply_filter(drives, filter, errp); +	    if (*errp != 0) { +		drives = NULL; +	    } else { +		drives = found; +	    } +	} + +	return (drives); +} + +char * +drive_get_name(descriptor_t *dp) +{ +	return (dp->p.disk->device_id); +} + +nvlist_t * +drive_get_stats(descriptor_t *dp, int stat_type, int *errp) +{ +	disk_t		*diskp; +	nvlist_t	*stats; + +	diskp = dp->p.disk; + +	if (nvlist_alloc(&stats, NVATTRS, 0) != 0) { +	    *errp = ENOMEM; +	    return (NULL); +	} + +	if (stat_type == DM_DRV_STAT_PERFORMANCE || +	    stat_type == DM_DRV_STAT_DIAGNOSTIC) { + +	    alias_t	*ap; +	    kstat_ctl_t	*kc; + +	    ap = diskp->aliases; +	    if (ap == NULL || ap->kstat_name == NULL) { +		nvlist_free(stats); +		*errp = EACCES; +		return (NULL); +	    } + +	    if ((kc = kstat_open()) == NULL) { +		nvlist_free(stats); +		*errp = EACCES; +		return (NULL); +	    } + +	    while (ap != NULL) { +		int	status; + +		if (ap->kstat_name == NULL) { +		    continue; +		} + +		if (stat_type == DM_DRV_STAT_PERFORMANCE) { +		    status = get_io_kstats(kc, ap->kstat_name, stats); +		} else { +		    status = get_err_kstats(kc, ap->kstat_name, stats); +		} + +		if (status != 0) { +		    nvlist_free(stats); +		    (void) kstat_close(kc); +		    *errp = ENOMEM; +		    return (NULL); +		} + +		ap = ap->next; +	    } + +	    (void) kstat_close(kc); + +	    *errp = 0; +	    return (stats); +	} + +	if (stat_type == DM_DRV_STAT_TEMPERATURE) { +	    int		fd; + +	    if ((fd = drive_open_disk(diskp, NULL, 0)) >= 0) { +		struct dk_temperature	temp; + +		if (ioctl(fd, DKIOCGTEMPERATURE, &temp) >= 0) { +		    if (nvlist_add_uint32(stats, DM_TEMPERATURE, +			temp.dkt_cur_temp) != 0) { +			*errp = ENOMEM; +			nvlist_free(stats); +			return (NULL); +		    } +		} else { +		    *errp = errno; +		    nvlist_free(stats); +		    return (NULL); +		} +		(void) close(fd); +	    } else { +		*errp = errno; +		nvlist_free(stats); +		return (NULL); +	    } + +	    *errp = 0; +	    return (stats); +	} + +	nvlist_free(stats); +	*errp = EINVAL; +	return (NULL); +} + +int +drive_make_descriptors() +{ +	int	error; +	disk_t	*dp; + +	dp = cache_get_disklist(); +	while (dp != NULL) { +	    cache_load_desc(DM_DRIVE, dp, NULL, NULL, &error); +	    if (error != 0) { +		return (error); +	    } +	    dp = dp->next; +	} + +	return (0); +} + +/* + * This function opens the disk generically (any slice). + * + * Opening the disk could fail because the disk is managed by the volume + * manager.  Handle this if that is the case.  Note that the media APIs don't + * always return a device.  If the media has slices (e.g. a solaris install + * CD-ROM) then media_findname(volname) returns a directory with per slice + * devices underneath.  We need to open one of those devices in this case. + */ +int +drive_open_disk(disk_t *diskp, char *opath, int len) +{ +	char	rmmedia_devpath[MAXPATHLEN]; + +	if (diskp->removable && media_get_volm_path(diskp, rmmedia_devpath, +	    sizeof (rmmedia_devpath))) { + +	    int		fd; +	    struct stat	buf; + +	    if (rmmedia_devpath[0] == 0) { +		/* removable but no media */ +		return (-1); +	    } + +	    if ((fd = open(rmmedia_devpath, O_RDONLY|O_NDELAY)) < 0) { +		return (-1); +	    } + +	    if (fstat(fd, &buf) != 0) { +		(void) close(fd); +		return (-1); +	    } + +	    if (buf.st_mode & S_IFCHR) { +		/* opened, is device, so done */ +		if (opath != NULL) { +		    (void) strlcpy(opath, rmmedia_devpath, len); +		} +		return (fd); + +	    } else if (buf.st_mode & S_IFDIR) { +		/* disk w/ slices so handle the directory */ +		DIR		*dirp; +		struct dirent	*dentp; +		int		dfd; +#ifdef _LP64 +		struct  dirent *result; +#endif + +		/* each device file in the dir represents a slice */ + +		if ((dirp = fdopendir(fd)) == NULL) { +		    (void) close(fd); +		    return (-1); +		} + +		if ((dentp = (struct dirent *)malloc(sizeof (struct dirent) + +		    _PC_NAME_MAX + 1)) == NULL) { +		    /* out of memory */ +		    (void) close(fd); +		    return (-1); +		} +#ifdef _LP64 +		while (readdir_r(dirp, dentp, &result) != NULL) { +#else +		while (readdir_r(dirp, dentp) != NULL) { +#endif +		    char	slice_path[MAXPATHLEN]; + +		    if (libdiskmgt_str_eq(".", dentp->d_name) || +			libdiskmgt_str_eq("..", dentp->d_name)) { +			continue; +		    } + +		    (void) snprintf(slice_path, sizeof (slice_path), "%s/%s", +			rmmedia_devpath, dentp->d_name); + +		    if ((dfd = open(slice_path, O_RDONLY|O_NDELAY)) < 0) { +			continue; +		    } + +		    if (fstat(dfd, &buf) == 0 && (buf.st_mode & S_IFCHR)) { +			/* opened, is device, so done */ +			free(dentp); +			(void) close(fd); +			if (opath != NULL) { +			    (void) strlcpy(opath, slice_path, len); +			} +			return (dfd); +		    } + +		    /* not a device, keep looking */ +		    (void) close(dfd); +		} + +		/* did not find a device under the rmmedia_path */ +		free(dentp); +		(void) close(fd); +	    } + +	    /* didn't find a device under volume management control */ +	    return (-1); +	} + +	/* +	 * Not removable media under volume management control so just open the +	 * first devpath. +	 */ +	if (diskp->aliases != NULL && diskp->aliases->devpaths != NULL) { +	    if (opath != NULL) { +		(void) strlcpy(opath, diskp->aliases->devpaths->devpath, len); +	    } +	    return (open(diskp->aliases->devpaths->devpath, O_RDONLY|O_NDELAY)); +	} + +	return (-1); +} + +static descriptor_t ** +apply_filter(descriptor_t **drives, int filter[], int *errp) +{ +	int		i; +	descriptor_t	**found; +	int		cnt; +	int		pos; + +	/* count the number of drives in the snapshot */ +	for (cnt = 0; drives[cnt]; cnt++); + +	found = (descriptor_t **)calloc(cnt + 1, sizeof (descriptor_t *)); +	if (found == NULL) { +	    *errp = ENOMEM; +	    cache_free_descriptors(drives); +	    return (NULL); +	} + +	pos = 0; +	for (i = 0; drives[i]; i++) { +	    int j; +	    int match; + +	    /* Make sure the drive type is set */ +	    get_drive_type(drives[i]->p.disk, -1); + +	    match = 0; +	    for (j = 0; filter[j] != DM_FILTER_END; j++) { +		if (drives[i]->p.disk->drv_type == filter[j]) { +		    found[pos++] = drives[i]; +		    match = 1; +		    break; +		} +	    } + +	    if (!match) { +		cache_free_descriptor(drives[i]); +	    } +	} +	found[pos] = NULL; +	free(drives); + +	*errp = 0; +	return (found); +} + +static int +conv_drive_type(uint_t drive_type) +{ +	switch (drive_type) { +	case DK_UNKNOWN: +	    return (DM_DT_UNKNOWN); +	case DK_MO_ERASABLE: +	    return (DM_DT_MO_ERASABLE); +	case DK_MO_WRITEONCE: +	    return (DM_DT_MO_WRITEONCE); +	case DK_AS_MO: +	    return (DM_DT_AS_MO); +	case DK_CDROM: +	    return (DM_DT_CDROM); +	case DK_CDR: +	    return (DM_DT_CDR); +	case DK_CDRW: +	    return (DM_DT_CDRW); +	case DK_DVDROM: +	    return (DM_DT_DVDROM); +	case DK_DVDR: +	    return (DM_DT_DVDR); +	case DK_DVDRAM: +	    return (DM_DT_DVDRAM); +	case DK_FIXED_DISK: +	    return (DM_DT_FIXED); +	case DK_FLOPPY: +	    return (DM_DT_FLOPPY); +	case DK_ZIP: +	    return (DM_DT_ZIP); +	case DK_JAZ: +	    return (DM_DT_JAZ); +	default: +	    return (DM_DT_UNKNOWN); +	} +} + +static descriptor_t ** +get_assoc_alias(disk_t *diskp, int *errp) +{ +	alias_t		*aliasp; +	uint_t		cnt; +	descriptor_t	**out_array; +	int		pos; + +	*errp = 0; + +	aliasp = diskp->aliases; +	cnt = 0; + +	while (aliasp != NULL) { +	    if (aliasp->alias != NULL) { +		cnt++; +	    } +	    aliasp = aliasp->next; +	} + +	/* set up the new array */ +	out_array = (descriptor_t **)calloc(cnt + 1, sizeof (descriptor_t)); +	if (out_array == NULL) { +	    *errp = ENOMEM; +	    return (NULL); +	} + +	aliasp = diskp->aliases; +	pos = 0; +	while (aliasp != NULL) { +	    if (aliasp->alias != NULL) { +		out_array[pos++] = cache_get_desc(DM_ALIAS, diskp, +		    aliasp->alias, NULL, errp); +		if (*errp != 0) { +		    cache_free_descriptors(out_array); +		    return (NULL); +		} +	    } + +	    aliasp = aliasp->next; +	} + +	out_array[pos] = NULL; + +	return (out_array); +} + +static descriptor_t ** +get_assoc_controllers(descriptor_t *dp, int *errp) +{ +	disk_t		*diskp; +	int		cnt; +	descriptor_t	**controllers; +	int		i; + +	diskp = dp->p.disk; + +	/* Count how many we have. */ +	for (cnt = 0; diskp->controllers[cnt]; cnt++); + +	/* make the snapshot */ +	controllers = (descriptor_t **)calloc(cnt + 1, sizeof (descriptor_t *)); +	if (controllers == NULL) { +	    *errp = ENOMEM; +	    return (NULL); +	} + +	for (i = 0; diskp->controllers[i]; i++) { +	    controllers[i] = cache_get_desc(DM_CONTROLLER, +		diskp->controllers[i], NULL, NULL, errp); +	    if (*errp != 0) { +		cache_free_descriptors(controllers); +		return (NULL); +	    } +	} + +	controllers[i] = NULL; + +	*errp = 0; +	return (controllers); +} + +static descriptor_t ** +get_assoc_paths(descriptor_t *dp, int *errp) +{ +	path_t		**pp; +	int		cnt; +	descriptor_t	**paths; +	int		i; + +	pp = dp->p.disk->paths; + +	/* Count how many we have. */ +	cnt = 0; +	if (pp != NULL) { +	    for (; pp[cnt]; cnt++); +	} + +	/* make the snapshot */ +	paths = (descriptor_t **)calloc(cnt + 1, sizeof (descriptor_t *)); +	if (paths == NULL) { +	    *errp = ENOMEM; +	    return (NULL); +	} + +	/* +	 * We fill in the name field of the descriptor with the device_id +	 * when we deal with path descriptors originating from a drive. +	 * In that way we can use the device id within the path code to +	 * lookup the path state for this drive. +	 */ +	for (i = 0; i < cnt; i++) { +	    paths[i] = cache_get_desc(DM_PATH, pp[i], dp->p.disk->device_id, +		NULL, errp); +	    if (*errp != 0) { +		cache_free_descriptors(paths); +		return (NULL); +	    } +	} + +	paths[i] = NULL; + +	*errp = 0; +	return (paths); +} + +static int +get_attrs(disk_t *diskp, int fd, char *opath, nvlist_t *attrs) +{ +	if (diskp->removable) { +	    struct dk_minfo	minfo; + +	    if (nvlist_add_boolean(attrs, DM_REMOVABLE) != 0) { +		return (ENOMEM); +	    } + +	    /* Make sure media is inserted and spun up. */ +	    if (fd >= 0 && media_read_info(fd, &minfo)) { +		if (nvlist_add_boolean(attrs, DM_LOADED) != 0) { +		    return (ENOMEM); +		} +	    } + +	    /* can't tell diff between dead & no media on removable drives */ +	    if (nvlist_add_uint32(attrs, DM_STATUS, DM_DISK_UP) != 0) { +		return (ENOMEM); +	    } + +	    get_drive_type(diskp, fd); + +	} else { +	    struct dk_minfo	minfo; + +	    /* check if the fixed drive is up or not */ +	    if (fd >= 0 && media_read_info(fd, &minfo)) { +		if (nvlist_add_uint32(attrs, DM_STATUS, DM_DISK_UP) != 0) { +		    return (ENOMEM); +		} +	    } else { +		if (nvlist_add_uint32(attrs, DM_STATUS, DM_DISK_DOWN) != 0) { +		    return (ENOMEM); +		} +	    } + +	    get_drive_type(diskp, fd); +	} + +	if (nvlist_add_uint32(attrs, DM_DRVTYPE, diskp->drv_type) != 0) { +	    return (ENOMEM); +	} + +	if (diskp->product_id != NULL) { +	    if (nvlist_add_string(attrs, DM_PRODUCT_ID, diskp->product_id) +		!= 0) { +		return (ENOMEM); +	    } +	} +	if (diskp->vendor_id != NULL) { +	    if (nvlist_add_string(attrs, DM_VENDOR_ID, diskp->vendor_id) != 0) { +		return (ENOMEM); +	    } +	} + +	if (diskp->sync_speed != -1) { +	    if (nvlist_add_uint32(attrs, DM_SYNC_SPEED, diskp->sync_speed) +		!= 0) { +		return (ENOMEM); +	    } +	} + +	if (diskp->wide == 1) { +	    if (nvlist_add_boolean(attrs, DM_WIDE) != 0) { +		return (ENOMEM); +	    } +	} + +	if (diskp->rpm == 0) { +	    diskp->rpm = get_rpm(diskp, fd); +	} + +	if (diskp->rpm > 0) { +	    if (nvlist_add_uint32(attrs, DM_RPM, diskp->rpm) != 0) { +		return (ENOMEM); +	    } +	} + +	if (diskp->aliases != NULL && diskp->aliases->cluster) { +	    if (nvlist_add_boolean(attrs, DM_CLUSTERED) != 0) { +		return (ENOMEM); +	    } +	} + +	if (strlen(opath) > 0) { +	    if (nvlist_add_string(attrs, DM_OPATH, opath) != 0) { +		return (ENOMEM); +	    } +	} + +	return (0); +} + +static int +get_disk_kstats(kstat_ctl_t *kc, char *diskname, char *classname, +	nvlist_t *stats) +{ +	kstat_t		*ksp; +	size_t		class_len; +	int		err = 0; + +	class_len = strlen(classname); +	for (ksp = kc->kc_chain; ksp; ksp = ksp->ks_next) { +	    if (strncmp(ksp->ks_class, classname, class_len) == 0) { +		char	kstat_name[KSTAT_STRLEN]; +		char	*dname = kstat_name; +		char	*ename = ksp->ks_name; + +		/* names are format: "sd0,err" - copy chars up to comma */ +		while (*ename && *ename != ',') { +		    *dname++ = *ename++; +		} +		*dname = NULL; + +		if (libdiskmgt_str_eq(diskname, kstat_name)) { +		    (void) kstat_read(kc, ksp, NULL); +		    err = get_kstat_vals(ksp, stats); +		    break; +		} +	    } +	} + +	return (err); +} + +/* + * Getting the drive type depends on if the dev tree walk indicated that the + * drive was a CD-ROM or not.  The kernal lumps all of the removable multi-media + * drives (e.g. CD, DVD, MO, etc.) together as CD-ROMS, so we need to use + * a uscsi cmd to check the drive type. + */ +static void +get_drive_type(disk_t *dp, int fd) +{ +	if (dp->drv_type == DM_DT_UNKNOWN) { +	    int	opened_here = 0; + +	    /* We may have already opened the device. */ +	    if (fd < 0) { +		fd = drive_open_disk(dp, NULL, 0); +		opened_here = 1; +	    } + +	    if (fd >= 0) { +		if (dp->cd_rom) { +		    /* use uscsi to determine drive type */ +		    dp->drv_type = get_cdrom_drvtype(fd); + +		    /* if uscsi fails, just call it a cd-rom */ +		    if (dp->drv_type == DM_DT_UNKNOWN) { +			dp->drv_type = DM_DT_CDROM; +		    } + +		} else { +		    struct dk_minfo	minfo; + +		    if (media_read_info(fd, &minfo)) { +			dp->drv_type = conv_drive_type(minfo.dki_media_type); +		    } +		} + +		if (opened_here) { +		    (void) close(fd); +		} + +	    } else { +		/* couldn't open */ +		if (dp->cd_rom) { +		    dp->drv_type = DM_DT_CDROM; +		} +	    } +	} +} + +static char * +get_err_attr_name(char *kstat_name) +{ +	int	i; + +	for (i = 0; kstat_err_names[i] != NULL; i++) { +	    if (libdiskmgt_str_eq(kstat_name, kstat_err_names[i])) { +		return (err_attr_names[i]); +	    } +	} + +	return (NULL); +} + +static int +get_err_kstats(kstat_ctl_t *kc, char *diskname, nvlist_t *stats) +{ +	return (get_disk_kstats(kc, diskname, KSTAT_CLASS_ERROR, stats)); +} + +static int +get_io_kstats(kstat_ctl_t *kc, char *diskname, nvlist_t *stats) +{ +	return (get_disk_kstats(kc, diskname, KSTAT_CLASS_DISK, stats)); +} + +static int +get_kstat_vals(kstat_t *ksp, nvlist_t *stats) +{ +	if (ksp->ks_type == KSTAT_TYPE_IO) { +	    kstat_io_t *kiop; + +	    kiop = KSTAT_IO_PTR(ksp); + +	    /* see sys/kstat.h kstat_io_t struct for more fields */ + +	    if (update_stat64(stats, DM_NBYTESREAD, kiop->nread) != 0) { +		return (ENOMEM); +	    } +	    if (update_stat64(stats, DM_NBYTESWRITTEN, kiop->nwritten) != 0) { +		return (ENOMEM); +	    } +	    if (update_stat64(stats, DM_NREADOPS, kiop->reads) != 0) { +		return (ENOMEM); +	    } +	    if (update_stat64(stats, DM_NWRITEOPS, kiop->writes) != 0) { +		return (ENOMEM); +	    } + +	} else if (ksp->ks_type == KSTAT_TYPE_NAMED) { +	    kstat_named_t *knp; +	    int		i; + +	    knp = KSTAT_NAMED_PTR(ksp); +	    for (i = 0; i < ksp->ks_ndata; i++) { +		char	*attr_name; + +		if (knp[i].name[0] == 0) +		    continue; + +		if ((attr_name = get_err_attr_name(knp[i].name)) == NULL) { +		    continue; + +		} + +		switch (knp[i].data_type) { +		case KSTAT_DATA_UINT32: +		    if (update_stat32(stats, attr_name, knp[i].value.ui32) +			!= 0) { +			return (ENOMEM); +		    } +		    break; + +		default: +		    /* Right now all of the error types are uint32 */ +		    break; +		} +	    } +	} +	return (0); +} + +static int +update_stat32(nvlist_t *stats, char *attr, uint32_t value) +{ +	int32_t	currval; + +	if (nvlist_lookup_int32(stats, attr, &currval) == 0) { +	    value += currval; +	} + +	return (nvlist_add_uint32(stats, attr, value)); +} + +/* + * There can be more than one kstat value when we have multi-path drives + * that are not under mpxio (since there is more than one kstat name for + * the drive in this case).  So, we may have merge all of the kstat values + * to give an accurate set of stats for the drive. + */ +static int +update_stat64(nvlist_t *stats, char *attr, uint64_t value) +{ +	int64_t	currval; + +	if (nvlist_lookup_int64(stats, attr, &currval) == 0) { +	    value += currval; +	} +	return (nvlist_add_uint64(stats, attr, value)); +} + +/* + * uscsi function to get the rpm of the drive + */ +static int +get_rpm(disk_t *dp, int fd) +{ +	int	opened_here = 0; +	int	rpm = -1; + +	/* We may have already opened the device. */ +	if (fd < 0) { +	    fd = drive_open_disk(dp, NULL, 0); +	    opened_here = 1; +	} + +	if (fd >= 0) { +	    int				status; +	    struct mode_geometry	*page4; +	    struct scsi_ms_header	header; +	    union { +		struct mode_geometry	page4; +		char			rawbuf[MAX_MODE_SENSE_SIZE]; +	    } u_page4; + +	    page4 = &u_page4.page4; +	    (void) memset(&u_page4, 0, sizeof (u_page4)); + +	    status = uscsi_mode_sense(fd, DAD_MODE_GEOMETRY, +		MODE_SENSE_PC_DEFAULT, (caddr_t)page4, MAX_MODE_SENSE_SIZE, +		&header); + +	    if (status) { +		status = uscsi_mode_sense(fd, DAD_MODE_GEOMETRY, +		    MODE_SENSE_PC_SAVED, (caddr_t)page4, MAX_MODE_SENSE_SIZE, +		    &header); +	    } + +	    if (status) { +		status = uscsi_mode_sense(fd, DAD_MODE_GEOMETRY, +		    MODE_SENSE_PC_CURRENT, (caddr_t)page4, MAX_MODE_SENSE_SIZE, +		    &header); +	    } + +	    if (!status) { +#ifdef _LITTLE_ENDIAN +		page4->rpm = ntohs(page4->rpm); +#endif /* _LITTLE_ENDIAN */ + +		rpm = page4->rpm; +	    } + +	    if (opened_here) { +		(void) close(fd); +	    } +	} + +	return (rpm); +} + +/* + *	******** the rest of this is uscsi stuff for the drv type ******** + */ + +/* + * We try a get_configuration uscsi cmd.  If that fails, try a + * atapi_capabilities cmd.  If both fail then this is an older CD-ROM. + */ +static int +get_cdrom_drvtype(int fd) +{ +	union scsi_cdb cdb; +	struct uscsi_cmd cmd; +	uchar_t buff[SCSIBUFLEN]; + +	fill_general_page_cdb_g1(&cdb, SCMD_GET_CONFIGURATION, 0, +	    b0(sizeof (buff)), b1(sizeof (buff))); +	fill_command_g1(&cmd, &cdb, (caddr_t)buff, sizeof (buff)); + +	if (ioctl(fd, USCSICMD, &cmd) >= 0) { +	    struct get_configuration	*config; +	    struct conf_feature		*feature; +	    int				flen; + +	    /* The first profile is the preferred one for the drive. */ +	    config = (struct get_configuration *)buff; +	    feature = &config->feature; +	    flen = feature->len / sizeof (struct profile_list); +	    if (flen > 0) { +		int prof_num; + +		prof_num = (int)convnum(feature->features.plist[0].profile, 2); + +		if (dm_debug > 1) { +		    (void) fprintf(stderr, "INFO: uscsi get_configuration %d\n", +			prof_num); +		} + +		switch (prof_num) { +		case PROF_MAGNETO_OPTICAL: +		    return (DM_DT_MO_ERASABLE); +		case PROF_OPTICAL_WO: +		    return (DM_DT_MO_WRITEONCE); +		case PROF_OPTICAL_ASMO: +		    return (DM_DT_AS_MO); +		case PROF_CDROM: +		    return (DM_DT_CDROM); +		case PROF_CDR: +		    return (DM_DT_CDR); +		case PROF_CDRW: +		    return (DM_DT_CDRW); +		case PROF_DVDROM: +		    return (DM_DT_DVDROM); +		case PROF_DVDRAM: +		    return (DM_DT_DVDRAM); +		case PROF_DVDRW_REST: +		    return (DM_DT_DVDRW); +		case PROF_DVDRW_SEQ: +		    return (DM_DT_DVDRW); +		case PROF_DVDRW: +		    return (DM_DT_DVDRW); +		case PROF_DDCD_ROM: +		    return (DM_DT_DDCDROM); +		case PROF_DDCD_R: +		    return (DM_DT_DDCDR); +		case PROF_DDCD_RW: +		    return (DM_DT_DDCDRW); +		} +	    } +	} + +	/* see if the atapi capabilities give anything */ +	return (check_atapi(fd)); +} + +static int +check_atapi(int fd) +{ +	union scsi_cdb cdb; +	struct uscsi_cmd cmd; +	uchar_t buff[SCSIBUFLEN]; + +	fill_mode_page_cdb(&cdb, ATAPI_CAPABILITIES); +	fill_command_g1(&cmd, &cdb, (caddr_t)buff, sizeof (buff)); + +	if (ioctl(fd, USCSICMD, &cmd) >= 0) { +	    int			bdesclen; +	    struct capabilities	*cap; +	    struct mode_header_g2 *mode; + +	    mode = (struct mode_header_g2 *)buff; + +	    bdesclen = (int)convnum(mode->desclen, 2); +	    cap = (struct capabilities *) +		&buff[sizeof (struct mode_header_g2) + bdesclen]; + +	    if (dm_debug > 1) { +		(void) fprintf(stderr, "INFO: uscsi atapi capabilities\n"); +	    } + +	    /* These are in order of how we want to report the drv type. */ +	    if (cap->dvdram_write) { +		return (DM_DT_DVDRAM); +	    } +	    if (cap->dvdr_write) { +		return (DM_DT_DVDR); +	    } +	    if (cap->dvdrom_read) { +		return (DM_DT_DVDROM); +	    } +	    if (cap->cdrw_write) { +		return (DM_DT_CDRW); +	    } +	    if (cap->cdr_write) { +		return (DM_DT_CDR); +	    } +	    if (cap->cdr_read) { +		return (DM_DT_CDROM); +	    } +	} + +	/* everything failed, so this is an older CD-ROM */ +	if (dm_debug > 1) { +	    (void) fprintf(stderr, "INFO: uscsi failed\n"); +	} + +	return (DM_DT_CDROM); +} + +static uint64_t +convnum(uchar_t *nptr, int len) +{ +	uint64_t value; + +	for (value = 0; len > 0; len--, nptr++) +		value = (value << 8) | *nptr; +	return (value); +} + +static void +fill_command_g1(struct uscsi_cmd *cmd, union scsi_cdb *cdb, +	caddr_t buff, int blen) +{ +	bzero((caddr_t)cmd, sizeof (struct uscsi_cmd)); +	bzero(buff, blen); + +	cmd->uscsi_cdb = (caddr_t)cdb; +	cmd->uscsi_cdblen = CDB_GROUP1; + +	cmd->uscsi_bufaddr = buff; +	cmd->uscsi_buflen = blen; + +	cmd->uscsi_flags = USCSI_DIAGNOSE|USCSI_ISOLATE|USCSI_READ; +} + +static void +fill_general_page_cdb_g1(union scsi_cdb *cdb, int command, int lun, +	uchar_t c0, uchar_t c1) +{ +	bzero((caddr_t)cdb, sizeof (union scsi_cdb)); +	cdb->scc_cmd = command; +	cdb->scc_lun = lun; +	cdb->g1_count0 = c0; /* max length for page */ +	cdb->g1_count1 = c1; /* max length for page */ +} + +static void +fill_mode_page_cdb(union scsi_cdb *cdb, int page) +{ +	/* group 1 mode page */ +	bzero((caddr_t)cdb, sizeof (union scsi_cdb)); +	cdb->scc_cmd = SCMD_MODE_SENSE_G1; +	cdb->g1_count0 = 0xff; /* max length for mode page */ +	cdb->g1_count1 = 0xff; /* max length for mode page */ +	cdb->g1_addr3 = page; +} + +static int +uscsi_mode_sense(int fd, int page_code, int page_control, caddr_t page_data, +	int page_size, struct  scsi_ms_header *header) +{ +	caddr_t			mode_sense_buf; +	struct mode_header	*hdr; +	struct mode_page	*pg; +	int			nbytes; +	struct uscsi_cmd	ucmd; +	union scsi_cdb		cdb; +	int			status; +	int			maximum; +	char			rqbuf[255]; + +	/* +	 * Allocate a buffer for the mode sense headers +	 * and mode sense data itself. +	 */ +	nbytes = sizeof (struct block_descriptor) + +				sizeof (struct mode_header) + page_size; +	nbytes = page_size; +	if ((mode_sense_buf = malloc((uint_t)nbytes)) == NULL) { +	    return (-1); +	} + +	/* +	 * Build and execute the uscsi ioctl +	 */ +	(void) memset(mode_sense_buf, 0, nbytes); +	(void) memset((char *)&ucmd, 0, sizeof (ucmd)); +	(void) memset((char *)&cdb, 0, sizeof (union scsi_cdb)); + +	cdb.scc_cmd = SCMD_MODE_SENSE; +	FORMG0COUNT(&cdb, (uchar_t)nbytes); +	cdb.cdb_opaque[2] = page_control | page_code; +	ucmd.uscsi_cdb = (caddr_t)&cdb; +	ucmd.uscsi_cdblen = CDB_GROUP0; +	ucmd.uscsi_bufaddr = mode_sense_buf; +	ucmd.uscsi_buflen = nbytes; + +	ucmd.uscsi_flags |= USCSI_SILENT; +	ucmd.uscsi_flags |= USCSI_READ; +	ucmd.uscsi_timeout = 30; +	ucmd.uscsi_flags |= USCSI_RQENABLE; +	if (ucmd.uscsi_rqbuf == NULL)  { +	    ucmd.uscsi_rqbuf = rqbuf; +	    ucmd.uscsi_rqlen = sizeof (rqbuf); +	    ucmd.uscsi_rqresid = sizeof (rqbuf); +	} +	ucmd.uscsi_rqstatus = IMPOSSIBLE_SCSI_STATUS; + +	status = ioctl(fd, USCSICMD, &ucmd); + +	if (status || ucmd.uscsi_status != 0) { +	    free(mode_sense_buf); +	    return (-1); +	} + +	/* +	 * Verify that the returned data looks reasonabled, +	 * find the actual page data, and copy it into the +	 * user's buffer.  Copy the mode_header and block_descriptor +	 * into the header structure, which can then be used to +	 * return the same data to the drive when issuing a mode select. +	 */ +	hdr = (struct mode_header *)mode_sense_buf; +	(void) memset((caddr_t)header, 0, sizeof (struct scsi_ms_header)); +	if (hdr->bdesc_length != sizeof (struct block_descriptor) && +	    hdr->bdesc_length != 0) { +	    free(mode_sense_buf); +	    return (-1); +	} +	(void) memcpy((caddr_t)header, mode_sense_buf, +	    (int) (sizeof (struct mode_header) + hdr->bdesc_length)); +	pg = (struct mode_page *)((ulong_t)mode_sense_buf + +	    sizeof (struct mode_header) + hdr->bdesc_length); +	if (pg->code != page_code) { +	    free(mode_sense_buf); +	    return (-1); +	} + +	/* +	 * Accept up to "page_size" bytes of mode sense data. +	 * This allows us to accept both CCS and SCSI-2 +	 * structures, as long as we request the greater +	 * of the two. +	 */ +	maximum = page_size - sizeof (struct mode_page) - hdr->bdesc_length; +	if (((int)pg->length) > maximum) { +	    free(mode_sense_buf); +	    return (-1); +	} + +	(void) memcpy(page_data, (caddr_t)pg, MODESENSE_PAGE_LEN(pg)); + +	free(mode_sense_buf); +	return (0); +} | 
