1 /*-
2 * SPDX-License-Identifier: BSD-2-Clause
3 *
4 * Copyright (c) 2015-2016 Landon Fuller <[email protected]>
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 *
bcma_alloc_corecfg(u_int core_index,int core_unit,uint16_t vendor,uint16_t device,uint8_t hwrev)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
bcma_free_corecfg(struct bcma_corecfg * corecfg)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 *
bcma_corecfg_get_port_list(struct bcma_corecfg * cfg,bhnd_port_type type)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
bcma_dinfo_init_port_resource_info(device_t bus,struct bcma_devinfo * dinfo,struct bcma_sport_list * ports)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
bcma_dinfo_init_agent(device_t bus,device_t child,struct bcma_devinfo * dinfo)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
bcma_dinfo_init_intrs(device_t bus,device_t child,struct bcma_devinfo * dinfo)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 *
bcma_alloc_dinfo(device_t bus)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
bcma_init_dinfo(device_t bus,device_t child,struct bcma_devinfo * dinfo,struct bcma_corecfg * corecfg)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
bcma_free_dinfo(device_t bus,device_t child,struct bcma_devinfo * dinfo)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 *
bcma_alloc_intr(uint8_t bank,uint8_t sel,uint8_t line)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
bcma_free_intr(struct bcma_intr * intr)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 *
bcma_alloc_sport(bcma_pid_t port_num,bhnd_port_type port_type)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
bcma_free_sport(struct bcma_sport * sport)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
bcma_dmp_wait_reset(device_t child,struct bcma_devinfo * dinfo)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
bcma_dmp_write_reset(device_t child,struct bcma_devinfo * dinfo,uint32_t value)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