b7d7ad0b90
A lot of these dinky atheros based MIPS boards don't have a nice, well,
anything consistent defining their MAC addresses for things.
The Atheros reference design boards will happily put MAC addresses
into the wifi module calibration data like they should, and individual
ethernet MAC addresses into the calibration area in flash.
That makes my life easy - "hint.arge.X.eeprommac=<addr>" reads from
that flash address to extract a MAC, and everything works fine.
However, aside from some very well behaved vendors (eg the Carambola 2
board), everyone else does something odd.
eg:
* a MAC address in the environment (eg ubiquiti routerstation/RSPRO)
that you derive arge0/arge1 MAC addresses from.
* a MAC address in flash that you derive arge0/arge1 MAC addresses from.
* The wifi devices having their own MAC addresses in calibration data,
like normal.
* The wifi devices having a fixed, default or garbage value for a MAC
address in calibration data, and it has to be derived from the
system MAC.
So to support this complete nonsense of a situation, there needs to be
a few hacks:
* The "board" MAC address needs to be derived from somewhere and squirreled
away. For now it's either redboot or a MAC address stored in calibration
flash.
* Then, a "map" set of hints to populate kenv with some MAC addresses
that are derived/local, based on the board address. Each board has
a totally different idea of what you do to derive things, so each
map entry has an "offset" (+ve or -ve) that's added to the board
MAC address.
* Then if_arge (and later, if_ath) should check kenv for said hint and
if it's found, use that rather than the EEPROM MAC address - which may
be totally garbage and not actually work right.
In order to do this, I've undone some of the custom redboot expecting
hacks in if_arge and the stuff that magically adds one to the MAC
address supplied by the board - instead, as I continue to test this
out on more hardware, I'll update the hints file with a map explaining
(a) where the board MAC should come from, and (b) what offsets to use
for each device.
The aim is to have all of the tplink, dlink and other random hardware
we run on have valid MAC addresses at boot, so (a) people don't get
random B:S:D❌x:x ethernet MACs, and (b) the wifi MAC is valid
so it works rather than trying to use an invalid address that
actually upsets systems (think: multicast bit set in BSSID.)
Tested:
* TP-Link TL_WDR3600 - subsequent commits will add the hints map
and the if_ath support.
TODO:
* Since this is -HEAD, and I'm all for debugging, there's a lot of
printf()s in here. They'll eventually go under bootverbose.
* I'd like to turn the macaddr routines into something available
to all drivers - too many places hand-roll random MAC addresses
and parser stuff. I'd rather it just be shared code.
However, that'll require more formal review.
* More boards.
481 lines
12 KiB
C
481 lines
12 KiB
C
/*-
|
|
* Copyright (c) 2009 Oleksandr Tymoshenko
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
* SUCH DAMAGE.
|
|
*/
|
|
|
|
#include <sys/cdefs.h>
|
|
__FBSDID("$FreeBSD$");
|
|
|
|
#include "opt_ddb.h"
|
|
#include "opt_ar71xx.h"
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/conf.h>
|
|
#include <sys/kernel.h>
|
|
#include <sys/systm.h>
|
|
#include <sys/bus.h>
|
|
#include <sys/cons.h>
|
|
#include <sys/kdb.h>
|
|
#include <sys/reboot.h>
|
|
|
|
#include <vm/vm.h>
|
|
#include <vm/vm_page.h>
|
|
|
|
#include <net/ethernet.h>
|
|
|
|
#include <machine/clock.h>
|
|
#include <machine/cpu.h>
|
|
#include <machine/cpuregs.h>
|
|
#include <machine/hwfunc.h>
|
|
#include <machine/md_var.h>
|
|
#include <machine/trap.h>
|
|
#include <machine/vmparam.h>
|
|
|
|
#include <mips/atheros/ar71xxreg.h>
|
|
|
|
#include <mips/atheros/ar71xx_setup.h>
|
|
#include <mips/atheros/ar71xx_cpudef.h>
|
|
#include <mips/atheros/ar71xx_macaddr.h>
|
|
|
|
#include <mips/sentry5/s5reg.h>
|
|
|
|
extern char edata[], end[];
|
|
|
|
/* 4KB static data aread to keep a copy of the bootload env until
|
|
the dynamic kenv is setup */
|
|
char boot1_env[4096];
|
|
|
|
/*
|
|
* We get a string in from Redboot with the all the arguments together,
|
|
* "foo=bar bar=baz". Split them up and save in kenv.
|
|
*/
|
|
static void
|
|
parse_argv(char *str)
|
|
{
|
|
char *n, *v;
|
|
|
|
while ((v = strsep(&str, " ")) != NULL) {
|
|
if (*v == '\0')
|
|
continue;
|
|
if (*v == '-') {
|
|
while (*v != '\0') {
|
|
v++;
|
|
switch (*v) {
|
|
case 'a': boothowto |= RB_ASKNAME; break;
|
|
case 'd': boothowto |= RB_KDB; break;
|
|
case 'g': boothowto |= RB_GDB; break;
|
|
case 's': boothowto |= RB_SINGLE; break;
|
|
case 'v': boothowto |= RB_VERBOSE; break;
|
|
}
|
|
}
|
|
} else {
|
|
n = strsep(&v, "=");
|
|
if (v == NULL)
|
|
kern_setenv(n, "1");
|
|
else
|
|
kern_setenv(n, v);
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
platform_cpu_init()
|
|
{
|
|
/* Nothing special */
|
|
}
|
|
|
|
void
|
|
platform_reset(void)
|
|
{
|
|
ar71xx_device_stop(RST_RESET_FULL_CHIP);
|
|
/* Wait for reset */
|
|
while(1)
|
|
;
|
|
}
|
|
|
|
/*
|
|
* Obtain the MAC address via the Redboot environment.
|
|
*/
|
|
static int
|
|
ar71xx_redboot_get_macaddr(void)
|
|
{
|
|
char *var;
|
|
int count = 0, i;
|
|
uint32_t macaddr[ETHER_ADDR_LEN];
|
|
uint8_t tmpmac[ETHER_ADDR_LEN];
|
|
|
|
/*
|
|
* "ethaddr" is passed via envp on RedBoot platforms
|
|
* "kmac" is passed via argv on RouterBOOT platforms
|
|
*/
|
|
if ((var = kern_getenv("ethaddr")) != NULL ||
|
|
(var = kern_getenv("kmac")) != NULL) {
|
|
count = sscanf(var, "%x%*c%x%*c%x%*c%x%*c%x%*c%x",
|
|
&macaddr[0], &macaddr[1],
|
|
&macaddr[2], &macaddr[3],
|
|
&macaddr[4], &macaddr[5]);
|
|
|
|
if (count < 6) {
|
|
memset(macaddr, 0,
|
|
sizeof(macaddr));
|
|
} else {
|
|
for (i = 0; i < ETHER_ADDR_LEN; i++)
|
|
tmpmac[i] = macaddr[i] & 0xff;
|
|
(void) ar71xx_mac_addr_init(ar71xx_board_mac_addr,
|
|
tmpmac,
|
|
0, /* offset */
|
|
0); /* is_local */
|
|
}
|
|
freeenv(var);
|
|
return (0);
|
|
}
|
|
return (-1);
|
|
}
|
|
|
|
#ifdef AR71XX_ENV_ROUTERBOOT
|
|
/*
|
|
* RouterBoot gives us the board memory in a command line argument.
|
|
*/
|
|
static int
|
|
ar71xx_routerboot_get_mem(int argc, char **argv)
|
|
{
|
|
int i, board_mem;
|
|
|
|
/*
|
|
* Protect ourselves from garbage in registers.
|
|
*/
|
|
if (!MIPS_IS_VALID_PTR(argv))
|
|
return (0);
|
|
|
|
for (i = 0; i < argc; i++) {
|
|
if (argv[i] == NULL)
|
|
continue;
|
|
if (strncmp(argv[i], "mem=", 4) == 0) {
|
|
if (sscanf(argv[i] + 4, "%dM", &board_mem) == 1)
|
|
return (btoc(board_mem * 1024 * 1024));
|
|
}
|
|
}
|
|
|
|
return (0);
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* Handle initialising the MAC address from a specific EEPROM
|
|
* offset.
|
|
*
|
|
* This is done during (very) early boot.
|
|
*
|
|
* hint.ar71xx.0.eeprom_mac_addr=<address to read from>
|
|
* hint.ar71xx.0.eeprom_mac_isascii=<0|1>
|
|
*/
|
|
static int
|
|
ar71xx_platform_read_eeprom_mac(void)
|
|
{
|
|
long eeprom_mac_addr = 0;
|
|
const char *mac;
|
|
int i, readascii = 0;
|
|
uint8_t macaddr[ETHER_ADDR_LEN];
|
|
|
|
if (resource_long_value("ar71xx", 0, "eeprom_mac_addr",
|
|
&eeprom_mac_addr) != 0)
|
|
return (-1);
|
|
|
|
/* get a pointer to the EEPROM MAC address */
|
|
|
|
mac = (const char *) MIPS_PHYS_TO_KSEG1(eeprom_mac_addr);
|
|
|
|
/* Check if it's ASCII or not */
|
|
if (resource_int_value("ar71xx", 0, "eeprom_mac_isascii",
|
|
&readascii) == 0 && readascii == 1) {
|
|
printf("ar71xx: Overriding MAC from EEPROM (ascii)\n");
|
|
for (i = 0; i < 6; i++) {
|
|
macaddr[i] = strtol(&(mac[i*3]), NULL, 16);
|
|
}
|
|
} else {
|
|
printf("ar71xx: Overriding MAC from EEPROM\n");
|
|
for (i = 0; i < 6; i++) {
|
|
macaddr[i] = mac[i];
|
|
}
|
|
}
|
|
|
|
/* Set the default board MAC */
|
|
(void) ar71xx_mac_addr_init(ar71xx_board_mac_addr,
|
|
macaddr,
|
|
0, /* offset */
|
|
0); /* is_local */
|
|
printf("ar71xx: Board MAC: %6D\n", ar71xx_board_mac_addr, ":");
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* Populate a kenv hint for the given device based on the given
|
|
* MAC address and offset.
|
|
*
|
|
* Returns 0 if ok, < 0 on error.
|
|
*/
|
|
static int
|
|
ar71xx_platform_set_mac_hint(const char *dev, int unit,
|
|
const uint8_t *macaddr, int offset, int islocal)
|
|
{
|
|
char macstr[32];
|
|
uint8_t lclmac[ETHER_ADDR_LEN];
|
|
char devstr[32];
|
|
|
|
/* Initialise the MAC address, plus/minus the offset */
|
|
if (ar71xx_mac_addr_init(lclmac, macaddr, offset, islocal) != 0) {
|
|
return (-1);
|
|
}
|
|
|
|
/* Turn it into a string */
|
|
snprintf(macstr, 32, "%6D", lclmac, ":");
|
|
snprintf(devstr, 32, "hint.%s.%d.macaddr", dev, unit);
|
|
|
|
printf(" %s => %s\n", devstr, macstr);
|
|
|
|
/* Call setenv */
|
|
if (kern_setenv(devstr, macstr) != 0) {
|
|
printf("%s: failed to set hint (%s => %s)\n",
|
|
__func__,
|
|
devstr,
|
|
macstr);
|
|
return (-1);
|
|
}
|
|
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* Iterate through the list of boot time hints that populate
|
|
* a device MAC address hint based on the "board" MAC address.
|
|
*
|
|
* ar71xx_mac_map.X.devid=<device id, eg ath>
|
|
* ar71xx_mac_map.X.unitid=<unit id, eg 0>
|
|
* ar71xx_mac_map.X.offset=<mac address value offset>
|
|
* ar71xx_mac_map.X.is_local=<1 or 0>
|
|
*/
|
|
static int
|
|
ar71xx_platform_check_mac_hints(void)
|
|
{
|
|
int i;
|
|
const char *devid;
|
|
int offset, is_local, unitid;
|
|
|
|
for (i = 0; i < 8; i++) {
|
|
if (resource_string_value("ar71xx_mac_map", i, "devid",
|
|
&devid) != 0)
|
|
break;
|
|
if (resource_int_value("ar71xx_mac_map", i, "unitid",
|
|
&unitid) != 0)
|
|
break;
|
|
if (resource_int_value("ar71xx_mac_map", i, "offset",
|
|
&offset) != 0)
|
|
break;
|
|
if (resource_int_value("ar71xx_mac_map", i, "is_local",
|
|
&is_local) != 0)
|
|
break;
|
|
printf("ar71xx: devid '%s.%d', MAC offset '%d'\n",
|
|
devid, unitid, offset);
|
|
(void) ar71xx_platform_set_mac_hint(devid, unitid,
|
|
ar71xx_board_mac_addr, offset, is_local);
|
|
}
|
|
|
|
return (0);
|
|
}
|
|
|
|
void
|
|
platform_start(__register_t a0 __unused, __register_t a1 __unused,
|
|
__register_t a2 __unused, __register_t a3 __unused)
|
|
{
|
|
uint64_t platform_counter_freq;
|
|
int argc = 0, i;
|
|
char **argv = NULL, **envp = NULL;
|
|
vm_offset_t kernend;
|
|
|
|
/*
|
|
* clear the BSS and SBSS segments, this should be first call in
|
|
* the function
|
|
*/
|
|
kernend = (vm_offset_t)&end;
|
|
memset(&edata, 0, kernend - (vm_offset_t)(&edata));
|
|
|
|
mips_postboot_fixup();
|
|
|
|
/* Initialize pcpu stuff */
|
|
mips_pcpu0_init();
|
|
|
|
/*
|
|
* Until some more sensible abstractions for uboot/redboot
|
|
* environment handling, we have to make this a compile-time
|
|
* hack. The existing code handles the uboot environment
|
|
* very incorrectly so we should just ignore initialising
|
|
* the relevant pointers.
|
|
*/
|
|
#ifndef AR71XX_ENV_UBOOT
|
|
argc = a0;
|
|
argv = (char**)a1;
|
|
envp = (char**)a2;
|
|
#endif
|
|
/*
|
|
* Protect ourselves from garbage in registers
|
|
*/
|
|
if (MIPS_IS_VALID_PTR(envp)) {
|
|
for (i = 0; envp[i]; i += 2) {
|
|
if (strcmp(envp[i], "memsize") == 0)
|
|
realmem = btoc(strtoul(envp[i+1], NULL, 16));
|
|
else if (strcmp(envp[i], "bootverbose") == 0)
|
|
bootverbose = btoc(strtoul(envp[i+1], NULL, 10));
|
|
}
|
|
}
|
|
bootverbose = 1;
|
|
|
|
#ifdef AR71XX_ENV_ROUTERBOOT
|
|
/*
|
|
* RouterBoot informs the board memory as a command line argument.
|
|
*/
|
|
if (realmem == 0)
|
|
realmem = ar71xx_routerboot_get_mem(argc, argv);
|
|
#endif
|
|
|
|
/*
|
|
* Just wild guess. RedBoot let us down and didn't reported
|
|
* memory size
|
|
*/
|
|
if (realmem == 0)
|
|
realmem = btoc(32*1024*1024);
|
|
|
|
/*
|
|
* Allow build-time override in case Redboot lies
|
|
* or in other situations (eg where there's u-boot)
|
|
* where there isn't (yet) a convienent method of
|
|
* being told how much RAM is available.
|
|
*
|
|
* This happens on at least the Ubiquiti LS-SR71A
|
|
* board, where redboot says there's 16mb of RAM
|
|
* but in fact there's 32mb.
|
|
*/
|
|
#if defined(AR71XX_REALMEM)
|
|
realmem = btoc(AR71XX_REALMEM);
|
|
#endif
|
|
|
|
/* phys_avail regions are in bytes */
|
|
phys_avail[0] = MIPS_KSEG0_TO_PHYS(kernel_kseg0_end);
|
|
phys_avail[1] = ctob(realmem);
|
|
|
|
dump_avail[0] = phys_avail[0];
|
|
dump_avail[1] = phys_avail[1] - phys_avail[0];
|
|
|
|
physmem = realmem;
|
|
|
|
/*
|
|
* ns8250 uart code uses DELAY so ticker should be inititalized
|
|
* before cninit. And tick_init_params refers to hz, so * init_param1
|
|
* should be called first.
|
|
*/
|
|
init_param1();
|
|
|
|
/* Detect the system type - this is needed for subsequent chipset-specific calls */
|
|
ar71xx_detect_sys_type();
|
|
ar71xx_detect_sys_frequency();
|
|
|
|
platform_counter_freq = ar71xx_cpu_freq();
|
|
mips_timer_init_params(platform_counter_freq, 1);
|
|
cninit();
|
|
init_static_kenv(boot1_env, sizeof(boot1_env));
|
|
|
|
printf("CPU platform: %s\n", ar71xx_get_system_type());
|
|
printf("CPU Frequency=%d MHz\n", u_ar71xx_cpu_freq / 1000000);
|
|
printf("CPU DDR Frequency=%d MHz\n", u_ar71xx_ddr_freq / 1000000);
|
|
printf("CPU AHB Frequency=%d MHz\n", u_ar71xx_ahb_freq / 1000000);
|
|
printf("platform frequency: %lld MHz\n", platform_counter_freq / 1000000);
|
|
printf("CPU reference clock: %d MHz\n", u_ar71xx_refclk / 1000000);
|
|
printf("CPU MDIO clock: %d MHz\n", u_ar71xx_mdio_freq / 1000000);
|
|
printf("arguments: \n");
|
|
printf(" a0 = %08x\n", a0);
|
|
printf(" a1 = %08x\n", a1);
|
|
printf(" a2 = %08x\n", a2);
|
|
printf(" a3 = %08x\n", a3);
|
|
|
|
/*
|
|
* XXX this code is very redboot specific.
|
|
*/
|
|
printf("Cmd line:");
|
|
if (MIPS_IS_VALID_PTR(argv)) {
|
|
for (i = 0; i < argc; i++) {
|
|
printf(" %s", argv[i]);
|
|
parse_argv(argv[i]);
|
|
}
|
|
}
|
|
else
|
|
printf ("argv is invalid");
|
|
printf("\n");
|
|
|
|
printf("Environment:\n");
|
|
if (MIPS_IS_VALID_PTR(envp)) {
|
|
for (i = 0; envp[i]; i+=2) {
|
|
printf(" %s = %s\n", envp[i], envp[i+1]);
|
|
kern_setenv(envp[i], envp[i+1]);
|
|
}
|
|
}
|
|
else
|
|
printf ("envp is invalid\n");
|
|
|
|
/* Platform setup */
|
|
init_param2(physmem);
|
|
mips_cpu_init();
|
|
pmap_bootstrap();
|
|
mips_proc0_init();
|
|
mutex_init();
|
|
|
|
/*
|
|
* Reset USB devices
|
|
*/
|
|
ar71xx_init_usb_peripheral();
|
|
|
|
/*
|
|
* Reset internal ethernet switch, if one exists
|
|
*/
|
|
ar71xx_reset_ethernet_switch();
|
|
|
|
/*
|
|
* Initialise the gmac driver.
|
|
*/
|
|
ar71xx_init_gmac();
|
|
|
|
/* Redboot if_arge MAC address is in the environment */
|
|
(void) ar71xx_redboot_get_macaddr();
|
|
|
|
/* Various other boards need things to come out of EEPROM */
|
|
(void) ar71xx_platform_read_eeprom_mac();
|
|
|
|
/* Initialise the MAC address hint map */
|
|
ar71xx_platform_check_mac_hints();
|
|
|
|
kdb_init();
|
|
#ifdef KDB
|
|
if (boothowto & RB_KDB)
|
|
kdb_enter(KDB_WHY_BOOTFLAGS, "Boot flags requested debugger");
|
|
#endif
|
|
}
|