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