1/*
2 * Copyright (c) 2009-2016 Solarflare Communications Inc.
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright notice,
9 *    this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright notice,
11 *    this list of conditions and the following disclaimer in the documentation
12 *    and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
15 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
16 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
18 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
21 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
22 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
23 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
24 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 *
26 * The views and conclusions contained in the software and documentation are
27 * those of the authors and should not be interpreted as representing official
28 * policies, either expressed or implied, of the FreeBSD Project.
29 */
30
31#include <sys/types.h>
32#include <sys/ddi.h>
33#include <sys/sunddi.h>
34#include <sys/stream.h>
35#include <sys/dlpi.h>
36#include <sys/pci.h>
37
38#include "sfxge.h"
39#include "efsys.h"
40#include "efx.h"
41#include "efx_mcdi.h"
42#include "efx_regs_mcdi.h"
43
44/* MAC DMA attributes */
45static ddi_device_acc_attr_t sfxge_mcdi_devacc = {
46
47	DDI_DEVICE_ATTR_V0,	/* devacc_attr_version */
48	DDI_NEVERSWAP_ACC,	/* devacc_attr_endian_flags */
49	DDI_STRICTORDER_ACC	/* devacc_attr_dataorder */
50};
51
52static ddi_dma_attr_t sfxge_mcdi_dma_attr = {
53	DMA_ATTR_V0,		/* dma_attr_version	*/
54	0,			/* dma_attr_addr_lo	*/
55	0xffffffffffffffffull,	/* dma_attr_addr_hi	*/
56	0xffffffffffffffffull,	/* dma_attr_count_max	*/
57	0x1000,			/* dma_attr_align	*/
58	0xffffffff,		/* dma_attr_burstsizes	*/
59	1,			/* dma_attr_minxfer	*/
60	0xffffffffffffffffull,	/* dma_attr_maxxfer	*/
61	0xffffffffffffffffull,	/* dma_attr_seg		*/
62	1,			/* dma_attr_sgllen	*/
63	1,			/* dma_attr_granular	*/
64	0			/* dma_attr_flags	*/
65};
66
67/*
68 * Notes on MCDI operation:
69 * ------------------------
70 * MCDI requests can be made in arbitrary thread context, and as a synchronous
71 * API must therefore block until the response is available from the MC, or
72 * a watchdog timeout occurs.
73 *
74 * This interacts badly with the limited number of worker threads (2 per CPU)
75 * used by the Solaris callout subsystem to invoke timeout handlers. If both
76 * worker threads are blocked (e.g. waiting for a condvar or mutex) then timeout
77 * processing is deadlocked on that CPU, causing system failure.
78 *
79 * For this reason the driver does not use event based MCDI completion, as this
80 * leads to numerous paths involving timeouts and reentrant GLDv3 entrypoints
81 * that result in a deadlocked system.
82 */
83#define	SFXGE_MCDI_POLL_INTERVAL	10		/* 10us in 1us units */
84#define	SFXGE_MCDI_WATCHDOG_INTERVAL	10000000	/* 10s in 1us units */
85
86
87/* Acquire exclusive access to MCDI for the duration of a request */
88static void
89sfxge_mcdi_acquire(sfxge_mcdi_t *smp)
90{
91	mutex_enter(&(smp->sm_lock));
92	ASSERT3U(smp->sm_state, !=, SFXGE_MCDI_UNINITIALIZED);
93
94	while (smp->sm_state != SFXGE_MCDI_INITIALIZED) {
95		(void) cv_wait(&(smp->sm_kv), &(smp->sm_lock));
96	}
97	smp->sm_state = SFXGE_MCDI_BUSY;
98
99	mutex_exit(&(smp->sm_lock));
100}
101
102
103/* Release ownership of MCDI on request completion */
104static void
105sfxge_mcdi_release(sfxge_mcdi_t *smp)
106{
107	mutex_enter(&(smp->sm_lock));
108	ASSERT((smp->sm_state == SFXGE_MCDI_BUSY) ||
109	    (smp->sm_state == SFXGE_MCDI_COMPLETED));
110
111	smp->sm_state = SFXGE_MCDI_INITIALIZED;
112	cv_broadcast(&(smp->sm_kv));
113
114	mutex_exit(&(smp->sm_lock));
115}
116
117
118static void
119sfxge_mcdi_timeout(sfxge_t *sp)
120{
121	dev_info_t *dip = sp->s_dip;
122
123	dev_err(dip, CE_WARN, SFXGE_CMN_ERR "MC_TIMEOUT");
124
125	DTRACE_PROBE(mcdi_timeout);
126	(void) sfxge_restart_dispatch(sp, DDI_SLEEP, SFXGE_HW_ERR,
127	    "MCDI timeout", 0);
128}
129
130
131static void
132sfxge_mcdi_poll(sfxge_t *sp)
133{
134	efx_nic_t *enp = sp->s_enp;
135	clock_t timeout;
136	boolean_t aborted;
137
138	/* Poll until request completes or timeout */
139	timeout = ddi_get_lbolt() + drv_usectohz(SFXGE_MCDI_WATCHDOG_INTERVAL);
140	while (efx_mcdi_request_poll(enp) == B_FALSE) {
141
142		/* No response received yet */
143		if (ddi_get_lbolt() > timeout) {
144			/* Timeout expired */
145			goto fail;
146		}
147
148		/* Short delay to avoid excessive PCIe traffic */
149		drv_usecwait(SFXGE_MCDI_POLL_INTERVAL);
150	}
151
152	/* Request completed (or polling failed) */
153	return;
154
155fail:
156	/* Timeout before request completion */
157	DTRACE_PROBE(fail);
158	aborted = efx_mcdi_request_abort(enp);
159	ASSERT(aborted);
160	sfxge_mcdi_timeout(sp);
161}
162
163
164static void
165sfxge_mcdi_execute(void *arg, efx_mcdi_req_t *emrp)
166{
167	sfxge_t *sp = (sfxge_t *)arg;
168	sfxge_mcdi_t *smp = &(sp->s_mcdi);
169
170	sfxge_mcdi_acquire(smp);
171
172	/* Issue request and poll for completion */
173	efx_mcdi_request_start(sp->s_enp, emrp, B_FALSE);
174	sfxge_mcdi_poll(sp);
175
176	sfxge_mcdi_release(smp);
177}
178
179
180static void
181sfxge_mcdi_ev_cpl(void *arg)
182{
183	sfxge_t *sp = (sfxge_t *)arg;
184	sfxge_mcdi_t *smp = &(sp->s_mcdi);
185
186	mutex_enter(&(smp->sm_lock));
187	ASSERT(smp->sm_state == SFXGE_MCDI_BUSY);
188	smp->sm_state = SFXGE_MCDI_COMPLETED;
189	cv_broadcast(&(smp->sm_kv));
190	mutex_exit(&(smp->sm_lock));
191}
192
193
194static void
195sfxge_mcdi_exception(void *arg, efx_mcdi_exception_t eme)
196{
197	sfxge_t *sp = (sfxge_t *)arg;
198	const char *reason;
199
200	if (eme == EFX_MCDI_EXCEPTION_MC_REBOOT)
201		reason = "MC_REBOOT";
202	else if (eme == EFX_MCDI_EXCEPTION_MC_BADASSERT)
203		reason = "MC_BADASSERT";
204	else
205		reason = "MC_UNKNOWN";
206
207	DTRACE_PROBE(mcdi_exception);
208	/* sfxge_evq_t->se_lock held */
209	(void) sfxge_restart_dispatch(sp, DDI_SLEEP, SFXGE_HW_ERR, reason, 0);
210}
211
212#if EFSYS_OPT_MCDI_LOGGING
213#define	SFXGE_MCDI_LOG_BUF_SIZE	128
214
215static size_t
216sfxge_mcdi_do_log(char *buffer, void *data, size_t data_size,
217    size_t pfxsize, size_t position)
218{
219	uint32_t *words = data;
220	size_t i;
221
222	for (i = 0; i < data_size; i += sizeof (*words)) {
223		if (position + 2 * sizeof (*words) + 1 >=
224		    SFXGE_MCDI_LOG_BUF_SIZE) {
225			buffer[position] = '\0';
226			cmn_err(CE_NOTE, "%s \\", buffer);
227			position = pfxsize;
228		}
229		snprintf(buffer + position, SFXGE_MCDI_LOG_BUF_SIZE - position,
230		    " %08x", *words);
231		words++;
232		position += 2 * sizeof (uint32_t) + 1;
233	}
234	return (position);
235}
236
237
238static void
239sfxge_mcdi_logger(void *arg, efx_log_msg_t type,
240    void *header, size_t header_size, void *data, size_t data_size)
241{
242	sfxge_t *sp = (sfxge_t *)arg;
243	char buffer[SFXGE_MCDI_LOG_BUF_SIZE];
244	size_t pfxsize;
245	size_t start;
246
247	if (!sp->s_mcdi_logging)
248		return;
249
250	pfxsize = snprintf(buffer, sizeof (buffer),
251	    "sfc %04x:%02x:%02x.%02x %s%d MCDI RPC %s:",
252	    0,
253	    PCI_REG_BUS_G(sp->s_bus_addr),
254	    PCI_REG_DEV_G(sp->s_bus_addr),
255	    PCI_REG_FUNC_G(sp->s_bus_addr),
256	    ddi_driver_name(sp->s_dip),
257	    ddi_get_instance(sp->s_dip),
258	    type == EFX_LOG_MCDI_REQUEST ? "REQ" :
259	    type == EFX_LOG_MCDI_RESPONSE ? "RESP" : "???");
260	start = sfxge_mcdi_do_log(buffer, header, header_size,
261	    pfxsize, pfxsize);
262	start = sfxge_mcdi_do_log(buffer, data, data_size, pfxsize, start);
263	if (start != pfxsize) {
264		buffer[start] = '\0';
265		cmn_err(CE_NOTE, "%s", buffer);
266	}
267}
268#endif /* EFSYS_OPT_MCDI_LOGGING */
269
270int
271sfxge_mcdi_init(sfxge_t *sp)
272{
273	efx_nic_t *enp = sp->s_enp;
274	sfxge_mcdi_t *smp = &(sp->s_mcdi);
275	efsys_mem_t *esmp = &(smp->sm_mem);
276	efx_mcdi_transport_t *emtp = &(smp->sm_emt);
277	sfxge_dma_buffer_attr_t dma_attr;
278	int msg_buf_size;
279	int rc;
280
281	ASSERT3U(smp->sm_state, ==, SFXGE_MCDI_UNINITIALIZED);
282
283	msg_buf_size = sizeof (uint32_t) + MCDI_CTL_SDU_LEN_MAX_V2;
284
285	/* Allocate host DMA buffer for MCDI commands */
286	dma_attr.sdba_dip	 = sp->s_dip;
287	dma_attr.sdba_dattrp	 = &sfxge_mcdi_dma_attr;
288	dma_attr.sdba_callback	 = DDI_DMA_SLEEP;
289	dma_attr.sdba_length	 = msg_buf_size;
290	dma_attr.sdba_memflags	 = DDI_DMA_CONSISTENT;
291	dma_attr.sdba_devaccp	 = &sfxge_mcdi_devacc;
292	dma_attr.sdba_bindflags	 = DDI_DMA_RDWR | DDI_DMA_CONSISTENT;
293	dma_attr.sdba_maxcookies = 1;
294	dma_attr.sdba_zeroinit	 = B_TRUE;
295
296	if ((rc = sfxge_dma_buffer_create(esmp, &dma_attr)) != 0)
297		goto fail1;
298
299	mutex_init(&(smp->sm_lock), NULL, MUTEX_DRIVER, NULL);
300
301	smp->sm_state = SFXGE_MCDI_INITIALIZED;
302
303	emtp->emt_context   = sp;
304	emtp->emt_dma_mem   = esmp;
305	emtp->emt_execute   = sfxge_mcdi_execute;
306	emtp->emt_ev_cpl    = sfxge_mcdi_ev_cpl;
307	emtp->emt_exception = sfxge_mcdi_exception;
308#if EFSYS_OPT_MCDI_LOGGING
309	emtp->emt_logger    = sfxge_mcdi_logger;
310#endif
311
312	cv_init(&(smp->sm_kv), NULL, CV_DRIVER, NULL);
313
314	if ((rc = efx_mcdi_init(enp, emtp)) != 0)
315		goto fail2;
316
317	return (0);
318
319fail2:
320	DTRACE_PROBE(fail2);
321
322	cv_destroy(&(smp->sm_kv));
323	mutex_destroy(&(smp->sm_lock));
324
325	sfxge_dma_buffer_destroy(esmp);
326
327	smp->sm_state = SFXGE_MCDI_UNINITIALIZED;
328	smp->sm_sp = NULL;
329	SFXGE_OBJ_CHECK(smp, sfxge_mcdi_t);
330
331fail1:
332	DTRACE_PROBE1(fail1, int, rc);
333
334	return (rc);
335}
336
337
338void
339sfxge_mcdi_fini(sfxge_t *sp)
340{
341	efx_nic_t *enp = sp->s_enp;
342	sfxge_mcdi_t *smp = &(sp->s_mcdi);
343	efsys_mem_t *esmp = &(smp->sm_mem);
344	efx_mcdi_transport_t *emtp;
345
346	mutex_enter(&(smp->sm_lock));
347	ASSERT3U(smp->sm_state, ==, SFXGE_MCDI_INITIALIZED);
348
349	efx_mcdi_fini(enp);
350	emtp = &(smp->sm_emt);
351	bzero(emtp, sizeof (*emtp));
352
353	smp->sm_sp = NULL;
354
355	cv_destroy(&(smp->sm_kv));
356	mutex_exit(&(smp->sm_lock));
357
358	sfxge_dma_buffer_destroy(esmp);
359
360	mutex_destroy(&(smp->sm_lock));
361
362	smp->sm_state = SFXGE_MCDI_UNINITIALIZED;
363	SFXGE_OBJ_CHECK(smp, sfxge_mcdi_t);
364}
365
366
367int
368sfxge_mcdi_ioctl(sfxge_t *sp, sfxge_mcdi_ioc_t *smip)
369{
370	const efx_nic_cfg_t *encp = efx_nic_cfg_get(sp->s_enp);
371	sfxge_mcdi_t *smp = &(sp->s_mcdi);
372	efx_mcdi_req_t emr;
373	uint8_t *out;
374	int rc;
375
376	if (smp->sm_state == SFXGE_MCDI_UNINITIALIZED) {
377		rc = ENODEV;
378		goto fail1;
379	}
380
381	if (!(encp->enc_features & EFX_FEATURE_MCDI)) {
382		rc = ENOTSUP;
383		goto fail2;
384	}
385
386	out = kmem_zalloc(sizeof (smip->smi_payload), KM_NOSLEEP);
387	if (out == NULL) {
388		rc = ENOMEM;
389		goto fail3;
390	}
391
392	emr.emr_cmd = smip->smi_cmd;
393	emr.emr_in_buf = smip->smi_payload;
394	emr.emr_in_length = smip->smi_len;
395
396	emr.emr_out_buf = out;
397	emr.emr_out_length = sizeof (smip->smi_payload);
398
399	sfxge_mcdi_execute(sp, &emr);
400
401	smip->smi_rc = (uint8_t)emr.emr_rc;
402	smip->smi_cmd = (uint8_t)emr.emr_cmd;
403	smip->smi_len = (uint8_t)emr.emr_out_length_used;
404	bcopy(out, smip->smi_payload, smip->smi_len);
405
406	/*
407	 * Helpfully trigger a device reset in response to an MCDI_CMD_REBOOT
408	 * Both ports will see ->emt_exception callbacks on the next MCDI poll
409	 */
410	if (smip->smi_cmd == MC_CMD_REBOOT) {
411
412		DTRACE_PROBE(mcdi_ioctl_mc_reboot);
413		/* sfxge_t->s_state_lock held */
414		(void) sfxge_restart_dispatch(sp, DDI_SLEEP, SFXGE_HW_OK,
415		    "MC_REBOOT triggering restart", 0);
416	}
417
418	kmem_free(out, sizeof (smip->smi_payload));
419
420	return (0);
421
422fail3:
423	DTRACE_PROBE(fail3);
424fail2:
425	DTRACE_PROBE(fail2);
426fail1:
427	DTRACE_PROBE1(fail1, int, rc);
428	return (rc);
429}
430
431int
432sfxge_mcdi2_ioctl(sfxge_t *sp, sfxge_mcdi2_ioc_t *smip)
433{
434	const efx_nic_cfg_t *encp = efx_nic_cfg_get(sp->s_enp);
435	sfxge_mcdi_t *smp = &(sp->s_mcdi);
436	efx_mcdi_req_t emr;
437	uint8_t *out;
438	int rc;
439
440	if (smp->sm_state == SFXGE_MCDI_UNINITIALIZED) {
441		rc = ENODEV;
442		goto fail1;
443	}
444
445	if (!(encp->enc_features & EFX_FEATURE_MCDI)) {
446		rc = ENOTSUP;
447		goto fail2;
448	}
449
450	out = kmem_zalloc(sizeof (smip->smi_payload), KM_NOSLEEP);
451	if (out == NULL) {
452		rc = ENOMEM;
453		goto fail3;
454	}
455
456	emr.emr_cmd = smip->smi_cmd;
457	emr.emr_in_buf = smip->smi_payload;
458	emr.emr_in_length = smip->smi_len;
459
460	emr.emr_out_buf = out;
461	emr.emr_out_length = sizeof (smip->smi_payload);
462
463	sfxge_mcdi_execute(sp, &emr);
464
465	smip->smi_rc = emr.emr_rc;
466	smip->smi_cmd = emr.emr_cmd;
467	smip->smi_len = (uint32_t)emr.emr_out_length_used;
468	bcopy(out, smip->smi_payload, smip->smi_len);
469
470	/*
471	 * Helpfully trigger a device reset in response to an MCDI_CMD_REBOOT
472	 * Both ports will see ->emt_exception callbacks on the next MCDI poll
473	 */
474	if (smip->smi_cmd == MC_CMD_REBOOT) {
475
476		DTRACE_PROBE(mcdi_ioctl_mc_reboot);
477		/* sfxge_t->s_state_lock held */
478		(void) sfxge_restart_dispatch(sp, DDI_SLEEP, SFXGE_HW_OK,
479		    "MC_REBOOT triggering restart", 0);
480	}
481
482	kmem_free(out, sizeof (smip->smi_payload));
483
484	return (0);
485
486fail3:
487	DTRACE_PROBE(fail3);
488fail2:
489	DTRACE_PROBE(fail2);
490fail1:
491	DTRACE_PROBE1(fail1, int, rc);
492	return (rc);
493}
494