xref: /freebsd/sys/dev/bhnd/bcma/bcma_subr.c (revision 315ee00f)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
3  *
4  * Copyright (c) 2015-2016 Landon Fuller <landon@landonf.org>
5  * Copyright (c) 2017 The FreeBSD Foundation
6  * All rights reserved.
7  *
8  * Portions of this software were developed by Landon Fuller
9  * under sponsorship from the FreeBSD Foundation.
10  *
11  * Redistribution and use in source and binary forms, with or without
12  * modification, are permitted provided that the following conditions
13  * are met:
14  * 1. Redistributions of source code must retain the above copyright
15  *    notice, this list of conditions and the following disclaimer,
16  *    without modification.
17  * 2. Redistributions in binary form must reproduce at minimum a disclaimer
18  *    similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any
19  *    redistribution must be conditioned upon including a substantially
20  *    similar Disclaimer requirement for further binary redistribution.
21  *
22  * NO WARRANTY
23  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
24  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
25  * LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY
26  * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
27  * THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY,
28  * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
29  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
30  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
31  * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
32  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
33  * THE POSSIBILITY OF SUCH DAMAGES.
34  */
35 
36 #include <sys/cdefs.h>
37 #include <sys/param.h>
38 #include <sys/bus.h>
39 #include <sys/kernel.h>
40 #include <sys/limits.h>
41 #include <sys/systm.h>
42 
43 #include <machine/bus.h>
44 #include <machine/resource.h>
45 
46 #include <dev/bhnd/bhndvar.h>
47 
48 #include "bcma_dmp.h"
49 
50 #include "bcmavar.h"
51 
52 /* Return the resource ID for a device's agent register allocation */
53 #define	BCMA_AGENT_RID(_dinfo)	\
54     (BCMA_AGENT_RID_BASE + BCMA_DINFO_COREIDX(_dinfo))
55 
56  /**
57  * Allocate and initialize new core config structure.
58  *
59  * @param core_index Core index on the bus.
60  * @param core_unit Core unit number.
61  * @param vendor Core designer.
62  * @param device Core identifier (e.g. part number).
63  * @param hwrev Core revision.
64  */
65 struct bcma_corecfg *
66 bcma_alloc_corecfg(u_int core_index, int core_unit, uint16_t vendor,
67     uint16_t device, uint8_t hwrev)
68 {
69 	struct bcma_corecfg *cfg;
70 
71 	cfg = malloc(sizeof(*cfg), M_BHND, M_NOWAIT);
72 	if (cfg == NULL)
73 		return NULL;
74 
75 	cfg->core_info = (struct bhnd_core_info) {
76 		.vendor = vendor,
77 		.device = device,
78 		.hwrev = hwrev,
79 		.core_idx = core_index,
80 		.unit = core_unit
81 	};
82 
83 	STAILQ_INIT(&cfg->master_ports);
84 	cfg->num_master_ports = 0;
85 
86 	STAILQ_INIT(&cfg->dev_ports);
87 	cfg->num_dev_ports = 0;
88 
89 	STAILQ_INIT(&cfg->bridge_ports);
90 	cfg->num_bridge_ports = 0;
91 
92 	STAILQ_INIT(&cfg->wrapper_ports);
93 	cfg->num_wrapper_ports = 0;
94 
95 	return (cfg);
96 }
97 
98 /**
99  * Deallocate the given core config and any associated resources.
100  *
101  * @param corecfg Core info to be deallocated.
102  */
103 void
104 bcma_free_corecfg(struct bcma_corecfg *corecfg)
105 {
106 	struct bcma_mport *mport, *mnext;
107 	struct bcma_sport *sport, *snext;
108 
109 	STAILQ_FOREACH_SAFE(mport, &corecfg->master_ports, mp_link, mnext) {
110 		free(mport, M_BHND);
111 	}
112 
113 	STAILQ_FOREACH_SAFE(sport, &corecfg->dev_ports, sp_link, snext) {
114 		bcma_free_sport(sport);
115 	}
116 
117 	STAILQ_FOREACH_SAFE(sport, &corecfg->bridge_ports, sp_link, snext) {
118 		bcma_free_sport(sport);
119 	}
120 
121 	STAILQ_FOREACH_SAFE(sport, &corecfg->wrapper_ports, sp_link, snext) {
122 		bcma_free_sport(sport);
123 	}
124 
125 	free(corecfg, M_BHND);
126 }
127 
128 /**
129  * Return the @p cfg port list for @p type.
130  *
131  * @param cfg The core configuration.
132  * @param type The requested port type.
133  */
134 struct bcma_sport_list *
135 bcma_corecfg_get_port_list(struct bcma_corecfg *cfg, bhnd_port_type type)
136 {
137 	switch (type) {
138 	case BHND_PORT_DEVICE:
139 		return (&cfg->dev_ports);
140 		break;
141 	case BHND_PORT_BRIDGE:
142 		return (&cfg->bridge_ports);
143 		break;
144 	case BHND_PORT_AGENT:
145 		return (&cfg->wrapper_ports);
146 		break;
147 	default:
148 		return (NULL);
149 	}
150 }
151 
152 /**
153  * Populate the resource list and bcma_map RIDs using the maps defined on
154  * @p ports.
155  *
156  * @param bus The requesting bus device.
157  * @param dinfo The device info instance to be initialized.
158  * @param ports The set of ports to be enumerated
159  */
160 static void
161 bcma_dinfo_init_port_resource_info(device_t bus, struct bcma_devinfo *dinfo,
162     struct bcma_sport_list *ports)
163 {
164 	struct bcma_map		*map;
165 	struct bcma_sport	*port;
166 	bhnd_addr_t		 end;
167 
168 	STAILQ_FOREACH(port, ports, sp_link) {
169 		STAILQ_FOREACH(map, &port->sp_maps, m_link) {
170 			/*
171 			 * Create the corresponding device resource list entry.
172 			 *
173 			 * We necessarily skip registration if the region's
174 			 * device memory range is not representable via
175 			 * rman_res_t.
176 			 *
177 			 * When rman_res_t is migrated to uintmax_t, any
178 			 * range should be representable.
179 			 */
180 			end = map->m_base + map->m_size;
181 			if (map->m_base <= RM_MAX_END && end <= RM_MAX_END) {
182 				map->m_rid = resource_list_add_next(
183 				    &dinfo->resources, SYS_RES_MEMORY,
184 				    map->m_base, end, map->m_size);
185 			} else if (bootverbose) {
186 				device_printf(bus,
187 				    "core%u %s%u.%u: region %llx-%llx extends "
188 				        "beyond supported addressable range\n",
189 				    dinfo->corecfg->core_info.core_idx,
190 				    bhnd_port_type_name(port->sp_type),
191 				    port->sp_num, map->m_region_num,
192 				    (unsigned long long) map->m_base,
193 				    (unsigned long long) end);
194 			}
195 		}
196 	}
197 }
198 
199 /**
200  * Allocate the per-core agent register block for a device info structure.
201  *
202  * If an agent0.0 region is not defined on @p dinfo, the device info
203  * agent resource is set to NULL and 0 is returned.
204  *
205  * @param bus The requesting bus device.
206  * @param child The bcma child device.
207  * @param dinfo The device info associated with @p child
208  *
209  * @retval 0 success
210  * @retval non-zero resource allocation failed.
211  */
212 static int
213 bcma_dinfo_init_agent(device_t bus, device_t child, struct bcma_devinfo *dinfo)
214 {
215 	bhnd_addr_t	addr;
216 	bhnd_size_t	size;
217 	rman_res_t	r_start, r_count, r_end;
218 	int		error;
219 
220 	KASSERT(dinfo->res_agent == NULL, ("double allocation of agent"));
221 
222 	/* Verify that the agent register block exists and is
223 	 * mappable */
224 	if (bhnd_get_port_rid(child, BHND_PORT_AGENT, 0, 0) == -1)
225 		return (0);	/* nothing to do */
226 
227 	/* Fetch the address of the agent register block */
228 	error = bhnd_get_region_addr(child, BHND_PORT_AGENT, 0, 0,
229 	    &addr, &size);
230 	if (error) {
231 		device_printf(bus, "failed fetching agent register block "
232 		    "address for core %u\n", BCMA_DINFO_COREIDX(dinfo));
233 		return (error);
234 	}
235 
236 	/* Allocate the resource */
237 	r_start = addr;
238 	r_count = size;
239 	r_end = r_start + r_count - 1;
240 
241 	dinfo->rid_agent = BCMA_AGENT_RID(dinfo);
242 	dinfo->res_agent = BHND_BUS_ALLOC_RESOURCE(bus, bus, SYS_RES_MEMORY,
243 	    &dinfo->rid_agent, r_start, r_end, r_count, RF_ACTIVE|RF_SHAREABLE);
244 	if (dinfo->res_agent == NULL) {
245 		device_printf(bus, "failed allocating agent register block for "
246 		    "core %u\n", BCMA_DINFO_COREIDX(dinfo));
247 		return (ENXIO);
248 	}
249 
250 	return (0);
251 }
252 
253 /**
254  * Populate the list of interrupts for a device info structure
255  * previously initialized via bcma_dinfo_alloc_agent().
256  *
257  * If an agent0.0 region is not mapped on @p dinfo, the OOB interrupt bank is
258  * assumed to be unavailable and 0 is returned.
259  *
260  * @param bus The requesting bus device.
261  * @param dinfo The device info instance to be initialized.
262  */
263 static int
264 bcma_dinfo_init_intrs(device_t bus, device_t child,
265     struct bcma_devinfo *dinfo)
266 {
267 	uint32_t dmpcfg, oobw;
268 
269 	/* Agent block must be mapped */
270 	if (dinfo->res_agent == NULL)
271 		return (0);
272 
273 	/* Agent must support OOB */
274 	dmpcfg = bhnd_bus_read_4(dinfo->res_agent, BCMA_DMP_CONFIG);
275 	if (!BCMA_DMP_GET_FLAG(dmpcfg, BCMA_DMP_CFG_OOB))
276 		return (0);
277 
278 	/* Fetch width of the OOB interrupt bank */
279 	oobw = bhnd_bus_read_4(dinfo->res_agent,
280 	     BCMA_DMP_OOB_OUTWIDTH(BCMA_OOB_BANK_INTR));
281 	if (oobw >= BCMA_OOB_NUM_SEL) {
282 		device_printf(bus, "ignoring invalid OOBOUTWIDTH for core %u: "
283 		    "%#x\n", BCMA_DINFO_COREIDX(dinfo), oobw);
284 		return (0);
285 	}
286 
287 	/* Fetch OOBSEL busline values and populate list of interrupt
288 	 * descriptors */
289 	for (uint32_t sel = 0; sel < oobw; sel++) {
290 		struct bcma_intr	*intr;
291 		uint32_t		 selout;
292 		uint8_t			 line;
293 
294 		if (dinfo->num_intrs == UINT_MAX)
295 			return (ENOMEM);
296 
297 		selout = bhnd_bus_read_4(dinfo->res_agent, BCMA_DMP_OOBSELOUT(
298 		    BCMA_OOB_BANK_INTR, sel));
299 
300 		line = (selout >> BCMA_DMP_OOBSEL_SHIFT(sel)) &
301 		    BCMA_DMP_OOBSEL_BUSLINE_MASK;
302 
303 		intr = bcma_alloc_intr(BCMA_OOB_BANK_INTR, sel, line);
304 		if (intr == NULL) {
305 			device_printf(bus, "failed allocating interrupt "
306 			    "descriptor %#x for core %u\n", sel,
307 			    BCMA_DINFO_COREIDX(dinfo));
308 			return (ENOMEM);
309 		}
310 
311 		STAILQ_INSERT_HEAD(&dinfo->intrs, intr, i_link);
312 		dinfo->num_intrs++;
313 	}
314 
315 	return (0);
316 }
317 
318 /**
319  * Allocate and return a new empty device info structure.
320  *
321  * @param bus The requesting bus device.
322  *
323  * @retval NULL if allocation failed.
324  */
325 struct bcma_devinfo *
326 bcma_alloc_dinfo(device_t bus)
327 {
328 	struct bcma_devinfo *dinfo;
329 
330 	dinfo = malloc(sizeof(struct bcma_devinfo), M_BHND, M_NOWAIT|M_ZERO);
331 	if (dinfo == NULL)
332 		return (NULL);
333 
334 	dinfo->corecfg = NULL;
335 	dinfo->res_agent = NULL;
336 	dinfo->rid_agent = -1;
337 
338 	STAILQ_INIT(&dinfo->intrs);
339 	dinfo->num_intrs = 0;
340 
341 	resource_list_init(&dinfo->resources);
342 
343 	return (dinfo);
344 }
345 
346 /**
347  * Initialize a device info structure previously allocated via
348  * bcma_alloc_dinfo, assuming ownership of the provided core
349  * configuration.
350  *
351  * @param bus The requesting bus device.
352  * @param child The bcma child device.
353  * @param dinfo The device info associated with @p child
354  * @param corecfg Device core configuration; ownership of this value
355  * will be assumed by @p dinfo.
356  *
357  * @retval 0 success
358  * @retval non-zero initialization failed.
359  */
360 int
361 bcma_init_dinfo(device_t bus, device_t child, struct bcma_devinfo *dinfo,
362     struct bcma_corecfg *corecfg)
363 {
364 	struct bcma_intr	*intr;
365 	int			 error;
366 
367 	KASSERT(dinfo->corecfg == NULL, ("dinfo previously initialized"));
368 
369 	/* Save core configuration value */
370 	dinfo->corecfg = corecfg;
371 
372 	/* The device ports must always be initialized first to ensure that
373 	 * rid 0 maps to the first device port */
374 	bcma_dinfo_init_port_resource_info(bus, dinfo, &corecfg->dev_ports);
375 	bcma_dinfo_init_port_resource_info(bus, dinfo, &corecfg->bridge_ports);
376 	bcma_dinfo_init_port_resource_info(bus, dinfo, &corecfg->wrapper_ports);
377 
378 	/* Now that we've defined the port resources, we can map the device's
379 	 * agent registers (if any) */
380 	if ((error = bcma_dinfo_init_agent(bus, child, dinfo)))
381 		goto failed;
382 
383 	/* With agent registers mapped, we can populate the device's interrupt
384 	 * descriptors */
385 	if ((error = bcma_dinfo_init_intrs(bus, child, dinfo)))
386 		goto failed;
387 
388 	/* Finally, map the interrupt descriptors */
389 	STAILQ_FOREACH(intr, &dinfo->intrs, i_link) {
390 		/* Already mapped? */
391 		if (intr->i_mapped)
392 			continue;
393 
394 		/* Map the interrupt */
395 		error = BHND_BUS_MAP_INTR(bus, child, intr->i_sel,
396 		    &intr->i_irq);
397 		if (error) {
398 			device_printf(bus, "failed mapping interrupt line %u "
399 			    "for core %u: %d\n", intr->i_sel,
400 			    BCMA_DINFO_COREIDX(dinfo), error);
401 			goto failed;
402 		}
403 
404 		intr->i_mapped = true;
405 
406 		/* Add to resource list */
407 		intr->i_rid = resource_list_add_next(&dinfo->resources,
408 		    SYS_RES_IRQ, intr->i_irq, intr->i_irq, 1);
409 	}
410 
411 	return (0);
412 
413 failed:
414 	/* Owned by the caller on failure */
415 	dinfo->corecfg = NULL;
416 
417 	return (error);
418 }
419 
420 /**
421  * Deallocate the given device info structure and any associated resources.
422  *
423  * @param bus The requesting bus device.
424  * @param dinfo Device info to be deallocated.
425  */
426 void
427 bcma_free_dinfo(device_t bus, device_t child, struct bcma_devinfo *dinfo)
428 {
429 	struct bcma_intr *intr, *inext;
430 
431 	resource_list_free(&dinfo->resources);
432 
433 	if (dinfo->corecfg != NULL)
434 		bcma_free_corecfg(dinfo->corecfg);
435 
436 	/* Release agent resource, if any */
437 	if (dinfo->res_agent != NULL) {
438 		bhnd_release_resource(bus, SYS_RES_MEMORY, dinfo->rid_agent,
439 		    dinfo->res_agent);
440 	}
441 
442 	/* Clean up interrupt descriptors */
443 	STAILQ_FOREACH_SAFE(intr, &dinfo->intrs, i_link, inext) {
444 		STAILQ_REMOVE(&dinfo->intrs, intr, bcma_intr, i_link);
445 
446 		/* Release our IRQ mapping */
447 		if (intr->i_mapped) {
448 			BHND_BUS_UNMAP_INTR(bus, child, intr->i_irq);
449 			intr->i_mapped = false;
450 		}
451 
452 		bcma_free_intr(intr);
453 	}
454 
455 	free(dinfo, M_BHND);
456 }
457 
458 /**
459  * Allocate and initialize a new interrupt descriptor.
460  *
461  * @param bank OOB bank.
462  * @param sel OOB selector.
463  * @param line OOB bus line.
464  */
465 struct bcma_intr *
466 bcma_alloc_intr(uint8_t bank, uint8_t sel, uint8_t line)
467 {
468 	struct bcma_intr *intr;
469 
470 	if (bank >= BCMA_OOB_NUM_BANKS)
471 		return (NULL);
472 
473 	if (sel >= BCMA_OOB_NUM_SEL)
474 		return (NULL);
475 
476 	if (line >= BCMA_OOB_NUM_BUSLINES)
477 		return (NULL);
478 
479 	intr = malloc(sizeof(*intr), M_BHND, M_NOWAIT);
480 	if (intr == NULL)
481 		return (NULL);
482 
483 	intr->i_bank = bank;
484 	intr->i_sel = sel;
485 	intr->i_busline = line;
486 	intr->i_mapped = false;
487 	intr->i_irq = 0;
488 
489 	return (intr);
490 }
491 
492 /**
493  * Deallocate all resources associated with the given interrupt descriptor.
494  *
495  * @param intr Interrupt descriptor to be deallocated.
496  */
497 void
498 bcma_free_intr(struct bcma_intr *intr)
499 {
500 	KASSERT(!intr->i_mapped, ("interrupt %u still mapped", intr->i_sel));
501 
502 	free(intr, M_BHND);
503 }
504 
505 /**
506  * Allocate and initialize new slave port descriptor.
507  *
508  * @param port_num Per-core port number.
509  * @param port_type Port type.
510  */
511 struct bcma_sport *
512 bcma_alloc_sport(bcma_pid_t port_num, bhnd_port_type port_type)
513 {
514 	struct bcma_sport *sport;
515 
516 	sport = malloc(sizeof(struct bcma_sport), M_BHND, M_NOWAIT);
517 	if (sport == NULL)
518 		return NULL;
519 
520 	sport->sp_num = port_num;
521 	sport->sp_type = port_type;
522 	sport->sp_num_maps = 0;
523 	STAILQ_INIT(&sport->sp_maps);
524 
525 	return sport;
526 }
527 
528 /**
529  * Deallocate all resources associated with the given port descriptor.
530  *
531  * @param sport Port descriptor to be deallocated.
532  */
533 void
534 bcma_free_sport(struct bcma_sport *sport) {
535 	struct bcma_map *map, *mapnext;
536 
537 	STAILQ_FOREACH_SAFE(map, &sport->sp_maps, m_link, mapnext) {
538 		free(map, M_BHND);
539 	}
540 
541 	free(sport, M_BHND);
542 }
543 
544 /**
545  * Given a bcma(4) child's device info, spin waiting for the device's DMP
546  * resetstatus register to clear.
547  *
548  * @param child The bcma(4) child device.
549  * @param dinfo The @p child device info.
550  *
551  * @retval 0 success
552  * @retval ENODEV if @p dinfo does not map an agent register resource.
553  * @retval ETIMEDOUT if timeout occurs
554  */
555 int
556 bcma_dmp_wait_reset(device_t child, struct bcma_devinfo *dinfo)
557 {
558 	uint32_t rst;
559 
560 	if (dinfo->res_agent == NULL)
561 		return (ENODEV);
562 
563 	/* 300us should be long enough, but there are references to this
564 	 * requiring up to 10ms when performing reset of an 80211 core
565 	 * after a MAC PSM microcode watchdog event. */
566 	for (int i = 0; i < 10000; i += 10) {
567 		rst = bhnd_bus_read_4(dinfo->res_agent, BCMA_DMP_RESETSTATUS);
568 		if (rst == 0)
569 			return (0);
570 
571 		DELAY(10);
572 	}
573 
574 	device_printf(child, "BCMA_DMP_RESETSTATUS timeout\n");
575 	return (ETIMEDOUT);
576 }
577 
578 /**
579  * Set the bcma(4) child's DMP resetctrl register value, and then wait
580  * for all backplane operations to complete.
581  *
582  * @param child The bcma(4) child device.
583  * @param dinfo The @p child device info.
584  * @param value The new ioctrl value to set.
585  *
586  * @retval 0 success
587  * @retval ENODEV if @p dinfo does not map an agent register resource.
588  * @retval ETIMEDOUT if timeout occurs waiting for reset completion
589  */
590 int
591 bcma_dmp_write_reset(device_t child, struct bcma_devinfo *dinfo, uint32_t value)
592 {
593 	uint32_t rst;
594 
595 	if (dinfo->res_agent == NULL)
596 		return (ENODEV);
597 
598 	/* Already in requested reset state? */
599 	rst = bhnd_bus_read_4(dinfo->res_agent, BCMA_DMP_RESETCTRL);
600 	if (rst == value)
601 		return (0);
602 
603 	bhnd_bus_write_4(dinfo->res_agent, BCMA_DMP_RESETCTRL, value);
604 	bhnd_bus_read_4(dinfo->res_agent, BCMA_DMP_RESETCTRL); /* read-back */
605 	DELAY(10);
606 
607 	return (bcma_dmp_wait_reset(child, dinfo));
608 }
609