/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright (c) 2002, 2012, Oracle and/or its affiliates. All rights reserved.
*/
#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 <sys/vtoc.h>
#include <unistd.h>
#include <kstat.h>
#include <errno.h>
#include <devid.h>
#include <dirent.h>
#include <sys/dktp/fdisk.h>
#include <sys/efi_partition.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"
/*
* Reduce SCSIBUFLEN to 0xfffc to ensure its 2 byte and 4 byte aligned
* since some HBAs on x86 architecture require 4 byte alignment.
* SCSI command get_configuration can return 64K -2 bytes of data.
* But the spec adds a comment that says current usage is only 1K.
*/
#define SCSIBUFLEN 0xfffc
/* 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_get_part_format_by_name(char *name, int *errp)
{
struct extvtoc vtoc;
struct dk_gpt *efip;
descriptor_t *dp;
int part_format = FMT_UNKNOWN;
int status;
int fd;
dp = slice_get_descriptor_by_name(name, errp);
if (dp == NULL) {
*errp = ENODEV;
return (part_format);
}
if ((fd = drive_open_disk(dp->p.disk, NULL, 0)) < 0) {
*errp = ENODEV;
return (part_format);
}
/*
* Find the partition format.
*/
if ((status = read_extvtoc(fd, &vtoc)) >= 0) {
part_format = FMT_VTOC;
} else if ((status == VT_ENOTSUP) &&
(efi_alloc_and_read(fd, &efip) >= 0)) {
part_format = FMT_EFI;
efi_free(efip);
}
(void) close(fd);
return (part_format);
}
int
drive_make_descriptors()
{
disk_t *dp;
int error = 0;
int locked;
cache_rlock(&locked);
dp = cache_get_disklist();
while ((error == 0) && (dp != NULL)) {
cache_load_desc(DM_DRIVE, dp, NULL, NULL, &error);
dp = dp->next;
}
cache_unlock(&locked);
return (error);
}
/*
* This function opens the disk generically (any slice).
*/
int
drive_open_disk(disk_t *diskp, char *opath, int len)
{
/*
* 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_cxtydz != NULL) {
cnt++;
}
if (aliasp->cluster && (aliasp->alias_did != 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_cxtydz != NULL) {
out_array[pos++] = cache_get_desc(DM_ALIAS, diskp,
aliasp->alias_cxtydz, NULL, errp);
if (*errp != 0) {
cache_free_descriptors(out_array);
return (NULL);
}
}
if (aliasp->cluster && (aliasp->alias_did != NULL)) {
out_array[pos++] = cache_get_desc(DM_ALIAS, diskp,
aliasp->alias_did, 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_READ | USCSI_SILENT;
}
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);
}