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); +} |
