1 /*
2  * This file and its contents are supplied under the terms of the
3  * Common Development and Distribution License ("CDDL"), version 1.0.
4  * You may only use this file in accordance with the terms of version
5  * 1.0 of the CDDL.
6  *
7  * A full copy of the text of the CDDL should have accompanied this
8  * source.  A copy of the CDDL is also available via the Internet at
9  * http://www.illumos.org/license/CDDL.
10  */
11 
12 /*
13  * Copyright 2016 Joyent, Inc.
14  */
15 
16 #include <sys/scsi/adapters/smrt/smrt.h>
17 
18 uint_t
smrt_isr_hw_simple(caddr_t arg1,caddr_t arg2)19 smrt_isr_hw_simple(caddr_t arg1, caddr_t arg2)
20 {
21 	_NOTE(ARGUNUSED(arg2))
22 
23 	/* LINTED: E_BAD_PTR_CAST_ALIGN */
24 	smrt_t *smrt = (smrt_t *)arg1;
25 	uint32_t isr = smrt_get32(smrt, CISS_I2O_INTERRUPT_STATUS);
26 	hrtime_t now = gethrtime();
27 
28 	mutex_enter(&smrt->smrt_mutex);
29 	if (!(smrt->smrt_status & SMRT_CTLR_STATUS_RUNNING)) {
30 		smrt->smrt_stats.smrts_unclaimed_interrupts++;
31 		smrt->smrt_last_interrupt_unclaimed = now;
32 
33 		/*
34 		 * We should not be receiving interrupts from the controller
35 		 * while the driver is not running.
36 		 */
37 		mutex_exit(&smrt->smrt_mutex);
38 		return (DDI_INTR_UNCLAIMED);
39 	}
40 
41 	/*
42 	 * Check to see if this interrupt came from the device:
43 	 */
44 	if ((isr & CISS_ISR_BIT_SIMPLE_INTR) == 0) {
45 		smrt->smrt_stats.smrts_unclaimed_interrupts++;
46 		smrt->smrt_last_interrupt_unclaimed = now;
47 
48 		/*
49 		 * Check to see if the firmware has come to rest.  If it has,
50 		 * this routine will panic the system.
51 		 */
52 		smrt_lockup_check(smrt);
53 
54 		mutex_exit(&smrt->smrt_mutex);
55 		return (DDI_INTR_UNCLAIMED);
56 	}
57 
58 	smrt->smrt_stats.smrts_claimed_interrupts++;
59 	smrt->smrt_last_interrupt_claimed = now;
60 
61 	/*
62 	 * The interrupt was from our controller, so collect any pending
63 	 * command completions.
64 	 */
65 	smrt_retrieve_simple(smrt);
66 
67 	/*
68 	 * Process any commands in the completion queue.
69 	 */
70 	smrt_process_finishq(smrt);
71 
72 	mutex_exit(&smrt->smrt_mutex);
73 	return (DDI_INTR_CLAIMED);
74 }
75 
76 /*
77  * Read tags and process completion of the associated command until the supply
78  * of tags is exhausted.
79  */
80 void
smrt_retrieve_simple(smrt_t * smrt)81 smrt_retrieve_simple(smrt_t *smrt)
82 {
83 	uint32_t opq;
84 	uint32_t none = 0xffffffff;
85 
86 	VERIFY(MUTEX_HELD(&smrt->smrt_mutex));
87 
88 	while ((opq = smrt_get32(smrt, CISS_I2O_OUTBOUND_POST_Q)) != none) {
89 		uint32_t tag = CISS_OPQ_READ_TAG(opq);
90 		smrt_command_t *smcm;
91 
92 		if ((smcm = smrt_lookup_inflight(smrt, tag)) == NULL) {
93 			dev_err(smrt->smrt_dip, CE_WARN, "spurious tag %x",
94 			    tag);
95 			continue;
96 		}
97 
98 		avl_remove(&smrt->smrt_inflight, smcm);
99 		smcm->smcm_status &= ~SMRT_CMD_STATUS_INFLIGHT;
100 		if (CISS_OPQ_READ_ERROR(opq) != 0) {
101 			smcm->smcm_status |= SMRT_CMD_STATUS_ERROR;
102 		}
103 		smcm->smcm_time_complete = gethrtime();
104 
105 		/*
106 		 * Push this command onto the completion queue.
107 		 */
108 		list_insert_tail(&smrt->smrt_finishq, smcm);
109 	}
110 }
111 
112 /*
113  * Submit a command to the controller by posting it to the Inbound Post Queue
114  * Register.
115  */
116 void
smrt_submit_simple(smrt_t * smrt,smrt_command_t * smcm)117 smrt_submit_simple(smrt_t *smrt, smrt_command_t *smcm)
118 {
119 	smrt_put32(smrt, CISS_I2O_INBOUND_POST_Q, smcm->smcm_pa_cmd);
120 }
121 
122 /*
123  * Submit a command to the controller by posting it to the Inbound Post Queue
124  * Register.  Immediately begin polling on the completion of that command.
125  *
126  * NOTE: This function is for controller initialisation only.  It discards
127  * completions of commands other than the expected command as spurious, and
128  * will not interact correctly with the rest of the driver once it is running.
129  */
130 int
smrt_preinit_command_simple(smrt_t * smrt,smrt_command_t * smcm)131 smrt_preinit_command_simple(smrt_t *smrt, smrt_command_t *smcm)
132 {
133 	/*
134 	 * The controller must be initialised to use the Simple Transport
135 	 * Method, but not be marked RUNNING.  The command to process must be a
136 	 * PREINIT command with the expected tag number, marked for polling.
137 	 */
138 	VERIFY(smrt->smrt_ctlr_mode == SMRT_CTLR_MODE_SIMPLE);
139 	VERIFY(!(smrt->smrt_status & SMRT_CTLR_STATUS_RUNNING));
140 	VERIFY(smcm->smcm_type == SMRT_CMDTYPE_PREINIT);
141 	VERIFY(smcm->smcm_status & SMRT_CMD_STATUS_POLLED);
142 	VERIFY3U(smcm->smcm_tag, ==, SMRT_PRE_TAG_NUMBER);
143 
144 	/*
145 	 * Submit this command to the controller.
146 	 */
147 	smcm->smcm_status |= SMRT_CMD_STATUS_INFLIGHT;
148 	smrt_put32(smrt, CISS_I2O_INBOUND_POST_Q, smcm->smcm_pa_cmd);
149 
150 	/*
151 	 * Poll the controller for completions until we see the command we just
152 	 * sent, or the timeout expires.
153 	 */
154 	for (;;) {
155 		uint32_t none = 0xffffffff;
156 		uint32_t opq = smrt_get32(smrt, CISS_I2O_OUTBOUND_POST_Q);
157 		uint32_t tag;
158 
159 		if (smcm->smcm_expiry != 0) {
160 			/*
161 			 * This command has an expiry time.  Check to see
162 			 * if it has already passed:
163 			 */
164 			if (smcm->smcm_expiry < gethrtime()) {
165 				return (ETIMEDOUT);
166 			}
167 		}
168 
169 		if (opq == none) {
170 			delay(drv_usectohz(10 * 1000));
171 			continue;
172 		}
173 
174 		if ((tag = CISS_OPQ_READ_TAG(opq)) != SMRT_PRE_TAG_NUMBER) {
175 			dev_err(smrt->smrt_dip, CE_WARN, "unexpected tag 0x%x"
176 			    " completed during driver init", tag);
177 			delay(drv_usectohz(10 * 1000));
178 			continue;
179 		}
180 
181 		smcm->smcm_status &= ~SMRT_CMD_STATUS_INFLIGHT;
182 		if (CISS_OPQ_READ_ERROR(opq) != 0) {
183 			smcm->smcm_status |= SMRT_CMD_STATUS_ERROR;
184 		}
185 		smcm->smcm_time_complete = gethrtime();
186 		smcm->smcm_status |= SMRT_CMD_STATUS_POLL_COMPLETE;
187 
188 		return (0);
189 	}
190 }
191 
192 int
smrt_ctlr_init_simple(smrt_t * smrt)193 smrt_ctlr_init_simple(smrt_t *smrt)
194 {
195 	VERIFY(smrt->smrt_ctlr_mode == SMRT_CTLR_MODE_UNKNOWN);
196 
197 	if (smrt_cfgtbl_transport_has_support(smrt,
198 	    CISS_CFGTBL_XPORT_SIMPLE) != DDI_SUCCESS) {
199 		return (DDI_FAILURE);
200 	}
201 	smrt->smrt_ctlr_mode = SMRT_CTLR_MODE_SIMPLE;
202 
203 	/*
204 	 * Disable device interrupts while we are setting up.
205 	 */
206 	smrt_intr_set(smrt, B_FALSE);
207 
208 	if ((smrt->smrt_maxcmds = smrt_ctlr_get_cmdsoutmax(smrt)) == 0) {
209 		dev_err(smrt->smrt_dip, CE_WARN, "maximum outstanding "
210 		    "commands set to zero");
211 		return (DDI_FAILURE);
212 	}
213 
214 	/*
215 	 * Determine the number of Scatter/Gather List entries this controller
216 	 * supports.  The maximum number we allow is CISS_MAXSGENTRIES: the
217 	 * number of elements in the static struct we use for command
218 	 * submission.
219 	 */
220 	if ((smrt->smrt_sg_cnt = smrt_ctlr_get_maxsgelements(smrt)) == 0) {
221 		/*
222 		 * The CISS specification states that if this value is
223 		 * zero, we should assume a value of 31 for compatibility
224 		 * with older firmware.
225 		 */
226 		smrt->smrt_sg_cnt = CISS_SGCNT_FALLBACK;
227 
228 	} else if (smrt->smrt_sg_cnt > CISS_MAXSGENTRIES) {
229 		/*
230 		 * If the controller supports more than we have allocated,
231 		 * just cap the count at the allocation size.
232 		 */
233 		smrt->smrt_sg_cnt = CISS_MAXSGENTRIES;
234 	}
235 
236 	/*
237 	 * Zero the upper 32 bits of the address in the Controller.
238 	 */
239 	ddi_put32(smrt->smrt_ct_handle, &smrt->smrt_ct->Upper32Addr, 0);
240 
241 	/*
242 	 * Set the Transport Method and flush the changes to the
243 	 * Configuration Table.
244 	 */
245 	smrt_cfgtbl_transport_set(smrt, CISS_CFGTBL_XPORT_SIMPLE);
246 	if (smrt_cfgtbl_flush(smrt) != DDI_SUCCESS) {
247 		return (DDI_FAILURE);
248 	}
249 
250 	if (smrt_cfgtbl_transport_confirm(smrt,
251 	    CISS_CFGTBL_XPORT_SIMPLE) != DDI_SUCCESS) {
252 		return (DDI_FAILURE);
253 	}
254 
255 	/*
256 	 * Check the outstanding command cap a second time now that we have
257 	 * flushed out the new Transport Method.  This is entirely defensive;
258 	 * we do not expect this value to change.
259 	 */
260 	uint32_t check_again = smrt_ctlr_get_cmdsoutmax(smrt);
261 	if (check_again != smrt->smrt_maxcmds) {
262 		dev_err(smrt->smrt_dip, CE_WARN, "maximum outstanding commands "
263 		    "changed during initialisation (was %u, now %u)",
264 		    smrt->smrt_maxcmds, check_again);
265 		return (DDI_FAILURE);
266 	}
267 
268 	return (DDI_SUCCESS);
269 }
270 
271 void
smrt_ctlr_teardown_simple(smrt_t * smrt)272 smrt_ctlr_teardown_simple(smrt_t *smrt)
273 {
274 	VERIFY(smrt->smrt_ctlr_mode == SMRT_CTLR_MODE_SIMPLE);
275 
276 	/*
277 	 * Due to the nominal simplicity of the simple mode, we have no
278 	 * particular teardown to perform as we do not allocate anything
279 	 * on the way up.
280 	 */
281 	smrt->smrt_ctlr_mode = SMRT_CTLR_MODE_UNKNOWN;
282 }
283