summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRobert Mustacchi <rm@joyent.com>2018-09-08 22:23:48 +0000
committerRobert Mustacchi <rm@joyent.com>2018-09-22 15:54:14 +0000
commitfd6d41c5025e9fb45a115fc82d86e9983d1e9fd6 (patch)
treece9a9ff0f89db351d1ce2ab44cfc45a2e0781f2d
parent9a48f6c443e5968307491ba7cc134bbdd0328801 (diff)
downloadillumos-joyent-fd6d41c5025e9fb45a115fc82d86e9983d1e9fd6.tar.gz
9815 Want basic AHCI enclosure services
Reviewed by: Patrick Mooney <patrick.mooney@joyent.com> Reviewed by: Rob Johnston <rob.johnston@joyent.com> Reviewed by: Yuri Pankov <yuripv@yuripv.net> Approved by: Dan McDonald <danmcd@joyent.com>
-rw-r--r--usr/src/cmd/Makefile3
-rw-r--r--usr/src/cmd/ahciem/Makefile39
-rw-r--r--usr/src/cmd/ahciem/ahciem.c302
-rw-r--r--usr/src/pkg/manifests/driver-storage-ahci.mf3
-rw-r--r--usr/src/uts/common/io/sata/adapters/ahci/ahci.c565
-rw-r--r--usr/src/uts/common/io/sata/impl/sata.c24
-rw-r--r--usr/src/uts/common/sys/sata/adapters/ahci/ahciem.h74
-rw-r--r--usr/src/uts/common/sys/sata/adapters/ahci/ahcivar.h63
8 files changed, 1059 insertions, 14 deletions
diff --git a/usr/src/cmd/Makefile b/usr/src/cmd/Makefile
index 57644f077b..37e979d619 100644
--- a/usr/src/cmd/Makefile
+++ b/usr/src/cmd/Makefile
@@ -21,7 +21,7 @@
#
# Copyright (c) 1989, 2010, Oracle and/or its affiliates. All rights reserved.
-# Copyright (c) 2017, Joyent, Inc.
+# Copyright (c) 2018, Joyent, Inc.
# Copyright (c) 2012 by Delphix. All rights reserved.
# Copyright (c) 2013 DEY Storage Systems, Inc. All rights reserved.
# Copyright 2014 Garrett D'Amore <garrett@damore.org>
@@ -62,6 +62,7 @@ COMMON_SUBDIRS= \
adbgen \
acct \
acctadm \
+ ahciem \
arch \
asa \
ast \
diff --git a/usr/src/cmd/ahciem/Makefile b/usr/src/cmd/ahciem/Makefile
new file mode 100644
index 0000000000..7c9efa5f27
--- /dev/null
+++ b/usr/src/cmd/ahciem/Makefile
@@ -0,0 +1,39 @@
+#
+# This file and its contents are supplied under the terms of the
+# Common Development and Distribution License ("CDDL"), version 1.0.
+# You may only use this file in accordance with the terms of version
+# 1.0 of the CDDL.
+#
+# A full copy of the text of the CDDL should have accompanied this
+# source. A copy of the CDDL is also available via the Internet at
+# http://www.illumos.org/license/CDDL.
+#
+
+#
+# Copyright (c) 2018, Joyent, Inc.
+#
+
+PROG= ahciem
+LINTPROGS= $(PROG:%=%.ln)
+
+include ../Makefile.cmd
+
+ROOTCMDDIR = $(ROOTLIB)/ahci
+CPPFLAGS += -I$(SRC)/uts/common/
+CFLAGS += $(CCVERBOSE)
+LDLIBS += -ldevinfo
+
+.KEEP_STATE:
+
+all: $(PROG)
+
+install: all $(ROOTCMD)
+
+clean:
+
+%.ln: %.c
+ $(LINT.c) $< $(LDLIBS)
+
+lint: $(LINTPROGS)
+
+include ../Makefile.targ
diff --git a/usr/src/cmd/ahciem/ahciem.c b/usr/src/cmd/ahciem/ahciem.c
new file mode 100644
index 0000000000..3c3a473f51
--- /dev/null
+++ b/usr/src/cmd/ahciem/ahciem.c
@@ -0,0 +1,302 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source. A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright (c) 2018 Joyent, Inc.
+ */
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <string.h>
+#include <strings.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <err.h>
+#include <libgen.h>
+#include <libdevinfo.h>
+
+#include <sys/sata/adapters/ahci/ahciem.h>
+
+#define AHCIEM_IDENT "ident"
+#define AHCIEM_FAULT "fault"
+#define AHCIEM_NOACTIVITY "noactivity"
+#define AHCIEM_DEFAULT "default"
+#define AHCIEM_UNKNOWN "unknown"
+
+#define EXIT_USAGE 2
+
+static const char *ahciem_progname;
+
+typedef struct {
+ boolean_t ahci_set;
+ ahci_em_led_state_t ahci_led;
+ int ahci_argc;
+ char **ahci_argv;
+ boolean_t *ahci_found;
+ int ahci_err;
+} ahciem_t;
+
+static void
+ahciem_usage(const char *fmt, ...)
+{
+ if (fmt != NULL) {
+ va_list ap;
+
+ va_start(ap, fmt);
+ vwarnx(fmt, ap);
+ va_end(ap);
+ }
+
+ (void) fprintf(stderr, "Usage: %s [-s mode] [port]\n"
+ "\n"
+ "\t-s mode\t\tset LED to mode\n",
+ ahciem_progname);
+}
+
+static const char *
+ahciem_led_to_string(uint_t led)
+{
+ switch (led) {
+ case AHCI_EM_LED_IDENT_ENABLE:
+ return (AHCIEM_IDENT);
+ case AHCI_EM_LED_FAULT_ENABLE:
+ return (AHCIEM_FAULT);
+ case AHCI_EM_LED_ACTIVITY_DISABLE:
+ return (AHCIEM_NOACTIVITY);
+ case (AHCI_EM_LED_IDENT_ENABLE | AHCI_EM_LED_FAULT_ENABLE):
+ return (AHCIEM_IDENT "," AHCIEM_FAULT);
+ case (AHCI_EM_LED_IDENT_ENABLE | AHCI_EM_LED_ACTIVITY_DISABLE):
+ return (AHCIEM_IDENT "," AHCIEM_NOACTIVITY);
+ case (AHCI_EM_LED_FAULT_ENABLE | AHCI_EM_LED_ACTIVITY_DISABLE):
+ return (AHCIEM_FAULT "," AHCIEM_NOACTIVITY);
+ /* BEGIN CSTYLED */
+ case (AHCI_EM_LED_IDENT_ENABLE | AHCI_EM_LED_FAULT_ENABLE |
+ AHCI_EM_LED_ACTIVITY_DISABLE):
+ return (AHCIEM_IDENT "," AHCIEM_FAULT "," AHCIEM_NOACTIVITY);
+ /* END CSTYLED */
+ case 0:
+ return (AHCIEM_DEFAULT);
+ default:
+ return (AHCIEM_UNKNOWN);
+ }
+}
+
+static boolean_t
+ahciem_match(ahciem_t *ahci, const char *port)
+{
+ int i;
+
+ if (ahci->ahci_argc == 0)
+ return (B_TRUE);
+
+ for (i = 0; i < ahci->ahci_argc; i++) {
+ size_t len = strlen(ahci->ahci_argv[i]);
+
+ /*
+ * Perform a partial match on the base name. This allows us to
+ * match all of a controller by using a string like "ahci0".
+ */
+ if (strncmp(ahci->ahci_argv[i], port, len) == 0) {
+ ahci->ahci_found[i] = B_TRUE;
+ return (B_TRUE);
+ }
+
+ }
+
+ return (B_FALSE);
+}
+
+static ahci_em_led_state_t
+ahciem_parse(const char *arg)
+{
+ if (strcmp(arg, AHCIEM_IDENT) == 0) {
+ return (AHCI_EM_LED_IDENT_ENABLE);
+ } else if (strcmp(arg, AHCIEM_FAULT) == 0) {
+ return (AHCI_EM_LED_FAULT_ENABLE);
+ } else if (strcmp(arg, AHCIEM_NOACTIVITY) == 0) {
+ return (AHCI_EM_LED_ACTIVITY_DISABLE);
+ } else if (strcmp(arg, AHCIEM_DEFAULT) == 0) {
+ return (0);
+ }
+
+ errx(EXIT_USAGE, "invalid LED mode with -s: %s", arg);
+}
+
+static void
+ahciem_set(ahciem_t *ahci, const char *portstr, int fd, int port)
+{
+ ahci_ioc_em_set_t set;
+
+ bzero(&set, sizeof (set));
+
+ set.aiems_port = port;
+ set.aiems_op = AHCI_EM_IOC_SET_OP_SET;
+ set.aiems_leds = ahci->ahci_led;
+
+ if (ioctl(fd, AHCI_EM_IOC_SET, &set) != 0) {
+ warn("failed to set LEDs on %s", portstr);
+ ahci->ahci_err = 1;
+ }
+}
+
+static int
+ahciem_devinfo(di_node_t node, void *arg)
+{
+ char *driver, *mpath, *fullpath;
+ const char *sup;
+ int inst, fd;
+ uint_t i;
+ ahciem_t *ahci = arg;
+ di_minor_t m;
+ ahci_ioc_em_get_t get;
+
+ if ((driver = di_driver_name(node)) == NULL)
+ return (DI_WALK_CONTINUE);
+ if (strcmp(driver, "ahci") != 0)
+ return (DI_WALK_CONTINUE);
+ inst = di_instance(node);
+
+ m = DI_MINOR_NIL;
+ while ((m = di_minor_next(node, m)) != DI_MINOR_NIL) {
+ char *mname = di_minor_name(m);
+
+ if (mname != NULL && strcmp("devctl", mname) == 0)
+ break;
+ }
+
+ if (m == DI_MINOR_NIL) {
+ warnx("encountered ahci%d without devctl node", inst);
+ return (DI_WALK_PRUNECHILD);
+ }
+
+ if ((mpath = di_devfs_minor_path(m)) == NULL) {
+ warnx("failed to get path for ahci%d devctl minor", inst);
+ return (DI_WALK_PRUNECHILD);
+ }
+
+ if (asprintf(&fullpath, "/devices/%s", mpath) == -1) {
+ warn("failed to construct /devices path from %s", mpath);
+ return (DI_WALK_PRUNECHILD);
+ }
+
+ if ((fd = open(fullpath, O_RDWR)) < 0) {
+ warn("failed to open ahci%d devctl path %s", inst, fullpath);
+ goto out;
+ }
+
+ bzero(&get, sizeof (get));
+ if (ioctl(fd, AHCI_EM_IOC_GET, &get) != 0) {
+ warn("failed to get AHCI enclosure information for ahci%d",
+ inst);
+ ahci->ahci_err = 1;
+ goto out;
+ }
+
+ if ((get.aiemg_flags & AHCI_EM_FLAG_CONTROL_ACTIVITY) != 0) {
+ sup = ahciem_led_to_string(AHCI_EM_LED_IDENT_ENABLE |
+ AHCI_EM_LED_FAULT_ENABLE | AHCI_EM_LED_ACTIVITY_DISABLE);
+ } else {
+ sup = ahciem_led_to_string(AHCI_EM_LED_IDENT_ENABLE |
+ AHCI_EM_LED_FAULT_ENABLE);
+ }
+
+ for (i = 0; i < AHCI_EM_IOC_MAX_PORTS; i++) {
+ char port[64];
+ const char *state;
+
+ if (((1 << i) & get.aiemg_nports) == 0)
+ continue;
+
+ (void) snprintf(port, sizeof (port), "ahci%d/%u", inst, i);
+ if (!ahciem_match(ahci, port))
+ continue;
+
+ if (ahci->ahci_set) {
+ ahciem_set(ahci, port, fd, i);
+ continue;
+ }
+
+ state = ahciem_led_to_string(get.aiemg_status[i]);
+ (void) printf("%-20s %-12s %s,default\n", port, state, sup);
+ }
+
+out:
+ free(fullpath);
+ return (DI_WALK_PRUNECHILD);
+}
+
+int
+main(int argc, char *argv[])
+{
+ int c, i, ret;
+ di_node_t root;
+ ahciem_t ahci;
+
+ ahciem_progname = basename(argv[0]);
+
+ bzero(&ahci, sizeof (ahciem_t));
+ while ((c = getopt(argc, argv, ":s:")) != -1) {
+ switch (c) {
+ case 's':
+ ahci.ahci_set = B_TRUE;
+ ahci.ahci_led = ahciem_parse(optarg);
+ break;
+ case ':':
+ ahciem_usage("option -%c requires an operand\n",
+ optopt);
+ return (EXIT_USAGE);
+ case '?':
+ default:
+ ahciem_usage("unknown option: -%c\n", optopt);
+ return (EXIT_USAGE);
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+ ahci.ahci_argc = argc;
+ ahci.ahci_argv = argv;
+ if (argc > 0) {
+ ahci.ahci_found = calloc(argc, sizeof (boolean_t));
+ if (ahci.ahci_found == NULL) {
+ err(EXIT_FAILURE, "failed to alloc memory for %d "
+ "booleans", argc);
+ }
+ }
+
+ if ((root = di_init("/", DINFOCPYALL)) == DI_NODE_NIL) {
+ err(EXIT_FAILURE, "failed to open devinfo tree");
+ }
+
+ if (!ahci.ahci_set) {
+ (void) printf("%-20s %-12s %s\n", "PORT", "ACTIVE",
+ "SUPPORTED");
+ }
+
+ if (di_walk_node(root, DI_WALK_CLDFIRST, &ahci,
+ ahciem_devinfo) != 0) {
+ err(EXIT_FAILURE, "failed to walk devinfo tree");
+ }
+
+ ret = ahci.ahci_err;
+ for (i = 0; i < argc; i++) {
+ if (ahci.ahci_found[i])
+ continue;
+ warnx("failed to find ahci enclosure port \"%s\"",
+ ahci.ahci_argv[i]);
+ ret = 1;
+ }
+
+ return (ret);
+}
diff --git a/usr/src/pkg/manifests/driver-storage-ahci.mf b/usr/src/pkg/manifests/driver-storage-ahci.mf
index 031baffd31..e01b367360 100644
--- a/usr/src/pkg/manifests/driver-storage-ahci.mf
+++ b/usr/src/pkg/manifests/driver-storage-ahci.mf
@@ -21,6 +21,7 @@
#
# Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2018, Joyent, Inc.
#
#
@@ -39,11 +40,13 @@ set name=variant.arch value=i386
dir path=kernel group=sys
dir path=kernel/drv group=sys
dir path=kernel/drv/$(ARCH64) group=sys
+dir path=usr/lib/ahci
dir path=usr/share/man
dir path=usr/share/man/man7d
driver name=ahci alias=pciclass,010601 class=scsi-self-identifying \
perms="* 0644 root sys"
file path=kernel/drv/$(ARCH64)/ahci group=sys
+file path=usr/lib/ahci/ahciem mode=0555
file path=usr/share/man/man7d/ahci.7d
legacy pkg=SUNWahci \
desc="Advanced Host Controller Interface (AHCI) SATA HBA Driver" \
diff --git a/usr/src/uts/common/io/sata/adapters/ahci/ahci.c b/usr/src/uts/common/io/sata/adapters/ahci/ahci.c
index 811fc26bb6..227fc76b6f 100644
--- a/usr/src/uts/common/io/sata/adapters/ahci/ahci.c
+++ b/usr/src/uts/common/io/sata/adapters/ahci/ahci.c
@@ -22,6 +22,7 @@
/*
* Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
* Copyright 2018 Nexenta Systems, Inc. All rights reserved.
+ * Copyright (c) 2018, Joyent, Inc.
* Copyright 2018 OmniOS Community Edition (OmniOSce) Association.
*/
@@ -42,6 +43,42 @@
* handled these conditions, and blocked these requests. For the detailed
* information, please check with sdopen, sdclose and sdioctl routines.
*
+ *
+ * Enclosure Management Support
+ * ----------------------------
+ *
+ * The ahci driver has basic support for AHCI Enclosure Management (EM)
+ * services. The AHCI specification provides an area in the primary ahci BAR for
+ * posting data to send out to the enclosure management and provides a register
+ * that provides both information and control about this. While the
+ * specification allows for multiple forms of enclosure management, the only
+ * supported, and commonly found form, is the AHCI specified LED format. The LED
+ * format is often implemented as a one-way communication mechanism. Software
+ * can write out what it cares about into the aforementioned data buffer and
+ * then we wait for the transmission to be sent.
+ *
+ * This has some drawbacks. It means that we cannot know whether or not it has
+ * succeeded. This means we cannot ask hardware what it thinks the LEDs are
+ * set to. There's also the added unfortunate reality that firmware on the
+ * microcontroller driving this will often not show the LEDs if no drive is
+ * present and that actions taken may potentially cause this to get out of sync
+ * with what we expect it to be. For example, the specification does not
+ * describe what should happen if a drive is removed from the enclosure while
+ * this is set and what should happen when it returns. We can only infer that it
+ * should be the same.
+ *
+ * Because only a single command can be sent at any time and we don't want to
+ * interfere with controller I/O, we create a taskq dedicated to this that has a
+ * single thread. Both resets (which occur on attach and resume) and normal
+ * changes to the LED state will be driven through this taskq. Because the taskq
+ * has a single thread, this guarantees serial processing.
+ *
+ * Each userland-submitted task (basically not resets) has a reference counted
+ * task structure. This allows the thread that called it to be cancelled and
+ * have the system clean itself up. The user thread in ioctl blocks on a CV that
+ * can receive signals as it waits for completion. Note, there is no guarantee
+ * provided by the kernel that the first thread to enter the kernel will be the
+ * first one to change state.
*/
#include <sys/note.h>
@@ -61,6 +98,17 @@
#include <sys/fm/io/ddi.h>
/*
+ * EM Control header files
+ */
+#include <sys/types.h>
+#include <sys/file.h>
+#include <sys/errno.h>
+#include <sys/open.h>
+#include <sys/cred.h>
+#include <sys/ddi.h>
+#include <sys/sunddi.h>
+
+/*
* This is the string displayed by modinfo, etc.
*/
static char ahci_ident[] = "ahci driver";
@@ -222,6 +270,12 @@ static void ahci_log_serror_message(ahci_ctl_t *, uint8_t, uint32_t, int);
static void ahci_log(ahci_ctl_t *, uint_t, char *, ...);
#endif
+static boolean_t ahci_em_init(ahci_ctl_t *);
+static void ahci_em_fini(ahci_ctl_t *);
+static void ahci_em_suspend(ahci_ctl_t *);
+static void ahci_em_resume(ahci_ctl_t *);
+static int ahci_em_ioctl(dev_info_t *, int, intptr_t);
+
/*
* DMA attributes for the data buffer
@@ -316,7 +370,6 @@ static ddi_device_acc_attr_t accattr = {
DDI_DEFAULT_ACC
};
-
static struct dev_ops ahcictl_dev_ops = {
DEVO_REV, /* devo_rev */
0, /* refcnt */
@@ -326,7 +379,7 @@ static struct dev_ops ahcictl_dev_ops = {
ahci_attach, /* attach */
ahci_detach, /* detach */
nodev, /* no reset */
- (struct cb_ops *)0, /* driver operations */
+ NULL, /* driver operations */
NULL, /* bus operations */
NULL, /* power */
ahci_quiesce, /* quiesce */
@@ -411,6 +464,15 @@ boolean_t sb600_buf_64bit_dma_disable = B_TRUE;
*/
boolean_t sbxxx_commu_64bit_dma_disable = B_TRUE;
+/*
+ * These values control the default delay and default number of times to wait
+ * for an enclosure message to complete.
+ */
+uint_t ahci_em_reset_delay_ms = 1;
+uint_t ahci_em_reset_delay_count = 1000;
+uint_t ahci_em_tx_delay_ms = 1;
+uint_t ahci_em_tx_delay_count = 1000;
+
/*
* End of global tunable variable definition
@@ -581,6 +643,11 @@ ahci_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
return (DDI_FAILURE);
}
+ /*
+ * Reset the enclosure services.
+ */
+ ahci_em_resume(ahci_ctlp);
+
mutex_enter(&ahci_ctlp->ahcictl_mutex);
ahci_ctlp->ahcictl_flags &= ~AHCI_SUSPEND;
mutex_exit(&ahci_ctlp->ahcictl_mutex);
@@ -715,6 +782,16 @@ ahci_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
"hba capabilities extended = 0x%x", cap2_status);
}
+ if (cap_status & AHCI_HBA_CAP_EMS) {
+ ahci_ctlp->ahcictl_cap |= AHCI_CAP_EMS;
+ ahci_ctlp->ahcictl_em_loc =
+ ddi_get32(ahci_ctlp->ahcictl_ahci_acc_handle,
+ (uint32_t *)AHCI_GLOBAL_EM_LOC(ahci_ctlp));
+ ahci_ctlp->ahcictl_em_ctl =
+ ddi_get32(ahci_ctlp->ahcictl_ahci_acc_handle,
+ (uint32_t *)AHCI_GLOBAL_EM_CTL(ahci_ctlp));
+ }
+
#if AHCI_DEBUG
/* Get the interface speed supported by the HBA */
speed = (cap_status & AHCI_HBA_CAP_ISS) >> AHCI_HBA_CAP_ISS_SHIFT;
@@ -966,6 +1043,13 @@ intr_done:
attach_state |= AHCI_ATTACH_STATE_TIMEOUT_ENABLED;
+ if (!ahci_em_init(ahci_ctlp)) {
+ cmn_err(CE_WARN, "!ahci%d: failed to initialize enclosure "
+ "services", instance);
+ goto err_out;
+ }
+ attach_state |= AHCI_ATTACH_STATE_ENCLOSURE;
+
if (ahci_register_sata_hba_tran(ahci_ctlp, cap_status)) {
cmn_err(CE_WARN, "!ahci%d: sata hba tran registration failed",
instance);
@@ -990,6 +1074,10 @@ err_out:
ahci_fm_ereport(ahci_ctlp, DDI_FM_DEVICE_NO_RESPONSE);
ddi_fm_service_impact(ahci_ctlp->ahcictl_dip, DDI_SERVICE_LOST);
+ if (attach_state & AHCI_ATTACH_STATE_ENCLOSURE) {
+ ahci_em_fini(ahci_ctlp);
+ }
+
if (attach_state & AHCI_ATTACH_STATE_TIMEOUT_ENABLED) {
mutex_enter(&ahci_ctlp->ahcictl_mutex);
(void) untimeout(ahci_ctlp->ahcictl_timeout_id);
@@ -1064,6 +1152,8 @@ ahci_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
return (DDI_FAILURE);
}
+ ahci_em_fini(ahci_ctlp);
+
mutex_enter(&ahci_ctlp->ahcictl_mutex);
/* stop the watchdog handler */
@@ -1113,6 +1203,8 @@ ahci_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
ahci_ctlp->ahcictl_flags |= AHCI_SUSPEND;
+ ahci_em_suspend(ahci_ctlp);
+
/* stop the watchdog handler */
if (ahci_ctlp->ahcictl_timeout_id) {
(void) untimeout(ahci_ctlp->ahcictl_timeout_id);
@@ -1263,7 +1355,7 @@ ahci_register_sata_hba_tran(ahci_ctl_t *ahci_ctlp, uint32_t cap_status)
* pwrmgt_ops needs to be updated
*/
sata_hba_tran->sata_tran_pwrmgt_ops = NULL;
- sata_hba_tran->sata_tran_ioctl = NULL;
+ sata_hba_tran->sata_tran_ioctl = ahci_em_ioctl;
ahci_ctlp->ahcictl_sata_hba_tran = sata_hba_tran;
@@ -10211,7 +10303,10 @@ ahci_log(ahci_ctl_t *ahci_ctlp, uint_t level, char *fmt, ...)
*
* This function is called when the system is single-threaded at high
* PIL with preemption disabled. Therefore, this function must not be
- * blocked.
+ * blocked. Because no taskqs are running, there is no need for us to
+ * take any action for enclosure services which are running in the
+ * taskq context, especially as no interrupts are generated by it nor
+ * are any messages expected to come in.
*
* This function returns DDI_SUCCESS on success, or DDI_FAILURE on failure.
* DDI_FAILURE indicates an error condition and should almost never happen.
@@ -10329,3 +10424,465 @@ ahci_flush_doneq(ahci_port_t *ahci_portp)
mutex_enter(&ahci_portp->ahciport_mutex);
}
}
+
+/*
+ * Sets the state for the specified port on the controller to desired state.
+ * This must be run in the context of the enclosure taskq which ensures that
+ * only one event is outstanding at any time.
+ */
+static boolean_t
+ahci_em_set_led(ahci_ctl_t *ahci_ctlp, uint8_t port, ahci_em_led_state_t desire)
+{
+ ahci_em_led_msg_t msg;
+ ahci_em_msg_hdr_t hdr;
+ uint32_t msgval, hdrval;
+ uint_t i, max_delay = ahci_em_tx_delay_count;
+
+ msg.alm_hba = port;
+ msg.alm_pminfo = 0;
+ msg.alm_value = 0;
+
+ if (desire & AHCI_EM_LED_IDENT_ENABLE) {
+ msg.alm_value |= AHCI_LED_ON << AHCI_LED_IDENT_OFF;
+ }
+
+ if (desire & AHCI_EM_LED_FAULT_ENABLE) {
+ msg.alm_value |= AHCI_LED_ON << AHCI_LED_FAULT_OFF;
+ }
+
+ if ((ahci_ctlp->ahcictl_em_ctl & AHCI_HBA_EM_CTL_ATTR_ALHD) == 0 &&
+ (desire & AHCI_EM_LED_ACTIVITY_DISABLE) == 0) {
+ msg.alm_value |= AHCI_LED_ON << AHCI_LED_ACTIVITY_OFF;
+ }
+
+ hdr.aemh_rsvd = 0;
+ hdr.aemh_mlen = sizeof (ahci_em_led_msg_t);
+ hdr.aemh_dlen = 0;
+ hdr.aemh_mtype = AHCI_EM_MSG_TYPE_LED;
+
+ bcopy(&msg, &msgval, sizeof (msgval));
+ bcopy(&hdr, &hdrval, sizeof (hdrval));
+
+ /*
+ * First, make sure we can transmit. We should not have been placed in a
+ * situation where an outstanding transmission is going on.
+ */
+ for (i = 0; i < max_delay; i++) {
+ uint32_t val;
+
+ val = ddi_get32(ahci_ctlp->ahcictl_ahci_acc_handle,
+ (uint32_t *)AHCI_GLOBAL_EM_CTL(ahci_ctlp));
+ if ((val & AHCI_HBA_EM_CTL_CTL_TM) == 0)
+ break;
+
+ delay(drv_usectohz(ahci_em_tx_delay_ms * 1000));
+ }
+
+ if (i == max_delay)
+ return (B_FALSE);
+
+ ddi_put32(ahci_ctlp->ahcictl_ahci_acc_handle,
+ (uint32_t *)ahci_ctlp->ahcictl_em_tx_off, hdrval);
+ ddi_put32(ahci_ctlp->ahcictl_ahci_acc_handle,
+ (uint32_t *)(ahci_ctlp->ahcictl_em_tx_off + 4), msgval);
+ ddi_put32(ahci_ctlp->ahcictl_ahci_acc_handle,
+ (uint32_t *)AHCI_GLOBAL_EM_CTL(ahci_ctlp), AHCI_HBA_EM_CTL_CTL_TM);
+
+ for (i = 0; i < max_delay; i++) {
+ uint32_t val;
+
+ val = ddi_get32(ahci_ctlp->ahcictl_ahci_acc_handle,
+ (uint32_t *)AHCI_GLOBAL_EM_CTL(ahci_ctlp));
+ if ((val & AHCI_HBA_EM_CTL_CTL_TM) == 0)
+ break;
+
+ delay(drv_usectohz(ahci_em_tx_delay_ms * 1000));
+ }
+
+ if (i == max_delay)
+ return (B_FALSE);
+
+ return (B_TRUE);
+}
+
+typedef struct ahci_em_led_task_arg {
+ ahci_ctl_t *aelta_ctl;
+ uint8_t aelta_port;
+ uint_t aelta_op;
+ ahci_em_led_state_t aelta_state;
+ uint_t aelta_ret;
+ kcondvar_t aelta_cv;
+ uint_t aelta_ref;
+} ahci_em_led_task_arg_t;
+
+static void
+ahci_em_led_task_free(ahci_em_led_task_arg_t *task)
+{
+ ASSERT3U(task->aelta_ref, ==, 0);
+ cv_destroy(&task->aelta_cv);
+ kmem_free(task, sizeof (*task));
+}
+
+static void
+ahci_em_led_task(void *arg)
+{
+ boolean_t ret, cleanup = B_FALSE;
+ ahci_em_led_task_arg_t *led = arg;
+ ahci_em_led_state_t state;
+
+ mutex_enter(&led->aelta_ctl->ahcictl_mutex);
+ if (led->aelta_ctl->ahcictl_em_flags != AHCI_EM_USABLE) {
+ led->aelta_ret = EIO;
+ mutex_exit(&led->aelta_ctl->ahcictl_mutex);
+ return;
+ }
+
+ state = led->aelta_ctl->ahcictl_em_state[led->aelta_port];
+ mutex_exit(&led->aelta_ctl->ahcictl_mutex);
+
+ switch (led->aelta_op) {
+ case AHCI_EM_IOC_SET_OP_ADD:
+ state |= led->aelta_state;
+ break;
+ case AHCI_EM_IOC_SET_OP_REM:
+ state &= ~led->aelta_state;
+ break;
+ case AHCI_EM_IOC_SET_OP_SET:
+ state = led->aelta_state;
+ break;
+ default:
+ led->aelta_ret = ENOTSUP;
+ return;
+ }
+
+ ret = ahci_em_set_led(led->aelta_ctl, led->aelta_port, state);
+
+ mutex_enter(&led->aelta_ctl->ahcictl_mutex);
+ if (ret) {
+ led->aelta_ctl->ahcictl_em_state[led->aelta_port] =
+ led->aelta_state;
+ led->aelta_ret = 0;
+ } else {
+ led->aelta_ret = EIO;
+ led->aelta_ctl->ahcictl_em_flags |= AHCI_EM_TIMEOUT;
+ }
+ led->aelta_ref--;
+ if (led->aelta_ref > 0) {
+ cv_signal(&led->aelta_cv);
+ } else {
+ cleanup = B_TRUE;
+ }
+ mutex_exit(&led->aelta_ctl->ahcictl_mutex);
+
+ if (cleanup) {
+ ahci_em_led_task_free(led);
+ }
+}
+
+static void
+ahci_em_reset(void *arg)
+{
+ uint_t i, max_delay = ahci_em_reset_delay_count;
+ ahci_ctl_t *ahci_ctlp = arg;
+
+ /*
+ * We've been asked to reset the device. The caller should have set the
+ * resetting flag. Make sure that we don't have a request to quiesce.
+ */
+ mutex_enter(&ahci_ctlp->ahcictl_mutex);
+ ASSERT(ahci_ctlp->ahcictl_em_flags & AHCI_EM_RESETTING);
+ if (ahci_ctlp->ahcictl_em_flags & AHCI_EM_QUIESCE) {
+ ahci_ctlp->ahcictl_em_flags &= ~AHCI_EM_RESETTING;
+ mutex_exit(&ahci_ctlp->ahcictl_mutex);
+ return;
+ }
+ mutex_exit(&ahci_ctlp->ahcictl_mutex);
+
+ ddi_put32(ahci_ctlp->ahcictl_ahci_acc_handle,
+ (uint32_t *)AHCI_GLOBAL_EM_CTL(ahci_ctlp), AHCI_HBA_EM_CTL_CTL_RST);
+ for (i = 0; i < max_delay; i++) {
+ uint32_t val;
+
+ val = ddi_get32(ahci_ctlp->ahcictl_ahci_acc_handle,
+ (uint32_t *)AHCI_GLOBAL_EM_CTL(ahci_ctlp));
+ if ((val & AHCI_HBA_EM_CTL_CTL_RST) == 0)
+ break;
+
+ delay(drv_usectohz(ahci_em_reset_delay_ms * 1000));
+ }
+
+ if (i == max_delay) {
+ mutex_enter(&ahci_ctlp->ahcictl_mutex);
+ ahci_ctlp->ahcictl_em_flags &= ~AHCI_EM_RESETTING;
+ ahci_ctlp->ahcictl_em_flags |= AHCI_EM_TIMEOUT;
+ mutex_exit(&ahci_ctlp->ahcictl_mutex);
+ cmn_err(CE_WARN, "!ahci%d: enclosure timed out resetting",
+ ddi_get_instance(ahci_ctlp->ahcictl_dip));
+ return;
+ }
+
+ for (i = 0; i < ahci_ctlp->ahcictl_num_ports; i++) {
+
+ if (!AHCI_PORT_IMPLEMENTED(ahci_ctlp, i))
+ continue;
+
+ /*
+ * Try to flush all the LEDs as part of reset. If it fails,
+ * drive on.
+ */
+ if (!ahci_em_set_led(ahci_ctlp, i,
+ ahci_ctlp->ahcictl_em_state[i])) {
+ mutex_enter(&ahci_ctlp->ahcictl_mutex);
+ ahci_ctlp->ahcictl_em_flags &= ~AHCI_EM_RESETTING;
+ ahci_ctlp->ahcictl_em_flags |= AHCI_EM_TIMEOUT;
+ mutex_exit(&ahci_ctlp->ahcictl_mutex);
+ cmn_err(CE_WARN, "!ahci%d: enclosure timed out "
+ "setting port %u",
+ ddi_get_instance(ahci_ctlp->ahcictl_dip), i);
+ return;
+ }
+ }
+
+ mutex_enter(&ahci_ctlp->ahcictl_mutex);
+ ahci_ctlp->ahcictl_em_flags &= ~AHCI_EM_RESETTING;
+ ahci_ctlp->ahcictl_em_flags |= AHCI_EM_READY;
+ mutex_exit(&ahci_ctlp->ahcictl_mutex);
+}
+
+static boolean_t
+ahci_em_init(ahci_ctl_t *ahci_ctlp)
+{
+ char name[128];
+
+ /*
+ * First make sure we actually have enclosure services and if so, that
+ * we have the hardware support that we care about for this.
+ */
+ if (ahci_ctlp->ahcictl_em_loc == 0 ||
+ (ahci_ctlp->ahcictl_em_ctl & AHCI_HBA_EM_CTL_SUPP_LED) == 0)
+ return (B_TRUE);
+
+ /*
+ * Next, make sure that the buffer is large enough for us. We need two
+ * dwords or 8 bytes. The location register is stored in dwords.
+ */
+ if ((ahci_ctlp->ahcictl_em_loc & AHCI_HBA_EM_LOC_SZ_MASK) <
+ AHCI_EM_BUFFER_MIN) {
+ return (B_TRUE);
+ }
+
+ ahci_ctlp->ahcictl_em_flags |= AHCI_EM_PRESENT;
+
+ ahci_ctlp->ahcictl_em_tx_off = ((ahci_ctlp->ahcictl_em_loc &
+ AHCI_HBA_EM_LOC_OFST_MASK) >> AHCI_HBA_EM_LOC_OFST_SHIFT) * 4;
+ ahci_ctlp->ahcictl_em_tx_off += ahci_ctlp->ahcictl_ahci_addr;
+
+ bzero(ahci_ctlp->ahcictl_em_state,
+ sizeof (ahci_ctlp->ahcictl_em_state));
+
+ (void) snprintf(name, sizeof (name), "ahcti_em_taskq%d",
+ ddi_get_instance(ahci_ctlp->ahcictl_dip));
+ if ((ahci_ctlp->ahcictl_em_taskq =
+ ddi_taskq_create(ahci_ctlp->ahcictl_dip, name, 1,
+ TASKQ_DEFAULTPRI, 0)) == NULL) {
+ cmn_err(CE_WARN, "!ahci%d: ddi_tasq_create failed for em "
+ "services", ddi_get_instance(ahci_ctlp->ahcictl_dip));
+ return (B_FALSE);
+ }
+
+ mutex_enter(&ahci_ctlp->ahcictl_mutex);
+ ahci_ctlp->ahcictl_em_flags |= AHCI_EM_RESETTING;
+ mutex_exit(&ahci_ctlp->ahcictl_mutex);
+ (void) ddi_taskq_dispatch(ahci_ctlp->ahcictl_em_taskq, ahci_em_reset,
+ ahci_ctlp, DDI_SLEEP);
+
+ return (B_TRUE);
+}
+
+static int
+ahci_em_ioctl_get(ahci_ctl_t *ahci_ctlp, intptr_t arg)
+{
+ int i;
+ ahci_ioc_em_get_t get;
+
+ bzero(&get, sizeof (get));
+ get.aiemg_nports = ahci_ctlp->ahcictl_ports_implemented;
+ if ((ahci_ctlp->ahcictl_em_ctl & AHCI_HBA_EM_CTL_ATTR_ALHD) == 0) {
+ get.aiemg_flags |= AHCI_EM_FLAG_CONTROL_ACTIVITY;
+ }
+
+ mutex_enter(&ahci_ctlp->ahcictl_mutex);
+ for (i = 0; i < ahci_ctlp->ahcictl_num_ports; i++) {
+ if (!AHCI_PORT_IMPLEMENTED(ahci_ctlp, i)) {
+ continue;
+ }
+ get.aiemg_status[i] = ahci_ctlp->ahcictl_em_state[i];
+ }
+ mutex_exit(&ahci_ctlp->ahcictl_mutex);
+
+ if (ddi_copyout(&get, (void *)arg, sizeof (get), 0) != 0)
+ return (EFAULT);
+
+ return (0);
+}
+
+static int
+ahci_em_ioctl_set(ahci_ctl_t *ahci_ctlp, intptr_t arg)
+{
+ int ret;
+ ahci_ioc_em_set_t set;
+ ahci_em_led_task_arg_t *task;
+ boolean_t signal, cleanup;
+
+ if (ddi_copyin((void *)arg, &set, sizeof (set), 0) != 0)
+ return (EFAULT);
+
+ if (set.aiems_port > ahci_ctlp->ahcictl_num_ports)
+ return (EINVAL);
+
+ if (!AHCI_PORT_IMPLEMENTED(ahci_ctlp, set.aiems_port)) {
+ return (EINVAL);
+ }
+
+ if ((set.aiems_leds & ~(AHCI_EM_LED_IDENT_ENABLE |
+ AHCI_EM_LED_FAULT_ENABLE |
+ AHCI_EM_LED_ACTIVITY_DISABLE)) != 0) {
+ return (EINVAL);
+ }
+
+ switch (set.aiems_op) {
+ case AHCI_EM_IOC_SET_OP_ADD:
+ case AHCI_EM_IOC_SET_OP_REM:
+ case AHCI_EM_IOC_SET_OP_SET:
+ break;
+ default:
+ return (EINVAL);
+ }
+
+ if ((set.aiems_leds & AHCI_EM_LED_ACTIVITY_DISABLE) != 0 &&
+ ((ahci_ctlp->ahcictl_em_ctl & AHCI_HBA_EM_CTL_ATTR_ALHD) != 0)) {
+ return (ENOTSUP);
+ }
+
+ task = kmem_alloc(sizeof (*task), KM_NOSLEEP | KM_NORMALPRI);
+ if (task == NULL) {
+ return (ENOMEM);
+ }
+
+ task->aelta_ctl = ahci_ctlp;
+ task->aelta_port = (uint8_t)set.aiems_port;
+ task->aelta_op = set.aiems_op;
+ task->aelta_state = set.aiems_leds;
+
+ cv_init(&task->aelta_cv, NULL, CV_DRIVER, NULL);
+
+ /*
+ * Initialize the reference count to two. One for us and one for the
+ * taskq. This will be used in case we get canceled.
+ */
+ task->aelta_ref = 2;
+
+ /*
+ * Once dispatched, the task state is protected by our global mutex.
+ */
+ (void) ddi_taskq_dispatch(ahci_ctlp->ahcictl_em_taskq,
+ ahci_em_led_task, task, DDI_SLEEP);
+
+ signal = B_FALSE;
+ mutex_enter(&ahci_ctlp->ahcictl_mutex);
+ while (task->aelta_ref > 1) {
+ if (cv_wait_sig(&task->aelta_cv, &ahci_ctlp->ahcictl_mutex) ==
+ 0) {
+ signal = B_TRUE;
+ break;
+ }
+ }
+
+ /*
+ * Remove our reference count. If we were woken up because of a signal
+ * then the taskq may still be dispatched. In which case we shouldn't
+ * free this memory until it is done. In that case, the taskq will take
+ * care of it.
+ */
+ task->aelta_ref--;
+ cleanup = (task->aelta_ref == 0);
+ if (signal) {
+ ret = EINTR;
+ } else {
+ ret = task->aelta_ret;
+ }
+ mutex_exit(&ahci_ctlp->ahcictl_mutex);
+
+ if (cleanup) {
+ ahci_em_led_task_free(task);
+ }
+
+ return (ret);
+}
+
+static int
+ahci_em_ioctl(dev_info_t *dip, int cmd, intptr_t arg)
+{
+ int inst;
+ ahci_ctl_t *ahci_ctlp;
+
+ inst = ddi_get_instance(dip);
+ if ((ahci_ctlp = ddi_get_soft_state(ahci_statep, inst)) == NULL) {
+ return (ENXIO);
+ }
+
+ switch (cmd) {
+ case AHCI_EM_IOC_GET:
+ return (ahci_em_ioctl_get(ahci_ctlp, arg));
+ case AHCI_EM_IOC_SET:
+ return (ahci_em_ioctl_set(ahci_ctlp, arg));
+ default:
+ return (ENOTTY);
+ }
+
+}
+
+static void
+ahci_em_quiesce(ahci_ctl_t *ahci_ctlp)
+{
+ ASSERT(ahci_ctlp->ahcictl_em_flags & AHCI_EM_PRESENT);
+
+ mutex_enter(&ahci_ctlp->ahcictl_mutex);
+ ahci_ctlp->ahcictl_em_flags |= AHCI_EM_QUIESCE;
+ mutex_exit(&ahci_ctlp->ahcictl_mutex);
+
+ ddi_taskq_wait(ahci_ctlp->ahcictl_em_taskq);
+}
+
+static void
+ahci_em_suspend(ahci_ctl_t *ahci_ctlp)
+{
+ ahci_em_quiesce(ahci_ctlp);
+
+ mutex_enter(&ahci_ctlp->ahcictl_mutex);
+ ahci_ctlp->ahcictl_em_flags &= ~AHCI_EM_READY;
+ mutex_exit(&ahci_ctlp->ahcictl_mutex);
+}
+
+static void
+ahci_em_resume(ahci_ctl_t *ahci_ctlp)
+{
+ mutex_enter(&ahci_ctlp->ahcictl_mutex);
+ ahci_ctlp->ahcictl_em_flags |= AHCI_EM_RESETTING;
+ mutex_exit(&ahci_ctlp->ahcictl_mutex);
+
+ (void) ddi_taskq_dispatch(ahci_ctlp->ahcictl_em_taskq, ahci_em_reset,
+ ahci_ctlp, DDI_SLEEP);
+}
+
+static void
+ahci_em_fini(ahci_ctl_t *ahci_ctlp)
+{
+ if ((ahci_ctlp->ahcictl_em_flags & AHCI_EM_PRESENT) == 0) {
+ return;
+ }
+
+ ahci_em_quiesce(ahci_ctlp);
+ ddi_taskq_destroy(ahci_ctlp->ahcictl_em_taskq);
+ ahci_ctlp->ahcictl_em_taskq = NULL;
+}
diff --git a/usr/src/uts/common/io/sata/impl/sata.c b/usr/src/uts/common/io/sata/impl/sata.c
index fc168e68cc..29b7cf2005 100644
--- a/usr/src/uts/common/io/sata/impl/sata.c
+++ b/usr/src/uts/common/io/sata/impl/sata.c
@@ -25,6 +25,7 @@
/*
* Copyright 2017 Nexenta Systems, Inc. All rights reserved.
* Copyright 2016 Argo Technologies SA
+ * Copyright (c) 2018, Joyent, Inc.
*/
/*
@@ -1349,7 +1350,8 @@ sata_hba_ioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *credp,
}
/* read devctl ioctl data */
- if (cmd != DEVCTL_AP_CONTROL) {
+ if (cmd != DEVCTL_AP_CONTROL && cmd >= DEVCTL_IOC &&
+ cmd <= DEVCTL_IOC_MAX) {
if (ndi_dc_allochdl((void *)arg, &dcp) != NDI_SUCCESS)
return (EFAULT);
@@ -1677,9 +1679,13 @@ sata_hba_ioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *credp,
if (dcp) {
ndi_dc_freehdl(dcp);
}
- mutex_enter(&SATA_CPORT_INFO(sata_hba_inst, cport)->cport_mutex);
- cportinfo->cport_event_flags &= ~SATA_APCTL_LOCK_PORT_BUSY;
- mutex_exit(&SATA_CPORT_INFO(sata_hba_inst, cport)->cport_mutex);
+
+ if (cmd >= DEVCTL_IOC && cmd <= DEVCTL_IOC_MAX) {
+ mutex_enter(&SATA_CPORT_INFO(sata_hba_inst,
+ cport)->cport_mutex);
+ cportinfo->cport_event_flags &= ~SATA_APCTL_LOCK_PORT_BUSY;
+ mutex_exit(&SATA_CPORT_INFO(sata_hba_inst, cport)->cport_mutex);
+ }
return (rv);
}
@@ -9214,7 +9220,7 @@ sata_build_lsense_page_30(
*/
static int
sata_build_lsense_page_0e(sata_drive_info_t *sdinfo, uint8_t *buf,
- sata_pkt_txlate_t *spx)
+ sata_pkt_txlate_t *spx)
{
struct start_stop_cycle_counter_log *log_page;
int i, rval, index;
@@ -10907,7 +10913,7 @@ sata_offline_device(sata_hba_inst_t *sata_hba_inst,
static dev_info_t *
sata_create_target_node(dev_info_t *dip, sata_hba_inst_t *sata_hba_inst,
- sata_address_t *sata_addr)
+ sata_address_t *sata_addr)
{
dev_info_t *cdip = NULL;
int rval;
@@ -11095,7 +11101,7 @@ fail:
*/
static void
sata_remove_target_node(sata_hba_inst_t *sata_hba_inst,
- sata_address_t *sata_addr)
+ sata_address_t *sata_addr)
{
dev_info_t *tdip;
uint8_t cport = sata_addr->cport;
@@ -12237,7 +12243,7 @@ sata_init_write_cache_mode(sata_drive_info_t *sdinfo)
*/
static int
sata_validate_sata_address(sata_hba_inst_t *sata_hba_inst, int cport,
- int pmport, int qual)
+ int pmport, int qual)
{
if (qual == SATA_ADDR_DCPORT && pmport != 0)
goto invalid_address;
@@ -12270,7 +12276,7 @@ invalid_address:
*/
static int
sata_validate_scsi_address(sata_hba_inst_t *sata_hba_inst,
- struct scsi_address *ap, sata_device_t *sata_device)
+ struct scsi_address *ap, sata_device_t *sata_device)
{
int cport, pmport, qual, rval;
diff --git a/usr/src/uts/common/sys/sata/adapters/ahci/ahciem.h b/usr/src/uts/common/sys/sata/adapters/ahci/ahciem.h
new file mode 100644
index 0000000000..7d100c882a
--- /dev/null
+++ b/usr/src/uts/common/sys/sata/adapters/ahci/ahciem.h
@@ -0,0 +1,74 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source. A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright (c) 2018 Joyent, Inc.
+ */
+
+#ifndef _AHCIEM_H
+#define _AHCIEM_H
+
+/*
+ * Private interface to AHCI Enclosure services
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <sys/types.h>
+
+#define AHCI_EM_IOC (('a' << 24) | ('e' << 16) | ('m' << 8))
+#define AHCI_EM_IOC_GET (AHCI_EM_IOC | 0x00)
+#define AHCI_EM_IOC_SET (AHCI_EM_IOC | 0x01)
+
+#define AHCI_EM_IOC_MAX_PORTS 32
+
+/*
+ * The default state for LEDs is to have ident and fault disabled and activity
+ * enabled, if in hardware control.
+ */
+typedef enum ahci_em_led_state {
+ AHCI_EM_LED_IDENT_ENABLE = 1 << 0,
+ AHCI_EM_LED_FAULT_ENABLE = 1 << 1,
+ AHCI_EM_LED_ACTIVITY_DISABLE = 1 << 2
+} ahci_em_led_state_t;
+
+#define AHCI_EM_FLAG_CONTROL_ACTIVITY 0x01
+
+typedef struct ahci_ioc_em_get {
+ uint_t aiemg_nports;
+ uint_t aiemg_flags;
+ uint_t aiemg_status[AHCI_EM_IOC_MAX_PORTS];
+} ahci_ioc_em_get_t;
+
+
+/*
+ * Values set in aiems_op that control the behavior of the ioctl. If ADD is set,
+ * the listed flags are added to the current set. If, REM is set, then the flags
+ * are removed. If SET is set, then the flags are replaced.
+ */
+#define AHCI_EM_IOC_SET_OP_ADD 0x01
+#define AHCI_EM_IOC_SET_OP_REM 0x02
+#define AHCI_EM_IOC_SET_OP_SET 0x03
+
+typedef struct ahci_ioc_em_set {
+ uint_t aiems_port;
+ uint_t aiems_op;
+ uint_t aiems_leds;
+ uint_t aiems_pad;
+} ahci_ioc_em_set_t;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _AHCIEM_H */
diff --git a/usr/src/uts/common/sys/sata/adapters/ahci/ahcivar.h b/usr/src/uts/common/sys/sata/adapters/ahci/ahcivar.h
index 4e55863414..8195242dd3 100644
--- a/usr/src/uts/common/sys/sata/adapters/ahci/ahcivar.h
+++ b/usr/src/uts/common/sys/sata/adapters/ahci/ahcivar.h
@@ -22,6 +22,7 @@
/*
* Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
* Copyright 2013 Nexenta Systems, Inc. All rights reserved.
+ * Copyright (c) 2018, Joyent, Inc.
*/
@@ -32,6 +33,8 @@
extern "C" {
#endif
+#include <sys/sata/adapters/ahci/ahciem.h>
+
/*
* AHCI address qualifier flags (in qual field of ahci_addr struct).
*/
@@ -364,6 +367,16 @@ _NOTE(MUTEX_PROTECTS_DATA(ahci_port_t::ahciport_mutex,
else \
AHCIPORT_PMSTATE(portp, addrp) = state;
+typedef enum ahci_em_flags {
+ AHCI_EM_PRESENT = 1 << 0,
+ AHCI_EM_RESETTING = 1 << 1,
+ AHCI_EM_TIMEOUT = 1 << 2,
+ AHCI_EM_QUIESCE = 1 << 3,
+ AHCI_EM_READY = 1 << 4,
+} ahci_em_flags_t;
+
+#define AHCI_EM_USABLE (AHCI_EM_PRESENT | AHCI_EM_READY)
+
typedef struct ahci_ctl {
dev_info_t *ahcictl_dip;
@@ -440,6 +453,16 @@ typedef struct ahci_ctl {
/* FMA capabilities */
int ahcictl_fm_cap;
+
+ /*
+ * Enclosure information
+ */
+ uint32_t ahcictl_em_loc;
+ uint32_t ahcictl_em_ctl;
+ uintptr_t ahcictl_em_tx_off;
+ ahci_em_flags_t ahcictl_em_flags;
+ ddi_taskq_t *ahcictl_em_taskq;
+ ahci_em_led_state_t ahcictl_em_state[AHCI_MAX_PORTS];
} ahci_ctl_t;
/* Warlock annotation */
@@ -492,6 +515,8 @@ _NOTE(MUTEX_PROTECTS_DATA(ahci_ctl_t::ahcictl_mutex,
#define AHCI_CAP_PMULT_FBSS 0x400
/* Software Reset FIS cannot set pmport with 0xf for direct access device */
#define AHCI_CAP_SRST_NO_HOSTPORT 0x800
+/* Enclosure Management Services available */
+#define AHCI_CAP_EMS 0x1000
/* Flags controlling the restart port behavior */
#define AHCI_PORT_RESET 0x0001 /* Reset the port */
@@ -531,6 +556,7 @@ _NOTE(MUTEX_PROTECTS_DATA(ahci_ctl_t::ahcictl_mutex,
#define AHCI_ATTACH_STATE_PORT_ALLOC (0x1 << 7)
#define AHCI_ATTACH_STATE_HW_INIT (0x1 << 8)
#define AHCI_ATTACH_STATE_TIMEOUT_ENABLED (0x1 << 9)
+#define AHCI_ATTACH_STATE_ENCLOSURE (0x1 << 10)
/* Interval used for delay */
#define AHCI_10MS_TICKS (drv_usectohz(10000)) /* ticks in 10 ms */
@@ -610,6 +636,43 @@ extern uint32_t ahci_debug_flags;
#endif /* DEBUG */
+/*
+ * Minimum size required for the enclosure message buffer. This value is in
+ * 4-byte quantities. So we need to multiply it by two.
+ */
+#define AHCI_EM_BUFFER_MIN 2
+
+/*
+ * Enclosure Management LED message format values
+ */
+#define AHCI_LED_OFF 0
+#define AHCI_LED_ON 1
+
+#define AHCI_LED_ACTIVITY_OFF 0
+#define AHCI_LED_IDENT_OFF 3
+#define AHCI_LED_FAULT_OFF 6
+
+#define AHCI_LED_MASK 0x7
+
+#define AHCI_EM_MSG_TYPE_LED 0
+#define AHCI_EM_MSG_TYPE_SAFTE 1
+#define AHCI_EM_MSG_TYPE_SES 2
+#define AHCI_EM_MSG_TYPE_SGPIO 3
+
+#pragma pack(1)
+typedef struct ahci_em_led_msg {
+ uint8_t alm_hba;
+ uint8_t alm_pminfo;
+ uint16_t alm_value;
+} ahci_em_led_msg_t;
+
+typedef struct ahci_em_msg_hdr {
+ uint8_t aemh_rsvd;
+ uint8_t aemh_mlen;
+ uint8_t aemh_dlen;
+ uint8_t aemh_mtype;
+} ahci_em_msg_hdr_t;
+#pragma pack()
#ifdef __cplusplus
}