diff options
-rw-r--r-- | usr/src/cmd/Makefile | 3 | ||||
-rw-r--r-- | usr/src/cmd/ahciem/Makefile | 39 | ||||
-rw-r--r-- | usr/src/cmd/ahciem/ahciem.c | 302 | ||||
-rw-r--r-- | usr/src/pkg/manifests/driver-storage-ahci.mf | 3 | ||||
-rw-r--r-- | usr/src/uts/common/io/sata/adapters/ahci/ahci.c | 565 | ||||
-rw-r--r-- | usr/src/uts/common/io/sata/impl/sata.c | 24 | ||||
-rw-r--r-- | usr/src/uts/common/sys/sata/adapters/ahci/ahciem.h | 74 | ||||
-rw-r--r-- | usr/src/uts/common/sys/sata/adapters/ahci/ahcivar.h | 63 |
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 } |