LSI SAS 2008 (mfi) on SuperMicro X8SI6-F
Kenneth D. Merry
ken at freebsd.org
Wed Feb 16 17:37:34 UTC 2011
On Wed, Feb 16, 2011 at 19:23:26 +0300, Dmitry Morozovsky wrote:
> On Wed, 16 Feb 2011, Daniel Kalchev wrote:
>
> DK> I have sucessfully used that motherboard with FreeBSD 9 and the mps driver.
> DK> The mfi driver found on the LSI site does not support this controller.
>
> Ah that makes sense. I'm a bit reluctant to use -current on this particular
> machine, so I would discuss MFCing mps driver wirh ken@
I've been planning to MFC it for a while, it has just been a time issue.
There are a few outstanding issues with the driver, but even with the
issues it would probably be better for people to be able to use it than
have nothing to use in -stable.
A few of the known issues:
- Out of chain frames handling. I'm working on this one now.
- No device queue freezing on errors. I'm working on this one.
- No Integrated RAID handling. This will get fixed when LSI completes
their version of the driver.
- Firmware upgrade code doesn't work properly. This will get fixed when
LSI completes their version of the driver.
For the happy path, things should work well enough I suppose.
I have attached a patch against -stable, try it out and let me know whether
it works. If so I'll go ahead and MFC it.
Ken
--
Kenneth Merry
ken at FreeBSD.ORG
-------------- next part --------------
Property changes on: sys
___________________________________________________________________
Modified: svn:mergeinfo
Merged /head/sys:r212420,212616,212772,212802,213535,213702,213704,213707-213708,213743,213839-213840,213882,213898,216088,216227,216363,216368
Index: sys/conf/files
===================================================================
--- sys/conf/files (revision 218743)
+++ sys/conf/files (working copy)
@@ -1303,6 +1303,11 @@
dev/mmc/mmcbus_if.m standard
dev/mmc/mmcsd.c optional mmcsd
dev/mn/if_mn.c optional mn pci
+dev/mps/mps.c optional mps
+dev/mps/mps_pci.c optional mps pci
+dev/mps/mps_sas.c optional mps
+dev/mps/mps_table.c optional mps
+dev/mps/mps_user.c optional mps
dev/mpt/mpt.c optional mpt
dev/mpt/mpt_cam.c optional mpt
dev/mpt/mpt_debug.c optional mpt
Index: sys/modules/mps/Makefile
===================================================================
--- sys/modules/mps/Makefile (revision 212420)
+++ sys/modules/mps/Makefile (working copy)
@@ -4,7 +4,8 @@
KMOD= mps
SRCS= mps_pci.c mps.c mps_sas.c mps_table.c mps_user.c
-SRCS+= opt_mps.h opt_cam.h
+SRCS+= opt_compat.h
+SRCS+= opt_cam.h
SRCS+= device_if.h bus_if.h pci_if.h
#CFLAGS += -DMPS_DEBUG
Index: sys/modules/Makefile
===================================================================
--- sys/modules/Makefile (revision 218743)
+++ sys/modules/Makefile (working copy)
@@ -186,6 +186,7 @@
mmc \
mmcsd \
${_mpt} \
+ mps \
mqueue \
msdosfs \
msdosfs_iconv \
Index: sys/mips/mips/mp_machdep.c
===================================================================
--- sys/mips/mips/mp_machdep.c (revision 218743)
+++ sys/mips/mips/mp_machdep.c (working copy)
@@ -165,7 +165,7 @@
#if 0
case IPI_HARDCLOCK:
CTR1(KTR_SMP, "%s: IPI_HARDCLOCK", __func__);
- hardclockintr();;
+ hardclockintr();
break;
#endif
default:
Index: sys/dev/sis/if_sisreg.h
===================================================================
--- sys/dev/sis/if_sisreg.h (revision 218743)
+++ sys/dev/sis/if_sisreg.h (working copy)
@@ -497,7 +497,7 @@
int sis_tx_prod;
int sis_tx_cons;
int sis_tx_cnt;
- int sis_rx_cons;;
+ int sis_rx_cons;
bus_addr_t sis_rx_paddr;
bus_addr_t sis_tx_paddr;
struct callout sis_stat_ch;
Index: sys/dev/siba/siba_bwn.c
===================================================================
--- sys/dev/siba/siba_bwn.c (revision 218743)
+++ sys/dev/siba/siba_bwn.c (working copy)
@@ -326,7 +326,7 @@
siba_bwn_read_ivar(device_t dev, device_t child, int which, uintptr_t *result)
{
struct siba_dev_softc *sd;
- struct siba_softc *siba;;
+ struct siba_softc *siba;
sd = device_get_ivars(child);
siba = sd->sd_bus;
Index: sys/dev/mps/mps_ioctl.h
===================================================================
--- sys/dev/mps/mps_ioctl.h (revision 212420)
+++ sys/dev/mps/mps_ioctl.h (working copy)
@@ -103,44 +103,4 @@
#define MPSIO_RAID_ACTION _IOWR('M', 205, struct mps_raid_action)
#define MPSIO_MPS_COMMAND _IOWR('M', 210, struct mps_usr_command)
-#if defined(__amd64__)
-struct mps_cfg_page_req32 {
- MPI2_CONFIG_PAGE_HEADER header;
- uint32_t page_address;
- uint32_t buf;
- int len;
- uint16_t ioc_status;
-};
-
-struct mps_ext_cfg_page_req32 {
- MPI2_CONFIG_EXTENDED_PAGE_HEADER header;
- uint32_t page_address;
- uint32_t buf;
- int len;
- uint16_t ioc_status;
-};
-
-struct mps_raid_action32 {
- uint8_t action;
- uint8_t volume_bus;
- uint8_t volume_id;
- uint8_t phys_disk_num;
- uint32_t action_data_word;
- uint32_t buf;
- int len;
- uint32_t volume_status;
- uint32_t action_data[4];
- uint16_t action_status;
- uint16_t ioc_status;
- uint8_t write;
-};
-
-#define MPSIO_READ_CFG_HEADER32 _IOWR('M', 100, struct mps_cfg_page_req32)
-#define MPSIO_READ_CFG_PAGE32 _IOWR('M', 101, struct mps_cfg_page_req32)
-#define MPSIO_READ_EXT_CFG_HEADER32 _IOWR('M', 102, struct mps_ext_cfg_page_req32)
-#define MPSIO_READ_EXT_CFG_PAGE32 _IOWR('M', 103, struct mps_ext_cfg_page_req32)
-#define MPSIO_WRITE_CFG_PAGE32 _IOWR('M', 104, struct mps_cfg_page_req32)
-#define MPSIO_RAID_ACTION32 _IOWR('M', 105, struct mps_raid_action32)
-#endif
-
#endif /* !_MPS_IOCTL_H_ */
Index: sys/dev/mps/mps.c
===================================================================
--- sys/dev/mps/mps.c (revision 212420)
+++ sys/dev/mps/mps.c (working copy)
@@ -43,6 +43,7 @@
#include <sys/malloc.h>
#include <sys/uio.h>
#include <sys/sysctl.h>
+#include <sys/endian.h>
#include <machine/bus.h>
#include <machine/resource.h>
@@ -380,7 +381,7 @@
return (0);
}
-static void
+void
mps_enqueue_request(struct mps_softc *sc, struct mps_command *cm)
{
@@ -607,9 +608,16 @@
static int
mps_alloc_replies(struct mps_softc *sc)
{
- int rsize;
+ int rsize, num_replies;
- rsize = sc->facts->ReplyFrameSize * sc->num_replies * 4;
+ /*
+ * sc->num_replies should be one less than sc->fqdepth. We need to
+ * allocate space for sc->fqdepth replies, but only sc->num_replies
+ * replies can be used at once.
+ */
+ num_replies = max(sc->fqdepth, sc->num_replies);
+
+ rsize = sc->facts->ReplyFrameSize * num_replies * 4;
if (bus_dma_tag_create( sc->mps_parent_dmat, /* parent */
4, 0, /* algnmnt, boundary */
BUS_SPACE_MAXADDR_32BIT,/* lowaddr */
@@ -782,11 +790,19 @@
memset((uint8_t *)sc->post_queue, 0xff, sc->pqdepth * 8);
+ /*
+ * According to the spec, we need to use one less reply than we
+ * have space for on the queue. So sc->num_replies (the number we
+ * use) should be less than sc->fqdepth (allocated size).
+ */
if (sc->num_replies >= sc->fqdepth)
return (EINVAL);
- for (i = 0; i < sc->num_replies; i++)
- sc->free_queue[i] = sc->reply_busaddr + i * sc->facts->ReplyFrameSize * 4;
+ /*
+ * Initialize all of the free queue entries.
+ */
+ for (i = 0; i < sc->fqdepth; i++)
+ sc->free_queue[i] = sc->reply_busaddr + (i * sc->facts->ReplyFrameSize * 4);
sc->replyfreeindex = sc->num_replies;
return (0);
@@ -805,6 +821,9 @@
snprintf(tmpstr, sizeof(tmpstr), "hw.mps.%d.debug_level",
device_get_unit(sc->mps_dev));
TUNABLE_INT_FETCH(tmpstr, &sc->mps_debug);
+ snprintf(tmpstr, sizeof(tmpstr), "hw.mps.%d.allow_multiple_tm_cmds",
+ device_get_unit(sc->mps_dev));
+ TUNABLE_INT_FETCH(tmpstr, &sc->allow_multiple_tm_cmds);
mps_dprint(sc, MPS_TRACE, "%s\n", __func__);
@@ -831,6 +850,11 @@
OID_AUTO, "debug_level", CTLFLAG_RW, &sc->mps_debug, 0,
"mps debug level");
+ SYSCTL_ADD_INT(&sc->sysctl_ctx, SYSCTL_CHILDREN(sc->sysctl_tree),
+ OID_AUTO, "allow_multiple_tm_cmds", CTLFLAG_RW,
+ &sc->allow_multiple_tm_cmds, 0,
+ "allow multiple simultaneous task management cmds");
+
if ((error = mps_transition_ready(sc)) != 0)
return (error);
@@ -873,6 +897,7 @@
sc->facts->MaxReplyDescriptorPostQueueDepth) - 1;
TAILQ_INIT(&sc->req_list);
TAILQ_INIT(&sc->chain_list);
+ TAILQ_INIT(&sc->tm_list);
if (((error = mps_alloc_queues(sc)) != 0) ||
((error = mps_alloc_replies(sc)) != 0) ||
@@ -898,7 +923,6 @@
* replies.
*/
sc->replypostindex = 0;
- sc->replycurindex = 0;
mps_regwrite(sc, MPI2_REPLY_FREE_HOST_INDEX_OFFSET, sc->replyfreeindex);
mps_regwrite(sc, MPI2_REPLY_POST_HOST_INDEX_OFFSET, 0);
@@ -915,7 +939,10 @@
/* Attach the subsystems so they can prepare their event masks. */
/* XXX Should be dynamic so that IM/IR and user modules can attach */
if (((error = mps_attach_log(sc)) != 0) ||
- ((error = mps_attach_sas(sc)) != 0)) {
+ ((error = mps_attach_sas(sc)) != 0) ||
+ ((error = mps_attach_user(sc)) != 0)) {
+ mps_printf(sc, "%s failed to attach all subsystems: error %d\n",
+ __func__, error);
mps_free(sc);
return (error);
}
@@ -1199,7 +1226,8 @@
desc = &sc->post_queue[pq];
flags = desc->Default.ReplyFlags &
MPI2_RPY_DESCRIPT_FLAGS_TYPE_MASK;
- if (flags == MPI2_RPY_DESCRIPT_FLAGS_UNUSED)
+ if ((flags == MPI2_RPY_DESCRIPT_FLAGS_UNUSED)
+ || (desc->Words.High == 0xffffffff))
break;
switch (flags) {
@@ -1212,9 +1240,36 @@
uint32_t baddr;
uint8_t *reply;
+ /*
+ * Re-compose the reply address from the address
+ * sent back from the chip. The ReplyFrameAddress
+ * is the lower 32 bits of the physical address of
+ * particular reply frame. Convert that address to
+ * host format, and then use that to provide the
+ * offset against the virtual address base
+ * (sc->reply_frames).
+ */
+ baddr = le32toh(desc->AddressReply.ReplyFrameAddress);
reply = sc->reply_frames +
- sc->replycurindex * sc->facts->ReplyFrameSize * 4;
- baddr = desc->AddressReply.ReplyFrameAddress;
+ (baddr - ((uint32_t)sc->reply_busaddr));
+ /*
+ * Make sure the reply we got back is in a valid
+ * range. If not, go ahead and panic here, since
+ * we'll probably panic as soon as we deference the
+ * reply pointer anyway.
+ */
+ if ((reply < sc->reply_frames)
+ || (reply > (sc->reply_frames +
+ (sc->fqdepth * sc->facts->ReplyFrameSize * 4)))) {
+ printf("%s: WARNING: reply %p out of range!\n",
+ __func__, reply);
+ printf("%s: reply_frames %p, fqdepth %d, "
+ "frame size %d\n", __func__,
+ sc->reply_frames, sc->fqdepth,
+ sc->facts->ReplyFrameSize * 4);
+ printf("%s: baddr %#x,\n", __func__, baddr);
+ panic("Reply address out of range");
+ }
if (desc->AddressReply.SMID == 0) {
mps_dispatch_event(sc, baddr,
(MPI2_EVENT_NOTIFICATION_REPLY *) reply);
@@ -1224,8 +1279,6 @@
cm->cm_reply_data =
desc->AddressReply.ReplyFrameAddress;
}
- if (++sc->replycurindex >= sc->fqdepth)
- sc->replycurindex = 0;
break;
}
case MPI2_RPY_DESCRIPT_FLAGS_TARGETASSIST_SUCCESS:
@@ -1270,7 +1323,7 @@
MPI2_EVENT_NOTIFICATION_REPLY *reply)
{
struct mps_event_handle *eh;
- int event, handled = 0;;
+ int event, handled = 0;
event = reply->Event;
TAILQ_FOREACH(eh, &sc->event_list, eh_list) {
@@ -1365,118 +1418,281 @@
return (mps_update_events(sc, NULL, NULL));
}
-static void
-mps_data_cb(void *arg, bus_dma_segment_t *segs, int nsegs, int error)
+/*
+ * Add a chain element as the next SGE for the specified command.
+ * Reset cm_sge and cm_sgesize to indicate all the available space.
+ */
+static int
+mps_add_chain(struct mps_command *cm)
{
- MPI2_SGE_SIMPLE64 *sge;
MPI2_SGE_CHAIN32 *sgc;
- struct mps_softc *sc;
- struct mps_command *cm;
struct mps_chain *chain;
- u_int i, segsleft, sglspace, dir, flags, sflags;
+ int space;
- cm = (struct mps_command *)arg;
- sc = cm->cm_sc;
+ if (cm->cm_sglsize < MPS_SGC_SIZE)
+ panic("MPS: Need SGE Error Code\n");
- segsleft = nsegs;
- sglspace = cm->cm_sglsize;
- sge = (MPI2_SGE_SIMPLE64 *)&cm->cm_sge->MpiSimple;
+ chain = mps_alloc_chain(cm->cm_sc);
+ if (chain == NULL)
+ return (ENOBUFS);
+ space = (int)cm->cm_sc->facts->IOCRequestFrameSize * 4;
+
/*
- * Set up DMA direction flags. Note no support for
- * bi-directional transactions.
+ * Note: a double-linked list is used to make it easier to
+ * walk for debugging.
*/
- sflags = MPI2_SGE_FLAGS_ADDRESS_SIZE;
- if (cm->cm_flags & MPS_CM_FLAGS_DATAOUT) {
- sflags |= MPI2_SGE_FLAGS_DIRECTION;
- dir = BUS_DMASYNC_PREWRITE;
- } else
- dir = BUS_DMASYNC_PREREAD;
+ TAILQ_INSERT_TAIL(&cm->cm_chain_list, chain, chain_link);
+ sgc = (MPI2_SGE_CHAIN32 *)&cm->cm_sge->MpiChain;
+ sgc->Length = space;
+ sgc->NextChainOffset = 0;
+ sgc->Flags = MPI2_SGE_FLAGS_CHAIN_ELEMENT;
+ sgc->Address = chain->chain_busaddr;
+
+ cm->cm_sge = (MPI2_SGE_IO_UNION *)&chain->chain->MpiSimple;
+ cm->cm_sglsize = space;
+ return (0);
+}
+
+/*
+ * Add one scatter-gather element (chain, simple, transaction context)
+ * to the scatter-gather list for a command. Maintain cm_sglsize and
+ * cm_sge as the remaining size and pointer to the next SGE to fill
+ * in, respectively.
+ */
+int
+mps_push_sge(struct mps_command *cm, void *sgep, size_t len, int segsleft)
+{
+ MPI2_SGE_TRANSACTION_UNION *tc = sgep;
+ MPI2_SGE_SIMPLE64 *sge = sgep;
+ int error, type;
+
+ type = (tc->Flags & MPI2_SGE_FLAGS_ELEMENT_MASK);
+
+#ifdef INVARIANTS
+ switch (type) {
+ case MPI2_SGE_FLAGS_TRANSACTION_ELEMENT: {
+ if (len != tc->DetailsLength + 4)
+ panic("TC %p length %u or %zu?", tc,
+ tc->DetailsLength + 4, len);
+ }
+ break;
+ case MPI2_SGE_FLAGS_CHAIN_ELEMENT:
+ /* Driver only uses 32-bit chain elements */
+ if (len != MPS_SGC_SIZE)
+ panic("CHAIN %p length %u or %zu?", sgep,
+ MPS_SGC_SIZE, len);
+ break;
+ case MPI2_SGE_FLAGS_SIMPLE_ELEMENT:
+ /* Driver only uses 64-bit SGE simple elements */
+ sge = sgep;
+ if (len != MPS_SGE64_SIZE)
+ panic("SGE simple %p length %u or %zu?", sge,
+ MPS_SGE64_SIZE, len);
+ if (((sge->FlagsLength >> MPI2_SGE_FLAGS_SHIFT) &
+ MPI2_SGE_FLAGS_ADDRESS_SIZE) == 0)
+ panic("SGE simple %p flags %02x not marked 64-bit?",
+ sge, sge->FlagsLength >> MPI2_SGE_FLAGS_SHIFT);
+
+ break;
+ default:
+ panic("Unexpected SGE %p, flags %02x", tc, tc->Flags);
+ }
+#endif
+
/*
* case 1: 1 more segment, enough room for it
* case 2: 2 more segments, enough room for both
* case 3: >=2 more segments, only enough room for 1 and a chain
* case 4: >=1 more segment, enough room for only a chain
* case 5: >=1 more segment, no room for anything (error)
+ */
+
+ /*
+ * There should be room for at least a chain element, or this
+ * code is buggy. Case (5).
*/
+ if (cm->cm_sglsize < MPS_SGC_SIZE)
+ panic("MPS: Need SGE Error Code\n");
- for (i = 0; i < nsegs; i++) {
-
- /* Case 5 Error. This should never happen. */
- if (sglspace < MPS_SGC_SIZE) {
- panic("MPS: Need SGE Error Code\n");
+ if (segsleft >= 2 &&
+ cm->cm_sglsize < len + MPS_SGC_SIZE + MPS_SGE64_SIZE) {
+ /*
+ * There are 2 or more segments left to add, and only
+ * enough room for 1 and a chain. Case (3).
+ *
+ * Mark as last element in this chain if necessary.
+ */
+ if (type == MPI2_SGE_FLAGS_SIMPLE_ELEMENT) {
+ sge->FlagsLength |=
+ (MPI2_SGE_FLAGS_LAST_ELEMENT << MPI2_SGE_FLAGS_SHIFT);
}
/*
- * Case 4, Fill in a chain element, allocate a chain,
- * fill in one SGE element, continue.
+ * Add the item then a chain. Do the chain now,
+ * rather than on the next iteration, to simplify
+ * understanding the code.
*/
- if ((sglspace >= MPS_SGC_SIZE) && (sglspace < MPS_SGE64_SIZE)) {
- chain = mps_alloc_chain(sc);
- if (chain == NULL) {
- /* Resource shortage, roll back! */
- printf("out of chain frames\n");
- return;
- }
+ cm->cm_sglsize -= len;
+ bcopy(sgep, cm->cm_sge, len);
+ cm->cm_sge = (MPI2_SGE_IO_UNION *)((uintptr_t)cm->cm_sge + len);
+ return (mps_add_chain(cm));
+ }
- /*
- * Note: a double-linked list is used to make it
- * easier to walk for debugging.
- */
- TAILQ_INSERT_TAIL(&cm->cm_chain_list, chain,chain_link);
+ if (segsleft >= 1 && cm->cm_sglsize < len + MPS_SGC_SIZE) {
+ /*
+ * 1 or more segment, enough room for only a chain.
+ * Hope the previous element wasn't a Simple entry
+ * that needed to be marked with
+ * MPI2_SGE_FLAGS_LAST_ELEMENT. Case (4).
+ */
+ if ((error = mps_add_chain(cm)) != 0)
+ return (error);
+ }
- sgc = (MPI2_SGE_CHAIN32 *)sge;
- sgc->Length = 128;
- sgc->NextChainOffset = 0;
- sgc->Flags = MPI2_SGE_FLAGS_CHAIN_ELEMENT;
- sgc->Address = chain->chain_busaddr;
+#ifdef INVARIANTS
+ /* Case 1: 1 more segment, enough room for it. */
+ if (segsleft == 1 && cm->cm_sglsize < len)
+ panic("1 seg left and no room? %u versus %zu",
+ cm->cm_sglsize, len);
- sge = (MPI2_SGE_SIMPLE64 *)&chain->chain->MpiSimple;
- sglspace = 128;
- }
+ /* Case 2: 2 more segments, enough room for both */
+ if (segsleft == 2 && cm->cm_sglsize < len + MPS_SGE64_SIZE)
+ panic("2 segs left and no room? %u versus %zu",
+ cm->cm_sglsize, len);
+#endif
- flags = MPI2_SGE_FLAGS_SIMPLE_ELEMENT;
- sge->FlagsLength = segs[i].ds_len |
- ((sflags | flags) << MPI2_SGE_FLAGS_SHIFT);
- mps_from_u64(segs[i].ds_addr, &sge->Address);
+ if (segsleft == 1 && type == MPI2_SGE_FLAGS_SIMPLE_ELEMENT) {
+ /*
+ * Last element of the last segment of the entire
+ * buffer.
+ */
+ sge->FlagsLength |= ((MPI2_SGE_FLAGS_LAST_ELEMENT |
+ MPI2_SGE_FLAGS_END_OF_BUFFER |
+ MPI2_SGE_FLAGS_END_OF_LIST) << MPI2_SGE_FLAGS_SHIFT);
+ }
- /* Case 1, Fill in one SGE element and break */
- if (segsleft == 1)
- break;
+ cm->cm_sglsize -= len;
+ bcopy(sgep, cm->cm_sge, len);
+ cm->cm_sge = (MPI2_SGE_IO_UNION *)((uintptr_t)cm->cm_sge + len);
+ return (0);
+}
- sglspace -= MPS_SGE64_SIZE;
- segsleft--;
+/*
+ * Add one dma segment to the scatter-gather list for a command.
+ */
+int
+mps_add_dmaseg(struct mps_command *cm, vm_paddr_t pa, size_t len, u_int flags,
+ int segsleft)
+{
+ MPI2_SGE_SIMPLE64 sge;
- /* Case 3, prepare for a chain on the next loop */
- if ((segsleft > 0) && (sglspace < MPS_SGE64_SIZE))
- sge->FlagsLength |=
- (MPI2_SGE_FLAGS_LAST_ELEMENT <<
- MPI2_SGE_FLAGS_SHIFT);
+ /*
+ * This driver always uses 64-bit address elements for
+ * simplicity.
+ */
+ flags |= MPI2_SGE_FLAGS_SIMPLE_ELEMENT | MPI2_SGE_FLAGS_ADDRESS_SIZE;
+ sge.FlagsLength = len | (flags << MPI2_SGE_FLAGS_SHIFT);
+ mps_from_u64(pa, &sge.Address);
- /* Advance to the next element to be filled in. */
- sge++;
+ return (mps_push_sge(cm, &sge, sizeof sge, segsleft));
+}
+
+static void
+mps_data_cb(void *arg, bus_dma_segment_t *segs, int nsegs, int error)
+{
+ struct mps_softc *sc;
+ struct mps_command *cm;
+ u_int i, dir, sflags;
+
+ cm = (struct mps_command *)arg;
+ sc = cm->cm_sc;
+
+ /*
+ * In this case, just print out a warning and let the chip tell the
+ * user they did the wrong thing.
+ */
+ if ((cm->cm_max_segs != 0) && (nsegs > cm->cm_max_segs)) {
+ mps_printf(sc, "%s: warning: busdma returned %d segments, "
+ "more than the %d allowed\n", __func__, nsegs,
+ cm->cm_max_segs);
}
- /* Last element of the last segment of the entire buffer */
- flags = MPI2_SGE_FLAGS_LAST_ELEMENT |
- MPI2_SGE_FLAGS_END_OF_BUFFER |
- MPI2_SGE_FLAGS_END_OF_LIST;
- sge->FlagsLength |= (flags << MPI2_SGE_FLAGS_SHIFT);
+ /*
+ * Set up DMA direction flags. Note that we don't support
+ * bi-directional transfers, with the exception of SMP passthrough.
+ */
+ sflags = 0;
+ if (cm->cm_flags & MPS_CM_FLAGS_SMP_PASS) {
+ /*
+ * We have to add a special case for SMP passthrough, there
+ * is no easy way to generically handle it. The first
+ * S/G element is used for the command (therefore the
+ * direction bit needs to be set). The second one is used
+ * for the reply. We'll leave it to the caller to make
+ * sure we only have two buffers.
+ */
+ /*
+ * Even though the busdma man page says it doesn't make
+ * sense to have both direction flags, it does in this case.
+ * We have one s/g element being accessed in each direction.
+ */
+ dir = BUS_DMASYNC_PREWRITE | BUS_DMASYNC_PREREAD;
+ /*
+ * Set the direction flag on the first buffer in the SMP
+ * passthrough request. We'll clear it for the second one.
+ */
+ sflags |= MPI2_SGE_FLAGS_DIRECTION |
+ MPI2_SGE_FLAGS_END_OF_BUFFER;
+ } else if (cm->cm_flags & MPS_CM_FLAGS_DATAOUT) {
+ sflags |= MPI2_SGE_FLAGS_DIRECTION;
+ dir = BUS_DMASYNC_PREWRITE;
+ } else
+ dir = BUS_DMASYNC_PREREAD;
+
+ for (i = 0; i < nsegs; i++) {
+ if ((cm->cm_flags & MPS_CM_FLAGS_SMP_PASS)
+ && (i != 0)) {
+ sflags &= ~MPI2_SGE_FLAGS_DIRECTION;
+ }
+ error = mps_add_dmaseg(cm, segs[i].ds_addr, segs[i].ds_len,
+ sflags, nsegs - i);
+ if (error != 0) {
+ /* Resource shortage, roll back! */
+ mps_printf(sc, "out of chain frames\n");
+ return;
+ }
+ }
+
bus_dmamap_sync(sc->buffer_dmat, cm->cm_dmamap, dir);
mps_enqueue_request(sc, cm);
return;
}
+static void
+mps_data_cb2(void *arg, bus_dma_segment_t *segs, int nsegs, bus_size_t mapsize,
+ int error)
+{
+ mps_data_cb(arg, segs, nsegs, error);
+}
+
+/*
+ * Note that the only error path here is from bus_dmamap_load(), which can
+ * return EINPROGRESS if it is waiting for resources.
+ */
int
mps_map_command(struct mps_softc *sc, struct mps_command *cm)
{
MPI2_SGE_SIMPLE32 *sge;
int error = 0;
- if ((cm->cm_data != NULL) && (cm->cm_length != 0)) {
+ if (cm->cm_flags & MPS_CM_FLAGS_USE_UIO) {
+ error = bus_dmamap_load_uio(sc->buffer_dmat, cm->cm_dmamap,
+ &cm->cm_uio, mps_data_cb2, cm, 0);
+ } else if ((cm->cm_data != NULL) && (cm->cm_length != 0)) {
error = bus_dmamap_load(sc->buffer_dmat, cm->cm_dmamap,
cm->cm_data, cm->cm_length, mps_data_cb, cm, 0);
} else {
@@ -1490,7 +1706,7 @@
MPI2_SGE_FLAGS_SHIFT;
sge->Address = 0;
}
- mps_enqueue_request(sc, cm);
+ mps_enqueue_request(sc, cm);
}
return (error);
@@ -1549,9 +1765,9 @@
cm->cm_flags = MPS_CM_FLAGS_SGE_SIMPLE | MPS_CM_FLAGS_DATAIN;
cm->cm_desc.Default.RequestFlags = MPI2_REQ_DESCRIPT_FLAGS_DEFAULT_TYPE;
+ cm->cm_complete_data = params;
if (params->callback != NULL) {
cm->cm_complete = mps_config_complete;
- cm->cm_complete_data = params;
return (mps_map_command(sc, cm));
} else {
cm->cm_complete = NULL;
Index: sys/dev/mps/mps_sas.c
===================================================================
--- sys/dev/mps/mps_sas.c (revision 212420)
+++ sys/dev/mps/mps_sas.c (working copy)
@@ -41,6 +41,8 @@
#include <sys/malloc.h>
#include <sys/uio.h>
#include <sys/sysctl.h>
+#include <sys/sglist.h>
+#include <sys/endian.h>
#include <machine/bus.h>
#include <machine/resource.h>
@@ -55,6 +57,9 @@
#include <cam/cam_periph.h>
#include <cam/scsi/scsi_all.h>
#include <cam/scsi/scsi_message.h>
+#if __FreeBSD_version >= 900026
+#include <cam/scsi/smp_all.h>
+#endif
#include <dev/mps/mpi/mpi2_type.h>
#include <dev/mps/mpi/mpi2.h>
@@ -69,9 +74,11 @@
uint16_t handle;
uint8_t linkrate;
uint64_t devname;
+ uint64_t sasaddr;
uint32_t devinfo;
uint16_t encl_handle;
uint16_t encl_slot;
+ uint16_t parent_handle;
int flags;
#define MPSSAS_TARGET_INABORT (1 << 0)
#define MPSSAS_TARGET_INRESET (1 << 1)
@@ -114,6 +121,7 @@
MALLOC_DEFINE(M_MPSSAS, "MPSSAS", "MPS SAS memory");
+static __inline int mpssas_set_lun(uint8_t *lun, u_int ccblun);
static struct mpssas_target * mpssas_alloc_target(struct mpssas_softc *,
struct mpssas_target *);
static struct mpssas_target * mpssas_find_target(struct mpssas_softc *, int,
@@ -135,14 +143,64 @@
static void mpssas_scsiio_timeout(void *data);
static void mpssas_abort_complete(struct mps_softc *sc, struct mps_command *cm);
static void mpssas_recovery(struct mps_softc *, struct mps_command *);
+static int mpssas_map_tm_request(struct mps_softc *sc, struct mps_command *cm);
+static void mpssas_issue_tm_request(struct mps_softc *sc,
+ struct mps_command *cm);
+static void mpssas_tm_complete(struct mps_softc *sc, struct mps_command *cm,
+ int error);
+static int mpssas_complete_tm_request(struct mps_softc *sc,
+ struct mps_command *cm, int free_cm);
static void mpssas_action_scsiio(struct mpssas_softc *, union ccb *);
static void mpssas_scsiio_complete(struct mps_softc *, struct mps_command *);
-static int mpssas_resetdev(struct mpssas_softc *, struct mps_command *);
+#if __FreeBSD_version >= 900026
+static void mpssas_smpio_complete(struct mps_softc *sc, struct mps_command *cm);
+static void mpssas_send_smpcmd(struct mpssas_softc *sassc, union ccb *ccb,
+ uint64_t sasaddr);
+static void mpssas_action_smpio(struct mpssas_softc *sassc, union ccb *ccb);
+#endif /* __FreeBSD_version >= 900026 */
+static void mpssas_resetdev(struct mpssas_softc *, struct mps_command *);
static void mpssas_action_resetdev(struct mpssas_softc *, union ccb *);
static void mpssas_resetdev_complete(struct mps_softc *, struct mps_command *);
static void mpssas_freeze_device(struct mpssas_softc *, struct mpssas_target *);
static void mpssas_unfreeze_device(struct mpssas_softc *, struct mpssas_target *) __unused;
+/*
+ * Abstracted so that the driver can be backwards and forwards compatible
+ * with future versions of CAM that will provide this functionality.
+ */
+#define MPS_SET_LUN(lun, ccblun) \
+ mpssas_set_lun(lun, ccblun)
+
+static __inline int
+mpssas_set_lun(uint8_t *lun, u_int ccblun)
+{
+ uint64_t *newlun;
+
+ newlun = (uint64_t *)lun;
+ *newlun = 0;
+ if (ccblun <= 0xff) {
+ /* Peripheral device address method, LUN is 0 to 255 */
+ lun[1] = ccblun;
+ } else if (ccblun <= 0x3fff) {
+ /* Flat space address method, LUN is <= 16383 */
+ scsi_ulto2b(ccblun, lun);
+ lun[0] |= 0x40;
+ } else if (ccblun <= 0xffffff) {
+ /* Extended flat space address method, LUN is <= 16777215 */
+ scsi_ulto3b(ccblun, &lun[1]);
+ /* Extended Flat space address method */
+ lun[0] = 0xc0;
+ /* Length = 1, i.e. LUN is 3 bytes long */
+ lun[0] |= 0x10;
+ /* Extended Address Method */
+ lun[0] |= 0x02;
+ } else {
+ return (EINVAL);
+ }
+
+ return (0);
+}
+
static struct mpssas_target *
mpssas_alloc_target(struct mpssas_softc *sassc, struct mpssas_target *probe)
{
@@ -305,6 +363,8 @@
probe->target.devinfo = buf->DeviceInfo;
probe->target.encl_handle = buf->EnclosureHandle;
probe->target.encl_slot = buf->Slot;
+ probe->target.sasaddr = mps_to_u64(&buf->SASAddress);
+ probe->target.parent_handle = buf->ParentDevHandle;
if (buf->DeviceInfo & MPI2_SAS_DEVICE_INFO_DIRECT_ATTACH) {
params->page_address =
@@ -438,7 +498,7 @@
cm->cm_desc.Default.RequestFlags = MPI2_REQ_DESCRIPT_FLAGS_DEFAULT_TYPE;
cm->cm_complete = mpssas_remove_device;
cm->cm_targ = targ;
- mps_map_command(sc, cm);
+ mpssas_issue_tm_request(sc, cm);
}
static void
@@ -453,6 +513,9 @@
reply = (MPI2_SCSI_TASK_MANAGE_REPLY *)cm->cm_reply;
handle = cm->cm_targ->handle;
+
+ mpssas_complete_tm_request(sc, cm, /*free_cm*/ 0);
+
if (reply->IOCStatus != MPI2_IOCSTATUS_SUCCESS) {
mps_printf(sc, "Failure 0x%x reseting device 0x%04x\n",
reply->IOCStatus, handle);
@@ -594,6 +657,7 @@
{
struct mpssas_softc *sassc;
int error = 0;
+ int num_sim_reqs;
mps_dprint(sc, MPS_TRACE, "%s\n", __func__);
@@ -603,15 +667,30 @@
sc->sassc = sassc;
sassc->sc = sc;
- if ((sassc->devq = cam_simq_alloc(sc->num_reqs)) == NULL) {
+ /*
+ * Tell CAM that we can handle 5 fewer requests than we have
+ * allocated. If we allow the full number of requests, all I/O
+ * will halt when we run out of resources. Things work fine with
+ * just 1 less request slot given to CAM than we have allocated.
+ * We also need a couple of extra commands so that we can send down
+ * abort, reset, etc. requests when commands time out. Otherwise
+ * we could wind up in a situation with sc->num_reqs requests down
+ * on the card and no way to send an abort.
+ *
+ * XXX KDM need to figure out why I/O locks up if all commands are
+ * used.
+ */
+ num_sim_reqs = sc->num_reqs - 5;
+
+ if ((sassc->devq = cam_simq_alloc(num_sim_reqs)) == NULL) {
mps_dprint(sc, MPS_FAULT, "Cannot allocate SIMQ\n");
error = ENOMEM;
goto out;
}
sassc->sim = cam_sim_alloc(mpssas_action, mpssas_poll, "mps", sassc,
- device_get_unit(sc->mps_dev), &sc->mps_mtx, sc->num_reqs, sc->num_reqs,
- sassc->devq);
+ device_get_unit(sc->mps_dev), &sc->mps_mtx, num_sim_reqs,
+ num_sim_reqs, sassc->devq);
if (sassc->sim == NULL) {
mps_dprint(sc, MPS_FAULT, "Cannot allocate SIM\n");
error = EINVAL;
@@ -890,6 +969,11 @@
case XPT_SCSI_IO:
mpssas_action_scsiio(sassc, ccb);
return;
+#if __FreeBSD_version >= 900026
+ case XPT_SMP_IO:
+ mpssas_action_smpio(sassc, ccb);
+ return;
+#endif /* __FreeBSD_version >= 900026 */
default:
ccb->ccb_h.status = CAM_FUNC_NOTAVAIL;
break;
@@ -928,6 +1012,9 @@
struct mps_softc *sc;
struct mps_command *cm;
struct mpssas_target *targ;
+#if 0
+ char cdb_str[(SCSI_MAX_CDBLEN * 3) + 1];
+#endif
cm = (struct mps_command *)data;
sc = cm->cm_sc;
@@ -952,6 +1039,22 @@
xpt_print(ccb->ccb_h.path, "SCSI command timeout on device handle "
"0x%04x SMID %d\n", targ->handle, cm->cm_desc.Default.SMID);
+ /*
+ * XXX KDM this is useful for debugging purposes, but the existing
+ * scsi_op_desc() implementation can't handle a NULL value for
+ * inq_data. So this will remain commented out until I bring in
+ * those changes as well.
+ */
+#if 0
+ xpt_print(ccb->ccb_h.path, "Timed out command: %s. CDB %s\n",
+ scsi_op_desc((ccb->ccb_h.flags & CAM_CDB_POINTER) ?
+ ccb->csio.cdb_io.cdb_ptr[0] :
+ ccb->csio.cdb_io.cdb_bytes[0], NULL),
+ scsi_cdb_string((ccb->ccb_h.flags & CAM_CDB_POINTER) ?
+ ccb->csio.cdb_io.cdb_ptr :
+ ccb->csio.cdb_io.cdb_bytes, cdb_str,
+ sizeof(cdb_str)));
+#endif
/* Inform CAM about the timeout and that recovery is starting. */
#if 0
@@ -983,7 +1086,7 @@
mps_printf(sc, "%s: abort request on handle %#04x SMID %d "
"complete\n", __func__, req->DevHandle, req->TaskMID);
- mps_free_command(sc, cm);
+ mpssas_complete_tm_request(sc, cm, /*free_cm*/ 1);
}
static void
@@ -991,7 +1094,6 @@
{
struct mps_command *cm;
MPI2_SCSI_TASK_MANAGE_REQUEST *req, *orig_req;
- int error;
cm = mps_alloc_command(sc);
if (cm == NULL) {
@@ -1013,27 +1115,206 @@
cm->cm_data = NULL;
cm->cm_desc.Default.RequestFlags = MPI2_REQ_DESCRIPT_FLAGS_DEFAULT_TYPE;
+ mpssas_issue_tm_request(sc, cm);
+
+}
+
+/*
+ * Can return 0 or EINPROGRESS on success. Any other value means failure.
+ */
+static int
+mpssas_map_tm_request(struct mps_softc *sc, struct mps_command *cm)
+{
+ int error;
+
+ error = 0;
+
+ cm->cm_flags |= MPS_CM_FLAGS_ACTIVE;
error = mps_map_command(sc, cm);
+ if ((error == 0)
+ || (error == EINPROGRESS))
+ sc->tm_cmds_active++;
- if (error != 0) {
- mps_printf(sc, "%s: error mapping abort request!\n", __func__);
+ return (error);
+}
+
+static void
+mpssas_issue_tm_request(struct mps_softc *sc, struct mps_command *cm)
+{
+ int freeze_queue, send_command, error;
+
+ freeze_queue = 0;
+ send_command = 0;
+ error = 0;
+
+ mtx_assert(&sc->mps_mtx, MA_OWNED);
+
+ /*
+ * If there are no other pending task management commands, go
+ * ahead and send this one. There is a small amount of anecdotal
+ * evidence that sending lots of task management commands at once
+ * may cause the controller to lock up. Or, if the user has
+ * configured the driver (via the allow_multiple_tm_cmds variable) to
+ * not serialize task management commands, go ahead and send the
+ * command if even other task management commands are pending.
+ */
+ if (TAILQ_FIRST(&sc->tm_list) == NULL) {
+ send_command = 1;
+ freeze_queue = 1;
+ } else if (sc->allow_multiple_tm_cmds != 0)
+ send_command = 1;
+
+ TAILQ_INSERT_TAIL(&sc->tm_list, cm, cm_link);
+ if (send_command != 0) {
+ /*
+ * Freeze the SIM queue while we issue the task management
+ * command. According to the Fusion-MPT 2.0 spec, task
+ * management requests are serialized, and so the host
+ * should not send any I/O requests while task management
+ * requests are pending.
+ */
+ if (freeze_queue != 0)
+ xpt_freeze_simq(sc->sassc->sim, 1);
+
+ error = mpssas_map_tm_request(sc, cm);
+
+ /*
+ * At present, there is no error path back from
+ * mpssas_map_tm_request() (which calls mps_map_command())
+ * when cm->cm_data == NULL. But since there is a return
+ * value, we check it just in case the implementation
+ * changes later.
+ */
+ if ((error != 0)
+ && (error != EINPROGRESS))
+ mpssas_tm_complete(sc, cm,
+ MPI2_SCSITASKMGMT_RSP_TM_FAILED);
}
-#if 0
- error = mpssas_reset(sc, targ, &resetcm);
- if ((error != 0) && (error != EBUSY)) {
- mps_printf(sc, "Error resetting device!\n");
- mps_unlock(sc);
- return;
- }
+}
- targ->flags |= MPSSAS_TARGET_INRESET;
+static void
+mpssas_tm_complete(struct mps_softc *sc, struct mps_command *cm, int error)
+{
+ MPI2_SCSI_TASK_MANAGE_REPLY *resp;
- cm->cm_complete = mpssas_resettimeout_complete;
- cm->cm_complete_data = cm;
- mps_map_command(sassc->sc, cm);
-#endif
+ resp = (MPI2_SCSI_TASK_MANAGE_REPLY *)cm->cm_reply;
+
+ resp->ResponseCode = error;
+
+ /*
+ * Call the callback for this command, it will be
+ * removed from the list and freed via the callback.
+ */
+ cm->cm_complete(sc, cm);
}
+/*
+ * Complete a task management request. The basic completion operation will
+ * always succeed. Returns status for sending any further task management
+ * commands that were queued.
+ */
+static int
+mpssas_complete_tm_request(struct mps_softc *sc, struct mps_command *cm,
+ int free_cm)
+{
+ int error;
+
+ error = 0;
+
+ mtx_assert(&sc->mps_mtx, MA_OWNED);
+
+ TAILQ_REMOVE(&sc->tm_list, cm, cm_link);
+ cm->cm_flags &= ~MPS_CM_FLAGS_ACTIVE;
+ sc->tm_cmds_active--;
+
+ if (free_cm != 0)
+ mps_free_command(sc, cm);
+
+ if (TAILQ_FIRST(&sc->tm_list) == NULL) {
+ /*
+ * Release the SIM queue, we froze it when we sent the first
+ * task management request.
+ */
+ xpt_release_simq(sc->sassc->sim, 1);
+ } else if ((sc->tm_cmds_active == 0)
+ || (sc->allow_multiple_tm_cmds != 0)) {
+ int error;
+ struct mps_command *cm2;
+
+restart_traversal:
+
+ /*
+ * We don't bother using TAILQ_FOREACH_SAFE here, but
+ * rather use the standard version and just restart the
+ * list traversal if we run into the error case.
+ * TAILQ_FOREACH_SAFE allows safe removal of the current
+ * list element, but if you have a queue of task management
+ * commands, all of which have mapping errors, you'll end
+ * up with recursive calls to this routine and so you could
+ * wind up removing more than just the current list element.
+ */
+ TAILQ_FOREACH(cm2, &sc->tm_list, cm_link) {
+ MPI2_SCSI_TASK_MANAGE_REQUEST *req;
+
+ /* This command is active, no need to send it again */
+ if (cm2->cm_flags & MPS_CM_FLAGS_ACTIVE)
+ continue;
+
+ req = (MPI2_SCSI_TASK_MANAGE_REQUEST *)cm2->cm_req;
+
+ mps_printf(sc, "%s: sending deferred task management "
+ "request for handle %#04x SMID %d\n", __func__,
+ req->DevHandle, req->TaskMID);
+
+ error = mpssas_map_tm_request(sc, cm2);
+
+ /*
+ * Check for errors. If we had an error, complete
+ * this command with an error, and keep going through
+ * the list until we are able to send at least one
+ * command or all of them are completed with errors.
+ *
+ * We don't want to wind up in a situation where
+ * we're stalled out with no way for queued task
+ * management commands to complete.
+ *
+ * Note that there is not currently an error path
+ * back from mpssas_map_tm_request() (which calls
+ * mps_map_command()) when cm->cm_data == NULL.
+ * But we still want to check for errors here in
+ * case the implementation changes, or in case
+ * there is some reason for a data payload here.
+ */
+ if ((error != 0)
+ && (error != EINPROGRESS)) {
+ mpssas_tm_complete(sc, cm,
+ MPI2_SCSITASKMGMT_RSP_TM_FAILED);
+
+ /*
+ * If we don't currently have any commands
+ * active, go back to the beginning and see
+ * if there are any more that can be started.
+ * Otherwise, we're done here.
+ */
+ if (sc->tm_cmds_active == 0)
+ goto restart_traversal;
+ else
+ break;
+ }
+
+ /*
+ * If the user only wants one task management command
+ * active at a time, we're done, since we've
+ * already successfully sent a command at this point.
+ */
+ if (sc->allow_multiple_tm_cmds == 0)
+ break;
+ }
+ }
+
+ return (error);
+}
+
static void
mpssas_action_scsiio(struct mpssas_softc *sassc, union ccb *ccb)
{
@@ -1123,14 +1404,12 @@
break;
}
- /* XXX Need to handle multi-level LUNs */
- if (csio->ccb_h.target_lun > 255) {
+ if (MPS_SET_LUN(req->LUN, csio->ccb_h.target_lun) != 0) {
mps_free_command(sc, cm);
ccb->ccb_h.status = CAM_LUN_INVALID;
xpt_done(ccb);
return;
}
- req->LUN[1] = csio->ccb_h.target_lun;
if (csio->ccb_h.flags & CAM_CDB_POINTER)
bcopy(csio->cdb_io.cdb_ptr, &req->CDB.CDB32[0], csio->cdb_len);
@@ -1138,6 +1417,9 @@
bcopy(csio->cdb_io.cdb_bytes, &req->CDB.CDB32[0],csio->cdb_len);
req->IoFlags = csio->cdb_len;
+ /*
+ * XXX need to handle S/G lists and physical addresses here.
+ */
cm->cm_data = csio->data_ptr;
cm->cm_length = csio->dxfer_len;
cm->cm_sge = &req->SGL;
@@ -1219,11 +1501,9 @@
ccb->ccb_h.status = CAM_REQ_CMP;
break;
case MPI2_IOCSTATUS_SCSI_DATA_OVERRUN:
- /*
- * XXX any way to report this?
- */
+ /* resid is ignored for this condition */
ccb->csio.resid = 0;
- ccb->ccb_h.status = CAM_REQ_CMP;
+ ccb->ccb_h.status = CAM_DATA_RUN_ERR;
break;
case MPI2_IOCSTATUS_SCSI_INVALID_DEVHANDLE:
case MPI2_IOCSTATUS_SCSI_DEVICE_NOT_THERE:
@@ -1304,13 +1584,335 @@
xpt_done(ccb);
}
+#if __FreeBSD_version >= 900026
static void
+mpssas_smpio_complete(struct mps_softc *sc, struct mps_command *cm)
+{
+ MPI2_SMP_PASSTHROUGH_REPLY *rpl;
+ MPI2_SMP_PASSTHROUGH_REQUEST *req;
+ uint64_t sasaddr;
+ union ccb *ccb;
+
+ ccb = cm->cm_complete_data;
+ rpl = (MPI2_SMP_PASSTHROUGH_REPLY *)cm->cm_reply;
+ if (rpl == NULL) {
+ mps_dprint(sc, MPS_INFO, "%s: NULL cm_reply!\n", __func__);
+ ccb->ccb_h.status = CAM_REQ_CMP_ERR;
+ goto bailout;
+ }
+
+ req = (MPI2_SMP_PASSTHROUGH_REQUEST *)cm->cm_req;
+ sasaddr = le32toh(req->SASAddress.Low);
+ sasaddr |= ((uint64_t)(le32toh(req->SASAddress.High))) << 32;
+
+ if ((rpl->IOCStatus & MPI2_IOCSTATUS_MASK) != MPI2_IOCSTATUS_SUCCESS ||
+ rpl->SASStatus != MPI2_SASSTATUS_SUCCESS) {
+ mps_dprint(sc, MPS_INFO, "%s: IOCStatus %04x SASStatus %02x\n",
+ __func__, rpl->IOCStatus, rpl->SASStatus);
+ ccb->ccb_h.status = CAM_REQ_CMP_ERR;
+ goto bailout;
+ }
+
+ mps_dprint(sc, MPS_INFO, "%s: SMP request to SAS address "
+ "%#jx completed successfully\n", __func__,
+ (uintmax_t)sasaddr);
+
+ if (ccb->smpio.smp_response[2] == SMP_FR_ACCEPTED)
+ ccb->ccb_h.status = CAM_REQ_CMP;
+ else
+ ccb->ccb_h.status = CAM_SMP_STATUS_ERROR;
+
+bailout:
+ /*
+ * We sync in both directions because we had DMAs in the S/G list
+ * in both directions.
+ */
+ bus_dmamap_sync(sc->buffer_dmat, cm->cm_dmamap,
+ BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE);
+ bus_dmamap_unload(sc->buffer_dmat, cm->cm_dmamap);
+ mps_free_command(sc, cm);
+ xpt_done(ccb);
+}
+
+static void
+mpssas_send_smpcmd(struct mpssas_softc *sassc, union ccb *ccb, uint64_t sasaddr)
+{
+ struct mps_command *cm;
+ uint8_t *request, *response;
+ MPI2_SMP_PASSTHROUGH_REQUEST *req;
+ struct mps_softc *sc;
+ struct sglist *sg;
+ int error;
+
+ sc = sassc->sc;
+ sg = NULL;
+ error = 0;
+
+ /*
+ * XXX We don't yet support physical addresses here.
+ */
+ if (ccb->ccb_h.flags & (CAM_DATA_PHYS|CAM_SG_LIST_PHYS)) {
+ mps_printf(sc, "%s: physical addresses not supported\n",
+ __func__);
+ ccb->ccb_h.status = CAM_REQ_INVALID;
+ xpt_done(ccb);
+ return;
+ }
+
+ /*
+ * If the user wants to send an S/G list, check to make sure they
+ * have single buffers.
+ */
+ if (ccb->ccb_h.flags & CAM_SCATTER_VALID) {
+ /*
+ * The chip does not support more than one buffer for the
+ * request or response.
+ */
+ if ((ccb->smpio.smp_request_sglist_cnt > 1)
+ || (ccb->smpio.smp_response_sglist_cnt > 1)) {
+ mps_printf(sc, "%s: multiple request or response "
+ "buffer segments not supported for SMP\n",
+ __func__);
+ ccb->ccb_h.status = CAM_REQ_INVALID;
+ xpt_done(ccb);
+ return;
+ }
+
+ /*
+ * The CAM_SCATTER_VALID flag was originally implemented
+ * for the XPT_SCSI_IO CCB, which only has one data pointer.
+ * We have two. So, just take that flag to mean that we
+ * might have S/G lists, and look at the S/G segment count
+ * to figure out whether that is the case for each individual
+ * buffer.
+ */
+ if (ccb->smpio.smp_request_sglist_cnt != 0) {
+ bus_dma_segment_t *req_sg;
+
+ req_sg = (bus_dma_segment_t *)ccb->smpio.smp_request;
+ request = (uint8_t *)req_sg[0].ds_addr;
+ } else
+ request = ccb->smpio.smp_request;
+
+ if (ccb->smpio.smp_response_sglist_cnt != 0) {
+ bus_dma_segment_t *rsp_sg;
+
+ rsp_sg = (bus_dma_segment_t *)ccb->smpio.smp_response;
+ response = (uint8_t *)rsp_sg[0].ds_addr;
+ } else
+ response = ccb->smpio.smp_response;
+ } else {
+ request = ccb->smpio.smp_request;
+ response = ccb->smpio.smp_response;
+ }
+
+ cm = mps_alloc_command(sc);
+ if (cm == NULL) {
+ mps_printf(sc, "%s: cannot allocate command\n", __func__);
+ ccb->ccb_h.status = CAM_RESRC_UNAVAIL;
+ xpt_done(ccb);
+ return;
+ }
+
+ req = (MPI2_SMP_PASSTHROUGH_REQUEST *)cm->cm_req;
+ bzero(req, sizeof(*req));
+ req->Function = MPI2_FUNCTION_SMP_PASSTHROUGH;
+
+ /* Allow the chip to use any route to this SAS address. */
+ req->PhysicalPort = 0xff;
+
+ req->RequestDataLength = ccb->smpio.smp_request_len;
+ req->SGLFlags =
+ MPI2_SGLFLAGS_SYSTEM_ADDRESS_SPACE | MPI2_SGLFLAGS_SGL_TYPE_MPI;
+
+ mps_dprint(sc, MPS_INFO, "%s: sending SMP request to SAS "
+ "address %#jx\n", __func__, (uintmax_t)sasaddr);
+
+ mpi_init_sge(cm, req, &req->SGL);
+
+ /*
+ * Set up a uio to pass into mps_map_command(). This allows us to
+ * do one map command, and one busdma call in there.
+ */
+ cm->cm_uio.uio_iov = cm->cm_iovec;
+ cm->cm_uio.uio_iovcnt = 2;
+ cm->cm_uio.uio_segflg = UIO_SYSSPACE;
+
+ /*
+ * The read/write flag isn't used by busdma, but set it just in
+ * case. This isn't exactly accurate, either, since we're going in
+ * both directions.
+ */
+ cm->cm_uio.uio_rw = UIO_WRITE;
+
+ cm->cm_iovec[0].iov_base = request;
+ cm->cm_iovec[0].iov_len = req->RequestDataLength;
+ cm->cm_iovec[1].iov_base = response;
+ cm->cm_iovec[1].iov_len = ccb->smpio.smp_response_len;
+
+ cm->cm_uio.uio_resid = cm->cm_iovec[0].iov_len +
+ cm->cm_iovec[1].iov_len;
+
+ /*
+ * Trigger a warning message in mps_data_cb() for the user if we
+ * wind up exceeding two S/G segments. The chip expects one
+ * segment for the request and another for the response.
+ */
+ cm->cm_max_segs = 2;
+
+ cm->cm_desc.Default.RequestFlags = MPI2_REQ_DESCRIPT_FLAGS_DEFAULT_TYPE;
+ cm->cm_complete = mpssas_smpio_complete;
+ cm->cm_complete_data = ccb;
+
+ /*
+ * Tell the mapping code that we're using a uio, and that this is
+ * an SMP passthrough request. There is a little special-case
+ * logic there (in mps_data_cb()) to handle the bidirectional
+ * transfer.
+ */
+ cm->cm_flags |= MPS_CM_FLAGS_USE_UIO | MPS_CM_FLAGS_SMP_PASS |
+ MPS_CM_FLAGS_DATAIN | MPS_CM_FLAGS_DATAOUT;
+
+ /* The chip data format is little endian. */
+ req->SASAddress.High = htole32(sasaddr >> 32);
+ req->SASAddress.Low = htole32(sasaddr);
+
+ /*
+ * XXX Note that we don't have a timeout/abort mechanism here.
+ * From the manual, it looks like task management requests only
+ * work for SCSI IO and SATA passthrough requests. We may need to
+ * have a mechanism to retry requests in the event of a chip reset
+ * at least. Hopefully the chip will insure that any errors short
+ * of that are relayed back to the driver.
+ */
+ error = mps_map_command(sc, cm);
+ if ((error != 0) && (error != EINPROGRESS)) {
+ mps_printf(sc, "%s: error %d returned from mps_map_command()\n",
+ __func__, error);
+ goto bailout_error;
+ }
+
+ return;
+
+bailout_error:
+ mps_free_command(sc, cm);
+ ccb->ccb_h.status = CAM_RESRC_UNAVAIL;
+ xpt_done(ccb);
+ return;
+
+}
+
+static void
+mpssas_action_smpio(struct mpssas_softc *sassc, union ccb *ccb)
+{
+ struct mps_softc *sc;
+ struct mpssas_target *targ;
+ uint64_t sasaddr = 0;
+
+ sc = sassc->sc;
+
+ /*
+ * Make sure the target exists.
+ */
+ targ = &sassc->targets[ccb->ccb_h.target_id];
+ if (targ->handle == 0x0) {
+ mps_printf(sc, "%s: target %d does not exist!\n", __func__,
+ ccb->ccb_h.target_id);
+ ccb->ccb_h.status = CAM_SEL_TIMEOUT;
+ xpt_done(ccb);
+ return;
+ }
+
+ /*
+ * If this device has an embedded SMP target, we'll talk to it
+ * directly.
+ * figure out what the expander's address is.
+ */
+ if ((targ->devinfo & MPI2_SAS_DEVICE_INFO_SMP_TARGET) != 0)
+ sasaddr = targ->sasaddr;
+
+ /*
+ * If we don't have a SAS address for the expander yet, try
+ * grabbing it from the page 0x83 information cached in the
+ * transport layer for this target. LSI expanders report the
+ * expander SAS address as the port-associated SAS address in
+ * Inquiry VPD page 0x83. Maxim expanders don't report it in page
+ * 0x83.
+ *
+ * XXX KDM disable this for now, but leave it commented out so that
+ * it is obvious that this is another possible way to get the SAS
+ * address.
+ *
+ * The parent handle method below is a little more reliable, and
+ * the other benefit is that it works for devices other than SES
+ * devices. So you can send a SMP request to a da(4) device and it
+ * will get routed to the expander that device is attached to.
+ * (Assuming the da(4) device doesn't contain an SMP target...)
+ */
+#if 0
+ if (sasaddr == 0)
+ sasaddr = xpt_path_sas_addr(ccb->ccb_h.path);
+#endif
+
+ /*
+ * If we still don't have a SAS address for the expander, look for
+ * the parent device of this device, which is probably the expander.
+ */
+ if (sasaddr == 0) {
+ struct mpssas_target *parent_target;
+
+ if (targ->parent_handle == 0x0) {
+ mps_printf(sc, "%s: handle %d does not have a valid "
+ "parent handle!\n", __func__, targ->handle);
+ ccb->ccb_h.status = CAM_REQ_INVALID;
+ goto bailout;
+ }
+ parent_target = mpssas_find_target(sassc, 0,
+ targ->parent_handle);
+
+ if (parent_target == NULL) {
+ mps_printf(sc, "%s: handle %d does not have a valid "
+ "parent target!\n", __func__, targ->handle);
+ ccb->ccb_h.status = CAM_REQ_INVALID;
+ goto bailout;
+ }
+
+ if ((parent_target->devinfo &
+ MPI2_SAS_DEVICE_INFO_SMP_TARGET) == 0) {
+ mps_printf(sc, "%s: handle %d parent %d does not "
+ "have an SMP target!\n", __func__,
+ targ->handle, parent_target->handle);
+ ccb->ccb_h.status = CAM_REQ_INVALID;
+ goto bailout;
+
+ }
+
+ sasaddr = parent_target->sasaddr;
+ }
+
+ if (sasaddr == 0) {
+ mps_printf(sc, "%s: unable to find SAS address for handle %d\n",
+ __func__, targ->handle);
+ ccb->ccb_h.status = CAM_REQ_INVALID;
+ goto bailout;
+ }
+ mpssas_send_smpcmd(sassc, ccb, sasaddr);
+
+ return;
+
+bailout:
+ xpt_done(ccb);
+
+}
+
+#endif /* __FreeBSD_version >= 900026 */
+
+static void
mpssas_action_resetdev(struct mpssas_softc *sassc, union ccb *ccb)
{
struct mps_softc *sc;
struct mps_command *cm;
struct mpssas_target *targ;
- int error;
sc = sassc->sc;
targ = &sassc->targets[ccb->ccb_h.target_id];
@@ -1323,7 +1925,7 @@
cm = mps_alloc_command(sc);
if (cm == NULL) {
- mps_printf(sc, "mpssas_action_resetdev: cannot alloc command\n");
+ mps_printf(sc, "%s: cannot alloc command\n", __func__);
ccb->ccb_h.status = CAM_RESRC_UNAVAIL;
xpt_done(ccb);
return;
@@ -1333,20 +1935,14 @@
cm->cm_complete = mpssas_resetdev_complete;
cm->cm_complete_data = ccb;
- error = mpssas_resetdev(sassc, cm);
- if (error) {
- ccb->ccb_h.status = CAM_RESRC_UNAVAIL;
- xpt_done(ccb);
- return;
- }
+ mpssas_resetdev(sassc, cm);
}
-static int
+static void
mpssas_resetdev(struct mpssas_softc *sassc, struct mps_command *cm)
{
MPI2_SCSI_TASK_MANAGE_REQUEST *req;
struct mps_softc *sc;
- int error;
mps_dprint(sassc->sc, MPS_TRACE, "%s\n", __func__);
@@ -1363,8 +1959,7 @@
cm->cm_data = NULL;
cm->cm_desc.Default.RequestFlags = MPI2_REQ_DESCRIPT_FLAGS_DEFAULT_TYPE;
- error = mps_map_command(sassc->sc, cm);
- return (error);
+ mpssas_issue_tm_request(sc, cm);
}
static void
@@ -1386,7 +1981,8 @@
else
ccb->ccb_h.status = CAM_REQ_CMP_ERR;
- mps_free_command(sc, cm);
+ mpssas_complete_tm_request(sc, cm, /*free_cm*/ 1);
+
xpt_done(ccb);
}
Index: sys/dev/mps/mps_pci.c
===================================================================
--- sys/dev/mps/mps_pci.c (revision 212420)
+++ sys/dev/mps/mps_pci.c (working copy)
@@ -38,6 +38,7 @@
#include <sys/conf.h>
#include <sys/malloc.h>
#include <sys/sysctl.h>
+#include <sys/uio.h>
#include <machine/bus.h>
#include <machine/resource.h>
Index: sys/dev/mps/mps_user.c
===================================================================
--- sys/dev/mps/mps_user.c (revision 212420)
+++ sys/dev/mps/mps_user.c (working copy)
@@ -33,6 +33,8 @@
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
+#include "opt_compat.h"
+
#include <sys/types.h>
#include <sys/param.h>
#include <sys/systm.h>
@@ -47,6 +49,8 @@
#include <sys/sysctl.h>
#include <sys/ioccom.h>
#include <sys/endian.h>
+#include <sys/proc.h>
+#include <sys/sysent.h>
#include <machine/bus.h>
#include <machine/resource.h>
@@ -64,17 +68,41 @@
static d_open_t mps_open;
static d_close_t mps_close;
-static d_ioctl_t mps_ioctl;
+static d_ioctl_t mps_ioctl_devsw;
static struct cdevsw mps_cdevsw = {
.d_version = D_VERSION,
.d_flags = 0,
.d_open = mps_open,
.d_close = mps_close,
- .d_ioctl = mps_ioctl,
+ .d_ioctl = mps_ioctl_devsw,
.d_name = "mps",
};
+typedef int (mps_user_f)(struct mps_command *, struct mps_usr_command *);
+static mps_user_f mpi_pre_ioc_facts;
+static mps_user_f mpi_pre_port_facts;
+static mps_user_f mpi_pre_fw_download;
+static mps_user_f mpi_pre_fw_upload;
+static mps_user_f mpi_pre_sata_passthrough;
+static mps_user_f mpi_pre_smp_passthrough;
+static mps_user_f mpi_pre_config;
+static mps_user_f mpi_pre_sas_io_unit_control;
+
+static int mps_user_read_cfg_header(struct mps_softc *,
+ struct mps_cfg_page_req *);
+static int mps_user_read_cfg_page(struct mps_softc *,
+ struct mps_cfg_page_req *, void *);
+static int mps_user_read_extcfg_header(struct mps_softc *,
+ struct mps_ext_cfg_page_req *);
+static int mps_user_read_extcfg_page(struct mps_softc *,
+ struct mps_ext_cfg_page_req *, void *);
+static int mps_user_write_cfg_page(struct mps_softc *,
+ struct mps_cfg_page_req *, void *);
+static int mps_user_setup_request(struct mps_command *,
+ struct mps_usr_command *);
+static int mps_user_command(struct mps_softc *, struct mps_usr_command *);
+
static MALLOC_DEFINE(M_MPSUSER, "mps_user", "Buffers for mps(4) ioctls");
int
@@ -294,43 +322,255 @@
return (0);
}
+void
+mpi_init_sge(struct mps_command *cm, void *req, void *sge)
+{
+ int off, space;
+
+ space = (int)cm->cm_sc->facts->IOCRequestFrameSize * 4;
+ off = (uintptr_t)sge - (uintptr_t)req;
+
+ KASSERT(off < space, ("bad pointers %p %p, off %d, space %d",
+ req, sge, off, space));
+
+ cm->cm_sge = sge;
+ cm->cm_sglsize = space - off;
+}
+
+/*
+ * Prepare the mps_command for an IOC_FACTS request.
+ */
+static int
+mpi_pre_ioc_facts(struct mps_command *cm, struct mps_usr_command *cmd)
+{
+ MPI2_IOC_FACTS_REQUEST *req = (void *)cm->cm_req;
+ MPI2_IOC_FACTS_REPLY *rpl;
+
+ if (cmd->req_len != sizeof *req)
+ return (EINVAL);
+ if (cmd->rpl_len != sizeof *rpl)
+ return (EINVAL);
+
+ cm->cm_sge = NULL;
+ cm->cm_sglsize = 0;
+ return (0);
+}
+
+/*
+ * Prepare the mps_command for a PORT_FACTS request.
+ */
+static int
+mpi_pre_port_facts(struct mps_command *cm, struct mps_usr_command *cmd)
+{
+ MPI2_PORT_FACTS_REQUEST *req = (void *)cm->cm_req;
+ MPI2_PORT_FACTS_REPLY *rpl;
+
+ if (cmd->req_len != sizeof *req)
+ return (EINVAL);
+ if (cmd->rpl_len != sizeof *rpl)
+ return (EINVAL);
+
+ cm->cm_sge = NULL;
+ cm->cm_sglsize = 0;
+ return (0);
+}
+
+/*
+ * Prepare the mps_command for a FW_DOWNLOAD request.
+ */
+static int
+mpi_pre_fw_download(struct mps_command *cm, struct mps_usr_command *cmd)
+{
+ MPI2_FW_DOWNLOAD_REQUEST *req = (void *)cm->cm_req;
+ MPI2_FW_DOWNLOAD_REPLY *rpl;
+ MPI2_FW_DOWNLOAD_TCSGE tc;
+ int error;
+
+ /*
+ * This code assumes there is room in the request's SGL for
+ * the TransactionContext plus at least a SGL chain element.
+ */
+ CTASSERT(sizeof req->SGL >= sizeof tc + MPS_SGC_SIZE);
+
+ if (cmd->req_len != sizeof *req)
+ return (EINVAL);
+ if (cmd->rpl_len != sizeof *rpl)
+ return (EINVAL);
+
+ if (cmd->len == 0)
+ return (EINVAL);
+
+ error = copyin(cmd->buf, cm->cm_data, cmd->len);
+ if (error != 0)
+ return (error);
+
+ mpi_init_sge(cm, req, &req->SGL);
+ bzero(&tc, sizeof tc);
+
+ /*
+ * For now, the F/W image must be provided in a single request.
+ */
+ if ((req->MsgFlags & MPI2_FW_DOWNLOAD_MSGFLGS_LAST_SEGMENT) == 0)
+ return (EINVAL);
+ if (req->TotalImageSize != cmd->len)
+ return (EINVAL);
+
+ /*
+ * The value of the first two elements is specified in the
+ * Fusion-MPT Message Passing Interface document.
+ */
+ tc.ContextSize = 0;
+ tc.DetailsLength = 12;
+ tc.ImageOffset = 0;
+ tc.ImageSize = cmd->len;
+
+ cm->cm_flags |= MPS_CM_FLAGS_DATAOUT;
+
+ return (mps_push_sge(cm, &tc, sizeof tc, 0));
+}
+
+/*
+ * Prepare the mps_command for a FW_UPLOAD request.
+ */
+static int
+mpi_pre_fw_upload(struct mps_command *cm, struct mps_usr_command *cmd)
+{
+ MPI2_FW_UPLOAD_REQUEST *req = (void *)cm->cm_req;
+ MPI2_FW_UPLOAD_REPLY *rpl;
+ MPI2_FW_UPLOAD_TCSGE tc;
+
+ /*
+ * This code assumes there is room in the request's SGL for
+ * the TransactionContext plus at least a SGL chain element.
+ */
+ CTASSERT(sizeof req->SGL >= sizeof tc + MPS_SGC_SIZE);
+
+ if (cmd->req_len != sizeof *req)
+ return (EINVAL);
+ if (cmd->rpl_len != sizeof *rpl)
+ return (EINVAL);
+
+ mpi_init_sge(cm, req, &req->SGL);
+ if (cmd->len == 0) {
+ /* Perhaps just asking what the size of the fw is? */
+ return (0);
+ }
+
+ bzero(&tc, sizeof tc);
+
+ /*
+ * The value of the first two elements is specified in the
+ * Fusion-MPT Message Passing Interface document.
+ */
+ tc.ContextSize = 0;
+ tc.DetailsLength = 12;
+ /*
+ * XXX Is there any reason to fetch a partial image? I.e. to
+ * set ImageOffset to something other than 0?
+ */
+ tc.ImageOffset = 0;
+ tc.ImageSize = cmd->len;
+
+ return (mps_push_sge(cm, &tc, sizeof tc, 0));
+}
+
+/*
+ * Prepare the mps_command for a SATA_PASSTHROUGH request.
+ */
+static int
+mpi_pre_sata_passthrough(struct mps_command *cm, struct mps_usr_command *cmd)
+{
+ MPI2_SATA_PASSTHROUGH_REQUEST *req = (void *)cm->cm_req;
+ MPI2_SATA_PASSTHROUGH_REPLY *rpl;
+
+ if (cmd->req_len != sizeof *req)
+ return (EINVAL);
+ if (cmd->rpl_len != sizeof *rpl)
+ return (EINVAL);
+
+ mpi_init_sge(cm, req, &req->SGL);
+ return (0);
+}
+
+/*
+ * Prepare the mps_command for a SMP_PASSTHROUGH request.
+ */
+static int
+mpi_pre_smp_passthrough(struct mps_command *cm, struct mps_usr_command *cmd)
+{
+ MPI2_SMP_PASSTHROUGH_REQUEST *req = (void *)cm->cm_req;
+ MPI2_SMP_PASSTHROUGH_REPLY *rpl;
+
+ if (cmd->req_len != sizeof *req)
+ return (EINVAL);
+ if (cmd->rpl_len != sizeof *rpl)
+ return (EINVAL);
+
+ mpi_init_sge(cm, req, &req->SGL);
+ return (0);
+}
+
+/*
+ * Prepare the mps_command for a CONFIG request.
+ */
+static int
+mpi_pre_config(struct mps_command *cm, struct mps_usr_command *cmd)
+{
+ MPI2_CONFIG_REQUEST *req = (void *)cm->cm_req;
+ MPI2_CONFIG_REPLY *rpl;
+
+ if (cmd->req_len != sizeof *req)
+ return (EINVAL);
+ if (cmd->rpl_len != sizeof *rpl)
+ return (EINVAL);
+
+ mpi_init_sge(cm, req, &req->PageBufferSGE);
+ return (0);
+}
+
+/*
+ * Prepare the mps_command for a SAS_IO_UNIT_CONTROL request.
+ */
+static int
+mpi_pre_sas_io_unit_control(struct mps_command *cm,
+ struct mps_usr_command *cmd)
+{
+
+ cm->cm_sge = NULL;
+ cm->cm_sglsize = 0;
+ return (0);
+}
+
+/*
+ * A set of functions to prepare an mps_command for the various
+ * supported requests.
+ */
struct mps_user_func {
- U8 Func;
- U8 SgOff;
+ U8 Function;
+ mps_user_f *f_pre;
} mps_user_func_list[] = {
- { MPI2_FUNCTION_IOC_FACTS, 0 },
- { MPI2_FUNCTION_PORT_FACTS, 0 },
- { MPI2_FUNCTION_FW_DOWNLOAD, offsetof(Mpi2FWDownloadRequest,SGL)},
- { MPI2_FUNCTION_FW_UPLOAD, offsetof(Mpi2FWUploadRequest_t,SGL)},
- { MPI2_FUNCTION_SATA_PASSTHROUGH,offsetof(Mpi2SataPassthroughRequest_t,SGL)},
- { MPI2_FUNCTION_SMP_PASSTHROUGH, offsetof(Mpi2SmpPassthroughRequest_t,SGL)},
- { MPI2_FUNCTION_CONFIG, offsetof(Mpi2ConfigRequest_t,PageBufferSGE)},
- { MPI2_FUNCTION_SAS_IO_UNIT_CONTROL, 0 },
-};
+ { MPI2_FUNCTION_IOC_FACTS, mpi_pre_ioc_facts },
+ { MPI2_FUNCTION_PORT_FACTS, mpi_pre_port_facts },
+ { MPI2_FUNCTION_FW_DOWNLOAD, mpi_pre_fw_download },
+ { MPI2_FUNCTION_FW_UPLOAD, mpi_pre_fw_upload },
+ { MPI2_FUNCTION_SATA_PASSTHROUGH, mpi_pre_sata_passthrough },
+ { MPI2_FUNCTION_SMP_PASSTHROUGH, mpi_pre_smp_passthrough},
+ { MPI2_FUNCTION_CONFIG, mpi_pre_config},
+ { MPI2_FUNCTION_SAS_IO_UNIT_CONTROL, mpi_pre_sas_io_unit_control },
+ { 0xFF, NULL } /* list end */
+};
static int
-mps_user_verify_request(MPI2_REQUEST_HEADER *hdr, MPI2_SGE_IO_UNION **psgl)
+mps_user_setup_request(struct mps_command *cm, struct mps_usr_command *cmd)
{
- int i, err = EINVAL;
+ MPI2_REQUEST_HEADER *hdr = (MPI2_REQUEST_HEADER *)cm->cm_req;
+ struct mps_user_func *f;
- for (i = 0; i < sizeof(mps_user_func_list) /
- sizeof(mps_user_func_list[0]); i++ ) {
- struct mps_user_func *func = &mps_user_func_list[i];
-
- if (hdr->Function == func->Func) {
- if (psgl != NULL) {
- if (func->SgOff != 0)
- *psgl = (PTR_MPI2_SGE_IO_UNION)
- ((char*)hdr + func->SgOff);
- else
- *psgl = NULL;
- err = 0;
- break;
- }
- }
- }
-
- return err;
+ for (f = mps_user_func_list; f->f_pre != NULL; f++) {
+ if (hdr->Function == f->Function)
+ return (f->f_pre(cm, cmd));
+ }
+ return (EINVAL);
}
static int
@@ -338,9 +578,8 @@
{
MPI2_REQUEST_HEADER *hdr;
MPI2_DEFAULT_REPLY *rpl;
- MPI2_SGE_IO_UNION *sgl;
- void *buf;
- struct mps_command *cm;
+ void *buf = NULL;
+ struct mps_command *cm = NULL;
int err = 0;
int sz;
@@ -359,16 +598,22 @@
mps_dprint(sc, MPS_INFO, "mps_user_command: req %p %d rpl %p %d\n",
cmd->req, cmd->req_len, cmd->rpl, cmd->rpl_len );
- copyin(cmd->req, hdr, cmd->req_len);
+ if (cmd->req_len > (int)sc->facts->IOCRequestFrameSize * 4) {
+ err = EINVAL;
+ goto RetFreeUnlocked;
+ }
+ err = copyin(cmd->req, hdr, cmd->req_len);
+ if (err != 0)
+ goto RetFreeUnlocked;
mps_dprint(sc, MPS_INFO, "mps_user_command: Function %02X "
"MsgFlags %02X\n", hdr->Function, hdr->MsgFlags );
- err = mps_user_verify_request(hdr, &sgl);
+ err = mps_user_setup_request(cm, cmd);
if (err != 0) {
mps_printf(sc, "mps_user_command: unsupported function 0x%X\n",
hdr->Function );
- goto RetFree;
+ goto RetFreeUnlocked;
}
if (cmd->len > 0) {
@@ -376,24 +621,22 @@
cm->cm_data = buf;
cm->cm_length = cmd->len;
} else {
- buf = NULL;
cm->cm_data = NULL;
cm->cm_length = 0;
}
- cm->cm_sge = sgl;
- cm->cm_sglsize = sizeof(MPI2_SGE_IO_UNION);
cm->cm_flags = MPS_CM_FLAGS_SGE_SIMPLE | MPS_CM_FLAGS_WAKEUP;
cm->cm_desc.Default.RequestFlags = MPI2_REQ_DESCRIPT_FLAGS_DEFAULT_TYPE;
mps_lock(sc);
err = mps_map_command(sc, cm);
- if (err != 0) {
- mps_printf(sc, "mps_user_command: request timed out\n");
+ if (err != 0 && err != EINPROGRESS) {
+ mps_printf(sc, "%s: invalid request: error %d\n",
+ __func__, err);
goto Ret;
}
- msleep(cm, &sc->mps_mtx, 0, "mpsuser", 0); /* 30 seconds */
+ msleep(cm, &sc->mps_mtx, 0, "mpsuser", 0);
rpl = (MPI2_DEFAULT_REPLY *)cm->cm_reply;
sz = rpl->MsgLength * 4;
@@ -408,41 +651,29 @@
mps_unlock(sc);
copyout(rpl, cmd->rpl, sz);
- if (buf != NULL) {
+ if (buf != NULL)
copyout(buf, cmd->buf, cmd->len);
- free(buf, M_MPSUSER);
- }
- mps_lock(sc);
-
mps_dprint(sc, MPS_INFO, "mps_user_command: reply size %d\n", sz );
-RetFree:
- mps_free_command(sc, cm);
-
+RetFreeUnlocked:
+ mps_lock(sc);
+ if (cm != NULL)
+ mps_free_command(sc, cm);
Ret:
mps_unlock(sc);
- return err;
+ if (buf != NULL)
+ free(buf, M_MPSUSER);
+ return (err);
}
-#ifdef __amd64__
-#define PTRIN(p) ((void *)(uintptr_t)(p))
-#define PTROUT(v) ((u_int32_t)(uintptr_t)(v))
-#endif
-
static int
-mps_ioctl(struct cdev *dev, u_long cmd, caddr_t arg, int flag,
+mps_ioctl(struct cdev *dev, u_long cmd, void *arg, int flag,
struct thread *td)
{
struct mps_softc *sc;
struct mps_cfg_page_req *page_req;
struct mps_ext_cfg_page_req *ext_page_req;
void *mps_page;
-#ifdef __amd64__
- struct mps_cfg_page_req32 *page_req32;
- struct mps_cfg_page_req page_req_swab;
- struct mps_ext_cfg_page_req32 *ext_page_req32;
- struct mps_ext_cfg_page_req ext_page_req_swab;
-#endif
int error;
mps_page = NULL;
@@ -450,47 +681,12 @@
page_req = (void *)arg;
ext_page_req = (void *)arg;
-#ifdef __amd64__
- /* Convert 32-bit structs to native ones. */
- page_req32 = (void *)arg;
- ext_page_req32 = (void *)arg;
switch (cmd) {
- case MPSIO_READ_CFG_HEADER32:
- case MPSIO_READ_CFG_PAGE32:
- case MPSIO_WRITE_CFG_PAGE32:
- page_req = &page_req_swab;
- page_req->header = page_req32->header;
- page_req->page_address = page_req32->page_address;
- page_req->buf = PTRIN(page_req32->buf);
- page_req->len = page_req32->len;
- page_req->ioc_status = page_req32->ioc_status;
- break;
- case MPSIO_READ_EXT_CFG_HEADER32:
- case MPSIO_READ_EXT_CFG_PAGE32:
- ext_page_req = &ext_page_req_swab;
- ext_page_req->header = ext_page_req32->header;
- ext_page_req->page_address = ext_page_req32->page_address;
- ext_page_req->buf = PTRIN(ext_page_req32->buf);
- ext_page_req->len = ext_page_req32->len;
- ext_page_req->ioc_status = ext_page_req32->ioc_status;
- break;
- default:
- return (ENOIOCTL);
- }
-#endif
-
- switch (cmd) {
-#ifdef __amd64__
- case MPSIO_READ_CFG_HEADER32:
-#endif
case MPSIO_READ_CFG_HEADER:
mps_lock(sc);
error = mps_user_read_cfg_header(sc, page_req);
mps_unlock(sc);
break;
-#ifdef __amd64__
- case MPSIO_READ_CFG_PAGE32:
-#endif
case MPSIO_READ_CFG_PAGE:
mps_page = malloc(page_req->len, M_MPSUSER, M_WAITOK | M_ZERO);
error = copyin(page_req->buf, mps_page,
@@ -504,17 +700,11 @@
break;
error = copyout(mps_page, page_req->buf, page_req->len);
break;
-#ifdef __amd64__
- case MPSIO_READ_EXT_CFG_HEADER32:
-#endif
case MPSIO_READ_EXT_CFG_HEADER:
mps_lock(sc);
error = mps_user_read_extcfg_header(sc, ext_page_req);
mps_unlock(sc);
break;
-#ifdef __amd64__
- case MPSIO_READ_EXT_CFG_PAGE32:
-#endif
case MPSIO_READ_EXT_CFG_PAGE:
mps_page = malloc(ext_page_req->len, M_MPSUSER, M_WAITOK|M_ZERO);
error = copyin(ext_page_req->buf, mps_page,
@@ -528,9 +718,6 @@
break;
error = copyout(mps_page, ext_page_req->buf, ext_page_req->len);
break;
-#ifdef __amd64__
- case MPSIO_WRITE_CFG_PAGE32:
-#endif
case MPSIO_WRITE_CFG_PAGE:
mps_page = malloc(page_req->len, M_MPSUSER, M_WAITOK|M_ZERO);
error = copyin(page_req->buf, mps_page, page_req->len);
@@ -551,33 +738,207 @@
if (mps_page != NULL)
free(mps_page, M_MPSUSER);
- if (error)
- return (error);
+ return (error);
+}
-#ifdef __amd64__
- /* Convert native structs to 32-bit ones. */
- switch (cmd) {
+#ifdef COMPAT_FREEBSD32
+
+/* Macros from compat/freebsd32/freebsd32.h */
+#define PTRIN(v) (void *)(uintptr_t)(v)
+#define PTROUT(v) (uint32_t)(uintptr_t)(v)
+
+#define CP(src,dst,fld) do { (dst).fld = (src).fld; } while (0)
+#define PTRIN_CP(src,dst,fld) \
+ do { (dst).fld = PTRIN((src).fld); } while (0)
+#define PTROUT_CP(src,dst,fld) \
+ do { (dst).fld = PTROUT((src).fld); } while (0)
+
+struct mps_cfg_page_req32 {
+ MPI2_CONFIG_PAGE_HEADER header;
+ uint32_t page_address;
+ uint32_t buf;
+ int len;
+ uint16_t ioc_status;
+};
+
+struct mps_ext_cfg_page_req32 {
+ MPI2_CONFIG_EXTENDED_PAGE_HEADER header;
+ uint32_t page_address;
+ uint32_t buf;
+ int len;
+ uint16_t ioc_status;
+};
+
+struct mps_raid_action32 {
+ uint8_t action;
+ uint8_t volume_bus;
+ uint8_t volume_id;
+ uint8_t phys_disk_num;
+ uint32_t action_data_word;
+ uint32_t buf;
+ int len;
+ uint32_t volume_status;
+ uint32_t action_data[4];
+ uint16_t action_status;
+ uint16_t ioc_status;
+ uint8_t write;
+};
+
+struct mps_usr_command32 {
+ uint32_t req;
+ uint32_t req_len;
+ uint32_t rpl;
+ uint32_t rpl_len;
+ uint32_t buf;
+ int len;
+ uint32_t flags;
+};
+
+#define MPSIO_READ_CFG_HEADER32 _IOWR('M', 200, struct mps_cfg_page_req32)
+#define MPSIO_READ_CFG_PAGE32 _IOWR('M', 201, struct mps_cfg_page_req32)
+#define MPSIO_READ_EXT_CFG_HEADER32 _IOWR('M', 202, struct mps_ext_cfg_page_req32)
+#define MPSIO_READ_EXT_CFG_PAGE32 _IOWR('M', 203, struct mps_ext_cfg_page_req32)
+#define MPSIO_WRITE_CFG_PAGE32 _IOWR('M', 204, struct mps_cfg_page_req32)
+#define MPSIO_RAID_ACTION32 _IOWR('M', 205, struct mps_raid_action32)
+#define MPSIO_MPS_COMMAND32 _IOWR('M', 210, struct mps_usr_command32)
+
+static int
+mps_ioctl32(struct cdev *dev, u_long cmd32, void *_arg, int flag,
+ struct thread *td)
+{
+ struct mps_cfg_page_req32 *page32 = _arg;
+ struct mps_ext_cfg_page_req32 *ext32 = _arg;
+ struct mps_raid_action32 *raid32 = _arg;
+ struct mps_usr_command32 *user32 = _arg;
+ union {
+ struct mps_cfg_page_req page;
+ struct mps_ext_cfg_page_req ext;
+ struct mps_raid_action raid;
+ struct mps_usr_command user;
+ } arg;
+ u_long cmd;
+ int error;
+
+ switch (cmd32) {
case MPSIO_READ_CFG_HEADER32:
case MPSIO_READ_CFG_PAGE32:
case MPSIO_WRITE_CFG_PAGE32:
- page_req32->header = page_req->header;
- page_req32->page_address = page_req->page_address;
- page_req32->buf = PTROUT(page_req->buf);
- page_req32->len = page_req->len;
- page_req32->ioc_status = page_req->ioc_status;
+ if (cmd32 == MPSIO_READ_CFG_HEADER32)
+ cmd = MPSIO_READ_CFG_HEADER;
+ else if (cmd32 == MPSIO_READ_CFG_PAGE32)
+ cmd = MPSIO_READ_CFG_PAGE;
+ else
+ cmd = MPSIO_WRITE_CFG_PAGE;
+ CP(*page32, arg.page, header);
+ CP(*page32, arg.page, page_address);
+ PTRIN_CP(*page32, arg.page, buf);
+ CP(*page32, arg.page, len);
+ CP(*page32, arg.page, ioc_status);
break;
+
case MPSIO_READ_EXT_CFG_HEADER32:
- case MPSIO_READ_EXT_CFG_PAGE32:
- ext_page_req32->header = ext_page_req->header;
- ext_page_req32->page_address = ext_page_req->page_address;
- ext_page_req32->buf = PTROUT(ext_page_req->buf);
- ext_page_req32->len = ext_page_req->len;
- ext_page_req32->ioc_status = ext_page_req->ioc_status;
+ case MPSIO_READ_EXT_CFG_PAGE32:
+ if (cmd32 == MPSIO_READ_EXT_CFG_HEADER32)
+ cmd = MPSIO_READ_EXT_CFG_HEADER;
+ else
+ cmd = MPSIO_READ_EXT_CFG_PAGE;
+ CP(*ext32, arg.ext, header);
+ CP(*ext32, arg.ext, page_address);
+ PTRIN_CP(*ext32, arg.ext, buf);
+ CP(*ext32, arg.ext, len);
+ CP(*ext32, arg.ext, ioc_status);
break;
+
+ case MPSIO_RAID_ACTION32:
+ cmd = MPSIO_RAID_ACTION;
+ CP(*raid32, arg.raid, action);
+ CP(*raid32, arg.raid, volume_bus);
+ CP(*raid32, arg.raid, volume_id);
+ CP(*raid32, arg.raid, phys_disk_num);
+ CP(*raid32, arg.raid, action_data_word);
+ PTRIN_CP(*raid32, arg.raid, buf);
+ CP(*raid32, arg.raid, len);
+ CP(*raid32, arg.raid, volume_status);
+ bcopy(raid32->action_data, arg.raid.action_data,
+ sizeof arg.raid.action_data);
+ CP(*raid32, arg.raid, ioc_status);
+ CP(*raid32, arg.raid, write);
+ break;
+
+ case MPSIO_MPS_COMMAND32:
+ cmd = MPSIO_MPS_COMMAND;
+ PTRIN_CP(*user32, arg.user, req);
+ CP(*user32, arg.user, req_len);
+ PTRIN_CP(*user32, arg.user, rpl);
+ CP(*user32, arg.user, rpl_len);
+ PTRIN_CP(*user32, arg.user, buf);
+ CP(*user32, arg.user, len);
+ CP(*user32, arg.user, flags);
+ break;
default:
return (ENOIOCTL);
}
-#endif
- return (0);
+ error = mps_ioctl(dev, cmd, &arg, flag, td);
+ if (error == 0 && (cmd32 & IOC_OUT) != 0) {
+ switch (cmd32) {
+ case MPSIO_READ_CFG_HEADER32:
+ case MPSIO_READ_CFG_PAGE32:
+ case MPSIO_WRITE_CFG_PAGE32:
+ CP(arg.page, *page32, header);
+ CP(arg.page, *page32, page_address);
+ PTROUT_CP(arg.page, *page32, buf);
+ CP(arg.page, *page32, len);
+ CP(arg.page, *page32, ioc_status);
+ break;
+
+ case MPSIO_READ_EXT_CFG_HEADER32:
+ case MPSIO_READ_EXT_CFG_PAGE32:
+ CP(arg.ext, *ext32, header);
+ CP(arg.ext, *ext32, page_address);
+ PTROUT_CP(arg.ext, *ext32, buf);
+ CP(arg.ext, *ext32, len);
+ CP(arg.ext, *ext32, ioc_status);
+ break;
+
+ case MPSIO_RAID_ACTION32:
+ CP(arg.raid, *raid32, action);
+ CP(arg.raid, *raid32, volume_bus);
+ CP(arg.raid, *raid32, volume_id);
+ CP(arg.raid, *raid32, phys_disk_num);
+ CP(arg.raid, *raid32, action_data_word);
+ PTROUT_CP(arg.raid, *raid32, buf);
+ CP(arg.raid, *raid32, len);
+ CP(arg.raid, *raid32, volume_status);
+ bcopy(arg.raid.action_data, raid32->action_data,
+ sizeof arg.raid.action_data);
+ CP(arg.raid, *raid32, ioc_status);
+ CP(arg.raid, *raid32, write);
+ break;
+
+ case MPSIO_MPS_COMMAND32:
+ PTROUT_CP(arg.user, *user32, req);
+ CP(arg.user, *user32, req_len);
+ PTROUT_CP(arg.user, *user32, rpl);
+ CP(arg.user, *user32, rpl_len);
+ PTROUT_CP(arg.user, *user32, buf);
+ CP(arg.user, *user32, len);
+ CP(arg.user, *user32, flags);
+ break;
+ }
+ }
+
+ return (error);
}
+#endif /* COMPAT_FREEBSD32 */
+
+static int
+mps_ioctl_devsw(struct cdev *dev, u_long com, caddr_t arg, int flag,
+ struct thread *td)
+{
+#ifdef COMPAT_FREEBSD32
+ if (SV_CURPROC_FLAG(SV_ILP32))
+ return (mps_ioctl32(dev, com, arg, flag, td));
+#endif
+ return (mps_ioctl(dev, com, arg, flag, td));
+}
Index: sys/dev/mps/mpsvar.h
===================================================================
--- sys/dev/mps/mpsvar.h (revision 212420)
+++ sys/dev/mps/mpsvar.h (working copy)
@@ -60,11 +60,19 @@
uint32_t chain_busaddr;
};
+/*
+ * This needs to be at least 2 to support SMP passthrough.
+ */
+#define MPS_IOVEC_COUNT 2
+
struct mps_command {
TAILQ_ENTRY(mps_command) cm_link;
struct mps_softc *cm_sc;
void *cm_data;
u_int cm_length;
+ struct uio cm_uio;
+ struct iovec cm_iovec[MPS_IOVEC_COUNT];
+ u_int cm_max_segs;
u_int cm_sglsize;
MPI2_SGE_IO_UNION *cm_sge;
uint8_t *cm_req;
@@ -81,6 +89,9 @@
#define MPS_CM_FLAGS_DATAOUT (1 << 3)
#define MPS_CM_FLAGS_DATAIN (1 << 4)
#define MPS_CM_FLAGS_WAKEUP (1 << 5)
+#define MPS_CM_FLAGS_ACTIVE (1 << 6)
+#define MPS_CM_FLAGS_USE_UIO (1 << 7)
+#define MPS_CM_FLAGS_SMP_PASS (1 << 8)
u_int cm_state;
#define MPS_CM_STATE_FREE 0
#define MPS_CM_STATE_BUSY 1
@@ -109,6 +120,8 @@
#define MPS_FLAGS_BUSY (1 << 2)
#define MPS_FLAGS_SHUTDOWN (1 << 3)
u_int mps_debug;
+ u_int allow_multiple_tm_cmds;
+ int tm_cmds_active;
struct sysctl_ctx_list sysctl_ctx;
struct sysctl_oid *sysctl_tree;
struct mps_command *commands;
@@ -119,9 +132,9 @@
TAILQ_HEAD(, mps_command) req_list;
TAILQ_HEAD(, mps_chain) chain_list;
+ TAILQ_HEAD(, mps_command) tm_list;
int replypostindex;
int replyfreeindex;
- int replycurindex;
struct resource *mps_regs_resource;
bus_space_handle_t mps_bhandle;
@@ -234,12 +247,15 @@
{
struct mps_chain *chain, *chain_temp;
- if (cm->cm_reply != NULL)
+ if (cm->cm_reply != NULL) {
mps_free_reply(sc, cm->cm_reply_data);
+ cm->cm_reply = NULL;
+ }
cm->cm_flags = 0;
cm->cm_complete = NULL;
cm->cm_complete_data = NULL;
cm->cm_targ = 0;
+ cm->cm_max_segs = 0;
cm->cm_state = MPS_CM_STATE_FREE;
TAILQ_FOREACH_SAFE(chain, &cm->cm_chain_list, chain_link, chain_temp) {
TAILQ_REMOVE(&cm->cm_chain_list, chain, chain_link);
@@ -355,12 +371,16 @@
int mps_update_events(struct mps_softc *, struct mps_event_handle *, uint8_t *);
int mps_deregister_events(struct mps_softc *, struct mps_event_handle *);
int mps_request_polled(struct mps_softc *sc, struct mps_command *cm);
+void mps_enqueue_request(struct mps_softc *, struct mps_command *);
+int mps_push_sge(struct mps_command *, void *, size_t, int);
+int mps_add_dmaseg(struct mps_command *, vm_paddr_t, size_t, u_int, int);
int mps_attach_sas(struct mps_softc *sc);
int mps_detach_sas(struct mps_softc *sc);
int mps_map_command(struct mps_softc *sc, struct mps_command *cm);
int mps_read_config_page(struct mps_softc *, struct mps_config_params *);
int mps_write_config_page(struct mps_softc *, struct mps_config_params *);
void mps_memaddr_cb(void *, bus_dma_segment_t *, int , int );
+void mpi_init_sge(struct mps_command *cm, void *req, void *sge);
int mps_attach_user(struct mps_softc *);
void mps_detach_user(struct mps_softc *);
Index: sys/dev/bwn/if_bwn.c
===================================================================
--- sys/dev/bwn/if_bwn.c (revision 218743)
+++ sys/dev/bwn/if_bwn.c (working copy)
@@ -2882,7 +2882,7 @@
error = bwn_switch_band(sc, ic->ic_curchan);
if (error)
- goto fail;;
+ goto fail;
bwn_mac_suspend(mac);
bwn_set_txretry(mac, BWN_RETRY_SHORT, BWN_RETRY_LONG);
chan = ieee80211_chan2ieee(ic, ic->ic_curchan);
@@ -8260,7 +8260,7 @@
device_printf(sc->sc_dev, "switching to %s-GHz band\n",
IEEE80211_IS_CHAN_2GHZ(chan) ? "2" : "5");
- down_dev = sc->sc_curmac;;
+ down_dev = sc->sc_curmac;
status = down_dev->mac_status;
if (status >= BWN_MAC_STATUS_STARTED)
bwn_core_stop(down_dev);
Property changes on: sys/contrib/pf
___________________________________________________________________
Modified: svn:mergeinfo
Merged /head/sys/contrib/pf:r212420,212616,212772,212802,213535,213702,213704,213707-213708,213743,213839-213840,213882,213898,216088,216227,216363,216368
Property changes on: sys/contrib/dev/acpica
___________________________________________________________________
Modified: svn:mergeinfo
Merged /head/sys/contrib/dev/acpica:r212420,212616,212772,212802,213535,213702,213704,213707-213708,213743,213839-213840,213882,213898,216088,216227,216363,216368
Property changes on: sys/cddl/contrib/opensolaris
___________________________________________________________________
Modified: svn:mergeinfo
Merged /head/sys/cddl/contrib/opensolaris:r212420,212616,212772,212802,213535,213702,213704,213707-213708,213743,213839-213840,213882,213898,216088,216227,216363,216368
Property changes on: sys/amd64/include/xen
___________________________________________________________________
Modified: svn:mergeinfo
Merged /head/sys/amd64/include/xen:r212420,212616,212772,212802,213535,213702,213704,213707-213708,213743,213839-213840,213882,213898,216088,216227,216363,216368
Index: sys/amd64/conf/GENERIC
===================================================================
--- sys/amd64/conf/GENERIC (revision 218743)
+++ sys/amd64/conf/GENERIC (working copy)
@@ -114,6 +114,7 @@
device isp # Qlogic family
#device ispfw # Firmware for QLogic HBAs- normally a module
device mpt # LSI-Logic MPT-Fusion
+device mps # LSI-Logic MPT-Fusion 2
#device ncr # NCR/Symbios Logic
device sym # NCR/Symbios Logic (newer chipsets + those of `ncr')
device trm # Tekram DC395U/UW/F DC315U adapters
More information about the freebsd-stable
mailing list