Page MenuHomeFreeBSD

arge_rxfilter(9) implemented for if_arge(4), some parts were externalized from arge_attach(9) and cleanup on some magic numbers.

Authored By
henning.matyschok_outlook.com
Jul 9 2018, 11:26 PM
Size
16 KB
Referenced Files
None
Subscribers
None

arge_rxfilter(9) implemented for if_arge(4), some parts were externalized from arge_attach(9) and cleanup on some magic numbers.

diff -u -r -N sys/mips/atheros/ar71xx_chip.c sys/mips/atheros/ar71xx_chip.c
--- sys/mips/atheros/ar71xx_chip.c 2018-07-07 20:50:12.824244000 +0200
+++ sys/mips/atheros/ar71xx_chip.c 2018-07-08 03:11:21.359088000 +0200
@@ -147,13 +147,15 @@
void
ar71xx_chip_set_mii_speed(uint32_t unit, uint32_t speed)
{
- uint32_t val, reg, ctrl;
+ uint32_t val;
+ uint32_t reg;
+ uint32_t ctrl;
switch (unit) {
- case 0:
+ case AR71XX_GE0:
reg = AR71XX_MII0_CTRL;
break;
- case 1:
+ case AR71XX_GE1:
reg = AR71XX_MII1_CTRL;
break;
default:
@@ -163,13 +165,13 @@
}
switch (speed) {
- case 10:
+ case AR71XX_GE_SPEED_10:
ctrl = MII_CTRL_SPEED_10;
break;
- case 100:
+ case AR71XX_GE_SPEED_100:
ctrl = MII_CTRL_SPEED_100;
break;
- case 1000:
+ case AR71XX_GE_SPEED_1000:
ctrl = MII_CTRL_SPEED_1000;
break;
default:
@@ -187,10 +189,12 @@
void
ar71xx_chip_set_mii_if(uint32_t unit, uint32_t mii_mode)
{
- uint32_t val, reg, mii_if;
+ uint32_t val;
+ uint32_t reg;
+ uint32_t mii_if;
switch (unit) {
- case 0:
+ case AR71XX_GE0:
reg = AR71XX_MII0_CTRL;
if (mii_mode == AR71XX_MII_MODE_GMII)
mii_if = MII0_CTRL_IF_GMII;
@@ -206,7 +210,7 @@
return;
}
break;
- case 1:
+ case AR71XX_GE1:
reg = AR71XX_MII1_CTRL;
if (mii_mode == AR71XX_MII_MODE_RGMII)
mii_if = MII1_CTRL_IF_RGMII;
@@ -236,12 +240,12 @@
{
switch (unit) {
- case 0:
+ case AR71XX_GE0:
ar71xx_write_pll(AR71XX_PLL_SEC_CONFIG,
AR71XX_PLL_ETH_INT0_CLK, pll,
AR71XX_PLL_ETH0_SHIFT);
break;
- case 1:
+ case AR71XX_GE1:
ar71xx_write_pll(AR71XX_PLL_SEC_CONFIG,
AR71XX_PLL_ETH_INT1_CLK, pll,
AR71XX_PLL_ETH1_SHIFT);
@@ -282,13 +286,13 @@
uint32_t pll;
switch (speed) {
- case 10:
+ case AR71XX_GE_SPEED_10:
pll = PLL_ETH_INT_CLK_10;
break;
- case 100:
+ case AR71XX_GE_SPEED_100:
pll = PLL_ETH_INT_CLK_100;
break;
- case 1000:
+ case AR71XX_GE_SPEED_1000:
pll = PLL_ETH_INT_CLK_1000;
break;
default:
diff -u -r -N sys/mips/atheros/ar71xx_macaddr.c sys/mips/atheros/ar71xx_macaddr.c
--- sys/mips/atheros/ar71xx_macaddr.c 2018-07-07 20:50:15.479230000 +0200
+++ sys/mips/atheros/ar71xx_macaddr.c 2018-07-09 23:49:15.371993000 +0200
@@ -35,6 +35,8 @@
#include <net/ethernet.h>
+#include <sys/bus.h>
+
#include <mips/atheros/ar71xx_macaddr.h>
/*
@@ -61,9 +63,10 @@
int t;
if (dst == NULL || src == NULL)
- return (-1);
+ return (EINVAL);
- /* XXX TODO: validate 'src' is a valid MAC address */
+ if (ETHER_IS_MULTICAST(src) != 0)
+ return (-1);
t = (((uint32_t) src[3]) << 16)
+ (((uint32_t) src[4]) << 8)
@@ -108,3 +111,110 @@
return (0);
}
+
+/*
+ * Initialize MAC Address by hints mechanism.
+ *
+ * Returns 1 if MAC Address exists, 0 if not.
+ */
+int
+ar71xx_mac_addr_hint_init(device_t dev, unsigned char *addr)
+{
+ char dev_id[32];
+ char * mac_addr;
+ uint32_t tmp_addr[ETHER_ADDR_LEN];
+ int count;
+ int i;
+ int local_mac;
+
+ (void)snprintf(dev_id, 32, "hint.%s.%d.macaddr",
+ device_get_name(dev), device_get_unit(dev));
+
+ if ((mac_addr = kern_getenv(dev_id)) != NULL) {
+ /* Have a MAC address; should use it. */
+ device_printf(dev, "Overriding MAC address "
+ "from environment: '%s'\n", mac_addr);
+
+ /* Extract out the MAC address. */
+ count = sscanf(mac_addr,
+ "%x%*c%x%*c%x%*c%x%*c%x%*c%x",
+ &tmp_addr[0], &tmp_addr[1],
+ &tmp_addr[2], &tmp_addr[3],
+ &tmp_addr[4], &tmp_addr[5]);
+
+ /* Valid! */
+ if (count == ETHER_ADDR_LEN) {
+ for (i = 0; i < count; i++)
+ addr[i] = tmp_addr[i];
+
+ local_mac = 1;
+ } else
+ local_mac = 0;
+
+ /* Done! */
+ freeenv(mac_addr);
+ mac_addr = NULL;
+ } else
+ local_mac = 0;
+
+ return (local_mac);
+}
+
+/*
+ * Initialize MAC Address by reading EEPROM.
+ *
+ * Some units (eg the TP-Link WR-1043ND) do not have a convenient
+ * EEPROM location to read the ethernet MAC address from.
+ * OpenWRT simply snaffles it from a fixed location.
+ *
+ * Since multiple units seem to use this feature, include
+ * a method of setting the MAC address based on an flash location
+ * in CPU address space.
+ *
+ * Some vendors have decided to store the mac address as a literal
+ * string of 18 characters in xx:xx:xx:xx:xx:xx format instead of
+ * an array of numbers. Expose a hint to turn on this conversion
+ * feature via strtol().
+ *
+ * Returns 1 if MAC Address exists, 0 if not.
+ */
+int
+ar71xx_mac_addr_eeprom_init(device_t dev, unsigned char *addr)
+{
+ const char *name;
+ int unit;
+ long eeprom_mac_addr;
+ const char *mac_addr;
+ int readascii;
+ int i;
+ int local_mac;
+
+ name = device_get_name(dev);
+ unit = device_get_unit(dev);
+ eeprom_mac_addr = 0;
+
+ if (resource_long_value(name, unit,
+ "eeprommac", &eeprom_mac_addr) == 0) {
+ device_printf(dev, "Overriding MAC from EEPROM\n");
+
+ mac_addr = (const char *)MIPS_PHYS_TO_KSEG1(eeprom_mac_addr);
+ readascii = 0;
+
+ if (resource_int_value(name, unit,
+ "readascii", &readascii) == 0) {
+ device_printf(dev, "Vendor stores MAC in ASCII format\n");
+
+ for (i = 0; i < ETHER_ADDR_LEN; i++) {
+ addr[i] = strtol(&(mac_addr[i*3]), NULL, 16);
+ }
+ } else {
+ for (i = 0; i < ETHER_ADDR_LEN; i++) {
+ addr[i] = mac_addr[i];
+ }
+ }
+ local_mac = 1;
+ } else
+ local_mac = 0;
+
+ return (local_mac);
+}
diff -u -r -N sys/mips/atheros/ar71xx_macaddr.h sys/mips/atheros/ar71xx_macaddr.h
--- sys/mips/atheros/ar71xx_macaddr.h 2018-07-07 20:50:15.231507000 +0200
+++ sys/mips/atheros/ar71xx_macaddr.h 2018-07-06 23:03:17.000000000 +0200
@@ -35,5 +35,6 @@
extern int ar71xx_mac_addr_init(unsigned char *dst, const unsigned char *src,
int offset, int is_local);
extern int ar71xx_mac_addr_random_init(unsigned char *dst);
-
+extern int ar71xx_mac_addr_hint_init(device_t dev, unsigned char *addr);
+extern int ar71xx_mac_addr_eeprom_init(device_t dev, unsigned char *addr);
#endif /* __ATHEROS_AR71XX_MACADDR_H__ */
diff -u -r -N sys/mips/atheros/ar71xxreg.h sys/mips/atheros/ar71xxreg.h
--- sys/mips/atheros/ar71xxreg.h 2018-07-07 20:50:15.191989000 +0200
+++ sys/mips/atheros/ar71xxreg.h 2018-07-10 01:39:00.458571000 +0200
@@ -31,6 +31,13 @@
#ifndef _AR71XX_REG_H_
#define _AR71XX_REG_H_
+#define AR71XX_GE0 0
+#define AR71XX_GE1 1
+
+#define AR71XX_GE_SPEED_10 10
+#define AR71XX_GE_SPEED_100 100
+#define AR71XX_GE_SPEED_1000 1000
+
/* PCI region */
#define AR71XX_PCI_MEM_BASE 0x10000000
/*
diff -u -r -N sys/mips/atheros/ar724x_chip.c sys/mips/atheros/ar724x_chip.c
--- sys/mips/atheros/ar724x_chip.c 2018-07-07 20:50:12.705614000 +0200
+++ sys/mips/atheros/ar724x_chip.c 2018-07-08 02:57:57.865243000 +0200
@@ -147,10 +147,10 @@
{
switch (unit) {
- case 0:
+ case AR71XX_GE0:
/* XXX TODO */
break;
- case 1:
+ case AR71XX_GE1:
/* XXX TODO */
break;
default:
diff -u -r -N sys/mips/atheros/ar91xx_chip.c sys/mips/atheros/ar91xx_chip.c
--- sys/mips/atheros/ar91xx_chip.c 2018-07-07 20:50:12.710871000 +0200
+++ sys/mips/atheros/ar91xx_chip.c 2018-07-08 02:52:21.980940000 +0200
@@ -120,12 +120,12 @@
{
switch (unit) {
- case 0:
+ case AR71XX_GE0:
ar71xx_write_pll(AR91XX_PLL_REG_ETH_CONFIG,
AR91XX_PLL_REG_ETH0_INT_CLOCK, pll,
AR91XX_ETH0_PLL_SHIFT);
break;
- case 1:
+ case AR71XX_GE1:
ar71xx_write_pll(AR91XX_PLL_REG_ETH_CONFIG,
AR91XX_PLL_REG_ETH1_INT_CLOCK, pll,
AR91XX_ETH1_PLL_SHIFT);
@@ -165,14 +165,14 @@
{
uint32_t pll;
- switch(speed) {
- case 10:
+ switch (speed) {
+ case AR71XX_GE_SPEED_10:
pll = AR91XX_PLL_VAL_10;
break;
- case 100:
+ case AR71XX_GE_SPEED_100:
pll = AR91XX_PLL_VAL_100;
break;
- case 1000:
+ case AR71XX_GE_SPEED_1000:
pll = AR91XX_PLL_VAL_1000;
break;
default:
diff -u -r -N sys/mips/atheros/ar933x_chip.c sys/mips/atheros/ar933x_chip.c
--- sys/mips/atheros/ar933x_chip.c 2018-07-07 20:50:15.205875000 +0200
+++ sys/mips/atheros/ar933x_chip.c 2018-07-08 03:11:03.367354000 +0200
@@ -179,10 +179,10 @@
{
switch (unit) {
- case 0:
+ case AR71XX_GE0:
/* XXX TODO */
break;
- case 1:
+ case AR71XX_GE1:
/* XXX TODO */
break;
default:
@@ -222,13 +222,13 @@
uint32_t pll;
switch (speed) {
- case 10:
+ case AR71XX_GE_SPEED_10:
pll = AR933X_PLL_VAL_10;
break;
- case 100:
+ case AR71XX_GE_SPEED_100:
pll = AR933X_PLL_VAL_100;
break;
- case 1000:
+ case AR71XX_GE_SPEED_1000:
pll = AR933X_PLL_VAL_1000;
break;
default:
diff -u -r -N sys/mips/atheros/ar934x_chip.c sys/mips/atheros/ar934x_chip.c
--- sys/mips/atheros/ar934x_chip.c 2018-07-07 20:50:15.208885000 +0200
+++ sys/mips/atheros/ar934x_chip.c 2018-07-08 03:16:39.557796000 +0200
@@ -247,10 +247,10 @@
{
switch (unit) {
- case 0:
+ case AR71XX_GE0:
ATH_WRITE_REG(AR934X_PLL_ETH_XMII_CONTROL_REG, pll);
break;
- case 1:
+ case AR71XX_GE1:
/* XXX nothing */
break;
default:
@@ -293,13 +293,13 @@
uint32_t pll;
switch (speed) {
- case 10:
+ case AR71XX_GE_SPEED_10:
pll = AR934X_PLL_VAL_10;
break;
- case 100:
+ case AR71XX_GE_SPEED_100:
pll = AR934X_PLL_VAL_100;
break;
- case 1000:
+ case AR71XX_GE_SPEED_1000:
pll = AR934X_PLL_VAL_1000;
break;
default:
diff -u -r -N sys/mips/atheros/if_arge.c sys/mips/atheros/if_arge.c
--- sys/mips/atheros/if_arge.c 2018-07-09 18:58:16.785548000 +0200
+++ sys/mips/atheros/if_arge.c 2018-07-10 01:31:24.767212000 +0200
@@ -55,6 +55,7 @@
#include <net/if.h>
#include <net/if_var.h>
+#include <net/if_dl.h>
#include <net/if_media.h>
#include <net/ethernet.h>
#include <net/if_types.h>
@@ -143,6 +144,7 @@
static void arge_link_task(void *, int);
static void arge_update_link_locked(struct arge_softc *sc);
static void arge_set_pll(struct arge_softc *, int, int);
+static void arge_rxfilter(struct arge_softc *sc);
static int arge_miibus_readreg(device_t, int, int);
static void arge_miibus_statchg(device_t);
static int arge_miibus_writereg(device_t, int, int, int);
@@ -641,15 +643,10 @@
struct arge_softc *sc;
int error = 0, rid, i;
uint32_t hint;
- long eeprom_mac_addr = 0;
int miicfg = 0;
- int readascii = 0;
- int local_mac = 0;
+ int local_mac;
uint8_t local_macaddr[ETHER_ADDR_LEN];
- char * local_macstr;
- char devid_str[32];
- int count;
-
+
sc = device_get_softc(dev);
sc->arge_dev = dev;
sc->arge_mac_unit = device_get_unit(dev);
@@ -662,32 +659,7 @@
* routines, but at the moment the system is booting, the resource hints
* are set to the 'static' map so they're not pulling from kenv.
*/
- snprintf(devid_str, 32, "hint.%s.%d.macaddr",
- device_get_name(dev),
- device_get_unit(dev));
- if ((local_macstr = kern_getenv(devid_str)) != NULL) {
- uint32_t tmpmac[ETHER_ADDR_LEN];
-
- /* Have a MAC address; should use it */
- device_printf(dev, "Overriding MAC address from environment: '%s'\n",
- local_macstr);
-
- /* Extract out the MAC address */
- /* XXX this should all be a generic method */
- count = sscanf(local_macstr, "%x%*c%x%*c%x%*c%x%*c%x%*c%x",
- &tmpmac[0], &tmpmac[1],
- &tmpmac[2], &tmpmac[3],
- &tmpmac[4], &tmpmac[5]);
- if (count == 6) {
- /* Valid! */
- local_mac = 1;
- for (i = 0; i < ETHER_ADDR_LEN; i++)
- local_macaddr[i] = tmpmac[i];
- }
- /* Done! */
- freeenv(local_macstr);
- local_macstr = NULL;
- }
+ local_mac = ar71xx_mac_addr_hint_init(dev, local_macaddr);
/*
* Hardware workarounds.
@@ -726,25 +698,8 @@
* an array of numbers. Expose a hint to turn on this conversion
* feature via strtol()
*/
- if (local_mac == 0 && resource_long_value(device_get_name(dev),
- device_get_unit(dev), "eeprommac", &eeprom_mac_addr) == 0) {
- local_mac = 1;
- int i;
- const char *mac =
- (const char *) MIPS_PHYS_TO_KSEG1(eeprom_mac_addr);
- device_printf(dev, "Overriding MAC from EEPROM\n");
- if (resource_int_value(device_get_name(dev), device_get_unit(dev),
- "readascii", &readascii) == 0) {
- device_printf(dev, "Vendor stores MAC in ASCII format\n");
- for (i = 0; i < 6; i++) {
- local_macaddr[i] = strtol(&(mac[i*3]), NULL, 16);
- }
- } else {
- for (i = 0; i < 6; i++) {
- local_macaddr[i] = mac[i];
- }
- }
- }
+ if (local_mac == 0)
+ local_mac = ar71xx_mac_addr_eeprom_init(dev, local_macaddr);
KASSERT(((sc->arge_mac_unit == 0) || (sc->arge_mac_unit == 1)),
("if_arge: Only MAC0 and MAC1 supported"));
@@ -790,11 +745,11 @@
"media", &hint) != 0)
hint = 0;
- if (hint == 1000)
+ if (hint == AR71XX_GE_SPEED_1000)
sc->arge_media_type = IFM_1000_T;
- else if (hint == 100)
+ else if (hint == AR71XX_GE_SPEED_100)
sc->arge_media_type = IFM_100_TX;
- else if (hint == 10)
+ else if (hint == AR71XX_GE_SPEED_10)
sc->arge_media_type = IFM_10_T;
else
sc->arge_media_type = 0;
@@ -867,8 +822,12 @@
/* If there's a local mac defined, copy that in */
if (local_mac == 1) {
- (void) ar71xx_mac_addr_init(sc->arge_eaddr,
- local_macaddr, 0, 0);
+ if (ar71xx_mac_addr_init(sc->arge_eaddr,
+ local_macaddr, 0, 0) != 0) {
+ device_printf(dev,
+ "Generating random ethernet address.\n");
+ (void) ar71xx_mac_addr_random_init(sc->arge_eaddr);
+ }
} else {
/*
* No MAC address configured. Generate the random one.
@@ -941,6 +900,11 @@
ARGE_WRITE(sc, AR71XX_MAC_FIFO_CFG2, 0x00001fff);
}
+ /*
+ * If bit of AR71XX_MAC_FIFO_RX_FILTMATCH does
+ * not match with AR71XX_MAC_FIFO_RX_FILTMASK
+ * then the frame is dropped.
+ */
ARGE_WRITE(sc, AR71XX_MAC_FIFO_RX_FILTMATCH,
FIFO_RX_FILTMATCH_DEFAULT);
@@ -1357,6 +1321,34 @@
#endif
}
+/*
+ * Enable / disable promiscuous, multicast or broadcast flags.
+ */
+static void
+arge_rxfilter(struct arge_softc *sc)
+{
+ struct ifnet *ifp;
+ uint32_t rxcfg;
+
+ ARGE_LOCK_ASSERT(sc);
+
+ ifp = sc->arge_ifp;
+
+ rxcfg = ARGE_READ(sc, AR71XX_MAC_FIFO_RX_FILTMASK);
+ rxcfg &= ~(FIFO_RX_MASK_BCAST|FIFO_RX_MASK_MCAST);
+
+ if ((ifp->if_flags & IFF_BROADCAST) != 0)
+ rxcfg |= FIFO_RX_MASK_BCAST;
+
+ if ((ifp->if_flags & (IFF_PROMISC | IFF_ALLMULTI)) != 0) {
+ if ((ifp->if_flags & IFF_PROMISC) != 0)
+ rxcfg |= (FIFO_RX_MASK_BCAST|FIFO_RX_MASK_MCAST);
+ if ((ifp->if_flags & IFF_ALLMULTI) != 0)
+ rxcfg |= FIFO_RX_MASK_MCAST;
+ }
+
+ ARGE_WRITE(sc, AR71XX_MAC_FIFO_RX_FILTMASK, rxcfg);
+}
static void
arge_reset_dma(struct arge_softc *sc)
@@ -1424,7 +1416,22 @@
return;
}
+ /* Setup MAC address */
+ if (ar71xx_mac_addr_init(sc->arge_eaddr,
+ IF_LLADDR(ifp), 0, 0) != 0) {
+ device_printf(sc->arge_dev,
+ "initialization failed: invalid MAC Address\n");
+ arge_stop(sc);
+ return;
+ }
+ ARGE_WRITE(sc, AR71XX_MAC_STA_ADDR1, (sc->arge_eaddr[2] << 24)
+ | (sc->arge_eaddr[3] << 16) | (sc->arge_eaddr[4] << 8)
+ | sc->arge_eaddr[5]);
+ ARGE_WRITE(sc, AR71XX_MAC_STA_ADDR2, (sc->arge_eaddr[0] << 8)
+ | sc->arge_eaddr[1]);
+ /* Setup rx filter */
+ arge_rxfilter(sc);
/* Init tx descriptors. */
arge_tx_ring_init(sc);
@@ -1738,7 +1745,7 @@
if ((ifp->if_drv_flags & IFF_DRV_RUNNING) != 0) {
if (((ifp->if_flags ^ sc->arge_if_flags)
& (IFF_PROMISC | IFF_ALLMULTI)) != 0) {
- /* XXX: handle promisc & multi flags */
+ arge_rxfilter(sc);
}
} else {
@@ -1755,7 +1762,9 @@
break;
case SIOCADDMULTI:
case SIOCDELMULTI:
- /* XXX: implement SIOCDELMULTI */
+ ARGE_LOCK(sc);
+ arge_rxfilter(sc);
+ ARGE_UNLOCK(sc);
error = 0;
break;
case SIOCGIFMEDIA:
diff -u -r -N sys/mips/atheros/qca953x_chip.c sys/mips/atheros/qca953x_chip.c
--- sys/mips/atheros/qca953x_chip.c 2018-07-07 20:50:13.248209000 +0200
+++ sys/mips/atheros/qca953x_chip.c 2018-07-08 03:19:45.864113000 +0200
@@ -192,10 +192,10 @@
qca953x_chip_set_pll_ge(int unit, int speed, uint32_t pll)
{
switch (unit) {
- case 0:
+ case AR71XX_GE0:
ATH_WRITE_REG(QCA953X_PLL_ETH_XMII_CONTROL_REG, pll);
break;
- case 1:
+ case AR71XX_GE1:
/* nothing */
break;
default:
@@ -236,13 +236,13 @@
uint32_t pll;
switch (speed) {
- case 10:
+ case AR71XX_GE_SPEED_10:
pll = QCA953X_PLL_VAL_10;
break;
- case 100:
+ case AR71XX_GE_SPEED_100:
pll = QCA953X_PLL_VAL_100;
break;
- case 1000:
+ case AR71XX_GE_SPEED_1000:
pll = QCA953X_PLL_VAL_1000;
break;
default:
diff -u -r -N sys/mips/atheros/qca955x_chip.c sys/mips/atheros/qca955x_chip.c
--- sys/mips/atheros/qca955x_chip.c 2018-07-07 20:50:15.473144000 +0200
+++ sys/mips/atheros/qca955x_chip.c 2018-07-08 03:22:12.891902000 +0200
@@ -193,10 +193,10 @@
qca955x_chip_set_pll_ge(int unit, int speed, uint32_t pll)
{
switch (unit) {
- case 0:
+ case AR71XX_GE0:
ATH_WRITE_REG(QCA955X_PLL_ETH_XMII_CONTROL_REG, pll);
break;
- case 1:
+ case AR71XX_GE1:
ATH_WRITE_REG(QCA955X_PLL_ETH_SGMII_CONTROL_REG, pll);
break;
default:
@@ -243,13 +243,13 @@
uint32_t pll;
switch (speed) {
- case 10:
+ case AR71XX_GE_SPEED_10:
pll = QCA955X_PLL_VAL_10;
break;
- case 100:
+ case AR71XX_GE_SPEED_100:
pll = QCA955X_PLL_VAL_100;
break;
- case 1000:
+ case AR71XX_GE_SPEED_1000:
pll = QCA955X_PLL_VAL_1000;
break;
default:

File Metadata

Mime Type
text/plain; charset=utf-8
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1472070
Default Alt Text
arge_rxfilter(9) implemented for if_arge(4), some parts were externalized from arge_attach(9) and cleanup on some magic numbers. (16 KB)

Event Timeline