| /* |
| * CDDL HEADER START |
| * |
| * The contents of this file are subject to the terms of the |
| * Common Development and Distribution License (the "License"). |
| * You may not use this file except in compliance with the License. |
| * |
| * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE |
| * or http://www.opensolaris.org/os/licensing. |
| * See the License for the specific language governing permissions |
| * and limitations under the License. |
| * |
| * When distributing Covered Code, include this CDDL HEADER in each |
| * file and include the License file at usr/src/OPENSOLARIS.LICENSE. |
| * If applicable, add the following below this CDDL HEADER, with the |
| * fields enclosed by brackets "[]" replaced with your own identifying |
| * information: Portions Copyright [yyyy] [name of copyright owner] |
| * |
| * CDDL HEADER END |
| */ |
| |
| /* |
| * Copyright (c) 1990, 2010, Oracle and/or its affiliates. All rights reserved. |
| */ |
| |
| /* |
| * Main Transport Routine for SCSA |
| */ |
| #include <sys/scsi/scsi.h> |
| #include <sys/thread.h> |
| #include <sys/bitmap.h> |
| |
| #define A_TO_TRAN(ap) ((ap)->a_hba_tran) |
| #define P_TO_TRAN(pkt) ((pkt)->pkt_address.a_hba_tran) |
| #define P_TO_ADDR(pkt) (&((pkt)->pkt_address)) |
| |
| #ifdef DEBUG |
| #define SCSI_POLL_STAT |
| #endif |
| |
| #ifdef SCSI_POLL_STAT |
| int scsi_poll_user; |
| int scsi_poll_intr; |
| #endif |
| |
| int scsi_pkt_bad_alloc_msg = 1; |
| extern ulong_t *scsi_pkt_bad_alloc_bitmap; |
| extern kmutex_t scsi_flag_nointr_mutex; |
| extern kcondvar_t scsi_flag_nointr_cv; |
| |
| extern int do_polled_io; |
| |
| extern int scsi_pkt_allow_naca; |
| extern uchar_t scsi_cdb_size[]; |
| #define NACA_IS_SET(cdb) \ |
| (((cdb)[scsi_cdb_size[GETGROUP((union scsi_cdb *)(cdb))] - 1] \ |
| & CDB_FLAG_NACA) ? 1 : 0) |
| |
| /* |
| * we used to set the callback_done value to NULL after the callback |
| * but this interfered with esp/fas drivers that also set the callback |
| * to NULL to prevent callbacks during error recovery |
| * to prevent confusion, create a truly unique value. |
| * The scsi_callback_done() function is used to detect a packet |
| * completion being called a second time. |
| */ |
| /* ARGSUSED */ |
| void |
| scsi_callback_done(struct scsi_pkt *pkt) |
| { |
| cmn_err(CE_PANIC, |
| "%s: duplicate scsi_callback_done() on same scsi_pkt(9s)", |
| mod_containing_pc(caller())); |
| } |
| |
| #define CALLBACK_DONE (scsi_callback_done) |
| |
| static void |
| scsi_flag_nointr_comp(struct scsi_pkt *pkt) |
| { |
| mutex_enter(&scsi_flag_nointr_mutex); |
| pkt->pkt_comp = CALLBACK_DONE; |
| /* |
| * We need cv_broadcast, because there can be more |
| * than one thread sleeping on the cv. We |
| * will wake all of them. The correct one will |
| * continue and the rest will again go to sleep. |
| */ |
| cv_broadcast(&scsi_flag_nointr_cv); |
| mutex_exit(&scsi_flag_nointr_mutex); |
| } |
| |
| /* |
| * A packet can have FLAG_NOINTR set because of target driver or |
| * scsi_poll(). If FLAG_NOINTR is set and we are in user context, |
| * we can avoid busy waiting in HBA by replacing the callback |
| * function with our own function and resetting FLAG_NOINTR. We |
| * can't do this in interrupt context because cv_wait will |
| * sleep with CPU priority raised high and in case of some failure, |
| * the CPU will be stuck in high priority. |
| */ |
| |
| int |
| scsi_transport(struct scsi_pkt *pkt) |
| { |
| struct scsi_address *ap = P_TO_ADDR(pkt); |
| int rval = TRAN_ACCEPT; |
| major_t major; |
| |
| /* |
| * Add an assertion check for debugging as use of the NACA flag |
| * can cause problems. If an initiator sets it but does not clear |
| * it, other initiators would end up waiting indefinitely for the |
| * first to clear ACA. |
| */ |
| if (!scsi_pkt_allow_naca) { |
| ASSERT(!NACA_IS_SET(pkt->pkt_cdbp)); |
| } |
| |
| /* |
| * The DDI does not allow drivers to allocate their own scsi_pkt(9S), |
| * a driver can't have *any* compiled in dependencies on the |
| * "sizeof (struct scsi_pkt)". While this has been the case for years, |
| * many drivers have still not been fixed (or have regressed - tempted |
| * by kmem_cache_alloc()). The correct way to allocate a scsi_pkt |
| * is by calling scsi_hba_pkt_alloc(9F), or by implementing the |
| * tran_setup_pkt(9E) interfaces. |
| * |
| * The code below will identify drivers that violate this rule, and |
| * print a message. The message will identify broken drivers, and |
| * encourage getting these drivers fixed - after which this code |
| * can be removed. Getting HBA drivers fixed is important because |
| * broken drivers are an impediment to SCSA enhancement. |
| * |
| * We use the scsi_pkt_allocated_correctly() to determine if the |
| * scsi_pkt we are about to start was correctly allocated. The |
| * scsi_pkt_bad_alloc_bitmap is used to limit messages to one per |
| * driver per reboot, and with non-debug code we only check the |
| * first scsi_pkt. |
| */ |
| if (scsi_pkt_bad_alloc_msg) { |
| major = ddi_driver_major(P_TO_TRAN(pkt)->tran_hba_dip); |
| if (!BT_TEST(scsi_pkt_bad_alloc_bitmap, major) && |
| !scsi_pkt_allocated_correctly(pkt)) { |
| BT_SET(scsi_pkt_bad_alloc_bitmap, major); |
| cmn_err(CE_WARN, "%s: violates DDI scsi_pkt(9S) " |
| "allocation rules", |
| ddi_driver_name(P_TO_TRAN(pkt)->tran_hba_dip)); |
| } |
| #ifndef DEBUG |
| /* On non-debug kernel, only check the first packet */ |
| BT_SET(scsi_pkt_bad_alloc_bitmap, major); |
| #endif /* DEBUG */ |
| } |
| |
| /* Some retryed packets come with this flag not cleared */ |
| pkt->pkt_flags &= ~FLAG_PKT_COMP_CALLED; |
| |
| /* |
| * Check if we are required to do polled I/O. We can |
| * get scsi_pkts that don't have the FLAG_NOINTR bit |
| * set in the pkt_flags. When do_polled_io is set |
| * we will probably be at a high IPL and not get any |
| * command completion interrupts. We force polled I/Os |
| * for such packets and do a callback of the completion |
| * routine ourselves. |
| */ |
| if (!do_polled_io && ((pkt->pkt_flags & FLAG_NOINTR) == 0)) { |
| return (*A_TO_TRAN(ap)->tran_start)(ap, pkt); |
| } else if ((curthread->t_flag & T_INTR_THREAD) || do_polled_io) { |
| #ifdef SCSI_POLL_STAT |
| mutex_enter(&scsi_flag_nointr_mutex); |
| scsi_poll_intr++; |
| mutex_exit(&scsi_flag_nointr_mutex); |
| #endif |
| /* |
| * If its an interrupt thread or we already have the |
| * the FLAG_NOINTR flag set, we go ahead and call the |
| * the hba's start routine directly. We force polling |
| * only if we have do_polled_io set and FLAG_NOINTR |
| * not set. |
| */ |
| if (!do_polled_io || (pkt->pkt_flags & FLAG_NOINTR)) { |
| return ((*A_TO_TRAN(ap)->tran_start)(ap, pkt)); |
| } else { |
| uint_t savef; |
| void (*savec)(); |
| /* |
| * save the completion routine and pkt_flags |
| */ |
| savef = pkt->pkt_flags; |
| savec = pkt->pkt_comp; |
| pkt->pkt_flags |= FLAG_NOINTR; |
| pkt->pkt_comp = 0; |
| |
| rval = (*A_TO_TRAN(ap)->tran_start)(ap, pkt); |
| |
| /* only continue of transport accepted request */ |
| if (rval == TRAN_ACCEPT) { |
| /* |
| * Restore the pkt_completion routine |
| * and pkt flags and call the completion |
| * routine. |
| */ |
| pkt->pkt_comp = savec; |
| pkt->pkt_flags = savef; |
| scsi_hba_pkt_comp(pkt); |
| return (rval); |
| } |
| |
| /* |
| * rval was not TRAN_ACCEPT -- don't want command |
| * to be retried |
| */ |
| return (TRAN_FATAL_ERROR); |
| } |
| } else { |
| uint_t savef; |
| void (*savec)(); |
| |
| #ifdef SCSI_POLL_STAT |
| mutex_enter(&scsi_flag_nointr_mutex); |
| scsi_poll_user++; |
| mutex_exit(&scsi_flag_nointr_mutex); |
| #endif |
| savef = pkt->pkt_flags; |
| savec = pkt->pkt_comp; |
| |
| pkt->pkt_comp = scsi_flag_nointr_comp; |
| pkt->pkt_flags &= ~FLAG_NOINTR; |
| pkt->pkt_flags |= FLAG_IMMEDIATE_CB; |
| |
| if ((rval = (*A_TO_TRAN(ap)->tran_start)(ap, pkt)) == |
| TRAN_ACCEPT) { |
| mutex_enter(&scsi_flag_nointr_mutex); |
| while (pkt->pkt_comp != CALLBACK_DONE) { |
| cv_wait(&scsi_flag_nointr_cv, |
| &scsi_flag_nointr_mutex); |
| } |
| mutex_exit(&scsi_flag_nointr_mutex); |
| } |
| |
| pkt->pkt_flags = savef; |
| pkt->pkt_comp = savec; |
| return (rval); |
| } |
| } |