Module Name: src
Committed By: bouyer
Date: Sat Nov 24 18:23:29 UTC 2018
Modified Files:
src/sys/arch/amd64/conf: ALL GENERIC XEN3_DOM0
src/sys/arch/i386/conf: ALL GENERIC XEN3PAE_DOM0
src/sys/dev/pci: files.pci
Added Files:
src/sys/dev/pci: mfii.c
Log Message:
Add mpii(4), a driver for LSI Megaraid Fusion controllers.
Ported from OpenBSD. This driver is MP-safe.
Note that the earlier fusion controllers (Megaraid 2208, codenamed Thunderbold)
are also supported by mfi(4). mpii will take precedence if both drivers
are enabled.
Tested on a
mfii0 at pci6 dev 0 function 0: "PERC H740P Adapter ", firmware 50.3.0-1512, 819
2MB cache
mfii0: interrupting at ioapic2 pin 2
scsibus0 at mfii0: 64 targets, 8 luns per target
scsibus0: waiting 2 seconds for devices to settle...
sd0 at scsibus0 target 0 lun 0: <DELL, PERC H740P Adp, 5.03> disk fixed
sd0: fabricating a geometry
sd0: 99 GB, 102399 cyl, 64 head, 32 sec, 512 bytes/sect x 209714688 sectors
sd0: tagged queueing
sd1 at scsibus0 target 1 lun 0: <DELL, PERC H740P Adp, 5.03> disk fixed
sd1: fabricating a geometry
sd1: 22254 GB, 22788608 cyl, 64 head, 32 sec, 512 bytes/sect x 46671069696
sectors
sd1: fabricating a geometry
It supports bioctl(8) ioctls, as well as sensors for the BBU and logical
drives.
Sponsored by LIP6.
To generate a diff of this commit:
cvs rdiff -u -r1.107 -r1.108 src/sys/arch/amd64/conf/ALL
cvs rdiff -u -r1.507 -r1.508 src/sys/arch/amd64/conf/GENERIC
cvs rdiff -u -r1.158 -r1.159 src/sys/arch/amd64/conf/XEN3_DOM0
cvs rdiff -u -r1.457 -r1.458 src/sys/arch/i386/conf/ALL
cvs rdiff -u -r1.1193 -r1.1194 src/sys/arch/i386/conf/GENERIC
cvs rdiff -u -r1.7 -r1.8 src/sys/arch/i386/conf/XEN3PAE_DOM0
cvs rdiff -u -r1.409 -r1.410 src/sys/dev/pci/files.pci
cvs rdiff -u -r0 -r1.1 src/sys/dev/pci/mfii.c
Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.
Modified files:
Index: src/sys/arch/amd64/conf/ALL
diff -u src/sys/arch/amd64/conf/ALL:1.107 src/sys/arch/amd64/conf/ALL:1.108
--- src/sys/arch/amd64/conf/ALL:1.107 Sun Sep 23 09:21:01 2018
+++ src/sys/arch/amd64/conf/ALL Sat Nov 24 18:23:29 2018
@@ -1,4 +1,4 @@
-# $NetBSD: ALL,v 1.107 2018/09/23 09:21:01 maxv Exp $
+# $NetBSD: ALL,v 1.108 2018/11/24 18:23:29 bouyer Exp $
# From NetBSD: GENERIC,v 1.787 2006/10/01 18:37:54 bouyer Exp
#
# ALL machine description file
@@ -17,7 +17,7 @@ include "arch/amd64/conf/std.amd64"
options INCLUDE_CONFIG_FILE # embed config file in kernel binary
-#ident "ALL-$Revision: 1.107 $"
+#ident "ALL-$Revision: 1.108 $"
maxusers 64 # estimated number of users
@@ -752,6 +752,7 @@ dpt* at pci? dev ? function ? # DPT Smar
iha* at pci? dev ? function ? # Initio INIC-940/950 SCSI
isp* at pci? dev ? function ? # Qlogic ISP [12]0x0 SCSI/FibreChannel
mfi* at pci? dev ? function ? # LSI MegaRAID SAS
+mfii* at pci? dev ? function ? # LSI MegaRAID SAS (Fusion and newer)
mly* at pci? dev ? function ? # Mylex AcceleRAID and eXtremeRAID
mpt* at pci? dev ? function ? # LSILogic 9x9 and 53c1030 (Fusion-MPT)
mpii* at pci? dev ? function ? # LSI Logic Fusion-MPT II
Index: src/sys/arch/amd64/conf/GENERIC
diff -u src/sys/arch/amd64/conf/GENERIC:1.507 src/sys/arch/amd64/conf/GENERIC:1.508
--- src/sys/arch/amd64/conf/GENERIC:1.507 Sat Nov 17 20:29:49 2018
+++ src/sys/arch/amd64/conf/GENERIC Sat Nov 24 18:23:29 2018
@@ -1,4 +1,4 @@
-# $NetBSD: GENERIC,v 1.507 2018/11/17 20:29:49 uwe Exp $
+# $NetBSD: GENERIC,v 1.508 2018/11/24 18:23:29 bouyer Exp $
#
# GENERIC machine description file
#
@@ -22,7 +22,7 @@ include "arch/amd64/conf/std.amd64"
options INCLUDE_CONFIG_FILE # embed config file in kernel binary
-#ident "GENERIC-$Revision: 1.507 $"
+#ident "GENERIC-$Revision: 1.508 $"
maxusers 64 # estimated number of users
@@ -613,6 +613,7 @@ dpt* at pci? dev ? function ? # DPT Smar
iha* at pci? dev ? function ? # Initio INIC-940/950 SCSI
isp* at pci? dev ? function ? # Qlogic ISP [12]0x0 SCSI/FibreChannel
mfi* at pci? dev ? function ? # LSI MegaRAID SAS
+mfii* at pci? dev ? function ? # LSI MegaRAID SAS (Fusion and newer)
mly* at pci? dev ? function ? # Mylex AcceleRAID and eXtremeRAID
mpt* at pci? dev ? function ? # LSILogic 9x9 and 53c1030 (Fusion-MPT)
mpii* at pci? dev ? function ? # LSI Logic Fusion-MPT II
Index: src/sys/arch/amd64/conf/XEN3_DOM0
diff -u src/sys/arch/amd64/conf/XEN3_DOM0:1.158 src/sys/arch/amd64/conf/XEN3_DOM0:1.159
--- src/sys/arch/amd64/conf/XEN3_DOM0:1.158 Tue Oct 23 19:58:52 2018
+++ src/sys/arch/amd64/conf/XEN3_DOM0 Sat Nov 24 18:23:29 2018
@@ -1,4 +1,4 @@
-# $NetBSD: XEN3_DOM0,v 1.158 2018/10/23 19:58:52 jdolecek Exp $
+# $NetBSD: XEN3_DOM0,v 1.159 2018/11/24 18:23:29 bouyer Exp $
include "arch/amd64/conf/std.xen"
@@ -10,7 +10,7 @@ options INCLUDE_CONFIG_FILE # embed con
#options UVMHIST_PRINT
#options SYSCALL_DEBUG
-#ident "XEN3_DOM0-$Revision: 1.158 $"
+#ident "XEN3_DOM0-$Revision: 1.159 $"
maxusers 32 # estimated number of users
@@ -519,6 +519,7 @@ dpt* at pci? dev ? function ? # DPT Smar
iha* at pci? dev ? function ? # Initio INIC-940/950 SCSI
isp* at pci? dev ? function ? # Qlogic ISP [12]0x0 SCSI/FibreChannel
mfi* at pci? dev ? function ? # LSI MegaRAID SAS
+mfii* at pci? dev ? function ? # LSI MegaRAID SAS (Fusion and newer)
mly* at pci? dev ? function ? # Mylex AcceleRAID and eXtremeRAID
mpt* at pci? dev ? function ? # LSILogic 9x9 and 53c1030 (Fusion-MPT)
mpii* at pci? dev ? function ? # LSI Logic Fusion-MPT II
Index: src/sys/arch/i386/conf/ALL
diff -u src/sys/arch/i386/conf/ALL:1.457 src/sys/arch/i386/conf/ALL:1.458
--- src/sys/arch/i386/conf/ALL:1.457 Sun Sep 23 09:21:02 2018
+++ src/sys/arch/i386/conf/ALL Sat Nov 24 18:23:29 2018
@@ -1,4 +1,4 @@
-# $NetBSD: ALL,v 1.457 2018/09/23 09:21:02 maxv Exp $
+# $NetBSD: ALL,v 1.458 2018/11/24 18:23:29 bouyer Exp $
# From NetBSD: GENERIC,v 1.787 2006/10/01 18:37:54 bouyer Exp
#
# ALL machine description file
@@ -17,7 +17,7 @@ include "arch/i386/conf/std.i386"
options INCLUDE_CONFIG_FILE # embed config file in kernel binary
-#ident "ALL-$Revision: 1.457 $"
+#ident "ALL-$Revision: 1.458 $"
maxusers 64 # estimated number of users
@@ -825,6 +825,7 @@ dpt* at pci? dev ? function ? # DPT Smar
iha* at pci? dev ? function ? # Initio INIC-940/950 SCSI
isp* at pci? dev ? function ? # Qlogic ISP [12]0x0 SCSI/FibreChannel
mfi* at pci? dev ? function ? # LSI MegaRAID SAS
+mfii* at pci? dev ? function ? # LSI MegaRAID SAS (Fusion and newer)
mly* at pci? dev ? function ? # Mylex AcceleRAID and eXtremeRAID
mpt* at pci? dev ? function ? # LSILogic 9x9 and 53c1030 (Fusion-MPT)
mpii* at pci? dev ? function ? # LSI Logic Fusion-MPT II
Index: src/sys/arch/i386/conf/GENERIC
diff -u src/sys/arch/i386/conf/GENERIC:1.1193 src/sys/arch/i386/conf/GENERIC:1.1194
--- src/sys/arch/i386/conf/GENERIC:1.1193 Thu Nov 1 13:11:11 2018
+++ src/sys/arch/i386/conf/GENERIC Sat Nov 24 18:23:29 2018
@@ -1,4 +1,4 @@
-# $NetBSD: GENERIC,v 1.1193 2018/11/01 13:11:11 martin Exp $
+# $NetBSD: GENERIC,v 1.1194 2018/11/24 18:23:29 bouyer Exp $
#
# GENERIC machine description file
#
@@ -22,7 +22,7 @@ include "arch/i386/conf/std.i386"
options INCLUDE_CONFIG_FILE # embed config file in kernel binary
-#ident "GENERIC-$Revision: 1.1193 $"
+#ident "GENERIC-$Revision: 1.1194 $"
maxusers 64 # estimated number of users
@@ -762,6 +762,7 @@ dpt* at pci? dev ? function ? # DPT Smar
iha* at pci? dev ? function ? # Initio INIC-940/950 SCSI
isp* at pci? dev ? function ? # Qlogic ISP [12]0x0 SCSI/FibreChannel
mfi* at pci? dev ? function ? # LSI MegaRAID SAS
+mfii* at pci? dev ? function ? # LSI MegaRAID SAS (Fusion and newer)
mly* at pci? dev ? function ? # Mylex AcceleRAID and eXtremeRAID
mpt* at pci? dev ? function ? # LSILogic 9x9 and 53c1030 (Fusion-MPT)
mpii* at pci? dev ? function ? # LSI Logic Fusion-MPT II
Index: src/sys/arch/i386/conf/XEN3PAE_DOM0
diff -u src/sys/arch/i386/conf/XEN3PAE_DOM0:1.7 src/sys/arch/i386/conf/XEN3PAE_DOM0:1.8
--- src/sys/arch/i386/conf/XEN3PAE_DOM0:1.7 Tue Oct 23 19:58:52 2018
+++ src/sys/arch/i386/conf/XEN3PAE_DOM0 Sat Nov 24 18:23:29 2018
@@ -1,4 +1,4 @@
-# $NetBSD: XEN3PAE_DOM0,v 1.7 2018/10/23 19:58:52 jdolecek Exp $
+# $NetBSD: XEN3PAE_DOM0,v 1.8 2018/11/24 18:23:29 bouyer Exp $
#
# XEN3_0: Xen 3.0 domain0 kernel
@@ -482,6 +482,7 @@ dpt* at pci? dev ? function ? # DPT Smar
iha* at pci? dev ? function ? # Initio INIC-940/950 SCSI
isp* at pci? dev ? function ? # Qlogic ISP [12]0x0 SCSI/FibreChannel
mfi* at pci? dev ? function ? # LSI MegaRAID SAS
+mfii* at pci? dev ? function ? # LSI MegaRAID SAS (Fusion and newer)
mly* at pci? dev ? function ? # Mylex AcceleRAID and eXtremeRAID
mpt* at pci? dev ? function ? # LSILogic 9x9 and 53c1030 (Fusion-MPT)
mpii* at pci? dev ? function ? # LSI Logic Fusion-MPT II
Index: src/sys/dev/pci/files.pci
diff -u src/sys/dev/pci/files.pci:1.409 src/sys/dev/pci/files.pci:1.410
--- src/sys/dev/pci/files.pci:1.409 Tue Nov 20 12:23:01 2018
+++ src/sys/dev/pci/files.pci Sat Nov 24 18:23:29 2018
@@ -1,4 +1,4 @@
-# $NetBSD: files.pci,v 1.409 2018/11/20 12:23:01 skrll Exp $
+# $NetBSD: files.pci,v 1.410 2018/11/24 18:23:29 bouyer Exp $
#
# Config file and device description for machine-independent PCI code.
# Included by ports that need it. Requires that the SCSI files be
@@ -152,6 +152,11 @@ file dev/pci/isp_pci.c isp_pci
attach mfi at pci with mfi_pci
file dev/pci/mfi_pci.c mfi_pci
+# LSI MegaRAID SAS Fusion RAID controllers
+device mfii: scsi
+attach mfii at pci
+file dev/pci/mfii.c mfii
+
# LSILogic Fusion-MPT I/O Processor family
# device declaration in sys/conf/files
attach mpt at pci with mpt_pci
Added files:
Index: src/sys/dev/pci/mfii.c
diff -u /dev/null src/sys/dev/pci/mfii.c:1.1
--- /dev/null Sat Nov 24 18:23:29 2018
+++ src/sys/dev/pci/mfii.c Sat Nov 24 18:23:29 2018
@@ -0,0 +1,3871 @@
+/* $NetBSD: mfii.c,v 1.1 2018/11/24 18:23:29 bouyer Exp $ */
+/* $OpenBSD: mfii.c,v 1.58 2018/08/14 05:22:21 jmatthew Exp $ */
+
+/*
+ * Copyright (c) 2012 David Gwynne <[email protected]>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/cdefs.h>
+__KERNEL_RCSID(0, "$NetBSD: mfii.c,v 1.1 2018/11/24 18:23:29 bouyer Exp $");
+
+#include "bio.h"
+
+#include <sys/atomic.h>
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/buf.h>
+#include <sys/ioctl.h>
+#include <sys/device.h>
+#include <sys/kernel.h>
+#include <sys/proc.h>
+#include <sys/cpu.h>
+#include <sys/conf.h>
+#include <sys/kauth.h>
+#include <sys/workqueue.h>
+#include <sys/malloc.h>
+
+#include <uvm/uvm_param.h>
+
+#include <dev/pci/pcidevs.h>
+#include <dev/pci/pcivar.h>
+
+#include <sys/bus.h>
+
+#include <dev/sysmon/sysmonvar.h>
+#include <sys/envsys.h>
+
+#include <dev/scsipi/scsipi_all.h>
+#include <dev/scsipi/scsi_all.h>
+#include <dev/scsipi/scsi_spc.h>
+#include <dev/scsipi/scsipi_disk.h>
+#include <dev/scsipi/scsi_disk.h>
+#include <dev/scsipi/scsiconf.h>
+
+#if NBIO > 0
+#include <dev/biovar.h>
+#endif /* NBIO > 0 */
+
+#include <dev/ic/mfireg.h>
+#include <dev/pci/mpiireg.h>
+
+#define MFII_BAR 0x14
+#define MFII_BAR_35 0x10
+#define MFII_PCI_MEMSIZE 0x2000 /* 8k */
+
+#define MFII_OSTS_INTR_VALID 0x00000009
+#define MFII_RPI 0x6c /* reply post host index */
+#define MFII_OSP2 0xb4 /* outbound scratch pad 2 */
+#define MFII_OSP3 0xb8 /* outbound scratch pad 3 */
+
+#define MFII_REQ_TYPE_SCSI MPII_REQ_DESCR_SCSI_IO
+#define MFII_REQ_TYPE_LDIO (0x7 << 1)
+#define MFII_REQ_TYPE_MFA (0x1 << 1)
+#define MFII_REQ_TYPE_NO_LOCK (0x2 << 1)
+#define MFII_REQ_TYPE_HI_PRI (0x6 << 1)
+
+#define MFII_REQ_MFA(_a) htole64((_a) | MFII_REQ_TYPE_MFA)
+
+#define MFII_FUNCTION_PASSTHRU_IO (0xf0)
+#define MFII_FUNCTION_LDIO_REQUEST (0xf1)
+
+#define MFII_MAX_CHAIN_UNIT 0x00400000
+#define MFII_MAX_CHAIN_MASK 0x000003E0
+#define MFII_MAX_CHAIN_SHIFT 5
+
+#define MFII_256K_IO 128
+#define MFII_1MB_IO (MFII_256K_IO * 4)
+
+#define MFII_CHAIN_FRAME_MIN 1024
+
+struct mfii_request_descr {
+ u_int8_t flags;
+ u_int8_t msix_index;
+ u_int16_t smid;
+
+ u_int16_t lmid;
+ u_int16_t dev_handle;
+} __packed;
+
+#define MFII_RAID_CTX_IO_TYPE_SYSPD (0x1 << 4)
+#define MFII_RAID_CTX_TYPE_CUDA (0x2 << 4)
+
+struct mfii_raid_context {
+ u_int8_t type_nseg;
+ u_int8_t _reserved1;
+ u_int16_t timeout_value;
+
+ u_int16_t reg_lock_flags;
+#define MFII_RAID_CTX_RL_FLAGS_SEQNO_EN (0x08)
+#define MFII_RAID_CTX_RL_FLAGS_CPU0 (0x00)
+#define MFII_RAID_CTX_RL_FLAGS_CPU1 (0x10)
+#define MFII_RAID_CTX_RL_FLAGS_CUDA (0x80)
+
+#define MFII_RAID_CTX_ROUTING_FLAGS_SQN (1 << 4)
+#define MFII_RAID_CTX_ROUTING_FLAGS_CPU0 0
+ u_int16_t virtual_disk_target_id;
+
+ u_int64_t reg_lock_row_lba;
+
+ u_int32_t reg_lock_length;
+
+ u_int16_t next_lm_id;
+ u_int8_t ex_status;
+ u_int8_t status;
+
+ u_int8_t raid_flags;
+ u_int8_t num_sge;
+ u_int16_t config_seq_num;
+
+ u_int8_t span_arm;
+ u_int8_t _reserved3[3];
+} __packed;
+
+struct mfii_sge {
+ u_int64_t sg_addr;
+ u_int32_t sg_len;
+ u_int16_t _reserved;
+ u_int8_t sg_next_chain_offset;
+ u_int8_t sg_flags;
+} __packed;
+
+#define MFII_SGE_ADDR_MASK (0x03)
+#define MFII_SGE_ADDR_SYSTEM (0x00)
+#define MFII_SGE_ADDR_IOCDDR (0x01)
+#define MFII_SGE_ADDR_IOCPLB (0x02)
+#define MFII_SGE_ADDR_IOCPLBNTA (0x03)
+#define MFII_SGE_END_OF_LIST (0x40)
+#define MFII_SGE_CHAIN_ELEMENT (0x80)
+
+#define MFII_REQUEST_SIZE 256
+
+#define MR_DCMD_LD_MAP_GET_INFO 0x0300e101
+
+#define MFII_MAX_ROW 32
+#define MFII_MAX_ARRAY 128
+
+struct mfii_array_map {
+ uint16_t mam_pd[MFII_MAX_ROW];
+} __packed;
+
+struct mfii_dev_handle {
+ uint16_t mdh_cur_handle;
+ uint8_t mdh_valid;
+ uint8_t mdh_reserved;
+ uint16_t mdh_handle[2];
+} __packed;
+
+struct mfii_ld_map {
+ uint32_t mlm_total_size;
+ uint32_t mlm_reserved1[5];
+ uint32_t mlm_num_lds;
+ uint32_t mlm_reserved2;
+ uint8_t mlm_tgtid_to_ld[2 * MFI_MAX_LD];
+ uint8_t mlm_pd_timeout;
+ uint8_t mlm_reserved3[7];
+ struct mfii_array_map mlm_am[MFII_MAX_ARRAY];
+ struct mfii_dev_handle mlm_dev_handle[MFI_MAX_PD];
+} __packed;
+
+struct mfii_task_mgmt {
+ union {
+ uint8_t request[128];
+ struct mpii_msg_scsi_task_request
+ mpii_request;
+ } __packed __aligned(8);
+
+ union {
+ uint8_t reply[128];
+ uint32_t flags;
+#define MFII_TASK_MGMT_FLAGS_LD (1 << 0)
+#define MFII_TASK_MGMT_FLAGS_PD (1 << 1)
+ struct mpii_msg_scsi_task_reply
+ mpii_reply;
+ } __packed __aligned(8);
+} __packed __aligned(8);
+
+/* We currently don't know the full details of the following struct */
+struct mfii_foreign_scan_cfg {
+ char data[24];
+} __packed;
+
+struct mfii_foreign_scan_info {
+ uint32_t count; /* Number of foreign configs found */
+ struct mfii_foreign_scan_cfg cfgs[8];
+} __packed;
+
+struct mfii_dmamem {
+ bus_dmamap_t mdm_map;
+ bus_dma_segment_t mdm_seg;
+ size_t mdm_size;
+ void * mdm_kva;
+};
+#define MFII_DMA_MAP(_mdm) ((_mdm)->mdm_map)
+#define MFII_DMA_LEN(_mdm) ((_mdm)->mdm_size)
+#define MFII_DMA_DVA(_mdm) ((u_int64_t)(_mdm)->mdm_map->dm_segs[0].ds_addr)
+#define MFII_DMA_KVA(_mdm) ((void *)(_mdm)->mdm_kva)
+
+struct mfii_softc;
+
+typedef enum mfii_direction {
+ MFII_DATA_NONE = 0,
+ MFII_DATA_IN,
+ MFII_DATA_OUT
+} mfii_direction_t;
+
+struct mfii_ccb {
+ struct mfii_softc *ccb_sc;
+ void *ccb_request;
+ u_int64_t ccb_request_dva;
+ bus_addr_t ccb_request_offset;
+
+ void *ccb_mfi;
+ u_int64_t ccb_mfi_dva;
+ bus_addr_t ccb_mfi_offset;
+
+ struct mfi_sense *ccb_sense;
+ u_int64_t ccb_sense_dva;
+ bus_addr_t ccb_sense_offset;
+
+ struct mfii_sge *ccb_sgl;
+ u_int64_t ccb_sgl_dva;
+ bus_addr_t ccb_sgl_offset;
+ u_int ccb_sgl_len;
+
+ struct mfii_request_descr ccb_req;
+
+ bus_dmamap_t ccb_dmamap64;
+ bus_dmamap_t ccb_dmamap32;
+ bool ccb_dma64;
+
+ /* data for sgl */
+ void *ccb_data;
+ size_t ccb_len;
+
+ mfii_direction_t ccb_direction;
+
+ void *ccb_cookie;
+ kmutex_t ccb_mtx;
+ kcondvar_t ccb_cv;
+ void (*ccb_done)(struct mfii_softc *,
+ struct mfii_ccb *);
+
+ u_int32_t ccb_flags;
+#define MFI_CCB_F_ERR (1<<0)
+ u_int ccb_smid;
+ u_int ccb_refcnt;
+ SIMPLEQ_ENTRY(mfii_ccb) ccb_link;
+};
+SIMPLEQ_HEAD(mfii_ccb_list, mfii_ccb);
+
+struct mfii_iop {
+ int bar;
+ int num_sge_loc;
+#define MFII_IOP_NUM_SGE_LOC_ORIG 0
+#define MFII_IOP_NUM_SGE_LOC_35 1
+ u_int16_t ldio_ctx_reg_lock_flags;
+ u_int8_t ldio_req_type;
+ u_int8_t ldio_ctx_type_nseg;
+ u_int8_t sge_flag_chain;
+ u_int8_t sge_flag_eol;
+};
+
+struct mfii_softc {
+ device_t sc_dev;
+ struct scsipi_channel sc_chan;
+ struct scsipi_adapter sc_adapt;
+
+ const struct mfii_iop *sc_iop;
+
+ pci_chipset_tag_t sc_pc;
+ pcitag_t sc_tag;
+
+ bus_space_tag_t sc_iot;
+ bus_space_handle_t sc_ioh;
+ bus_size_t sc_ios;
+ bus_dma_tag_t sc_dmat;
+ bus_dma_tag_t sc_dmat64;
+ bool sc_64bit_dma;
+
+ void *sc_ih;
+
+ kmutex_t sc_ccb_mtx;
+ kmutex_t sc_post_mtx;
+
+ u_int sc_max_fw_cmds;
+ u_int sc_max_cmds;
+ u_int sc_max_sgl;
+
+ u_int sc_reply_postq_depth;
+ u_int sc_reply_postq_index;
+ kmutex_t sc_reply_postq_mtx;
+ struct mfii_dmamem *sc_reply_postq;
+
+ struct mfii_dmamem *sc_requests;
+ struct mfii_dmamem *sc_mfi;
+ struct mfii_dmamem *sc_sense;
+ struct mfii_dmamem *sc_sgl;
+
+ struct mfii_ccb *sc_ccb;
+ struct mfii_ccb_list sc_ccb_freeq;
+
+ struct mfii_ccb *sc_aen_ccb;
+ struct workqueue *sc_aen_wq;
+ struct work sc_aen_work;
+
+ kmutex_t sc_abort_mtx;
+ struct mfii_ccb_list sc_abort_list;
+ struct workqueue *sc_abort_wq;
+ struct work sc_abort_work;
+
+ /* save some useful information for logical drives that is missing
+ * in sc_ld_list
+ */
+ struct {
+ bool ld_present;
+ char ld_dev[16]; /* device name sd? */
+ } sc_ld[MFI_MAX_LD];
+ int sc_target_lds[MFI_MAX_LD];
+
+ /* bio */
+ struct mfi_conf *sc_cfg;
+ struct mfi_ctrl_info sc_info;
+ struct mfi_ld_list sc_ld_list;
+ struct mfi_ld_details *sc_ld_details; /* array to all logical disks */
+ int sc_no_pd; /* used physical disks */
+ int sc_ld_sz; /* sizeof sc_ld_details */
+
+ /* mgmt lock */
+ kmutex_t sc_lock;
+ bool sc_running;
+
+ /* sensors */
+ struct sysmon_envsys *sc_sme;
+ envsys_data_t *sc_sensors;
+ bool sc_bbuok;
+
+ device_t sc_child;
+};
+
+// #define MFII_DEBUG
+#ifdef MFII_DEBUG
+#define DPRINTF(x...) do { if (mfii_debug) printf(x); } while(0)
+#define DNPRINTF(n,x...) do { if (mfii_debug & n) printf(x); } while(0)
+#define MFII_D_CMD 0x0001
+#define MFII_D_INTR 0x0002
+#define MFII_D_MISC 0x0004
+#define MFII_D_DMA 0x0008
+#define MFII_D_IOCTL 0x0010
+#define MFII_D_RW 0x0020
+#define MFII_D_MEM 0x0040
+#define MFII_D_CCB 0x0080
+uint32_t mfii_debug = 0
+/* | MFII_D_CMD */
+/* | MFII_D_INTR */
+ | MFII_D_MISC
+/* | MFII_D_DMA */
+/* | MFII_D_IOCTL */
+/* | MFII_D_RW */
+/* | MFII_D_MEM */
+/* | MFII_D_CCB */
+ ;
+#else
+#define DPRINTF(x...)
+#define DNPRINTF(n,x...)
+#endif
+
+int mfii_match(device_t, cfdata_t, void *);
+void mfii_attach(device_t, device_t, void *);
+int mfii_detach(device_t, int);
+int mfii_rescan(device_t, const char *, const int *);
+void mfii_childdetached(device_t, device_t);
+static bool mfii_suspend(device_t, const pmf_qual_t *);
+static bool mfii_resume(device_t, const pmf_qual_t *);
+static bool mfii_shutdown(device_t, int);
+
+
+CFATTACH_DECL3_NEW(mfii, sizeof(struct mfii_softc),
+ mfii_match, mfii_attach, mfii_detach, NULL, mfii_rescan,
+ mfii_childdetached, DVF_DETACH_SHUTDOWN);
+
+void mfii_scsipi_request(struct scsipi_channel *,
+ scsipi_adapter_req_t, void *);
+void mfii_scsi_cmd_done(struct mfii_softc *, struct mfii_ccb *);
+
+#define DEVNAME(_sc) (device_xname((_sc)->sc_dev))
+
+static u_int32_t mfii_read(struct mfii_softc *, bus_size_t);
+static void mfii_write(struct mfii_softc *, bus_size_t, u_int32_t);
+
+struct mfii_dmamem * mfii_dmamem_alloc(struct mfii_softc *, size_t);
+void mfii_dmamem_free(struct mfii_softc *,
+ struct mfii_dmamem *);
+
+struct mfii_ccb * mfii_get_ccb(struct mfii_softc *);
+void mfii_put_ccb(struct mfii_softc *, struct mfii_ccb *);
+int mfii_init_ccb(struct mfii_softc *);
+void mfii_scrub_ccb(struct mfii_ccb *);
+
+int mfii_transition_firmware(struct mfii_softc *);
+int mfii_initialise_firmware(struct mfii_softc *);
+int mfii_get_info(struct mfii_softc *);
+
+void mfii_start(struct mfii_softc *, struct mfii_ccb *);
+void mfii_done(struct mfii_softc *, struct mfii_ccb *);
+int mfii_poll(struct mfii_softc *, struct mfii_ccb *);
+void mfii_poll_done(struct mfii_softc *, struct mfii_ccb *);
+int mfii_exec(struct mfii_softc *, struct mfii_ccb *);
+void mfii_exec_done(struct mfii_softc *, struct mfii_ccb *);
+int mfii_my_intr(struct mfii_softc *);
+int mfii_intr(void *);
+void mfii_postq(struct mfii_softc *);
+
+int mfii_load_ccb(struct mfii_softc *, struct mfii_ccb *,
+ void *, int);
+int mfii_load_mfa(struct mfii_softc *, struct mfii_ccb *,
+ void *, int);
+
+int mfii_mfa_poll(struct mfii_softc *, struct mfii_ccb *);
+
+int mfii_mgmt(struct mfii_softc *, uint32_t,
+ const union mfi_mbox *, void *, size_t,
+ mfii_direction_t, bool);
+int mfii_do_mgmt(struct mfii_softc *, struct mfii_ccb *,
+ uint32_t, const union mfi_mbox *, void *, size_t,
+ mfii_direction_t, bool);
+void mfii_empty_done(struct mfii_softc *, struct mfii_ccb *);
+
+int mfii_scsi_cmd_io(struct mfii_softc *,
+ struct mfii_ccb *, struct scsipi_xfer *);
+int mfii_scsi_cmd_cdb(struct mfii_softc *,
+ struct mfii_ccb *, struct scsipi_xfer *);
+void mfii_scsi_cmd_tmo(void *);
+
+int mfii_dev_handles_update(struct mfii_softc *sc);
+void mfii_dev_handles_dtor(void *, void *);
+
+void mfii_abort_task(struct work *, void *);
+void mfii_abort(struct mfii_softc *, struct mfii_ccb *,
+ uint16_t, uint16_t, uint8_t, uint32_t);
+void mfii_scsi_cmd_abort_done(struct mfii_softc *,
+ struct mfii_ccb *);
+
+int mfii_aen_register(struct mfii_softc *);
+void mfii_aen_start(struct mfii_softc *, struct mfii_ccb *,
+ struct mfii_dmamem *, uint32_t);
+void mfii_aen_done(struct mfii_softc *, struct mfii_ccb *);
+void mfii_aen(struct work *, void *);
+void mfii_aen_unregister(struct mfii_softc *);
+
+void mfii_aen_pd_insert(struct mfii_softc *,
+ const struct mfi_evtarg_pd_address *);
+void mfii_aen_pd_remove(struct mfii_softc *,
+ const struct mfi_evtarg_pd_address *);
+void mfii_aen_pd_state_change(struct mfii_softc *,
+ const struct mfi_evtarg_pd_state *);
+void mfii_aen_ld_update(struct mfii_softc *);
+
+#if NBIO > 0
+int mfii_ioctl(device_t, u_long, void *);
+int mfii_ioctl_inq(struct mfii_softc *, struct bioc_inq *);
+int mfii_ioctl_vol(struct mfii_softc *, struct bioc_vol *);
+int mfii_ioctl_disk(struct mfii_softc *, struct bioc_disk *);
+int mfii_ioctl_alarm(struct mfii_softc *, struct bioc_alarm *);
+int mfii_ioctl_blink(struct mfii_softc *sc, struct bioc_blink *);
+int mfii_ioctl_setstate(struct mfii_softc *,
+ struct bioc_setstate *);
+int mfii_bio_hs(struct mfii_softc *, int, int, void *);
+int mfii_bio_getitall(struct mfii_softc *);
+#endif /* NBIO > 0 */
+
+#if 0
+static const char *mfi_bbu_indicators[] = {
+ "pack missing",
+ "voltage low",
+ "temp high",
+ "charge active",
+ "discharge active",
+ "learn cycle req'd",
+ "learn cycle active",
+ "learn cycle failed",
+ "learn cycle timeout",
+ "I2C errors",
+ "replace pack",
+ "low capacity",
+ "periodic learn req'd"
+};
+#endif
+
+void mfii_init_ld_sensor(struct mfii_softc *, envsys_data_t *, int);
+void mfii_refresh_ld_sensor(struct mfii_softc *, envsys_data_t *);
+static void mfii_attach_sensor(struct mfii_softc *, envsys_data_t *);
+int mfii_create_sensors(struct mfii_softc *);
+static int mfii_destroy_sensors(struct mfii_softc *);
+void mfii_refresh_sensor(struct sysmon_envsys *, envsys_data_t *);
+void mfii_bbu(struct mfii_softc *, envsys_data_t *);
+
+/*
+ * mfii boards support asynchronous (and non-polled) completion of
+ * dcmds by proxying them through a passthru mpii command that points
+ * at a dcmd frame. since the passthru command is submitted like
+ * the scsi commands using an SMID in the request descriptor,
+ * ccb_request memory * must contain the passthru command because
+ * that is what the SMID refers to. this means ccb_request cannot
+ * contain the dcmd. rather than allocating separate dma memory to
+ * hold the dcmd, we reuse the sense memory buffer for it.
+ */
+
+void mfii_dcmd_start(struct mfii_softc *,
+ struct mfii_ccb *);
+
+static inline void
+mfii_dcmd_scrub(struct mfii_ccb *ccb)
+{
+ memset(ccb->ccb_sense, 0, sizeof(*ccb->ccb_sense));
+}
+
+static inline struct mfi_dcmd_frame *
+mfii_dcmd_frame(struct mfii_ccb *ccb)
+{
+ CTASSERT(sizeof(struct mfi_dcmd_frame) <= sizeof(*ccb->ccb_sense));
+ return ((struct mfi_dcmd_frame *)ccb->ccb_sense);
+}
+
+static inline void
+mfii_dcmd_sync(struct mfii_softc *sc, struct mfii_ccb *ccb, int flags)
+{
+ bus_dmamap_sync(sc->sc_dmat, MFII_DMA_MAP(sc->sc_sense),
+ ccb->ccb_sense_offset, sizeof(*ccb->ccb_sense), flags);
+}
+
+#define mfii_fw_state(_sc) mfii_read((_sc), MFI_OSP)
+
+const struct mfii_iop mfii_iop_thunderbolt = {
+ MFII_BAR,
+ MFII_IOP_NUM_SGE_LOC_ORIG,
+ 0,
+ MFII_REQ_TYPE_LDIO,
+ 0,
+ MFII_SGE_CHAIN_ELEMENT | MFII_SGE_ADDR_IOCPLBNTA,
+ 0
+};
+
+/*
+ * a lot of these values depend on us not implementing fastpath yet.
+ */
+const struct mfii_iop mfii_iop_25 = {
+ MFII_BAR,
+ MFII_IOP_NUM_SGE_LOC_ORIG,
+ MFII_RAID_CTX_RL_FLAGS_CPU0, /* | MFII_RAID_CTX_RL_FLAGS_SEQNO_EN */
+ MFII_REQ_TYPE_NO_LOCK,
+ MFII_RAID_CTX_TYPE_CUDA | 0x1,
+ MFII_SGE_CHAIN_ELEMENT,
+ MFII_SGE_END_OF_LIST
+};
+
+const struct mfii_iop mfii_iop_35 = {
+ MFII_BAR_35,
+ MFII_IOP_NUM_SGE_LOC_35,
+ MFII_RAID_CTX_ROUTING_FLAGS_CPU0, /* | MFII_RAID_CTX_ROUTING_FLAGS_SQN */
+ MFII_REQ_TYPE_NO_LOCK,
+ MFII_RAID_CTX_TYPE_CUDA | 0x1,
+ MFII_SGE_CHAIN_ELEMENT,
+ MFII_SGE_END_OF_LIST
+};
+
+struct mfii_device {
+ pcireg_t mpd_vendor;
+ pcireg_t mpd_product;
+ const struct mfii_iop *mpd_iop;
+};
+
+const struct mfii_device mfii_devices[] = {
+ { PCI_VENDOR_SYMBIOS, PCI_PRODUCT_SYMBIOS_MEGARAID_2208,
+ &mfii_iop_thunderbolt },
+ { PCI_VENDOR_SYMBIOS, PCI_PRODUCT_SYMBIOS_MEGARAID_3008,
+ &mfii_iop_25 },
+ { PCI_VENDOR_SYMBIOS, PCI_PRODUCT_SYMBIOS_MEGARAID_3108,
+ &mfii_iop_25 },
+ { PCI_VENDOR_SYMBIOS, PCI_PRODUCT_SYMBIOS_MEGARAID_3404,
+ &mfii_iop_35 },
+ { PCI_VENDOR_SYMBIOS, PCI_PRODUCT_SYMBIOS_MEGARAID_3504,
+ &mfii_iop_35 },
+ { PCI_VENDOR_SYMBIOS, PCI_PRODUCT_SYMBIOS_MEGARAID_3408,
+ &mfii_iop_35 },
+ { PCI_VENDOR_SYMBIOS, PCI_PRODUCT_SYMBIOS_MEGARAID_3508,
+ &mfii_iop_35 },
+ { PCI_VENDOR_SYMBIOS, PCI_PRODUCT_SYMBIOS_MEGARAID_3416,
+ &mfii_iop_35 },
+ { PCI_VENDOR_SYMBIOS, PCI_PRODUCT_SYMBIOS_MEGARAID_3516,
+ &mfii_iop_35 }
+};
+
+const struct mfii_iop *mfii_find_iop(struct pci_attach_args *);
+
+const struct mfii_iop *
+mfii_find_iop(struct pci_attach_args *pa)
+{
+ const struct mfii_device *mpd;
+ int i;
+
+ for (i = 0; i < __arraycount(mfii_devices); i++) {
+ mpd = &mfii_devices[i];
+
+ if (mpd->mpd_vendor == PCI_VENDOR(pa->pa_id) &&
+ mpd->mpd_product == PCI_PRODUCT(pa->pa_id))
+ return (mpd->mpd_iop);
+ }
+
+ return (NULL);
+}
+
+int
+mfii_match(device_t parent, cfdata_t match, void *aux)
+{
+ return ((mfii_find_iop(aux) != NULL) ? 2 : 0);
+}
+
+void
+mfii_attach(device_t parent, device_t self, void *aux)
+{
+ struct mfii_softc *sc = device_private(self);
+ struct pci_attach_args *pa = aux;
+ pcireg_t memtype;
+ pci_intr_handle_t ih;
+ char intrbuf[PCI_INTRSTR_LEN];
+ const char *intrstr;
+ u_int32_t status, scpad2, scpad3;
+ int chain_frame_sz, nsge_in_io, nsge_in_chain, i;
+ struct scsipi_adapter *adapt = &sc->sc_adapt;
+ struct scsipi_channel *chan = &sc->sc_chan;
+
+ /* init sc */
+ sc->sc_dev = self;
+ sc->sc_iop = mfii_find_iop(aux);
+ sc->sc_dmat = pa->pa_dmat;
+ if (pci_dma64_available(pa)) {
+ sc->sc_dmat64 = pa->pa_dmat64;
+ sc->sc_64bit_dma = 1;
+ } else {
+ sc->sc_dmat64 = pa->pa_dmat;
+ sc->sc_64bit_dma = 0;
+ }
+ SIMPLEQ_INIT(&sc->sc_ccb_freeq);
+ mutex_init(&sc->sc_ccb_mtx, MUTEX_DEFAULT, IPL_BIO);
+ mutex_init(&sc->sc_post_mtx, MUTEX_DEFAULT, IPL_BIO);
+ mutex_init(&sc->sc_reply_postq_mtx, MUTEX_DEFAULT, IPL_BIO);
+
+ mutex_init(&sc->sc_lock, MUTEX_DEFAULT, IPL_NONE);
+
+ sc->sc_aen_ccb = NULL;
+ snprintf(intrbuf, sizeof(intrbuf) - 1, "%saen", device_xname(self));
+ workqueue_create(&sc->sc_aen_wq, intrbuf, mfii_aen, sc,
+ PRI_BIO, IPL_BIO, WQ_MPSAFE);
+
+ snprintf(intrbuf, sizeof(intrbuf) - 1, "%sabrt", device_xname(self));
+ workqueue_create(&sc->sc_abort_wq, intrbuf, mfii_abort_task,
+ sc, PRI_BIO, IPL_BIO, WQ_MPSAFE);
+
+ mutex_init(&sc->sc_abort_mtx, MUTEX_DEFAULT, IPL_BIO);
+ SIMPLEQ_INIT(&sc->sc_abort_list);
+
+ /* wire up the bus shizz */
+ memtype = pci_mapreg_type(pa->pa_pc, pa->pa_tag, sc->sc_iop->bar);
+ memtype |= PCI_MAPREG_MEM_TYPE_32BIT;
+ if (pci_mapreg_map(pa, sc->sc_iop->bar, memtype, 0,
+ &sc->sc_iot, &sc->sc_ioh, NULL, &sc->sc_ios)) {
+ aprint_error(": unable to map registers\n");
+ return;
+ }
+
+ /* disable interrupts */
+ mfii_write(sc, MFI_OMSK, 0xffffffff);
+
+ if (pci_intr_map(pa, &ih) != 0) {
+ aprint_error(": unable to map interrupt\n");
+ goto pci_unmap;
+ }
+ intrstr = pci_intr_string(pa->pa_pc, ih, intrbuf, sizeof(intrbuf));
+ pci_intr_setattr(pa->pa_pc, &ih, PCI_INTR_MPSAFE, true);
+
+ /* lets get started */
+ if (mfii_transition_firmware(sc))
+ goto pci_unmap;
+ sc->sc_running = true;
+
+ /* determine max_cmds (refer to the Linux megaraid_sas driver) */
+ scpad3 = mfii_read(sc, MFII_OSP3);
+ status = mfii_fw_state(sc);
+ sc->sc_max_fw_cmds = scpad3 & MFI_STATE_MAXCMD_MASK;
+ if (sc->sc_max_fw_cmds == 0)
+ sc->sc_max_fw_cmds = status & MFI_STATE_MAXCMD_MASK;
+ /*
+ * reduce max_cmds by 1 to ensure that the reply queue depth does not
+ * exceed FW supplied max_fw_cmds.
+ */
+ sc->sc_max_cmds = uimin(sc->sc_max_fw_cmds, 1024) - 1;
+
+ /* determine max_sgl (refer to the Linux megaraid_sas driver) */
+ scpad2 = mfii_read(sc, MFII_OSP2);
+ chain_frame_sz =
+ ((scpad2 & MFII_MAX_CHAIN_MASK) >> MFII_MAX_CHAIN_SHIFT) *
+ ((scpad2 & MFII_MAX_CHAIN_UNIT) ? MFII_1MB_IO : MFII_256K_IO);
+ if (chain_frame_sz < MFII_CHAIN_FRAME_MIN)
+ chain_frame_sz = MFII_CHAIN_FRAME_MIN;
+
+ nsge_in_io = (MFII_REQUEST_SIZE -
+ sizeof(struct mpii_msg_scsi_io) -
+ sizeof(struct mfii_raid_context)) / sizeof(struct mfii_sge);
+ nsge_in_chain = chain_frame_sz / sizeof(struct mfii_sge);
+
+ /* round down to nearest power of two */
+ sc->sc_max_sgl = 1;
+ while ((sc->sc_max_sgl << 1) <= (nsge_in_io + nsge_in_chain))
+ sc->sc_max_sgl <<= 1;
+
+ DNPRINTF(MFII_D_MISC, "%s: OSP 0x%08x, OSP2 0x%08x, OSP3 0x%08x\n",
+ DEVNAME(sc), status, scpad2, scpad3);
+ DNPRINTF(MFII_D_MISC, "%s: max_fw_cmds %d, max_cmds %d\n",
+ DEVNAME(sc), sc->sc_max_fw_cmds, sc->sc_max_cmds);
+ DNPRINTF(MFII_D_MISC, "%s: nsge_in_io %d, nsge_in_chain %d, "
+ "max_sgl %d\n", DEVNAME(sc), nsge_in_io, nsge_in_chain,
+ sc->sc_max_sgl);
+
+ /* sense memory */
+ CTASSERT(sizeof(struct mfi_sense) == MFI_SENSE_SIZE);
+ sc->sc_sense = mfii_dmamem_alloc(sc, sc->sc_max_cmds * MFI_SENSE_SIZE);
+ if (sc->sc_sense == NULL) {
+ aprint_error(": unable to allocate sense memory\n");
+ goto pci_unmap;
+ }
+
+ /* reply post queue */
+ sc->sc_reply_postq_depth = roundup(sc->sc_max_fw_cmds, 16);
+
+ sc->sc_reply_postq = mfii_dmamem_alloc(sc,
+ sc->sc_reply_postq_depth * sizeof(struct mpii_reply_descr));
+ if (sc->sc_reply_postq == NULL)
+ goto free_sense;
+
+ memset(MFII_DMA_KVA(sc->sc_reply_postq), 0xff,
+ MFII_DMA_LEN(sc->sc_reply_postq));
+
+ /* MPII request frame array */
+ sc->sc_requests = mfii_dmamem_alloc(sc,
+ MFII_REQUEST_SIZE * (sc->sc_max_cmds + 1));
+ if (sc->sc_requests == NULL)
+ goto free_reply_postq;
+
+ /* MFI command frame array */
+ sc->sc_mfi = mfii_dmamem_alloc(sc, sc->sc_max_cmds * MFI_FRAME_SIZE);
+ if (sc->sc_mfi == NULL)
+ goto free_requests;
+
+ /* MPII SGL array */
+ sc->sc_sgl = mfii_dmamem_alloc(sc, sc->sc_max_cmds *
+ sizeof(struct mfii_sge) * sc->sc_max_sgl);
+ if (sc->sc_sgl == NULL)
+ goto free_mfi;
+
+ if (mfii_init_ccb(sc) != 0) {
+ aprint_error(": could not init ccb list\n");
+ goto free_sgl;
+ }
+
+ /* kickstart firmware with all addresses and pointers */
+ if (mfii_initialise_firmware(sc) != 0) {
+ aprint_error(": could not initialize firmware\n");
+ goto free_sgl;
+ }
+
+ mutex_enter(&sc->sc_lock);
+ if (mfii_get_info(sc) != 0) {
+ mutex_exit(&sc->sc_lock);
+ aprint_error(": could not retrieve controller information\n");
+ goto free_sgl;
+ }
+ mutex_exit(&sc->sc_lock);
+
+ aprint_normal(": \"%s\", firmware %s",
+ sc->sc_info.mci_product_name, sc->sc_info.mci_package_version);
+ if (le16toh(sc->sc_info.mci_memory_size) > 0) {
+ aprint_normal(", %uMB cache",
+ le16toh(sc->sc_info.mci_memory_size));
+ }
+ aprint_normal("\n");
+ aprint_naive("\n");
+
+ sc->sc_ih = pci_intr_establish_xname(sc->sc_pc, ih, IPL_BIO,
+ mfii_intr, sc, DEVNAME(sc));
+ if (sc->sc_ih == NULL) {
+ aprint_error_dev(self, "can't establish interrupt");
+ if (intrstr)
+ aprint_error(" at %s", intrstr);
+ aprint_error("\n");
+ goto free_sgl;
+ }
+ aprint_normal_dev(self, "interrupting at %s\n", intrstr);
+
+ for (i = 0; i < sc->sc_info.mci_lds_present; i++)
+ sc->sc_ld[i].ld_present = 1;
+
+ memset(adapt, 0, sizeof(*adapt));
+ adapt->adapt_dev = sc->sc_dev;
+ adapt->adapt_nchannels = 1;
+ /* keep a few commands for management */
+ if (sc->sc_max_cmds > 4)
+ adapt->adapt_openings = sc->sc_max_cmds - 4;
+ else
+ adapt->adapt_openings = sc->sc_max_cmds;
+ adapt->adapt_max_periph = adapt->adapt_openings;
+ adapt->adapt_request = mfii_scsipi_request;
+ adapt->adapt_minphys = minphys;
+ adapt->adapt_flags = SCSIPI_ADAPT_MPSAFE;
+
+ memset(chan, 0, sizeof(*chan));
+ chan->chan_adapter = adapt;
+ chan->chan_bustype = &scsi_sas_bustype;
+ chan->chan_channel = 0;
+ chan->chan_flags = 0;
+ chan->chan_nluns = 8;
+ chan->chan_ntargets = sc->sc_info.mci_max_lds;
+ chan->chan_id = sc->sc_info.mci_max_lds;
+
+ mfii_rescan(sc->sc_dev, "scsi", NULL);
+
+ if (mfii_aen_register(sc) != 0) {
+ /* error printed by mfii_aen_register */
+ goto intr_disestablish;
+ }
+
+ mutex_enter(&sc->sc_lock);
+ if (mfii_mgmt(sc, MR_DCMD_LD_GET_LIST, NULL, &sc->sc_ld_list,
+ sizeof(sc->sc_ld_list), MFII_DATA_IN, true) != 0) {
+ mutex_exit(&sc->sc_lock);
+ aprint_error_dev(self,
+ "getting list of logical disks failed\n");
+ goto intr_disestablish;
+ }
+ mutex_exit(&sc->sc_lock);
+ memset(sc->sc_target_lds, -1, sizeof(sc->sc_target_lds));
+ for (i = 0; i < sc->sc_ld_list.mll_no_ld; i++) {
+ int target = sc->sc_ld_list.mll_list[i].mll_ld.mld_target;
+ sc->sc_target_lds[target] = i;
+ }
+
+ /* enable interrupts */
+ mfii_write(sc, MFI_OSTS, 0xffffffff);
+ mfii_write(sc, MFI_OMSK, ~MFII_OSTS_INTR_VALID);
+
+#if NBIO > 0
+ if (bio_register(sc->sc_dev, mfii_ioctl) != 0)
+ panic("%s: controller registration failed", DEVNAME(sc));
+#endif /* NBIO > 0 */
+
+ if (mfii_create_sensors(sc) != 0)
+ aprint_error_dev(self, "unable to create sensors\n");
+
+ if (!pmf_device_register1(sc->sc_dev, mfii_suspend, mfii_resume,
+ mfii_shutdown))
+ aprint_error_dev(self, "couldn't establish power handler\n");
+ return;
+intr_disestablish:
+ pci_intr_disestablish(sc->sc_pc, sc->sc_ih);
+free_sgl:
+ mfii_dmamem_free(sc, sc->sc_sgl);
+free_mfi:
+ mfii_dmamem_free(sc, sc->sc_mfi);
+free_requests:
+ mfii_dmamem_free(sc, sc->sc_requests);
+free_reply_postq:
+ mfii_dmamem_free(sc, sc->sc_reply_postq);
+free_sense:
+ mfii_dmamem_free(sc, sc->sc_sense);
+pci_unmap:
+ bus_space_unmap(sc->sc_iot, sc->sc_ioh, sc->sc_ios);
+}
+
+#if 0
+struct srp_gc mfii_dev_handles_gc =
+ SRP_GC_INITIALIZER(mfii_dev_handles_dtor, NULL);
+
+static inline uint16_t
+mfii_dev_handle(struct mfii_softc *sc, uint16_t target)
+{
+ struct srp_ref sr;
+ uint16_t *map, handle;
+
+ map = srp_enter(&sr, &sc->sc_pd->pd_dev_handles);
+ handle = map[target];
+ srp_leave(&sr);
+
+ return (handle);
+}
+
+int
+mfii_dev_handles_update(struct mfii_softc *sc)
+{
+ struct mfii_ld_map *lm;
+ uint16_t *dev_handles = NULL;
+ int i;
+ int rv = 0;
+
+ lm = malloc(sizeof(*lm), M_TEMP, M_WAITOK|M_ZERO);
+
+ rv = mfii_mgmt(sc, MR_DCMD_LD_MAP_GET_INFO, NULL, lm, sizeof(*lm),
+ MFII_DATA_IN, false);
+
+ if (rv != 0) {
+ rv = EIO;
+ goto free_lm;
+ }
+
+ dev_handles = mallocarray(MFI_MAX_PD, sizeof(*dev_handles),
+ M_DEVBUF, M_WAITOK);
+
+ for (i = 0; i < MFI_MAX_PD; i++)
+ dev_handles[i] = lm->mlm_dev_handle[i].mdh_cur_handle;
+
+ /* commit the updated info */
+ sc->sc_pd->pd_timeout = lm->mlm_pd_timeout;
+ srp_update_locked(&mfii_dev_handles_gc,
+ &sc->sc_pd->pd_dev_handles, dev_handles);
+
+free_lm:
+ free(lm, M_TEMP, sizeof(*lm));
+
+ return (rv);
+}
+
+void
+mfii_dev_handles_dtor(void *null, void *v)
+{
+ uint16_t *dev_handles = v;
+
+ free(dev_handles, M_DEVBUF, sizeof(*dev_handles) * MFI_MAX_PD);
+}
+#endif /* 0 */
+
+int
+mfii_detach(device_t self, int flags)
+{
+ struct mfii_softc *sc = device_private(self);
+ int error;
+
+ if (sc->sc_ih == NULL)
+ return (0);
+
+ if ((error = config_detach_children(sc->sc_dev, flags)) != 0)
+ return error;
+
+ mfii_destroy_sensors(sc);
+#if NBIO > 0
+ bio_unregister(sc->sc_dev);
+#endif
+ mfii_shutdown(sc->sc_dev, 0);
+ mfii_write(sc, MFI_OMSK, 0xffffffff);
+
+ mfii_aen_unregister(sc);
+ pci_intr_disestablish(sc->sc_pc, sc->sc_ih);
+ mfii_dmamem_free(sc, sc->sc_sgl);
+ mfii_dmamem_free(sc, sc->sc_mfi);
+ mfii_dmamem_free(sc, sc->sc_requests);
+ mfii_dmamem_free(sc, sc->sc_reply_postq);
+ mfii_dmamem_free(sc, sc->sc_sense);
+ bus_space_unmap(sc->sc_iot, sc->sc_ioh, sc->sc_ios);
+
+ return (0);
+}
+
+int
+mfii_rescan(device_t self, const char *ifattr, const int *locators)
+{
+ struct mfii_softc *sc = device_private(self);
+ if (sc->sc_child != NULL)
+ return 0;
+
+ sc->sc_child = config_found_sm_loc(self, ifattr, locators, &sc->sc_chan,
+ scsiprint, NULL);
+ return 0;
+}
+
+void
+mfii_childdetached(device_t self, device_t child)
+{
+ struct mfii_softc *sc = device_private(self);
+
+ KASSERT(self == sc->sc_dev);
+ KASSERT(child == sc->sc_child);
+
+ if (child == sc->sc_child)
+ sc->sc_child = NULL;
+}
+
+static bool
+mfii_suspend(device_t dev, const pmf_qual_t *q)
+{
+ /* XXX to be implemented */
+ return false;
+}
+
+static bool
+mfii_resume(device_t dev, const pmf_qual_t *q)
+{
+ /* XXX to be implemented */
+ return false;
+}
+
+static bool
+mfii_shutdown(device_t dev, int how)
+{
+ struct mfii_softc *sc = device_private(dev);
+ struct mfii_ccb *ccb;
+ union mfi_mbox mbox;
+ bool rv = true;;
+
+ memset(&mbox, 0, sizeof(mbox));
+
+ mutex_enter(&sc->sc_lock);
+ DNPRINTF(MFI_D_MISC, "%s: mfii_shutdown\n", DEVNAME(sc));
+ ccb = mfii_get_ccb(sc);
+ if (ccb == NULL)
+ return false;
+ mutex_enter(&sc->sc_ccb_mtx);
+ if (sc->sc_running) {
+ sc->sc_running = 0; /* prevent new commands */
+ mutex_exit(&sc->sc_ccb_mtx);
+#if 0 /* XXX why does this hang ? */
+ mbox.b[0] = MR_FLUSH_CTRL_CACHE | MR_FLUSH_DISK_CACHE;
+ mfii_scrub_ccb(ccb);
+ if (mfii_do_mgmt(sc, ccb, MR_DCMD_CTRL_CACHE_FLUSH, &mbox,
+ NULL, 0, MFII_DATA_NONE, true)) {
+ aprint_error_dev(dev, "shutdown: cache flush failed\n");
+ rv = false;
+ goto fail;
+ }
+ printf("ok1\n");
+#endif
+ mbox.b[0] = 0;
+ mfii_scrub_ccb(ccb);
+ if (mfii_do_mgmt(sc, ccb, MR_DCMD_CTRL_SHUTDOWN, &mbox,
+ NULL, 0, MFII_DATA_NONE, true)) {
+ aprint_error_dev(dev, "shutdown: "
+ "firmware shutdown failed\n");
+ rv = false;
+ goto fail;
+ }
+ } else {
+ mutex_exit(&sc->sc_ccb_mtx);
+ }
+fail:
+ mfii_put_ccb(sc, ccb);
+ mutex_exit(&sc->sc_lock);
+ return rv;
+}
+
+static u_int32_t
+mfii_read(struct mfii_softc *sc, bus_size_t r)
+{
+ bus_space_barrier(sc->sc_iot, sc->sc_ioh, r, 4,
+ BUS_SPACE_BARRIER_READ);
+ return (bus_space_read_4(sc->sc_iot, sc->sc_ioh, r));
+}
+
+static void
+mfii_write(struct mfii_softc *sc, bus_size_t r, u_int32_t v)
+{
+ bus_space_write_4(sc->sc_iot, sc->sc_ioh, r, v);
+ bus_space_barrier(sc->sc_iot, sc->sc_ioh, r, 4,
+ BUS_SPACE_BARRIER_WRITE);
+}
+
+struct mfii_dmamem *
+mfii_dmamem_alloc(struct mfii_softc *sc, size_t size)
+{
+ struct mfii_dmamem *m;
+ int nsegs;
+
+ m = malloc(sizeof(*m), M_DEVBUF, M_NOWAIT | M_ZERO);
+ if (m == NULL)
+ return (NULL);
+
+ m->mdm_size = size;
+
+ if (bus_dmamap_create(sc->sc_dmat, size, 1, size, 0,
+ BUS_DMA_NOWAIT | BUS_DMA_ALLOCNOW, &m->mdm_map) != 0)
+ goto mdmfree;
+
+ if (bus_dmamem_alloc(sc->sc_dmat, size, PAGE_SIZE, 0, &m->mdm_seg, 1,
+ &nsegs, BUS_DMA_NOWAIT) != 0)
+ goto destroy;
+
+ if (bus_dmamem_map(sc->sc_dmat, &m->mdm_seg, nsegs, size, &m->mdm_kva,
+ BUS_DMA_NOWAIT) != 0)
+ goto free;
+
+ if (bus_dmamap_load(sc->sc_dmat, m->mdm_map, m->mdm_kva, size, NULL,
+ BUS_DMA_NOWAIT) != 0)
+ goto unmap;
+
+ memset(m->mdm_kva, 0, size);
+ return (m);
+
+unmap:
+ bus_dmamem_unmap(sc->sc_dmat, m->mdm_kva, m->mdm_size);
+free:
+ bus_dmamem_free(sc->sc_dmat, &m->mdm_seg, 1);
+destroy:
+ bus_dmamap_destroy(sc->sc_dmat, m->mdm_map);
+mdmfree:
+ free(m, M_DEVBUF);
+
+ return (NULL);
+}
+
+void
+mfii_dmamem_free(struct mfii_softc *sc, struct mfii_dmamem *m)
+{
+ bus_dmamap_unload(sc->sc_dmat, m->mdm_map);
+ bus_dmamem_unmap(sc->sc_dmat, m->mdm_kva, m->mdm_size);
+ bus_dmamem_free(sc->sc_dmat, &m->mdm_seg, 1);
+ bus_dmamap_destroy(sc->sc_dmat, m->mdm_map);
+ free(m, M_DEVBUF);
+}
+
+void
+mfii_dcmd_start(struct mfii_softc *sc, struct mfii_ccb *ccb)
+{
+ struct mpii_msg_scsi_io *io = ccb->ccb_request;
+ struct mfii_raid_context *ctx = (struct mfii_raid_context *)(io + 1);
+ struct mfii_sge *sge = (struct mfii_sge *)(ctx + 1);
+
+ io->function = MFII_FUNCTION_PASSTHRU_IO;
+ io->sgl_offset0 = (uint32_t *)sge - (uint32_t *)io;
+ io->chain_offset = io->sgl_offset0 / 4;
+
+ sge->sg_addr = htole64(ccb->ccb_sense_dva);
+ sge->sg_len = htole32(sizeof(*ccb->ccb_sense));
+ sge->sg_flags = MFII_SGE_CHAIN_ELEMENT | MFII_SGE_ADDR_IOCPLBNTA;
+
+ ccb->ccb_req.flags = MFII_REQ_TYPE_SCSI;
+ ccb->ccb_req.smid = le16toh(ccb->ccb_smid);
+
+ mfii_start(sc, ccb);
+}
+
+int
+mfii_aen_register(struct mfii_softc *sc)
+{
+ struct mfi_evt_log_info mel;
+ struct mfii_ccb *ccb;
+ struct mfii_dmamem *mdm;
+ int rv;
+
+ ccb = mfii_get_ccb(sc);
+ if (ccb == NULL) {
+ printf("%s: unable to allocate ccb for aen\n", DEVNAME(sc));
+ return (ENOMEM);
+ }
+
+ memset(&mel, 0, sizeof(mel));
+ mfii_scrub_ccb(ccb);
+
+ rv = mfii_do_mgmt(sc, ccb, MR_DCMD_CTRL_EVENT_GET_INFO, NULL,
+ &mel, sizeof(mel), MFII_DATA_IN, true);
+ if (rv != 0) {
+ mfii_put_ccb(sc, ccb);
+ aprint_error_dev(sc->sc_dev, "unable to get event info\n");
+ return (EIO);
+ }
+
+ mdm = mfii_dmamem_alloc(sc, sizeof(struct mfi_evt_detail));
+ if (mdm == NULL) {
+ mfii_put_ccb(sc, ccb);
+ aprint_error_dev(sc->sc_dev, "unable to allocate event data\n");
+ return (ENOMEM);
+ }
+
+ /* replay all the events from boot */
+ mfii_aen_start(sc, ccb, mdm, le32toh(mel.mel_boot_seq_num));
+
+ return (0);
+}
+
+void
+mfii_aen_start(struct mfii_softc *sc, struct mfii_ccb *ccb,
+ struct mfii_dmamem *mdm, uint32_t seq)
+{
+ struct mfi_dcmd_frame *dcmd = mfii_dcmd_frame(ccb);
+ struct mfi_frame_header *hdr = &dcmd->mdf_header;
+ union mfi_sgl *sgl = &dcmd->mdf_sgl;
+ union mfi_evt_class_locale mec;
+
+ mfii_scrub_ccb(ccb);
+ mfii_dcmd_scrub(ccb);
+ memset(MFII_DMA_KVA(mdm), 0, MFII_DMA_LEN(mdm));
+
+ ccb->ccb_cookie = mdm;
+ ccb->ccb_done = mfii_aen_done;
+ sc->sc_aen_ccb = ccb;
+
+ mec.mec_members.class = MFI_EVT_CLASS_DEBUG;
+ mec.mec_members.reserved = 0;
+ mec.mec_members.locale = htole16(MFI_EVT_LOCALE_ALL);
+
+ hdr->mfh_cmd = MFI_CMD_DCMD;
+ hdr->mfh_sg_count = 1;
+ hdr->mfh_flags = htole16(MFI_FRAME_DIR_READ | MFI_FRAME_SGL64);
+ hdr->mfh_data_len = htole32(MFII_DMA_LEN(mdm));
+ dcmd->mdf_opcode = htole32(MR_DCMD_CTRL_EVENT_WAIT);
+ dcmd->mdf_mbox.w[0] = htole32(seq);
+ dcmd->mdf_mbox.w[1] = htole32(mec.mec_word);
+ sgl->sg64[0].addr = htole64(MFII_DMA_DVA(mdm));
+ sgl->sg64[0].len = htole32(MFII_DMA_LEN(mdm));
+
+ bus_dmamap_sync(sc->sc_dmat, MFII_DMA_MAP(mdm),
+ 0, MFII_DMA_LEN(mdm), BUS_DMASYNC_PREREAD);
+
+ mfii_dcmd_sync(sc, ccb, BUS_DMASYNC_PREREAD|BUS_DMASYNC_PREWRITE);
+ mfii_dcmd_start(sc, ccb);
+}
+
+void
+mfii_aen_done(struct mfii_softc *sc, struct mfii_ccb *ccb)
+{
+ KASSERT(sc->sc_aen_ccb == ccb);
+
+ /*
+ * defer to a thread with KERNEL_LOCK so we can run autoconf
+ * We shouldn't have more than one AEN command pending at a time,
+ * so no need to lock
+ */
+ if (sc->sc_running)
+ workqueue_enqueue(sc->sc_aen_wq, &sc->sc_aen_work, NULL);
+}
+
+void
+mfii_aen(struct work *wk, void *arg)
+{
+ struct mfii_softc *sc = arg;
+ struct mfii_ccb *ccb = sc->sc_aen_ccb;
+ struct mfii_dmamem *mdm = ccb->ccb_cookie;
+ const struct mfi_evt_detail *med = MFII_DMA_KVA(mdm);
+
+ mfii_dcmd_sync(sc, ccb,
+ BUS_DMASYNC_POSTREAD|BUS_DMASYNC_POSTWRITE);
+ bus_dmamap_sync(sc->sc_dmat, MFII_DMA_MAP(mdm),
+ 0, MFII_DMA_LEN(mdm), BUS_DMASYNC_POSTREAD);
+
+ DNPRINTF(MFII_D_MISC, "%s: %u %08x %02x %s\n", DEVNAME(sc),
+ le32toh(med->med_seq_num), le32toh(med->med_code),
+ med->med_arg_type, med->med_description);
+
+ switch (le32toh(med->med_code)) {
+ case MR_EVT_PD_INSERTED_EXT:
+ if (med->med_arg_type != MR_EVT_ARGS_PD_ADDRESS)
+ break;
+
+ mfii_aen_pd_insert(sc, &med->args.pd_address);
+ break;
+ case MR_EVT_PD_REMOVED_EXT:
+ if (med->med_arg_type != MR_EVT_ARGS_PD_ADDRESS)
+ break;
+
+ mfii_aen_pd_remove(sc, &med->args.pd_address);
+ break;
+
+ case MR_EVT_PD_STATE_CHANGE:
+ if (med->med_arg_type != MR_EVT_ARGS_PD_STATE)
+ break;
+
+ mfii_aen_pd_state_change(sc, &med->args.pd_state);
+ break;
+
+ case MR_EVT_LD_CREATED:
+ case MR_EVT_LD_DELETED:
+ mfii_aen_ld_update(sc);
+ break;
+
+ default:
+ break;
+ }
+
+ mfii_aen_start(sc, ccb, mdm, le32toh(med->med_seq_num) + 1);
+}
+
+void
+mfii_aen_pd_insert(struct mfii_softc *sc,
+ const struct mfi_evtarg_pd_address *pd)
+{
+ printf("%s: physical disk inserted id %d enclosure %d\n", DEVNAME(sc),
+ le16toh(pd->device_id), le16toh(pd->encl_id));
+}
+
+void
+mfii_aen_pd_remove(struct mfii_softc *sc,
+ const struct mfi_evtarg_pd_address *pd)
+{
+ printf("%s: physical disk removed id %d enclosure %d\n", DEVNAME(sc),
+ le16toh(pd->device_id), le16toh(pd->encl_id));
+}
+
+void
+mfii_aen_pd_state_change(struct mfii_softc *sc,
+ const struct mfi_evtarg_pd_state *state)
+{
+ return;
+}
+
+void
+mfii_aen_ld_update(struct mfii_softc *sc)
+{
+ int i, target, old, nld;
+ int newlds[MFI_MAX_LD];
+
+ mutex_enter(&sc->sc_lock);
+ if (mfii_mgmt(sc, MR_DCMD_LD_GET_LIST, NULL, &sc->sc_ld_list,
+ sizeof(sc->sc_ld_list), MFII_DATA_IN, false) != 0) {
+ mutex_exit(&sc->sc_lock);
+ DNPRINTF(MFII_D_MISC, "%s: getting list of logical disks failed\n",
+ DEVNAME(sc));
+ return;
+ }
+ mutex_exit(&sc->sc_lock);
+
+ memset(newlds, -1, sizeof(newlds));
+
+ for (i = 0; i < sc->sc_ld_list.mll_no_ld; i++) {
+ target = sc->sc_ld_list.mll_list[i].mll_ld.mld_target;
+ DNPRINTF(MFII_D_MISC, "%s: target %d: state %d\n",
+ DEVNAME(sc), target, sc->sc_ld_list.mll_list[i].mll_state);
+ newlds[target] = i;
+ }
+
+ for (i = 0; i < MFI_MAX_LD; i++) {
+ old = sc->sc_target_lds[i];
+ nld = newlds[i];
+
+ if (old == -1 && nld != -1) {
+ printf("%s: logical drive %d added (target %d)\n",
+ DEVNAME(sc), i, nld);
+
+ // XXX scsi_probe_target(sc->sc_scsibus, i);
+
+ mfii_init_ld_sensor(sc, &sc->sc_sensors[i], i);
+ mfii_attach_sensor(sc, &sc->sc_sensors[i]);
+ } else if (nld == -1 && old != -1) {
+ printf("%s: logical drive %d removed (target %d)\n",
+ DEVNAME(sc), i, old);
+
+ scsipi_target_detach(&sc->sc_chan, i, 0, DETACH_FORCE);
+ sysmon_envsys_sensor_detach(sc->sc_sme,
+ &sc->sc_sensors[i]);
+ }
+ }
+
+ memcpy(sc->sc_target_lds, newlds, sizeof(sc->sc_target_lds));
+}
+
+void
+mfii_aen_unregister(struct mfii_softc *sc)
+{
+ /* XXX */
+}
+
+int
+mfii_transition_firmware(struct mfii_softc *sc)
+{
+ int32_t fw_state, cur_state;
+ int max_wait, i;
+
+ fw_state = mfii_fw_state(sc) & MFI_STATE_MASK;
+
+ while (fw_state != MFI_STATE_READY) {
+ cur_state = fw_state;
+ switch (fw_state) {
+ case MFI_STATE_FAULT:
+ printf("%s: firmware fault\n", DEVNAME(sc));
+ return (1);
+ case MFI_STATE_WAIT_HANDSHAKE:
+ mfii_write(sc, MFI_SKINNY_IDB,
+ MFI_INIT_CLEAR_HANDSHAKE);
+ max_wait = 2;
+ break;
+ case MFI_STATE_OPERATIONAL:
+ mfii_write(sc, MFI_SKINNY_IDB, MFI_INIT_READY);
+ max_wait = 10;
+ break;
+ case MFI_STATE_UNDEFINED:
+ case MFI_STATE_BB_INIT:
+ max_wait = 2;
+ break;
+ case MFI_STATE_FW_INIT:
+ case MFI_STATE_DEVICE_SCAN:
+ case MFI_STATE_FLUSH_CACHE:
+ max_wait = 20;
+ break;
+ default:
+ printf("%s: unknown firmware state %d\n",
+ DEVNAME(sc), fw_state);
+ return (1);
+ }
+ for (i = 0; i < (max_wait * 10); i++) {
+ fw_state = mfii_fw_state(sc) & MFI_STATE_MASK;
+ if (fw_state == cur_state)
+ DELAY(100000);
+ else
+ break;
+ }
+ if (fw_state == cur_state) {
+ printf("%s: firmware stuck in state %#x\n",
+ DEVNAME(sc), fw_state);
+ return (1);
+ }
+ }
+
+ return (0);
+}
+
+int
+mfii_get_info(struct mfii_softc *sc)
+{
+ int i, rv;
+
+ rv = mfii_mgmt(sc, MR_DCMD_CTRL_GET_INFO, NULL, &sc->sc_info,
+ sizeof(sc->sc_info), MFII_DATA_IN, true);
+
+ if (rv != 0)
+ return (rv);
+
+ for (i = 0; i < sc->sc_info.mci_image_component_count; i++) {
+ DPRINTF("%s: active FW %s Version %s date %s time %s\n",
+ DEVNAME(sc),
+ sc->sc_info.mci_image_component[i].mic_name,
+ sc->sc_info.mci_image_component[i].mic_version,
+ sc->sc_info.mci_image_component[i].mic_build_date,
+ sc->sc_info.mci_image_component[i].mic_build_time);
+ }
+
+ for (i = 0; i < sc->sc_info.mci_pending_image_component_count; i++) {
+ DPRINTF("%s: pending FW %s Version %s date %s time %s\n",
+ DEVNAME(sc),
+ sc->sc_info.mci_pending_image_component[i].mic_name,
+ sc->sc_info.mci_pending_image_component[i].mic_version,
+ sc->sc_info.mci_pending_image_component[i].mic_build_date,
+ sc->sc_info.mci_pending_image_component[i].mic_build_time);
+ }
+
+ DPRINTF("%s: max_arms %d max_spans %d max_arrs %d max_lds %d name %s\n",
+ DEVNAME(sc),
+ sc->sc_info.mci_max_arms,
+ sc->sc_info.mci_max_spans,
+ sc->sc_info.mci_max_arrays,
+ sc->sc_info.mci_max_lds,
+ sc->sc_info.mci_product_name);
+
+ DPRINTF("%s: serial %s present %#x fw time %d max_cmds %d max_sg %d\n",
+ DEVNAME(sc),
+ sc->sc_info.mci_serial_number,
+ sc->sc_info.mci_hw_present,
+ sc->sc_info.mci_current_fw_time,
+ sc->sc_info.mci_max_cmds,
+ sc->sc_info.mci_max_sg_elements);
+
+ DPRINTF("%s: max_rq %d lds_pres %d lds_deg %d lds_off %d pd_pres %d\n",
+ DEVNAME(sc),
+ sc->sc_info.mci_max_request_size,
+ sc->sc_info.mci_lds_present,
+ sc->sc_info.mci_lds_degraded,
+ sc->sc_info.mci_lds_offline,
+ sc->sc_info.mci_pd_present);
+
+ DPRINTF("%s: pd_dsk_prs %d pd_dsk_pred_fail %d pd_dsk_fail %d\n",
+ DEVNAME(sc),
+ sc->sc_info.mci_pd_disks_present,
+ sc->sc_info.mci_pd_disks_pred_failure,
+ sc->sc_info.mci_pd_disks_failed);
+
+ DPRINTF("%s: nvram %d mem %d flash %d\n",
+ DEVNAME(sc),
+ sc->sc_info.mci_nvram_size,
+ sc->sc_info.mci_memory_size,
+ sc->sc_info.mci_flash_size);
+
+ DPRINTF("%s: ram_cor %d ram_uncor %d clus_all %d clus_act %d\n",
+ DEVNAME(sc),
+ sc->sc_info.mci_ram_correctable_errors,
+ sc->sc_info.mci_ram_uncorrectable_errors,
+ sc->sc_info.mci_cluster_allowed,
+ sc->sc_info.mci_cluster_active);
+
+ DPRINTF("%s: max_strps_io %d raid_lvl %#x adapt_ops %#x ld_ops %#x\n",
+ DEVNAME(sc),
+ sc->sc_info.mci_max_strips_per_io,
+ sc->sc_info.mci_raid_levels,
+ sc->sc_info.mci_adapter_ops,
+ sc->sc_info.mci_ld_ops);
+
+ DPRINTF("%s: strp_sz_min %d strp_sz_max %d pd_ops %#x pd_mix %#x\n",
+ DEVNAME(sc),
+ sc->sc_info.mci_stripe_sz_ops.min,
+ sc->sc_info.mci_stripe_sz_ops.max,
+ sc->sc_info.mci_pd_ops,
+ sc->sc_info.mci_pd_mix_support);
+
+ DPRINTF("%s: ecc_bucket %d pckg_prop %s\n",
+ DEVNAME(sc),
+ sc->sc_info.mci_ecc_bucket_count,
+ sc->sc_info.mci_package_version);
+
+ DPRINTF("%s: sq_nm %d prd_fail_poll %d intr_thrtl %d intr_thrtl_to %d\n",
+ DEVNAME(sc),
+ sc->sc_info.mci_properties.mcp_seq_num,
+ sc->sc_info.mci_properties.mcp_pred_fail_poll_interval,
+ sc->sc_info.mci_properties.mcp_intr_throttle_cnt,
+ sc->sc_info.mci_properties.mcp_intr_throttle_timeout);
+
+ DPRINTF("%s: rbld_rate %d patr_rd_rate %d bgi_rate %d cc_rate %d\n",
+ DEVNAME(sc),
+ sc->sc_info.mci_properties.mcp_rebuild_rate,
+ sc->sc_info.mci_properties.mcp_patrol_read_rate,
+ sc->sc_info.mci_properties.mcp_bgi_rate,
+ sc->sc_info.mci_properties.mcp_cc_rate);
+
+ DPRINTF("%s: rc_rate %d ch_flsh %d spin_cnt %d spin_dly %d clus_en %d\n",
+ DEVNAME(sc),
+ sc->sc_info.mci_properties.mcp_recon_rate,
+ sc->sc_info.mci_properties.mcp_cache_flush_interval,
+ sc->sc_info.mci_properties.mcp_spinup_drv_cnt,
+ sc->sc_info.mci_properties.mcp_spinup_delay,
+ sc->sc_info.mci_properties.mcp_cluster_enable);
+
+ DPRINTF("%s: coerc %d alarm %d dis_auto_rbld %d dis_bat_wrn %d ecc %d\n",
+ DEVNAME(sc),
+ sc->sc_info.mci_properties.mcp_coercion_mode,
+ sc->sc_info.mci_properties.mcp_alarm_enable,
+ sc->sc_info.mci_properties.mcp_disable_auto_rebuild,
+ sc->sc_info.mci_properties.mcp_disable_battery_warn,
+ sc->sc_info.mci_properties.mcp_ecc_bucket_size);
+
+ DPRINTF("%s: ecc_leak %d rest_hs %d exp_encl_dev %d\n",
+ DEVNAME(sc),
+ sc->sc_info.mci_properties.mcp_ecc_bucket_leak_rate,
+ sc->sc_info.mci_properties.mcp_restore_hotspare_on_insertion,
+ sc->sc_info.mci_properties.mcp_expose_encl_devices);
+
+ DPRINTF("%s: vendor %#x device %#x subvendor %#x subdevice %#x\n",
+ DEVNAME(sc),
+ sc->sc_info.mci_pci.mip_vendor,
+ sc->sc_info.mci_pci.mip_device,
+ sc->sc_info.mci_pci.mip_subvendor,
+ sc->sc_info.mci_pci.mip_subdevice);
+
+ DPRINTF("%s: type %#x port_count %d port_addr ",
+ DEVNAME(sc),
+ sc->sc_info.mci_host.mih_type,
+ sc->sc_info.mci_host.mih_port_count);
+
+ for (i = 0; i < 8; i++)
+ DPRINTF("%.0" PRIx64 " ", sc->sc_info.mci_host.mih_port_addr[i]);
+ DPRINTF("\n");
+
+ DPRINTF("%s: type %.x port_count %d port_addr ",
+ DEVNAME(sc),
+ sc->sc_info.mci_device.mid_type,
+ sc->sc_info.mci_device.mid_port_count);
+
+ for (i = 0; i < 8; i++)
+ DPRINTF("%.0" PRIx64 " ", sc->sc_info.mci_device.mid_port_addr[i]);
+ DPRINTF("\n");
+
+ return (0);
+}
+
+int
+mfii_mfa_poll(struct mfii_softc *sc, struct mfii_ccb *ccb)
+{
+ struct mfi_frame_header *hdr = ccb->ccb_request;
+ u_int64_t r;
+ int to = 0, rv = 0;
+
+#ifdef DIAGNOSTIC
+ if (ccb->ccb_cookie != NULL || ccb->ccb_done != NULL)
+ panic("mfii_mfa_poll called with cookie or done set");
+#endif
+
+ hdr->mfh_context = ccb->ccb_smid;
+ hdr->mfh_cmd_status = MFI_STAT_INVALID_STATUS;
+ hdr->mfh_flags |= htole16(MFI_FRAME_DONT_POST_IN_REPLY_QUEUE);
+
+ r = MFII_REQ_MFA(ccb->ccb_request_dva);
+ memcpy(&ccb->ccb_req, &r, sizeof(ccb->ccb_req));
+
+ mfii_start(sc, ccb);
+
+ for (;;) {
+ bus_dmamap_sync(sc->sc_dmat, MFII_DMA_MAP(sc->sc_requests),
+ ccb->ccb_request_offset, MFII_REQUEST_SIZE,
+ BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE);
+
+ if (hdr->mfh_cmd_status != MFI_STAT_INVALID_STATUS)
+ break;
+
+ if (to++ > 5000) { /* XXX 5 seconds busywait sucks */
+ printf("%s: timeout on ccb %d\n", DEVNAME(sc),
+ ccb->ccb_smid);
+ ccb->ccb_flags |= MFI_CCB_F_ERR;
+ rv = 1;
+ break;
+ }
+
+ bus_dmamap_sync(sc->sc_dmat, MFII_DMA_MAP(sc->sc_requests),
+ ccb->ccb_request_offset, MFII_REQUEST_SIZE,
+ BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE);
+
+ delay(1000);
+ }
+
+ if (ccb->ccb_len > 0) {
+ bus_dmamap_sync(sc->sc_dmat, ccb->ccb_dmamap32,
+ 0, ccb->ccb_dmamap32->dm_mapsize,
+ (ccb->ccb_direction == MFII_DATA_IN) ?
+ BUS_DMASYNC_POSTREAD : BUS_DMASYNC_POSTWRITE);
+
+ bus_dmamap_unload(sc->sc_dmat, ccb->ccb_dmamap32);
+ }
+
+ return (rv);
+}
+
+int
+mfii_poll(struct mfii_softc *sc, struct mfii_ccb *ccb)
+{
+ void (*done)(struct mfii_softc *, struct mfii_ccb *);
+ void *cookie;
+ int rv = 1;
+
+ done = ccb->ccb_done;
+ cookie = ccb->ccb_cookie;
+
+ ccb->ccb_done = mfii_poll_done;
+ ccb->ccb_cookie = &rv;
+
+ mfii_start(sc, ccb);
+
+ do {
+ delay(10);
+ mfii_postq(sc);
+ } while (rv == 1);
+
+ ccb->ccb_cookie = cookie;
+ done(sc, ccb);
+
+ return (0);
+}
+
+void
+mfii_poll_done(struct mfii_softc *sc, struct mfii_ccb *ccb)
+{
+ int *rv = ccb->ccb_cookie;
+
+ *rv = 0;
+}
+
+int
+mfii_exec(struct mfii_softc *sc, struct mfii_ccb *ccb)
+{
+#ifdef DIAGNOSTIC
+ if (ccb->ccb_cookie != NULL || ccb->ccb_done != NULL)
+ panic("mfii_exec called with cookie or done set");
+#endif
+
+ ccb->ccb_cookie = ccb;
+ ccb->ccb_done = mfii_exec_done;
+
+ mfii_start(sc, ccb);
+
+ mutex_enter(&ccb->ccb_mtx);
+ while (ccb->ccb_cookie != NULL)
+ cv_wait(&ccb->ccb_cv, &ccb->ccb_mtx);
+ mutex_exit(&ccb->ccb_mtx);
+
+ return (0);
+}
+
+void
+mfii_exec_done(struct mfii_softc *sc, struct mfii_ccb *ccb)
+{
+ mutex_enter(&ccb->ccb_mtx);
+ ccb->ccb_cookie = NULL;
+ cv_signal(&ccb->ccb_cv);
+ mutex_exit(&ccb->ccb_mtx);
+}
+
+int
+mfii_mgmt(struct mfii_softc *sc, uint32_t opc, const union mfi_mbox *mbox,
+ void *buf, size_t len, mfii_direction_t dir, bool poll)
+{
+ struct mfii_ccb *ccb;
+ int rv;
+
+ KASSERT(mutex_owned(&sc->sc_lock));
+ if (!sc->sc_running)
+ return EAGAIN;
+
+ ccb = mfii_get_ccb(sc);
+ if (ccb == NULL)
+ return (ENOMEM);
+
+ mfii_scrub_ccb(ccb);
+ rv = mfii_do_mgmt(sc, ccb, opc, mbox, buf, len, dir, poll);
+ mfii_put_ccb(sc, ccb);
+
+ return (rv);
+}
+
+int
+mfii_do_mgmt(struct mfii_softc *sc, struct mfii_ccb *ccb, uint32_t opc,
+ const union mfi_mbox *mbox, void *buf, size_t len, mfii_direction_t dir,
+ bool poll)
+{
+ struct mpii_msg_scsi_io *io = ccb->ccb_request;
+ struct mfii_raid_context *ctx = (struct mfii_raid_context *)(io + 1);
+ struct mfii_sge *sge = (struct mfii_sge *)(ctx + 1);
+ struct mfi_dcmd_frame *dcmd = ccb->ccb_mfi;
+ struct mfi_frame_header *hdr = &dcmd->mdf_header;
+ int rv = EIO;
+
+ if (cold)
+ poll = true;
+
+ ccb->ccb_data = buf;
+ ccb->ccb_len = len;
+ ccb->ccb_direction = dir;
+ switch (dir) {
+ case MFII_DATA_IN:
+ hdr->mfh_flags = htole16(MFI_FRAME_DIR_READ);
+ break;
+ case MFII_DATA_OUT:
+ hdr->mfh_flags = htole16(MFI_FRAME_DIR_WRITE);
+ break;
+ case MFII_DATA_NONE:
+ hdr->mfh_flags = htole16(MFI_FRAME_DIR_NONE);
+ break;
+ }
+
+ if (mfii_load_mfa(sc, ccb, &dcmd->mdf_sgl, poll) != 0) {
+ rv = ENOMEM;
+ goto done;
+ }
+
+ hdr->mfh_cmd = MFI_CMD_DCMD;
+ hdr->mfh_context = ccb->ccb_smid;
+ hdr->mfh_data_len = htole32(len);
+ hdr->mfh_sg_count = ccb->ccb_dmamap32->dm_nsegs;
+ KASSERT(!ccb->ccb_dma64);
+
+ dcmd->mdf_opcode = opc;
+ /* handle special opcodes */
+ if (mbox != NULL)
+ memcpy(&dcmd->mdf_mbox, mbox, sizeof(dcmd->mdf_mbox));
+
+ io->function = MFII_FUNCTION_PASSTHRU_IO;
+ io->sgl_offset0 = ((u_int8_t *)sge - (u_int8_t *)io) / 4;
+ io->chain_offset = ((u_int8_t *)sge - (u_int8_t *)io) / 16;
+
+ sge->sg_addr = htole64(ccb->ccb_mfi_dva);
+ sge->sg_len = htole32(MFI_FRAME_SIZE);
+ sge->sg_flags = MFII_SGE_CHAIN_ELEMENT | MFII_SGE_ADDR_IOCPLBNTA;
+
+ ccb->ccb_req.flags = MFII_REQ_TYPE_SCSI;
+ ccb->ccb_req.smid = le16toh(ccb->ccb_smid);
+
+ if (poll) {
+ ccb->ccb_done = mfii_empty_done;
+ mfii_poll(sc, ccb);
+ } else
+ mfii_exec(sc, ccb);
+
+ if (hdr->mfh_cmd_status == MFI_STAT_OK) {
+ rv = 0;
+ }
+
+done:
+ return (rv);
+}
+
+void
+mfii_empty_done(struct mfii_softc *sc, struct mfii_ccb *ccb)
+{
+ return;
+}
+
+int
+mfii_load_mfa(struct mfii_softc *sc, struct mfii_ccb *ccb,
+ void *sglp, int nosleep)
+{
+ union mfi_sgl *sgl = sglp;
+ bus_dmamap_t dmap = ccb->ccb_dmamap32;
+ int error;
+ int i;
+
+ KASSERT(!ccb->ccb_dma64);
+ if (ccb->ccb_len == 0)
+ return (0);
+
+ error = bus_dmamap_load(sc->sc_dmat, dmap,
+ ccb->ccb_data, ccb->ccb_len, NULL,
+ nosleep ? BUS_DMA_NOWAIT : BUS_DMA_WAITOK);
+ if (error) {
+ printf("%s: error %d loading dmamap\n", DEVNAME(sc), error);
+ return (1);
+ }
+
+ for (i = 0; i < dmap->dm_nsegs; i++) {
+ sgl->sg32[i].addr = htole32(dmap->dm_segs[i].ds_addr);
+ sgl->sg32[i].len = htole32(dmap->dm_segs[i].ds_len);
+ }
+
+ bus_dmamap_sync(sc->sc_dmat, dmap, 0, dmap->dm_mapsize,
+ ccb->ccb_direction == MFII_DATA_OUT ?
+ BUS_DMASYNC_PREWRITE : BUS_DMASYNC_PREREAD);
+
+ return (0);
+}
+
+void
+mfii_start(struct mfii_softc *sc, struct mfii_ccb *ccb)
+{
+ u_long *r = (u_long *)&ccb->ccb_req;
+
+ bus_dmamap_sync(sc->sc_dmat, MFII_DMA_MAP(sc->sc_requests),
+ ccb->ccb_request_offset, MFII_REQUEST_SIZE,
+ BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE);
+
+#if defined(__LP64__) && 0
+ bus_space_write_8(sc->sc_iot, sc->sc_ioh, MFI_IQPL, *r);
+#else
+ mutex_enter(&sc->sc_post_mtx);
+ bus_space_write_4(sc->sc_iot, sc->sc_ioh, MFI_IQPL, r[0]);
+ bus_space_barrier(sc->sc_iot, sc->sc_ioh,
+ MFI_IQPL, 8, BUS_SPACE_BARRIER_WRITE);
+
+ bus_space_write_4(sc->sc_iot, sc->sc_ioh, MFI_IQPH, r[1]);
+ bus_space_barrier(sc->sc_iot, sc->sc_ioh,
+ MFI_IQPH, 8, BUS_SPACE_BARRIER_WRITE);
+ mutex_exit(&sc->sc_post_mtx);
+#endif
+}
+
+void
+mfii_done(struct mfii_softc *sc, struct mfii_ccb *ccb)
+{
+ bus_dmamap_sync(sc->sc_dmat, MFII_DMA_MAP(sc->sc_requests),
+ ccb->ccb_request_offset, MFII_REQUEST_SIZE,
+ BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE);
+
+ if (ccb->ccb_sgl_len > 0) {
+ bus_dmamap_sync(sc->sc_dmat, MFII_DMA_MAP(sc->sc_sgl),
+ ccb->ccb_sgl_offset, ccb->ccb_sgl_len,
+ BUS_DMASYNC_POSTWRITE);
+ }
+
+ if (ccb->ccb_dma64) {
+ KASSERT(ccb->ccb_len > 0);
+ bus_dmamap_sync(sc->sc_dmat64, ccb->ccb_dmamap64,
+ 0, ccb->ccb_dmamap64->dm_mapsize,
+ (ccb->ccb_direction == MFII_DATA_IN) ?
+ BUS_DMASYNC_POSTREAD : BUS_DMASYNC_POSTWRITE);
+
+ bus_dmamap_unload(sc->sc_dmat64, ccb->ccb_dmamap64);
+ } else if (ccb->ccb_len > 0) {
+ bus_dmamap_sync(sc->sc_dmat, ccb->ccb_dmamap32,
+ 0, ccb->ccb_dmamap32->dm_mapsize,
+ (ccb->ccb_direction == MFII_DATA_IN) ?
+ BUS_DMASYNC_POSTREAD : BUS_DMASYNC_POSTWRITE);
+
+ bus_dmamap_unload(sc->sc_dmat, ccb->ccb_dmamap32);
+ }
+
+ ccb->ccb_done(sc, ccb);
+}
+
+int
+mfii_initialise_firmware(struct mfii_softc *sc)
+{
+ struct mpii_msg_iocinit_request *iiq;
+ struct mfii_dmamem *m;
+ struct mfii_ccb *ccb;
+ struct mfi_init_frame *init;
+ int rv;
+
+ m = mfii_dmamem_alloc(sc, sizeof(*iiq));
+ if (m == NULL)
+ return (1);
+
+ iiq = MFII_DMA_KVA(m);
+ memset(iiq, 0, sizeof(*iiq));
+
+ iiq->function = MPII_FUNCTION_IOC_INIT;
+ iiq->whoinit = MPII_WHOINIT_HOST_DRIVER;
+
+ iiq->msg_version_maj = 0x02;
+ iiq->msg_version_min = 0x00;
+ iiq->hdr_version_unit = 0x10;
+ iiq->hdr_version_dev = 0x0;
+
+ iiq->system_request_frame_size = htole16(MFII_REQUEST_SIZE / 4);
+
+ iiq->reply_descriptor_post_queue_depth =
+ htole16(sc->sc_reply_postq_depth);
+ iiq->reply_free_queue_depth = htole16(0);
+
+ iiq->sense_buffer_address_high = htole32(
+ MFII_DMA_DVA(sc->sc_sense) >> 32);
+
+ iiq->reply_descriptor_post_queue_address = htole64(
+ MFII_DMA_DVA(sc->sc_reply_postq));
+
+ iiq->system_request_frame_base_address =
+ htole64(MFII_DMA_DVA(sc->sc_requests));
+
+ iiq->timestamp = htole64(time_uptime);
+
+ ccb = mfii_get_ccb(sc);
+ if (ccb == NULL) {
+ /* shouldn't ever run out of ccbs during attach */
+ return (1);
+ }
+ mfii_scrub_ccb(ccb);
+ init = ccb->ccb_request;
+
+ init->mif_header.mfh_cmd = MFI_CMD_INIT;
+ init->mif_header.mfh_data_len = htole32(sizeof(*iiq));
+ init->mif_qinfo_new_addr_lo = htole32(MFII_DMA_DVA(m));
+ init->mif_qinfo_new_addr_hi = htole32((uint64_t)MFII_DMA_DVA(m) >> 32);
+
+ bus_dmamap_sync(sc->sc_dmat, MFII_DMA_MAP(sc->sc_reply_postq),
+ 0, MFII_DMA_LEN(sc->sc_reply_postq),
+ BUS_DMASYNC_PREREAD);
+
+ bus_dmamap_sync(sc->sc_dmat, MFII_DMA_MAP(m),
+ 0, sizeof(*iiq), BUS_DMASYNC_PREREAD);
+
+ rv = mfii_mfa_poll(sc, ccb);
+
+ bus_dmamap_sync(sc->sc_dmat, MFII_DMA_MAP(m),
+ 0, sizeof(*iiq), BUS_DMASYNC_POSTREAD);
+
+ mfii_put_ccb(sc, ccb);
+ mfii_dmamem_free(sc, m);
+
+ return (rv);
+}
+
+int
+mfii_my_intr(struct mfii_softc *sc)
+{
+ u_int32_t status;
+
+ status = mfii_read(sc, MFI_OSTS);
+
+ DNPRINTF(MFII_D_INTR, "%s: intr status 0x%x\n", DEVNAME(sc), status);
+ if (ISSET(status, 0x1)) {
+ mfii_write(sc, MFI_OSTS, status);
+ return (1);
+ }
+
+ return (ISSET(status, MFII_OSTS_INTR_VALID) ? 1 : 0);
+}
+
+int
+mfii_intr(void *arg)
+{
+ struct mfii_softc *sc = arg;
+
+ if (!mfii_my_intr(sc))
+ return (0);
+
+ mfii_postq(sc);
+
+ return (1);
+}
+
+void
+mfii_postq(struct mfii_softc *sc)
+{
+ struct mfii_ccb_list ccbs = SIMPLEQ_HEAD_INITIALIZER(ccbs);
+ struct mpii_reply_descr *postq = MFII_DMA_KVA(sc->sc_reply_postq);
+ struct mpii_reply_descr *rdp;
+ struct mfii_ccb *ccb;
+ int rpi = 0;
+
+ mutex_enter(&sc->sc_reply_postq_mtx);
+
+ bus_dmamap_sync(sc->sc_dmat, MFII_DMA_MAP(sc->sc_reply_postq),
+ 0, MFII_DMA_LEN(sc->sc_reply_postq),
+ BUS_DMASYNC_POSTREAD);
+
+ for (;;) {
+ rdp = &postq[sc->sc_reply_postq_index];
+ DNPRINTF(MFII_D_INTR, "%s: mfii_postq index %d flags 0x%x data 0x%x\n",
+ DEVNAME(sc), sc->sc_reply_postq_index, rdp->reply_flags,
+ rdp->data == 0xffffffff);
+ if ((rdp->reply_flags & MPII_REPLY_DESCR_TYPE_MASK) ==
+ MPII_REPLY_DESCR_UNUSED)
+ break;
+ if (rdp->data == 0xffffffff) {
+ /*
+ * ioc is still writing to the reply post queue
+ * race condition - bail!
+ */
+ break;
+ }
+
+ ccb = &sc->sc_ccb[le16toh(rdp->smid) - 1];
+ SIMPLEQ_INSERT_TAIL(&ccbs, ccb, ccb_link);
+ memset(rdp, 0xff, sizeof(*rdp));
+
+ sc->sc_reply_postq_index++;
+ sc->sc_reply_postq_index %= sc->sc_reply_postq_depth;
+ rpi = 1;
+ }
+
+ bus_dmamap_sync(sc->sc_dmat, MFII_DMA_MAP(sc->sc_reply_postq),
+ 0, MFII_DMA_LEN(sc->sc_reply_postq),
+ BUS_DMASYNC_PREREAD);
+
+ if (rpi)
+ mfii_write(sc, MFII_RPI, sc->sc_reply_postq_index);
+
+ mutex_exit(&sc->sc_reply_postq_mtx);
+
+ while ((ccb = SIMPLEQ_FIRST(&ccbs)) != NULL) {
+ SIMPLEQ_REMOVE_HEAD(&ccbs, ccb_link);
+ mfii_done(sc, ccb);
+ }
+}
+
+void
+mfii_scsipi_request(struct scsipi_channel *chan, scsipi_adapter_req_t req,
+ void *arg)
+{
+ struct scsipi_periph *periph;
+ struct scsipi_xfer *xs;
+ struct scsipi_adapter *adapt = chan->chan_adapter;
+ struct mfii_softc *sc = device_private(adapt->adapt_dev);
+ struct mfii_ccb *ccb;
+ int timeout;
+ int target;
+
+ switch(req) {
+ case ADAPTER_REQ_GROW_RESOURCES:
+ /* Not supported. */
+ return;
+ case ADAPTER_REQ_SET_XFER_MODE:
+ {
+ struct scsipi_xfer_mode *xm = arg;
+ xm->xm_mode = PERIPH_CAP_TQING;
+ xm->xm_period = 0;
+ xm->xm_offset = 0;
+ scsipi_async_event(&sc->sc_chan, ASYNC_EVENT_XFER_MODE, xm);
+ return;
+ }
+ case ADAPTER_REQ_RUN_XFER:
+ break;
+ }
+
+ xs = arg;
+ periph = xs->xs_periph;
+ target = periph->periph_target;
+
+ if (target >= MFI_MAX_LD || !sc->sc_ld[target].ld_present ||
+ periph->periph_lun != 0) {
+ xs->error = XS_SELTIMEOUT;
+ scsipi_done(xs);
+ return;
+ }
+
+ if ((xs->cmd->opcode == SCSI_SYNCHRONIZE_CACHE_10 ||
+ xs->cmd->opcode == SCSI_SYNCHRONIZE_CACHE_16) && sc->sc_bbuok) {
+ /* the cache is stable storage, don't flush */
+ xs->error = XS_NOERROR;
+ xs->status = SCSI_OK;
+ xs->resid = 0;
+ scsipi_done(xs);
+ return;
+ }
+
+ ccb = mfii_get_ccb(sc);
+ if (ccb == NULL) {
+ xs->error = XS_RESOURCE_SHORTAGE;
+ scsipi_done(xs);
+ return;
+ }
+ mfii_scrub_ccb(ccb);
+ ccb->ccb_cookie = xs;
+ ccb->ccb_done = mfii_scsi_cmd_done;
+ ccb->ccb_data = xs->data;
+ ccb->ccb_len = xs->datalen;
+
+ timeout = mstohz(xs->timeout);
+ if (timeout == 0)
+ timeout = 1;
+ callout_reset(&xs->xs_callout, timeout, mfii_scsi_cmd_tmo, ccb);
+
+ switch (xs->cmd->opcode) {
+ case SCSI_READ_6_COMMAND:
+ case READ_10:
+ case READ_12:
+ case READ_16:
+ case SCSI_WRITE_6_COMMAND:
+ case WRITE_10:
+ case WRITE_12:
+ case WRITE_16:
+ if (mfii_scsi_cmd_io(sc, ccb, xs) != 0)
+ goto stuffup;
+ break;
+
+ default:
+ if (mfii_scsi_cmd_cdb(sc, ccb, xs) != 0)
+ goto stuffup;
+ break;
+ }
+
+ xs->error = XS_NOERROR;
+ xs->resid = 0;
+
+ DNPRINTF(MFII_D_CMD, "%s: start io %d cmd %d\n", DEVNAME(sc), target,
+ xs->cmd->opcode);
+
+ if (xs->xs_control & XS_CTL_POLL) {
+ if (mfii_poll(sc, ccb) != 0)
+ goto stuffup;
+ return;
+ }
+
+ ccb->ccb_refcnt = 2; /* one for the chip, one for the timeout */
+ mfii_start(sc, ccb);
+
+ return;
+
+stuffup:
+ xs->error = XS_DRIVER_STUFFUP;
+ scsipi_done(xs);
+ mfii_put_ccb(sc, ccb);
+}
+
+void
+mfii_scsi_cmd_done(struct mfii_softc *sc, struct mfii_ccb *ccb)
+{
+ struct scsipi_xfer *xs = ccb->ccb_cookie;
+ struct mpii_msg_scsi_io *io = ccb->ccb_request;
+ struct mfii_raid_context *ctx = (struct mfii_raid_context *)(io + 1);
+ u_int refs = 2;
+
+ if (callout_stop(&xs->xs_callout) != 0)
+ refs = 1;
+
+ switch (ctx->status) {
+ case MFI_STAT_OK:
+ break;
+
+ case MFI_STAT_SCSI_DONE_WITH_ERROR:
+ xs->error = XS_SENSE;
+ memset(&xs->sense, 0, sizeof(xs->sense));
+ memcpy(&xs->sense, ccb->ccb_sense, sizeof(xs->sense));
+ break;
+
+ case MFI_STAT_LD_OFFLINE:
+ case MFI_STAT_DEVICE_NOT_FOUND:
+ xs->error = XS_SELTIMEOUT;
+ break;
+
+ default:
+ xs->error = XS_DRIVER_STUFFUP;
+ break;
+ }
+
+ if (atomic_add_int_nv(&ccb->ccb_refcnt, -refs) == 0) {
+ scsipi_done(xs);
+ mfii_put_ccb(sc, ccb);
+ }
+}
+
+int
+mfii_scsi_cmd_io(struct mfii_softc *sc, struct mfii_ccb *ccb,
+ struct scsipi_xfer *xs)
+{
+ struct scsipi_periph *periph = xs->xs_periph;
+ struct mpii_msg_scsi_io *io = ccb->ccb_request;
+ struct mfii_raid_context *ctx = (struct mfii_raid_context *)(io + 1);
+ int segs;
+
+ io->dev_handle = htole16(periph->periph_target);
+ io->function = MFII_FUNCTION_LDIO_REQUEST;
+ io->sense_buffer_low_address = htole32(ccb->ccb_sense_dva);
+ io->sgl_flags = htole16(0x02); /* XXX */
+ io->sense_buffer_length = sizeof(xs->sense);
+ io->sgl_offset0 = (sizeof(*io) + sizeof(*ctx)) / 4;
+ io->data_length = htole32(xs->datalen);
+ io->io_flags = htole16(xs->cmdlen);
+ switch (xs->xs_control & (XS_CTL_DATA_IN | XS_CTL_DATA_OUT)) {
+ case XS_CTL_DATA_IN:
+ ccb->ccb_direction = MFII_DATA_IN;
+ io->direction = MPII_SCSIIO_DIR_READ;
+ break;
+ case XS_CTL_DATA_OUT:
+ ccb->ccb_direction = MFII_DATA_OUT;
+ io->direction = MPII_SCSIIO_DIR_WRITE;
+ break;
+ default:
+ ccb->ccb_direction = MFII_DATA_NONE;
+ io->direction = MPII_SCSIIO_DIR_NONE;
+ break;
+ }
+ memcpy(io->cdb, xs->cmd, xs->cmdlen);
+
+ ctx->type_nseg = sc->sc_iop->ldio_ctx_type_nseg;
+ ctx->timeout_value = htole16(0x14); /* XXX */
+ ctx->reg_lock_flags = htole16(sc->sc_iop->ldio_ctx_reg_lock_flags);
+ ctx->virtual_disk_target_id = htole16(periph->periph_target);
+
+ if (mfii_load_ccb(sc, ccb, ctx + 1,
+ ISSET(xs->xs_control, XS_CTL_NOSLEEP)) != 0)
+ return (1);
+
+ KASSERT(ccb->ccb_len == 0 || ccb->ccb_dma64);
+ segs = (ccb->ccb_len == 0) ? 0 : ccb->ccb_dmamap64->dm_nsegs;
+ switch (sc->sc_iop->num_sge_loc) {
+ case MFII_IOP_NUM_SGE_LOC_ORIG:
+ ctx->num_sge = segs;
+ break;
+ case MFII_IOP_NUM_SGE_LOC_35:
+ /* 12 bit field, but we're only using the lower 8 */
+ ctx->span_arm = segs;
+ break;
+ }
+
+ ccb->ccb_req.flags = sc->sc_iop->ldio_req_type;
+ ccb->ccb_req.smid = le16toh(ccb->ccb_smid);
+
+ return (0);
+}
+
+int
+mfii_scsi_cmd_cdb(struct mfii_softc *sc, struct mfii_ccb *ccb,
+ struct scsipi_xfer *xs)
+{
+ struct scsipi_periph *periph = xs->xs_periph;
+ struct mpii_msg_scsi_io *io = ccb->ccb_request;
+ struct mfii_raid_context *ctx = (struct mfii_raid_context *)(io + 1);
+
+ io->dev_handle = htole16(periph->periph_target);
+ io->function = MFII_FUNCTION_LDIO_REQUEST;
+ io->sense_buffer_low_address = htole32(ccb->ccb_sense_dva);
+ io->sgl_flags = htole16(0x02); /* XXX */
+ io->sense_buffer_length = sizeof(xs->sense);
+ io->sgl_offset0 = (sizeof(*io) + sizeof(*ctx)) / 4;
+ io->data_length = htole32(xs->datalen);
+ io->io_flags = htole16(xs->cmdlen);
+ io->lun[0] = htobe16(periph->periph_lun);
+ switch (xs->xs_control & (XS_CTL_DATA_IN | XS_CTL_DATA_OUT)) {
+ case XS_CTL_DATA_IN:
+ ccb->ccb_direction = MFII_DATA_IN;
+ io->direction = MPII_SCSIIO_DIR_READ;
+ break;
+ case XS_CTL_DATA_OUT:
+ ccb->ccb_direction = MFII_DATA_OUT;
+ io->direction = MPII_SCSIIO_DIR_WRITE;
+ break;
+ default:
+ ccb->ccb_direction = MFII_DATA_NONE;
+ io->direction = MPII_SCSIIO_DIR_NONE;
+ break;
+ }
+ memcpy(io->cdb, xs->cmd, xs->cmdlen);
+
+ ctx->virtual_disk_target_id = htole16(periph->periph_target);
+
+ if (mfii_load_ccb(sc, ccb, ctx + 1,
+ ISSET(xs->xs_control, XS_CTL_NOSLEEP)) != 0)
+ return (1);
+
+ ctx->num_sge = (ccb->ccb_len == 0) ? 0 : ccb->ccb_dmamap64->dm_nsegs;
+ KASSERT(ccb->ccb_len == 0 || ccb->ccb_dma64);
+
+ ccb->ccb_req.flags = MFII_REQ_TYPE_SCSI;
+ ccb->ccb_req.smid = le16toh(ccb->ccb_smid);
+
+ return (0);
+}
+
+#if 0
+void
+mfii_pd_scsi_cmd(struct scsipi_xfer *xs)
+{
+ struct scsi_link *link = xs->sc_link;
+ struct mfii_softc *sc = link->adapter_softc;
+ struct mfii_ccb *ccb = xs->io;
+
+ mfii_scrub_ccb(ccb);
+ ccb->ccb_cookie = xs;
+ ccb->ccb_done = mfii_scsi_cmd_done;
+ ccb->ccb_data = xs->data;
+ ccb->ccb_len = xs->datalen;
+
+ // XXX timeout_set(&xs->stimeout, mfii_scsi_cmd_tmo, xs);
+
+ xs->error = mfii_pd_scsi_cmd_cdb(sc, xs);
+ if (xs->error != XS_NOERROR)
+ goto done;
+
+ xs->resid = 0;
+
+ if (ISSET(xs->xs_control, XS_CTL_POLL)) {
+ if (mfii_poll(sc, ccb) != 0)
+ goto stuffup;
+ return;
+ }
+
+ ccb->ccb_refcnt = 2; /* one for the chip, one for the timeout */
+ // XXX timeout_add_msec(&xs->stimeout, xs->timeout);
+ mfii_start(sc, ccb);
+
+ return;
+
+stuffup:
+ xs->error = XS_DRIVER_STUFFUP;
+done:
+ scsi_done(xs);
+}
+
+int
+mfii_pd_scsi_probe(struct scsi_link *link)
+{
+ struct mfii_softc *sc = link->adapter_softc;
+ struct mfi_pd_details mpd;
+ union mfi_mbox mbox;
+ int rv;
+
+ if (link->lun > 0)
+ return (0);
+
+ memset(&mbox, 0, sizeof(mbox));
+ mbox.s[0] = htole16(link->target);
+
+ rv = mfii_mgmt(sc, MR_DCMD_PD_GET_INFO, &mbox, &mpd, sizeof(mpd),
+ MFII_DATA_IN, true);
+ if (rv != 0)
+ return (EIO);
+
+ if (mpd.mpd_fw_state != htole16(MFI_PD_SYSTEM))
+ return (ENXIO);
+
+ return (0);
+}
+
+int
+mfii_pd_scsi_cmd_cdb(struct mfii_softc *sc, struct mfii_ccb *ccb,
+ struct scsipi_xfer *xs)
+{
+ struct scsi_link *link = xs->sc_link;
+ struct mpii_msg_scsi_io *io = ccb->ccb_request;
+ struct mfii_raid_context *ctx = (struct mfii_raid_context *)(io + 1);
+ uint16_t dev_handle;
+
+ dev_handle = mfii_dev_handle(sc, link->target);
+ if (dev_handle == htole16(0xffff))
+ return (XS_SELTIMEOUT);
+
+ io->dev_handle = dev_handle;
+ io->function = 0;
+ io->sense_buffer_low_address = htole32(ccb->ccb_sense_dva);
+ io->sgl_flags = htole16(0x02); /* XXX */
+ io->sense_buffer_length = sizeof(xs->sense);
+ io->sgl_offset0 = (sizeof(*io) + sizeof(*ctx)) / 4;
+ io->data_length = htole32(xs->datalen);
+ io->io_flags = htole16(xs->cmdlen);
+ io->lun[0] = htobe16(link->lun);
+ switch (xs->xs_control & (XS_CTL_DATA_IN | XS_CTL_DATA_OUT)) {
+ case XS_CTL_DATA_IN:
+ ccb->ccb_direction = MFII_DATA_IN;
+ io->direction = MPII_SCSIIO_DIR_READ;
+ break;
+ case XS_CTL_DATA_OUT:
+ ccb->ccb_direction = MFII_DATA_OUT;
+ io->direction = MPII_SCSIIO_DIR_WRITE;
+ break;
+ default:
+ ccb->ccb_direction = MFII_DATA_NONE;
+ io->direction = MPII_SCSIIO_DIR_NONE;
+ break;
+ }
+ memcpy(io->cdb, xs->cmd, xs->cmdlen);
+
+ ctx->virtual_disk_target_id = htole16(link->target);
+ ctx->raid_flags = MFII_RAID_CTX_IO_TYPE_SYSPD;
+ ctx->timeout_value = sc->sc_pd->pd_timeout;
+
+ if (mfii_load_ccb(sc, ccb, ctx + 1,
+ ISSET(xs->xs_control, XS_CTL_NOSLEEP)) != 0)
+ return (XS_DRIVER_STUFFUP);
+
+ ctx->num_sge = (ccb->ccb_len == 0) ? 0 : ccb->ccb_dmamap64->dm_nsegs;
+ KASSERT(ccb->ccb_dma64);
+
+ ccb->ccb_req.flags = MFII_REQ_TYPE_HI_PRI;
+ ccb->ccb_req.smid = le16toh(ccb->ccb_smid);
+ ccb->ccb_req.dev_handle = dev_handle;
+
+ return (XS_NOERROR);
+}
+#endif
+
+int
+mfii_load_ccb(struct mfii_softc *sc, struct mfii_ccb *ccb, void *sglp,
+ int nosleep)
+{
+ struct mpii_msg_request *req = ccb->ccb_request;
+ struct mfii_sge *sge = NULL, *nsge = sglp;
+ struct mfii_sge *ce = NULL;
+ bus_dmamap_t dmap = ccb->ccb_dmamap64;
+ u_int space;
+ int i;
+
+ int error;
+
+ if (ccb->ccb_len == 0)
+ return (0);
+
+ ccb->ccb_dma64 = true;
+ error = bus_dmamap_load(sc->sc_dmat64, dmap,
+ ccb->ccb_data, ccb->ccb_len, NULL,
+ nosleep ? BUS_DMA_NOWAIT : BUS_DMA_WAITOK);
+ if (error) {
+ printf("%s: error %d loading dmamap\n", DEVNAME(sc), error);
+ return (1);
+ }
+
+ space = (MFII_REQUEST_SIZE - ((u_int8_t *)nsge - (u_int8_t *)req)) /
+ sizeof(*nsge);
+ if (dmap->dm_nsegs > space) {
+ space--;
+
+ ccb->ccb_sgl_len = (dmap->dm_nsegs - space) * sizeof(*nsge);
+ memset(ccb->ccb_sgl, 0, ccb->ccb_sgl_len);
+
+ ce = nsge + space;
+ ce->sg_addr = htole64(ccb->ccb_sgl_dva);
+ ce->sg_len = htole32(ccb->ccb_sgl_len);
+ ce->sg_flags = sc->sc_iop->sge_flag_chain;
+
+ req->chain_offset = ((u_int8_t *)ce - (u_int8_t *)req) / 16;
+ }
+
+ for (i = 0; i < dmap->dm_nsegs; i++) {
+ if (nsge == ce)
+ nsge = ccb->ccb_sgl;
+
+ sge = nsge;
+
+ sge->sg_addr = htole64(dmap->dm_segs[i].ds_addr);
+ sge->sg_len = htole32(dmap->dm_segs[i].ds_len);
+ sge->sg_flags = MFII_SGE_ADDR_SYSTEM;
+
+ nsge = sge + 1;
+ }
+ sge->sg_flags |= sc->sc_iop->sge_flag_eol;
+
+ bus_dmamap_sync(sc->sc_dmat64, dmap, 0, dmap->dm_mapsize,
+ ccb->ccb_direction == MFII_DATA_OUT ?
+ BUS_DMASYNC_PREWRITE : BUS_DMASYNC_PREREAD);
+
+ if (ccb->ccb_sgl_len > 0) {
+ bus_dmamap_sync(sc->sc_dmat, MFII_DMA_MAP(sc->sc_sgl),
+ ccb->ccb_sgl_offset, ccb->ccb_sgl_len,
+ BUS_DMASYNC_PREWRITE);
+ }
+
+ return (0);
+}
+
+void
+mfii_scsi_cmd_tmo(void *p)
+{
+ struct mfii_ccb *ccb = p;
+ struct mfii_softc *sc = ccb->ccb_sc;
+ bool start_abort;
+
+ printf("%s: cmd timeout ccb %p\n", DEVNAME(sc), p);
+
+ mutex_enter(&sc->sc_abort_mtx);
+ start_abort = (SIMPLEQ_FIRST(&sc->sc_abort_list) == 0);
+ SIMPLEQ_INSERT_TAIL(&sc->sc_abort_list, ccb, ccb_link);
+ if (start_abort)
+ workqueue_enqueue(sc->sc_abort_wq, &sc->sc_abort_work, NULL);
+ mutex_exit(&sc->sc_abort_mtx);
+}
+
+void
+mfii_abort_task(struct work *wk, void *scp)
+{
+ struct mfii_softc *sc = scp;
+ struct mfii_ccb *list;
+
+ mutex_enter(&sc->sc_abort_mtx);
+ list = SIMPLEQ_FIRST(&sc->sc_abort_list);
+ SIMPLEQ_INIT(&sc->sc_abort_list);
+ mutex_exit(&sc->sc_abort_mtx);
+
+ while (list != NULL) {
+ struct mfii_ccb *ccb = list;
+ struct scsipi_xfer *xs = ccb->ccb_cookie;
+ struct scsipi_periph *periph = xs->xs_periph;
+ struct mfii_ccb *accb;
+
+ list = SIMPLEQ_NEXT(ccb, ccb_link);
+
+ if (!sc->sc_ld[periph->periph_target].ld_present) {
+ /* device is gone */
+ if (atomic_dec_uint_nv(&ccb->ccb_refcnt) == 0) {
+ scsipi_done(xs);
+ mfii_put_ccb(sc, ccb);
+ }
+ continue;
+ }
+
+ accb = mfii_get_ccb(sc);
+ mfii_scrub_ccb(accb);
+ mfii_abort(sc, accb, periph->periph_target, ccb->ccb_smid,
+ MPII_SCSI_TASK_ABORT_TASK,
+ htole32(MFII_TASK_MGMT_FLAGS_PD));
+
+ accb->ccb_cookie = ccb;
+ accb->ccb_done = mfii_scsi_cmd_abort_done;
+
+ mfii_start(sc, accb);
+ }
+}
+
+void
+mfii_abort(struct mfii_softc *sc, struct mfii_ccb *accb, uint16_t dev_handle,
+ uint16_t smid, uint8_t type, uint32_t flags)
+{
+ struct mfii_task_mgmt *msg;
+ struct mpii_msg_scsi_task_request *req;
+
+ msg = accb->ccb_request;
+ req = &msg->mpii_request;
+ req->dev_handle = dev_handle;
+ req->function = MPII_FUNCTION_SCSI_TASK_MGMT;
+ req->task_type = type;
+ req->task_mid = htole16( smid);
+ msg->flags = flags;
+
+ accb->ccb_req.flags = MFII_REQ_TYPE_HI_PRI;
+ accb->ccb_req.smid = le16toh(accb->ccb_smid);
+}
+
+void
+mfii_scsi_cmd_abort_done(struct mfii_softc *sc, struct mfii_ccb *accb)
+{
+ struct mfii_ccb *ccb = accb->ccb_cookie;
+ struct scsipi_xfer *xs = ccb->ccb_cookie;
+
+ /* XXX check accb completion? */
+
+ mfii_put_ccb(sc, accb);
+
+ if (atomic_dec_uint_nv(&ccb->ccb_refcnt) == 0) {
+ xs->error = XS_TIMEOUT;
+ scsipi_done(xs);
+ mfii_put_ccb(sc, ccb);
+ }
+}
+
+struct mfii_ccb *
+mfii_get_ccb(struct mfii_softc *sc)
+{
+ struct mfii_ccb *ccb;
+
+ mutex_enter(&sc->sc_ccb_mtx);
+ if (!sc->sc_running) {
+ ccb = NULL;
+ } else {
+ ccb = SIMPLEQ_FIRST(&sc->sc_ccb_freeq);
+ if (ccb != NULL)
+ SIMPLEQ_REMOVE_HEAD(&sc->sc_ccb_freeq, ccb_link);
+ }
+ mutex_exit(&sc->sc_ccb_mtx);
+ return (ccb);
+}
+
+void
+mfii_scrub_ccb(struct mfii_ccb *ccb)
+{
+ ccb->ccb_cookie = NULL;
+ ccb->ccb_done = NULL;
+ ccb->ccb_flags = 0;
+ ccb->ccb_data = NULL;
+ ccb->ccb_direction = MFII_DATA_NONE;
+ ccb->ccb_dma64 = false;
+ ccb->ccb_len = 0;
+ ccb->ccb_sgl_len = 0;
+ ccb->ccb_refcnt = 1;
+
+ memset(&ccb->ccb_req, 0, sizeof(ccb->ccb_req));
+ memset(ccb->ccb_request, 0, MFII_REQUEST_SIZE);
+ memset(ccb->ccb_mfi, 0, MFI_FRAME_SIZE);
+}
+
+void
+mfii_put_ccb(struct mfii_softc *sc, struct mfii_ccb *ccb)
+{
+ mutex_enter(&sc->sc_ccb_mtx);
+ SIMPLEQ_INSERT_HEAD(&sc->sc_ccb_freeq, ccb, ccb_link);
+ mutex_exit(&sc->sc_ccb_mtx);
+}
+
+int
+mfii_init_ccb(struct mfii_softc *sc)
+{
+ struct mfii_ccb *ccb;
+ u_int8_t *request = MFII_DMA_KVA(sc->sc_requests);
+ u_int8_t *mfi = MFII_DMA_KVA(sc->sc_mfi);
+ u_int8_t *sense = MFII_DMA_KVA(sc->sc_sense);
+ u_int8_t *sgl = MFII_DMA_KVA(sc->sc_sgl);
+ u_int i;
+ int error;
+
+ sc->sc_ccb = malloc(sc->sc_max_cmds * sizeof(struct mfii_ccb),
+ M_DEVBUF, M_WAITOK|M_ZERO);
+
+ for (i = 0; i < sc->sc_max_cmds; i++) {
+ ccb = &sc->sc_ccb[i];
+ ccb->ccb_sc = sc;
+
+ /* create a dma map for transfer */
+ error = bus_dmamap_create(sc->sc_dmat,
+ MAXPHYS, sc->sc_max_sgl, MAXPHYS, 0,
+ BUS_DMA_NOWAIT | BUS_DMA_ALLOCNOW, &ccb->ccb_dmamap32);
+ if (error) {
+ printf("%s: cannot create ccb dmamap32 (%d)\n",
+ DEVNAME(sc), error);
+ goto destroy;
+ }
+ error = bus_dmamap_create(sc->sc_dmat64,
+ MAXPHYS, sc->sc_max_sgl, MAXPHYS, 0,
+ BUS_DMA_NOWAIT | BUS_DMA_ALLOCNOW, &ccb->ccb_dmamap64);
+ if (error) {
+ printf("%s: cannot create ccb dmamap64 (%d)\n",
+ DEVNAME(sc), error);
+ goto destroy32;
+ }
+
+ /* select i + 1'th request. 0 is reserved for events */
+ ccb->ccb_smid = i + 1;
+ ccb->ccb_request_offset = MFII_REQUEST_SIZE * (i + 1);
+ ccb->ccb_request = request + ccb->ccb_request_offset;
+ ccb->ccb_request_dva = MFII_DMA_DVA(sc->sc_requests) +
+ ccb->ccb_request_offset;
+
+ /* select i'th MFI command frame */
+ ccb->ccb_mfi_offset = MFI_FRAME_SIZE * i;
+ ccb->ccb_mfi = mfi + ccb->ccb_mfi_offset;
+ ccb->ccb_mfi_dva = MFII_DMA_DVA(sc->sc_mfi) +
+ ccb->ccb_mfi_offset;
+
+ /* select i'th sense */
+ ccb->ccb_sense_offset = MFI_SENSE_SIZE * i;
+ ccb->ccb_sense = (struct mfi_sense *)(sense +
+ ccb->ccb_sense_offset);
+ ccb->ccb_sense_dva = MFII_DMA_DVA(sc->sc_sense) +
+ ccb->ccb_sense_offset;
+
+ /* select i'th sgl */
+ ccb->ccb_sgl_offset = sizeof(struct mfii_sge) *
+ sc->sc_max_sgl * i;
+ ccb->ccb_sgl = (struct mfii_sge *)(sgl + ccb->ccb_sgl_offset);
+ ccb->ccb_sgl_dva = MFII_DMA_DVA(sc->sc_sgl) +
+ ccb->ccb_sgl_offset;
+
+ mutex_init(&ccb->ccb_mtx, MUTEX_DEFAULT, IPL_BIO);
+ cv_init(&ccb->ccb_cv, "mfiiexec");
+
+ /* add ccb to queue */
+ mfii_put_ccb(sc, ccb);
+ }
+
+ return (0);
+
+destroy32:
+ bus_dmamap_destroy(sc->sc_dmat, ccb->ccb_dmamap32);
+destroy:
+ /* free dma maps and ccb memory */
+ while ((ccb = mfii_get_ccb(sc)) != NULL) {
+ bus_dmamap_destroy(sc->sc_dmat, ccb->ccb_dmamap32);
+ bus_dmamap_destroy(sc->sc_dmat, ccb->ccb_dmamap64);
+ }
+
+ free(sc->sc_ccb, M_DEVBUF);
+
+ return (1);
+}
+
+#if NBIO > 0
+int
+mfii_ioctl(device_t dev, u_long cmd, void *addr)
+{
+ struct mfii_softc *sc = device_private(dev);
+ int error = 0;
+
+ DNPRINTF(MFII_D_IOCTL, "%s: mfii_ioctl ", DEVNAME(sc));
+
+ mutex_enter(&sc->sc_lock);
+
+ switch (cmd) {
+ case BIOCINQ:
+ DNPRINTF(MFII_D_IOCTL, "inq\n");
+ error = mfii_ioctl_inq(sc, (struct bioc_inq *)addr);
+ break;
+
+ case BIOCVOL:
+ DNPRINTF(MFII_D_IOCTL, "vol\n");
+ error = mfii_ioctl_vol(sc, (struct bioc_vol *)addr);
+ break;
+
+ case BIOCDISK:
+ DNPRINTF(MFII_D_IOCTL, "disk\n");
+ error = mfii_ioctl_disk(sc, (struct bioc_disk *)addr);
+ break;
+
+ case BIOCALARM:
+ DNPRINTF(MFII_D_IOCTL, "alarm\n");
+ error = mfii_ioctl_alarm(sc, (struct bioc_alarm *)addr);
+ break;
+
+ case BIOCBLINK:
+ DNPRINTF(MFII_D_IOCTL, "blink\n");
+ error = mfii_ioctl_blink(sc, (struct bioc_blink *)addr);
+ break;
+
+ case BIOCSETSTATE:
+ DNPRINTF(MFII_D_IOCTL, "setstate\n");
+ error = mfii_ioctl_setstate(sc, (struct bioc_setstate *)addr);
+ break;
+
+#if 0
+ case BIOCPATROL:
+ DNPRINTF(MFII_D_IOCTL, "patrol\n");
+ error = mfii_ioctl_patrol(sc, (struct bioc_patrol *)addr);
+ break;
+#endif
+
+ default:
+ DNPRINTF(MFII_D_IOCTL, " invalid ioctl\n");
+ error = ENOTTY;
+ }
+
+ mutex_exit(&sc->sc_lock);
+
+ return (error);
+}
+
+int
+mfii_bio_getitall(struct mfii_softc *sc)
+{
+ int i, d, rv = EINVAL;
+ size_t size;
+ union mfi_mbox mbox;
+ struct mfi_conf *cfg = NULL;
+ struct mfi_ld_details *ld_det = NULL;
+
+ /* get info */
+ if (mfii_get_info(sc)) {
+ DNPRINTF(MFII_D_IOCTL, "%s: mfii_get_info failed\n",
+ DEVNAME(sc));
+ goto done;
+ }
+
+ /* send single element command to retrieve size for full structure */
+ cfg = malloc(sizeof *cfg, M_DEVBUF, M_NOWAIT | M_ZERO);
+ if (cfg == NULL)
+ goto done;
+ if (mfii_mgmt(sc, MR_DCMD_CONF_GET, NULL, cfg, sizeof(*cfg),
+ MFII_DATA_IN, false)) {
+ free(cfg, M_DEVBUF);
+ goto done;
+ }
+
+ size = cfg->mfc_size;
+ free(cfg, M_DEVBUF);
+
+ /* memory for read config */
+ cfg = malloc(size, M_DEVBUF, M_NOWAIT | M_ZERO);
+ if (cfg == NULL)
+ goto done;
+ if (mfii_mgmt(sc, MR_DCMD_CONF_GET, NULL, cfg, size,
+ MFII_DATA_IN, false)) {
+ free(cfg, M_DEVBUF);
+ goto done;
+ }
+
+ /* replace current pointer with new one */
+ if (sc->sc_cfg)
+ free(sc->sc_cfg, M_DEVBUF);
+ sc->sc_cfg = cfg;
+
+ /* get all ld info */
+ if (mfii_mgmt(sc, MR_DCMD_LD_GET_LIST, NULL, &sc->sc_ld_list,
+ sizeof(sc->sc_ld_list), MFII_DATA_IN, false))
+ goto done;
+
+ /* get memory for all ld structures */
+ size = cfg->mfc_no_ld * sizeof(struct mfi_ld_details);
+ if (sc->sc_ld_sz != size) {
+ if (sc->sc_ld_details)
+ free(sc->sc_ld_details, M_DEVBUF);
+
+ ld_det = malloc(size, M_DEVBUF, M_NOWAIT | M_ZERO);
+ if (ld_det == NULL)
+ goto done;
+ sc->sc_ld_sz = size;
+ sc->sc_ld_details = ld_det;
+ }
+
+ /* find used physical disks */
+ size = sizeof(struct mfi_ld_details);
+ for (i = 0, d = 0; i < cfg->mfc_no_ld; i++) {
+ memset(&mbox, 0, sizeof(mbox));
+ mbox.b[0] = sc->sc_ld_list.mll_list[i].mll_ld.mld_target;
+ if (mfii_mgmt(sc, MR_DCMD_LD_GET_INFO, &mbox,
+ &sc->sc_ld_details[i], size, MFII_DATA_IN, false))
+ goto done;
+
+ d += sc->sc_ld_details[i].mld_cfg.mlc_parm.mpa_no_drv_per_span *
+ sc->sc_ld_details[i].mld_cfg.mlc_parm.mpa_span_depth;
+ }
+ sc->sc_no_pd = d;
+
+ rv = 0;
+done:
+ return (rv);
+}
+
+int
+mfii_ioctl_inq(struct mfii_softc *sc, struct bioc_inq *bi)
+{
+ int rv = EINVAL;
+ struct mfi_conf *cfg = NULL;
+
+ DNPRINTF(MFII_D_IOCTL, "%s: mfii_ioctl_inq\n", DEVNAME(sc));
+
+ if (mfii_bio_getitall(sc)) {
+ DNPRINTF(MFII_D_IOCTL, "%s: mfii_bio_getitall failed\n",
+ DEVNAME(sc));
+ goto done;
+ }
+
+ /* count unused disks as volumes */
+ if (sc->sc_cfg == NULL)
+ goto done;
+ cfg = sc->sc_cfg;
+
+ bi->bi_nodisk = sc->sc_info.mci_pd_disks_present;
+ bi->bi_novol = cfg->mfc_no_ld + cfg->mfc_no_hs;
+#if notyet
+ bi->bi_novol = cfg->mfc_no_ld + cfg->mfc_no_hs +
+ (bi->bi_nodisk - sc->sc_no_pd);
+#endif
+ /* tell bio who we are */
+ strlcpy(bi->bi_dev, DEVNAME(sc), sizeof(bi->bi_dev));
+
+ rv = 0;
+done:
+ return (rv);
+}
+
+int
+mfii_ioctl_vol(struct mfii_softc *sc, struct bioc_vol *bv)
+{
+ int i, per, rv = EINVAL;
+
+ DNPRINTF(MFII_D_IOCTL, "%s: mfii_ioctl_vol %#x\n",
+ DEVNAME(sc), bv->bv_volid);
+
+ /* we really could skip and expect that inq took care of it */
+ if (mfii_bio_getitall(sc)) {
+ DNPRINTF(MFII_D_IOCTL, "%s: mfii_bio_getitall failed\n",
+ DEVNAME(sc));
+ goto done;
+ }
+
+ if (bv->bv_volid >= sc->sc_ld_list.mll_no_ld) {
+ /* go do hotspares & unused disks */
+ rv = mfii_bio_hs(sc, bv->bv_volid, MFI_MGMT_VD, bv);
+ goto done;
+ }
+
+ i = bv->bv_volid;
+ strlcpy(bv->bv_dev, sc->sc_ld_details[i].mld_cfg.mlc_prop.mlp_name,
+ sizeof(bv->bv_dev));
+
+ switch(sc->sc_ld_list.mll_list[i].mll_state) {
+ case MFI_LD_OFFLINE:
+ bv->bv_status = BIOC_SVOFFLINE;
+ break;
+
+ case MFI_LD_PART_DEGRADED:
+ case MFI_LD_DEGRADED:
+ bv->bv_status = BIOC_SVDEGRADED;
+ break;
+
+ case MFI_LD_ONLINE:
+ bv->bv_status = BIOC_SVONLINE;
+ break;
+
+ default:
+ bv->bv_status = BIOC_SVINVALID;
+ DNPRINTF(MFII_D_IOCTL, "%s: invalid logical disk state %#x\n",
+ DEVNAME(sc),
+ sc->sc_ld_list.mll_list[i].mll_state);
+ }
+
+ /* additional status can modify MFI status */
+ switch (sc->sc_ld_details[i].mld_progress.mlp_in_prog) {
+ case MFI_LD_PROG_CC:
+ case MFI_LD_PROG_BGI:
+ bv->bv_status = BIOC_SVSCRUB;
+ per = (int)sc->sc_ld_details[i].mld_progress.mlp_cc.mp_progress;
+ bv->bv_percent = (per * 100) / 0xffff;
+ bv->bv_seconds =
+ sc->sc_ld_details[i].mld_progress.mlp_cc.mp_elapsed_seconds;
+ break;
+
+ case MFI_LD_PROG_FGI:
+ case MFI_LD_PROG_RECONSTRUCT:
+ /* nothing yet */
+ break;
+ }
+
+#if 0
+ if (sc->sc_ld_details[i].mld_cfg.mlc_prop.mlp_cur_cache_policy & 0x01)
+ bv->bv_cache = BIOC_CVWRITEBACK;
+ else
+ bv->bv_cache = BIOC_CVWRITETHROUGH;
+#endif
+
+ /*
+ * The RAID levels are determined per the SNIA DDF spec, this is only
+ * a subset that is valid for the MFI controller.
+ */
+ bv->bv_level = sc->sc_ld_details[i].mld_cfg.mlc_parm.mpa_pri_raid;
+ if (sc->sc_ld_details[i].mld_cfg.mlc_parm.mpa_span_depth > 1)
+ bv->bv_level *= 10;
+
+ bv->bv_nodisk = sc->sc_ld_details[i].mld_cfg.mlc_parm.mpa_no_drv_per_span *
+ sc->sc_ld_details[i].mld_cfg.mlc_parm.mpa_span_depth;
+
+ bv->bv_size = sc->sc_ld_details[i].mld_size * 512; /* bytes per block */
+
+ rv = 0;
+done:
+ return (rv);
+}
+
+int
+mfii_ioctl_disk(struct mfii_softc *sc, struct bioc_disk *bd)
+{
+ struct mfi_conf *cfg;
+ struct mfi_array *ar;
+ struct mfi_ld_cfg *ld;
+ struct mfi_pd_details *pd;
+ struct mfi_pd_list *pl;
+ struct scsipi_inquiry_data *inqbuf;
+ char vend[8+16+4+1], *vendp;
+ int i, rv = EINVAL;
+ int arr, vol, disk, span;
+ union mfi_mbox mbox;
+
+ DNPRINTF(MFII_D_IOCTL, "%s: mfii_ioctl_disk %#x\n",
+ DEVNAME(sc), bd->bd_diskid);
+
+ /* we really could skip and expect that inq took care of it */
+ if (mfii_bio_getitall(sc)) {
+ DNPRINTF(MFII_D_IOCTL, "%s: mfii_bio_getitall failed\n",
+ DEVNAME(sc));
+ return (rv);
+ }
+ cfg = sc->sc_cfg;
+
+ pd = malloc(sizeof *pd, M_DEVBUF, M_WAITOK);
+ pl = malloc(sizeof *pl, M_DEVBUF, M_WAITOK);
+
+ ar = cfg->mfc_array;
+ vol = bd->bd_volid;
+ if (vol >= cfg->mfc_no_ld) {
+ /* do hotspares */
+ rv = mfii_bio_hs(sc, bd->bd_volid, MFI_MGMT_SD, bd);
+ goto freeme;
+ }
+
+ /* calculate offset to ld structure */
+ ld = (struct mfi_ld_cfg *)(
+ ((uint8_t *)cfg) + offsetof(struct mfi_conf, mfc_array) +
+ cfg->mfc_array_size * cfg->mfc_no_array);
+
+ /* use span 0 only when raid group is not spanned */
+ if (ld[vol].mlc_parm.mpa_span_depth > 1)
+ span = bd->bd_diskid / ld[vol].mlc_parm.mpa_no_drv_per_span;
+ else
+ span = 0;
+ arr = ld[vol].mlc_span[span].mls_index;
+
+ /* offset disk into pd list */
+ disk = bd->bd_diskid % ld[vol].mlc_parm.mpa_no_drv_per_span;
+
+ if (ar[arr].pd[disk].mar_pd.mfp_id == 0xffffU) {
+ /* disk is missing but succeed command */
+ bd->bd_status = BIOC_SDFAILED;
+ rv = 0;
+
+ /* try to find an unused disk for the target to rebuild */
+ if (mfii_mgmt(sc, MR_DCMD_PD_GET_LIST, NULL, pl, sizeof(*pl),
+ MFII_DATA_IN, false))
+ goto freeme;
+
+ for (i = 0; i < pl->mpl_no_pd; i++) {
+ if (pl->mpl_address[i].mpa_scsi_type != 0)
+ continue;
+
+ memset(&mbox, 0, sizeof(mbox));
+ mbox.s[0] = pl->mpl_address[i].mpa_pd_id;
+ if (mfii_mgmt(sc, MR_DCMD_PD_GET_INFO, &mbox,
+ pd, sizeof(*pd), MFII_DATA_IN, false))
+ continue;
+
+ if (pd->mpd_fw_state == MFI_PD_UNCONFIG_GOOD ||
+ pd->mpd_fw_state == MFI_PD_UNCONFIG_BAD)
+ break;
+ }
+
+ if (i == pl->mpl_no_pd)
+ goto freeme;
+ } else {
+ memset(&mbox, 0, sizeof(mbox));
+ mbox.s[0] = ar[arr].pd[disk].mar_pd.mfp_id;
+ if (mfii_mgmt(sc, MR_DCMD_PD_GET_INFO, &mbox, pd, sizeof(*pd),
+ MFII_DATA_IN, false)) {
+ bd->bd_status = BIOC_SDINVALID;
+ goto freeme;
+ }
+ }
+
+ /* get the remaining fields */
+ bd->bd_channel = pd->mpd_enc_idx;
+ bd->bd_target = pd->mpd_enc_slot;
+
+ /* get status */
+ switch (pd->mpd_fw_state){
+ case MFI_PD_UNCONFIG_GOOD:
+ case MFI_PD_UNCONFIG_BAD:
+ bd->bd_status = BIOC_SDUNUSED;
+ break;
+
+ case MFI_PD_HOTSPARE: /* XXX dedicated hotspare part of array? */
+ bd->bd_status = BIOC_SDHOTSPARE;
+ break;
+
+ case MFI_PD_OFFLINE:
+ bd->bd_status = BIOC_SDOFFLINE;
+ break;
+
+ case MFI_PD_FAILED:
+ bd->bd_status = BIOC_SDFAILED;
+ break;
+
+ case MFI_PD_REBUILD:
+ bd->bd_status = BIOC_SDREBUILD;
+ break;
+
+ case MFI_PD_ONLINE:
+ bd->bd_status = BIOC_SDONLINE;
+ break;
+
+ case MFI_PD_COPYBACK:
+ case MFI_PD_SYSTEM:
+ default:
+ bd->bd_status = BIOC_SDINVALID;
+ break;
+ }
+
+ bd->bd_size = pd->mpd_size * 512; /* bytes per block */
+
+ inqbuf = (struct scsipi_inquiry_data *)&pd->mpd_inq_data;
+ vendp = inqbuf->vendor;
+ memcpy(vend, vendp, sizeof vend - 1);
+ vend[sizeof vend - 1] = '\0';
+ strlcpy(bd->bd_vendor, vend, sizeof(bd->bd_vendor));
+
+ /* XXX find a way to retrieve serial nr from drive */
+ /* XXX find a way to get bd_procdev */
+
+#if 0
+ mfp = &pd->mpd_progress;
+ if (mfp->mfp_in_prog & MFI_PD_PROG_PR) {
+ mp = &mfp->mfp_patrol_read;
+ bd->bd_patrol.bdp_percent = (mp->mp_progress * 100) / 0xffff;
+ bd->bd_patrol.bdp_seconds = mp->mp_elapsed_seconds;
+ }
+#endif
+
+ rv = 0;
+freeme:
+ free(pd, M_DEVBUF);
+ free(pl, M_DEVBUF);
+
+ return (rv);
+}
+
+int
+mfii_ioctl_alarm(struct mfii_softc *sc, struct bioc_alarm *ba)
+{
+ uint32_t opc;
+ int rv = 0;
+ int8_t ret;
+ mfii_direction_t dir = MFII_DATA_NONE;
+
+ switch(ba->ba_opcode) {
+ case BIOC_SADISABLE:
+ opc = MR_DCMD_SPEAKER_DISABLE;
+ break;
+
+ case BIOC_SAENABLE:
+ opc = MR_DCMD_SPEAKER_ENABLE;
+ break;
+
+ case BIOC_SASILENCE:
+ opc = MR_DCMD_SPEAKER_SILENCE;
+ break;
+
+ case BIOC_GASTATUS:
+ opc = MR_DCMD_SPEAKER_GET;
+ dir = MFII_DATA_IN;
+ break;
+
+ case BIOC_SATEST:
+ opc = MR_DCMD_SPEAKER_TEST;
+ break;
+
+ default:
+ DNPRINTF(MFII_D_IOCTL, "%s: mfii_ioctl_alarm biocalarm invalid "
+ "opcode %x\n", DEVNAME(sc), ba->ba_opcode);
+ return (EINVAL);
+ }
+
+ if (mfii_mgmt(sc, opc, NULL, &ret, sizeof(ret), dir, false))
+ rv = EINVAL;
+ else
+ if (ba->ba_opcode == BIOC_GASTATUS)
+ ba->ba_status = ret;
+ else
+ ba->ba_status = 0;
+
+ return (rv);
+}
+
+int
+mfii_ioctl_blink(struct mfii_softc *sc, struct bioc_blink *bb)
+{
+ int i, found, rv = EINVAL;
+ union mfi_mbox mbox;
+ uint32_t cmd;
+ struct mfi_pd_list *pd;
+
+ DNPRINTF(MFII_D_IOCTL, "%s: mfii_ioctl_blink %x\n", DEVNAME(sc),
+ bb->bb_status);
+
+ /* channel 0 means not in an enclosure so can't be blinked */
+ if (bb->bb_channel == 0)
+ return (EINVAL);
+
+ pd = malloc(sizeof(*pd), M_DEVBUF, M_WAITOK);
+
+ if (mfii_mgmt(sc, MR_DCMD_PD_GET_LIST, NULL, pd, sizeof(*pd),
+ MFII_DATA_IN, false))
+ goto done;
+
+ for (i = 0, found = 0; i < pd->mpl_no_pd; i++)
+ if (bb->bb_channel == pd->mpl_address[i].mpa_enc_index &&
+ bb->bb_target == pd->mpl_address[i].mpa_enc_slot) {
+ found = 1;
+ break;
+ }
+
+ if (!found)
+ goto done;
+
+ memset(&mbox, 0, sizeof(mbox));
+ mbox.s[0] = pd->mpl_address[i].mpa_pd_id;
+
+ switch (bb->bb_status) {
+ case BIOC_SBUNBLINK:
+ cmd = MR_DCMD_PD_UNBLINK;
+ break;
+
+ case BIOC_SBBLINK:
+ cmd = MR_DCMD_PD_BLINK;
+ break;
+
+ case BIOC_SBALARM:
+ default:
+ DNPRINTF(MFII_D_IOCTL, "%s: mfii_ioctl_blink biocblink invalid "
+ "opcode %x\n", DEVNAME(sc), bb->bb_status);
+ goto done;
+ }
+
+
+ if (mfii_mgmt(sc, cmd, &mbox, NULL, 0, MFII_DATA_NONE, false))
+ goto done;
+
+ rv = 0;
+done:
+ free(pd, M_DEVBUF);
+ return (rv);
+}
+
+static int
+mfii_makegood(struct mfii_softc *sc, uint16_t pd_id)
+{
+ struct mfii_foreign_scan_info *fsi;
+ struct mfi_pd_details *pd;
+ union mfi_mbox mbox;
+ int rv;
+
+ fsi = malloc(sizeof *fsi, M_DEVBUF, M_WAITOK);
+ pd = malloc(sizeof *pd, M_DEVBUF, M_WAITOK);
+
+ memset(&mbox, 0, sizeof mbox);
+ mbox.s[0] = pd_id;
+ rv = mfii_mgmt(sc, MR_DCMD_PD_GET_INFO, &mbox, pd, sizeof(*pd),
+ MFII_DATA_IN, false);
+ if (rv != 0)
+ goto done;
+
+ if (pd->mpd_fw_state == MFI_PD_UNCONFIG_BAD) {
+ mbox.s[0] = pd_id;
+ mbox.s[1] = pd->mpd_pd.mfp_seq;
+ mbox.b[4] = MFI_PD_UNCONFIG_GOOD;
+ rv = mfii_mgmt(sc, MR_DCMD_PD_SET_STATE, &mbox, NULL, 0,
+ MFII_DATA_NONE, false);
+ if (rv != 0)
+ goto done;
+ }
+
+ memset(&mbox, 0, sizeof mbox);
+ mbox.s[0] = pd_id;
+ rv = mfii_mgmt(sc, MR_DCMD_PD_GET_INFO, &mbox, pd, sizeof(*pd),
+ MFII_DATA_IN, false);
+ if (rv != 0)
+ goto done;
+
+ if (pd->mpd_ddf_state & MFI_DDF_FOREIGN) {
+ rv = mfii_mgmt(sc, MR_DCMD_CFG_FOREIGN_SCAN, NULL,
+ fsi, sizeof(*fsi), MFII_DATA_IN, false);
+ if (rv != 0)
+ goto done;
+
+ if (fsi->count > 0) {
+ rv = mfii_mgmt(sc, MR_DCMD_CFG_FOREIGN_CLEAR, NULL,
+ NULL, 0, MFII_DATA_NONE, false);
+ if (rv != 0)
+ goto done;
+ }
+ }
+
+ memset(&mbox, 0, sizeof mbox);
+ mbox.s[0] = pd_id;
+ rv = mfii_mgmt(sc, MR_DCMD_PD_GET_INFO, &mbox, pd, sizeof(*pd),
+ MFII_DATA_IN, false);
+ if (rv != 0)
+ goto done;
+
+ if (pd->mpd_fw_state != MFI_PD_UNCONFIG_GOOD ||
+ pd->mpd_ddf_state & MFI_DDF_FOREIGN)
+ rv = ENXIO;
+
+done:
+ free(fsi, M_DEVBUF);
+ free(pd, M_DEVBUF);
+
+ return (rv);
+}
+
+static int
+mfii_makespare(struct mfii_softc *sc, uint16_t pd_id)
+{
+ struct mfi_hotspare *hs;
+ struct mfi_pd_details *pd;
+ union mfi_mbox mbox;
+ size_t size;
+ int rv = EINVAL;
+
+ /* we really could skip and expect that inq took care of it */
+ if (mfii_bio_getitall(sc)) {
+ DNPRINTF(MFII_D_IOCTL, "%s: mfii_bio_getitall failed\n",
+ DEVNAME(sc));
+ return (rv);
+ }
+ size = sizeof *hs + sizeof(uint16_t) * sc->sc_cfg->mfc_no_array;
+
+ hs = malloc(size, M_DEVBUF, M_WAITOK);
+ pd = malloc(sizeof *pd, M_DEVBUF, M_WAITOK);
+
+ memset(&mbox, 0, sizeof mbox);
+ mbox.s[0] = pd_id;
+ rv = mfii_mgmt(sc, MR_DCMD_PD_GET_INFO, &mbox, pd, sizeof(*pd),
+ MFII_DATA_IN, false);
+ if (rv != 0)
+ goto done;
+
+ memset(hs, 0, size);
+ hs->mhs_pd.mfp_id = pd->mpd_pd.mfp_id;
+ hs->mhs_pd.mfp_seq = pd->mpd_pd.mfp_seq;
+ rv = mfii_mgmt(sc, MR_DCMD_CFG_MAKE_SPARE, NULL, hs, size,
+ MFII_DATA_OUT, false);
+
+done:
+ free(hs, M_DEVBUF);
+ free(pd, M_DEVBUF);
+
+ return (rv);
+}
+
+int
+mfii_ioctl_setstate(struct mfii_softc *sc, struct bioc_setstate *bs)
+{
+ struct mfi_pd_details *pd;
+ struct mfi_pd_list *pl;
+ int i, found, rv = EINVAL;
+ union mfi_mbox mbox;
+
+ DNPRINTF(MFII_D_IOCTL, "%s: mfii_ioctl_setstate %x\n", DEVNAME(sc),
+ bs->bs_status);
+
+ pd = malloc(sizeof *pd, M_DEVBUF, M_WAITOK);
+ pl = malloc(sizeof *pl, M_DEVBUF, M_WAITOK);
+
+ if (mfii_mgmt(sc, MR_DCMD_PD_GET_LIST, NULL, pl, sizeof(*pl),
+ MFII_DATA_IN, false))
+ goto done;
+
+ for (i = 0, found = 0; i < pl->mpl_no_pd; i++)
+ if (bs->bs_channel == pl->mpl_address[i].mpa_enc_index &&
+ bs->bs_target == pl->mpl_address[i].mpa_enc_slot) {
+ found = 1;
+ break;
+ }
+
+ if (!found)
+ goto done;
+
+ memset(&mbox, 0, sizeof(mbox));
+ mbox.s[0] = pl->mpl_address[i].mpa_pd_id;
+
+ if (mfii_mgmt(sc, MR_DCMD_PD_GET_INFO, &mbox, pd, sizeof(*pd),
+ MFII_DATA_IN, false))
+ goto done;
+
+ mbox.s[0] = pl->mpl_address[i].mpa_pd_id;
+ mbox.s[1] = pd->mpd_pd.mfp_seq;
+
+ switch (bs->bs_status) {
+ case BIOC_SSONLINE:
+ mbox.b[4] = MFI_PD_ONLINE;
+ break;
+
+ case BIOC_SSOFFLINE:
+ mbox.b[4] = MFI_PD_OFFLINE;
+ break;
+
+ case BIOC_SSHOTSPARE:
+ mbox.b[4] = MFI_PD_HOTSPARE;
+ break;
+
+ case BIOC_SSREBUILD:
+ if (pd->mpd_fw_state != MFI_PD_OFFLINE) {
+ if ((rv = mfii_makegood(sc,
+ pl->mpl_address[i].mpa_pd_id)))
+ goto done;
+
+ if ((rv = mfii_makespare(sc,
+ pl->mpl_address[i].mpa_pd_id)))
+ goto done;
+
+ memset(&mbox, 0, sizeof(mbox));
+ mbox.s[0] = pl->mpl_address[i].mpa_pd_id;
+ rv = mfii_mgmt(sc, MR_DCMD_PD_GET_INFO, &mbox,
+ pd, sizeof(*pd), MFII_DATA_IN, false);
+ if (rv != 0)
+ goto done;
+
+ /* rebuilding might be started by mfii_makespare() */
+ if (pd->mpd_fw_state == MFI_PD_REBUILD) {
+ rv = 0;
+ goto done;
+ }
+
+ mbox.s[0] = pl->mpl_address[i].mpa_pd_id;
+ mbox.s[1] = pd->mpd_pd.mfp_seq;
+ }
+ mbox.b[4] = MFI_PD_REBUILD;
+ break;
+
+ default:
+ DNPRINTF(MFII_D_IOCTL, "%s: mfii_ioctl_setstate invalid "
+ "opcode %x\n", DEVNAME(sc), bs->bs_status);
+ goto done;
+ }
+
+
+ rv = mfii_mgmt(sc, MR_DCMD_PD_SET_STATE, &mbox, NULL, 0,
+ MFII_DATA_NONE, false);
+done:
+ free(pd, M_DEVBUF);
+ free(pl, M_DEVBUF);
+ return (rv);
+}
+
+#if 0
+int
+mfii_ioctl_patrol(struct mfii_softc *sc, struct bioc_patrol *bp)
+{
+ uint32_t opc;
+ int rv = 0;
+ struct mfi_pr_properties prop;
+ struct mfi_pr_status status;
+ uint32_t time, exec_freq;
+
+ switch (bp->bp_opcode) {
+ case BIOC_SPSTOP:
+ case BIOC_SPSTART:
+ if (bp->bp_opcode == BIOC_SPSTART)
+ opc = MR_DCMD_PR_START;
+ else
+ opc = MR_DCMD_PR_STOP;
+ if (mfii_mgmt(sc, opc, NULL, NULL, 0, MFII_DATA_IN, false))
+ return (EINVAL);
+ break;
+
+ case BIOC_SPMANUAL:
+ case BIOC_SPDISABLE:
+ case BIOC_SPAUTO:
+ /* Get device's time. */
+ opc = MR_DCMD_TIME_SECS_GET;
+ if (mfii_mgmt(sc, opc, NULL, &time, sizeof(time),
+ MFII_DATA_IN, false))
+ return (EINVAL);
+
+ opc = MR_DCMD_PR_GET_PROPERTIES;
+ if (mfii_mgmt(sc, opc, NULL, &prop, sizeof(prop),
+ MFII_DATA_IN, false))
+ return (EINVAL);
+
+ switch (bp->bp_opcode) {
+ case BIOC_SPMANUAL:
+ prop.op_mode = MFI_PR_OPMODE_MANUAL;
+ break;
+ case BIOC_SPDISABLE:
+ prop.op_mode = MFI_PR_OPMODE_DISABLED;
+ break;
+ case BIOC_SPAUTO:
+ if (bp->bp_autoival != 0) {
+ if (bp->bp_autoival == -1)
+ /* continuously */
+ exec_freq = 0xffffffffU;
+ else if (bp->bp_autoival > 0)
+ exec_freq = bp->bp_autoival;
+ else
+ return (EINVAL);
+ prop.exec_freq = exec_freq;
+ }
+ if (bp->bp_autonext != 0) {
+ if (bp->bp_autonext < 0)
+ return (EINVAL);
+ else
+ prop.next_exec = time + bp->bp_autonext;
+ }
+ prop.op_mode = MFI_PR_OPMODE_AUTO;
+ break;
+ }
+
+ opc = MR_DCMD_PR_SET_PROPERTIES;
+ if (mfii_mgmt(sc, opc, NULL, &prop, sizeof(prop),
+ MFII_DATA_OUT, false))
+ return (EINVAL);
+
+ break;
+
+ case BIOC_GPSTATUS:
+ opc = MR_DCMD_PR_GET_PROPERTIES;
+ if (mfii_mgmt(sc, opc, NULL, &prop, sizeof(prop),
+ MFII_DATA_IN, false))
+ return (EINVAL);
+
+ opc = MR_DCMD_PR_GET_STATUS;
+ if (mfii_mgmt(sc, opc, NULL, &status, sizeof(status),
+ MFII_DATA_IN, false))
+ return (EINVAL);
+
+ /* Get device's time. */
+ opc = MR_DCMD_TIME_SECS_GET;
+ if (mfii_mgmt(sc, opc, NULL, &time, sizeof(time),
+ MFII_DATA_IN, false))
+ return (EINVAL);
+
+ switch (prop.op_mode) {
+ case MFI_PR_OPMODE_AUTO:
+ bp->bp_mode = BIOC_SPMAUTO;
+ bp->bp_autoival = prop.exec_freq;
+ bp->bp_autonext = prop.next_exec;
+ bp->bp_autonow = time;
+ break;
+ case MFI_PR_OPMODE_MANUAL:
+ bp->bp_mode = BIOC_SPMMANUAL;
+ break;
+ case MFI_PR_OPMODE_DISABLED:
+ bp->bp_mode = BIOC_SPMDISABLED;
+ break;
+ default:
+ printf("%s: unknown patrol mode %d\n",
+ DEVNAME(sc), prop.op_mode);
+ break;
+ }
+
+ switch (status.state) {
+ case MFI_PR_STATE_STOPPED:
+ bp->bp_status = BIOC_SPSSTOPPED;
+ break;
+ case MFI_PR_STATE_READY:
+ bp->bp_status = BIOC_SPSREADY;
+ break;
+ case MFI_PR_STATE_ACTIVE:
+ bp->bp_status = BIOC_SPSACTIVE;
+ break;
+ case MFI_PR_STATE_ABORTED:
+ bp->bp_status = BIOC_SPSABORTED;
+ break;
+ default:
+ printf("%s: unknown patrol state %d\n",
+ DEVNAME(sc), status.state);
+ break;
+ }
+
+ break;
+
+ default:
+ DNPRINTF(MFII_D_IOCTL, "%s: mfii_ioctl_patrol biocpatrol invalid "
+ "opcode %x\n", DEVNAME(sc), bp->bp_opcode);
+ return (EINVAL);
+ }
+
+ return (rv);
+}
+#endif
+
+int
+mfii_bio_hs(struct mfii_softc *sc, int volid, int type, void *bio_hs)
+{
+ struct mfi_conf *cfg;
+ struct mfi_hotspare *hs;
+ struct mfi_pd_details *pd;
+ struct bioc_disk *sdhs;
+ struct bioc_vol *vdhs;
+ struct scsipi_inquiry_data *inqbuf;
+ char vend[8+16+4+1], *vendp;
+ int i, rv = EINVAL;
+ uint32_t size;
+ union mfi_mbox mbox;
+
+ DNPRINTF(MFII_D_IOCTL, "%s: mfii_vol_hs %d\n", DEVNAME(sc), volid);
+
+ if (!bio_hs)
+ return (EINVAL);
+
+ pd = malloc(sizeof *pd, M_DEVBUF, M_WAITOK);
+
+ /* send single element command to retrieve size for full structure */
+ cfg = malloc(sizeof *cfg, M_DEVBUF, M_WAITOK);
+ if (mfii_mgmt(sc, MR_DCMD_CONF_GET, NULL, cfg, sizeof(*cfg),
+ MFII_DATA_IN, false))
+ goto freeme;
+
+ size = cfg->mfc_size;
+ free(cfg, M_DEVBUF);
+
+ /* memory for read config */
+ cfg = malloc(size, M_DEVBUF, M_WAITOK|M_ZERO);
+ if (mfii_mgmt(sc, MR_DCMD_CONF_GET, NULL, cfg, size,
+ MFII_DATA_IN, false))
+ goto freeme;
+
+ /* calculate offset to hs structure */
+ hs = (struct mfi_hotspare *)(
+ ((uint8_t *)cfg) + offsetof(struct mfi_conf, mfc_array) +
+ cfg->mfc_array_size * cfg->mfc_no_array +
+ cfg->mfc_ld_size * cfg->mfc_no_ld);
+
+ if (volid < cfg->mfc_no_ld)
+ goto freeme; /* not a hotspare */
+
+ if (volid > (cfg->mfc_no_ld + cfg->mfc_no_hs))
+ goto freeme; /* not a hotspare */
+
+ /* offset into hotspare structure */
+ i = volid - cfg->mfc_no_ld;
+
+ DNPRINTF(MFII_D_IOCTL, "%s: mfii_vol_hs i %d volid %d no_ld %d no_hs %d "
+ "hs %p cfg %p id %02x\n", DEVNAME(sc), i, volid, cfg->mfc_no_ld,
+ cfg->mfc_no_hs, hs, cfg, hs[i].mhs_pd.mfp_id);
+
+ /* get pd fields */
+ memset(&mbox, 0, sizeof(mbox));
+ mbox.s[0] = hs[i].mhs_pd.mfp_id;
+ if (mfii_mgmt(sc, MR_DCMD_PD_GET_INFO, &mbox, pd, sizeof(*pd),
+ MFII_DATA_IN, false)) {
+ DNPRINTF(MFII_D_IOCTL, "%s: mfii_vol_hs illegal PD\n",
+ DEVNAME(sc));
+ goto freeme;
+ }
+
+ switch (type) {
+ case MFI_MGMT_VD:
+ vdhs = bio_hs;
+ vdhs->bv_status = BIOC_SVONLINE;
+ vdhs->bv_size = pd->mpd_size / 2 * 1024; /* XXX why? */
+ vdhs->bv_level = -1; /* hotspare */
+ vdhs->bv_nodisk = 1;
+ break;
+
+ case MFI_MGMT_SD:
+ sdhs = bio_hs;
+ sdhs->bd_status = BIOC_SDHOTSPARE;
+ sdhs->bd_size = pd->mpd_size / 2 * 1024; /* XXX why? */
+ sdhs->bd_channel = pd->mpd_enc_idx;
+ sdhs->bd_target = pd->mpd_enc_slot;
+ inqbuf = (struct scsipi_inquiry_data *)&pd->mpd_inq_data;
+ vendp = inqbuf->vendor;
+ memcpy(vend, vendp, sizeof vend - 1);
+ vend[sizeof vend - 1] = '\0';
+ strlcpy(sdhs->bd_vendor, vend, sizeof(sdhs->bd_vendor));
+ break;
+
+ default:
+ goto freeme;
+ }
+
+ DNPRINTF(MFII_D_IOCTL, "%s: mfii_vol_hs 6\n", DEVNAME(sc));
+ rv = 0;
+freeme:
+ free(pd, M_DEVBUF);
+ free(cfg, M_DEVBUF);
+
+ return (rv);
+}
+
+#endif /* NBIO > 0 */
+
+#define MFI_BBU_SENSORS 4
+
+void
+mfii_bbu(struct mfii_softc *sc, envsys_data_t *edata)
+{
+ struct mfi_bbu_status bbu;
+ u_int32_t status;
+ u_int32_t mask;
+ u_int32_t soh_bad;
+ int rv;
+
+ mutex_enter(&sc->sc_lock);
+ rv = mfii_mgmt(sc, MR_DCMD_BBU_GET_STATUS, NULL, &bbu,
+ sizeof(bbu), MFII_DATA_IN, false);
+ mutex_exit(&sc->sc_lock);
+ if (rv != 0) {
+ edata->state = ENVSYS_SINVALID;
+ edata->value_cur = 0;
+ return;
+ }
+
+ switch (bbu.battery_type) {
+ case MFI_BBU_TYPE_IBBU:
+ mask = MFI_BBU_STATE_BAD_IBBU;
+ soh_bad = 0;
+ break;
+ case MFI_BBU_TYPE_BBU:
+ mask = MFI_BBU_STATE_BAD_BBU;
+ soh_bad = (bbu.detail.bbu.is_SOH_good == 0);
+ break;
+
+ case MFI_BBU_TYPE_NONE:
+ default:
+ edata->state = ENVSYS_SCRITICAL;
+ edata->value_cur = 0;
+ return;
+ }
+
+ status = le32toh(bbu.fw_status) & mask;
+ switch(edata->sensor) {
+ case 0:
+ edata->value_cur = (status || soh_bad) ? 0 : 1;
+ edata->state =
+ edata->value_cur ? ENVSYS_SVALID : ENVSYS_SCRITICAL;
+ return;
+ case 1:
+ edata->value_cur = le16toh(bbu.voltage) * 1000;
+ edata->state = ENVSYS_SVALID;
+ return;
+ case 2:
+ edata->value_cur = (int16_t)le16toh(bbu.current) * 1000;
+ edata->state = ENVSYS_SVALID;
+ return;
+ case 3:
+ edata->value_cur = le16toh(bbu.temperature) * 1000000 + 273150000;
+ edata->state = ENVSYS_SVALID;
+ return;
+ }
+}
+
+void
+mfii_refresh_ld_sensor(struct mfii_softc *sc, envsys_data_t *edata)
+{
+ struct bioc_vol bv;
+ int error;
+
+ memset(&bv, 0, sizeof(bv));
+ bv.bv_volid = edata->sensor - MFI_BBU_SENSORS;
+ mutex_enter(&sc->sc_lock);
+ error = mfii_ioctl_vol(sc, &bv);
+ mutex_exit(&sc->sc_lock);
+ if (error)
+ bv.bv_status = BIOC_SVINVALID;
+ bio_vol_to_envsys(edata, &bv);
+}
+
+void
+mfii_init_ld_sensor(struct mfii_softc *sc, envsys_data_t *sensor, int i)
+{
+ sensor->units = ENVSYS_DRIVE;
+ sensor->state = ENVSYS_SINVALID;
+ sensor->value_cur = ENVSYS_DRIVE_EMPTY;
+ /* Enable monitoring for drive state changes */
+ sensor->flags |= ENVSYS_FMONSTCHANGED;
+ snprintf(sensor->desc, sizeof(sensor->desc), "%s:%d", DEVNAME(sc), i);
+}
+
+static void
+mfii_attach_sensor(struct mfii_softc *sc, envsys_data_t *s)
+{
+ if (sysmon_envsys_sensor_attach(sc->sc_sme, s))
+ aprint_error_dev(sc->sc_dev,
+ "failed to attach sensor %s\n", s->desc);
+}
+
+int
+mfii_create_sensors(struct mfii_softc *sc)
+{
+ int i, rv;
+ const int nsensors = MFI_BBU_SENSORS + MFI_MAX_LD;
+
+ sc->sc_sme = sysmon_envsys_create();
+ sc->sc_sensors = malloc(sizeof(envsys_data_t) * nsensors,
+ M_DEVBUF, M_NOWAIT | M_ZERO);
+
+ if (sc->sc_sensors == NULL) {
+ aprint_error_dev(sc->sc_dev, "can't allocate envsys_data_t\n");
+ return ENOMEM;
+ }
+ /* BBU */
+ sc->sc_sensors[0].units = ENVSYS_INDICATOR;
+ sc->sc_sensors[0].state = ENVSYS_SINVALID;
+ sc->sc_sensors[0].value_cur = 0;
+ sc->sc_sensors[1].units = ENVSYS_SVOLTS_DC;
+ sc->sc_sensors[1].state = ENVSYS_SINVALID;
+ sc->sc_sensors[1].value_cur = 0;
+ sc->sc_sensors[2].units = ENVSYS_SAMPS;
+ sc->sc_sensors[2].state = ENVSYS_SINVALID;
+ sc->sc_sensors[2].value_cur = 0;
+ sc->sc_sensors[3].units = ENVSYS_STEMP;
+ sc->sc_sensors[3].state = ENVSYS_SINVALID;
+ sc->sc_sensors[3].value_cur = 0;
+
+ if (ISSET(le32toh(sc->sc_info.mci_hw_present), MFI_INFO_HW_BBU)) {
+ sc->sc_bbuok = true;
+ sc->sc_sensors[0].flags |= ENVSYS_FMONCRITICAL;
+ snprintf(sc->sc_sensors[0].desc, sizeof(sc->sc_sensors[0].desc),
+ "%s BBU state", DEVNAME(sc));
+ snprintf(sc->sc_sensors[1].desc, sizeof(sc->sc_sensors[1].desc),
+ "%s BBU voltage", DEVNAME(sc));
+ snprintf(sc->sc_sensors[2].desc, sizeof(sc->sc_sensors[2].desc),
+ "%s BBU current", DEVNAME(sc));
+ snprintf(sc->sc_sensors[3].desc, sizeof(sc->sc_sensors[3].desc),
+ "%s BBU temperature", DEVNAME(sc));
+ for (i = 0; i < MFI_BBU_SENSORS; i++) {
+ mfii_attach_sensor(sc, &sc->sc_sensors[i]);
+ }
+ }
+
+ for (i = 0; i < sc->sc_ld_list.mll_no_ld; i++) {
+ mfii_init_ld_sensor(sc, &sc->sc_sensors[i + MFI_BBU_SENSORS], i);
+ mfii_attach_sensor(sc, &sc->sc_sensors[i + MFI_BBU_SENSORS]);
+ }
+
+ sc->sc_sme->sme_name = DEVNAME(sc);
+ sc->sc_sme->sme_cookie = sc;
+ sc->sc_sme->sme_refresh = mfii_refresh_sensor;
+ rv = sysmon_envsys_register(sc->sc_sme);
+ if (rv) {
+ aprint_error_dev(sc->sc_dev,
+ "unable to register with sysmon (rv = %d)\n", rv);
+ }
+ return rv;
+
+}
+
+static int
+mfii_destroy_sensors(struct mfii_softc *sc)
+{
+ if (sc->sc_sme == NULL)
+ return 0;
+ sysmon_envsys_unregister(sc->sc_sme);
+ sc->sc_sme = NULL;
+ free(sc->sc_sensors, M_DEVBUF);
+ return 0;
+}
+
+void
+mfii_refresh_sensor(struct sysmon_envsys *sme, envsys_data_t *edata)
+{
+ struct mfii_softc *sc = sme->sme_cookie;
+
+ if (edata->sensor >= MFI_BBU_SENSORS + MFI_MAX_LD)
+ return;
+
+ if (edata->sensor < MFI_BBU_SENSORS) {
+ if (sc->sc_bbuok)
+ mfii_bbu(sc, edata);
+ } else {
+ mfii_refresh_ld_sensor(sc, edata);
+ }
+}