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 *
bcma_alloc_corecfg(u_int core_index,int core_unit,uint16_t vendor,uint16_t device,uint8_t hwrev)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
bcma_free_corecfg(struct bcma_corecfg * corecfg)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 *
bcma_corecfg_get_port_list(struct bcma_corecfg * cfg,bhnd_port_type type)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
bcma_dinfo_init_port_resource_info(device_t bus,struct bcma_devinfo * dinfo,struct bcma_sport_list * ports)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
bcma_dinfo_init_agent(device_t bus,device_t child,struct bcma_devinfo * dinfo)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
bcma_dinfo_init_intrs(device_t bus,device_t child,struct bcma_devinfo * dinfo)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 *
bcma_alloc_dinfo(device_t bus)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
bcma_init_dinfo(device_t bus,device_t child,struct bcma_devinfo * dinfo,struct bcma_corecfg * corecfg)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
bcma_free_dinfo(device_t bus,device_t child,struct bcma_devinfo * dinfo)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 *
bcma_alloc_intr(uint8_t bank,uint8_t sel,uint8_t line)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
bcma_free_intr(struct bcma_intr * intr)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 *
bcma_alloc_sport(bcma_pid_t port_num,bhnd_port_type port_type)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
bcma_free_sport(struct bcma_sport * sport)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
bcma_dmp_wait_reset(device_t child,struct bcma_devinfo * dinfo)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
bcma_dmp_write_reset(device_t child,struct bcma_devinfo * dinfo,uint32_t value)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