1
0
Fork 0
mirror of https://github.com/Ysurac/openmptcprouter.git synced 2025-03-09 15:40:20 +00:00

Update 6.12 kernel patches

This commit is contained in:
Ycarus (Yannick Chabanois) 2024-12-26 18:19:04 +01:00
parent bdb9b0046f
commit 9d83c70ced
247 changed files with 53301 additions and 589 deletions

View file

@ -19,7 +19,7 @@ Signed-off-by: Jens Axboe <axboe@kernel.dk>
--- a/block/blk.h
+++ b/block/blk.h
@@ -424,6 +424,7 @@ void blk_free_ext_minor(unsigned int min
@@ -564,6 +564,7 @@ void blk_free_ext_minor(unsigned int min
#define ADDPART_FLAG_NONE 0
#define ADDPART_FLAG_RAID 1
#define ADDPART_FLAG_WHOLEDISK 2
@ -41,7 +41,7 @@ Signed-off-by: Jens Axboe <axboe@kernel.dk>
strscpy(info->volname, subpart->name, sizeof(info->volname));
--- a/block/partitions/core.c
+++ b/block/partitions/core.c
@@ -392,6 +392,9 @@ static struct block_device *add_partitio
@@ -373,6 +373,9 @@ static struct block_device *add_partitio
goto out_del;
}

View file

@ -48,7 +48,7 @@ Signed-off-by: Jens Axboe <axboe@kernel.dk>
{
struct device *ddev = disk_to_dev(disk);
@@ -451,6 +453,8 @@ int __must_check device_add_disk(struct
@@ -452,6 +454,8 @@ int __must_check device_add_disk(struct
ddev->parent = parent;
ddev->groups = groups;
dev_set_name(ddev, "%s", disk->disk_name);
@ -57,7 +57,7 @@ Signed-off-by: Jens Axboe <axboe@kernel.dk>
if (!(disk->flags & GENHD_FL_HIDDEN))
ddev->devt = MKDEV(disk->major, disk->first_minor);
ret = device_add(ddev);
@@ -552,6 +556,22 @@ out_exit_elevator:
@@ -553,6 +557,22 @@ out_exit_elevator:
elevator_exit(disk->queue);
return ret;
}
@ -82,7 +82,7 @@ Signed-off-by: Jens Axboe <axboe@kernel.dk>
static void blk_report_disk_dead(struct gendisk *disk, bool surprise)
--- a/include/linux/blkdev.h
+++ b/include/linux/blkdev.h
@@ -741,6 +741,9 @@ static inline unsigned int blk_queue_dep
@@ -735,6 +735,9 @@ static inline unsigned int blk_queue_dep
#define for_each_bio(_bio) \
for (; _bio; _bio = _bio->bi_next)

View file

@ -26,7 +26,7 @@ Signed-off-by: Jens Axboe <axboe@kernel.dk>
--- a/drivers/mmc/core/block.c
+++ b/drivers/mmc/core/block.c
@@ -2455,6 +2455,56 @@ static inline int mmc_blk_readonly(struc
@@ -2517,6 +2517,56 @@ static inline int mmc_blk_readonly(struc
!(card->csd.cmdclass & CCC_BLOCK_WRITE);
}
@ -83,7 +83,7 @@ Signed-off-by: Jens Axboe <axboe@kernel.dk>
static struct mmc_blk_data *mmc_blk_alloc_req(struct mmc_card *card,
struct device *parent,
sector_t size,
@@ -2463,6 +2513,7 @@ static struct mmc_blk_data *mmc_blk_allo
@@ -2525,6 +2575,7 @@ static struct mmc_blk_data *mmc_blk_allo
int area_type,
unsigned int part_type)
{
@ -91,7 +91,7 @@ Signed-off-by: Jens Axboe <axboe@kernel.dk>
struct mmc_blk_data *md;
int devidx, ret;
char cap_str[10];
@@ -2568,7 +2619,9 @@ static struct mmc_blk_data *mmc_blk_allo
@@ -2626,7 +2677,9 @@ static struct mmc_blk_data *mmc_blk_allo
/* used in ->open, must be set before add_disk: */
if (area_type == MMC_BLK_DATA_AREA_MAIN)
dev_set_drvdata(&card->dev, md);

View file

@ -0,0 +1,38 @@
From ae461cde5c559675fc4c0ba351c7c31ace705f56 Mon Sep 17 00:00:00 2001
From: Bohdan Chubuk <chbgdn@gmail.com>
Date: Sun, 10 Nov 2024 22:50:47 +0200
Subject: [PATCH] mtd: spinand: add support for FORESEE F35SQA001G
Add support for FORESEE F35SQA001G SPI NAND.
Similar to F35SQA002G, but differs in capacity.
Datasheet:
- https://cdn.ozdisan.com/ETicaret_Dosya/704795_871495.pdf
Tested on Xiaomi AX3000T flashed with OpenWRT.
Signed-off-by: Bohdan Chubuk <chbgdn@gmail.com>
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
---
drivers/mtd/nand/spi/foresee.c | 10 ++++++++++
1 file changed, 10 insertions(+)
--- a/drivers/mtd/nand/spi/foresee.c
+++ b/drivers/mtd/nand/spi/foresee.c
@@ -81,6 +81,16 @@ static const struct spinand_info foresee
SPINAND_HAS_QE_BIT,
SPINAND_ECCINFO(&f35sqa002g_ooblayout,
f35sqa002g_ecc_get_status)),
+ SPINAND_INFO("F35SQA001G",
+ SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x71, 0x71),
+ NAND_MEMORG(1, 2048, 64, 64, 1024, 20, 1, 1, 1),
+ NAND_ECCREQ(1, 512),
+ SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
+ &write_cache_variants,
+ &update_cache_variants),
+ SPINAND_HAS_QE_BIT,
+ SPINAND_ECCINFO(&f35sqa002g_ooblayout,
+ f35sqa002g_ecc_get_status)),
};
static const struct spinand_manufacturer_ops foresee_spinand_manuf_ops = {

View file

@ -0,0 +1,106 @@
From 081c9c0265c91b8333165aa6230c20bcbc6f7cbf Mon Sep 17 00:00:00 2001
From: Daniel Golle <daniel@makrotopia.org>
Date: Thu, 10 Oct 2024 14:07:16 +0100
Subject: [PATCH 3/5] net: phy: realtek: read duplex and gbit master from PHYSR
register
The PHYSR MMD register is present and defined equally for all RTL82xx
Ethernet PHYs.
Read duplex and Gbit master bits from rtlgen_decode_speed() and rename
it to rtlgen_decode_physr().
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
Link: https://patch.msgid.link/b9a76341da851a18c985bc4774fa295babec79bb.1728565530.git.daniel@makrotopia.org
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
---
drivers/net/phy/realtek.c | 41 +++++++++++++++++++++++++++++++--------
1 file changed, 33 insertions(+), 8 deletions(-)
--- a/drivers/net/phy/realtek.c
+++ b/drivers/net/phy/realtek.c
@@ -80,15 +80,18 @@
#define RTL822X_VND2_GANLPAR 0xa414
-#define RTL822X_VND2_PHYSR 0xa434
-
#define RTL8366RB_POWER_SAVE 0x15
#define RTL8366RB_POWER_SAVE_ON BIT(12)
#define RTL9000A_GINMR 0x14
#define RTL9000A_GINMR_LINK_STATUS BIT(4)
-#define RTLGEN_SPEED_MASK 0x0630
+#define RTL_VND2_PHYSR 0xa434
+#define RTL_VND2_PHYSR_DUPLEX BIT(3)
+#define RTL_VND2_PHYSR_SPEEDL GENMASK(5, 4)
+#define RTL_VND2_PHYSR_SPEEDH GENMASK(10, 9)
+#define RTL_VND2_PHYSR_MASTER BIT(11)
+#define RTL_VND2_PHYSR_SPEED_MASK (RTL_VND2_PHYSR_SPEEDL | RTL_VND2_PHYSR_SPEEDH)
#define RTL_GENERIC_PHYID 0x001cc800
#define RTL_8211FVD_PHYID 0x001cc878
@@ -660,9 +663,18 @@ static int rtl8366rb_config_init(struct
}
/* get actual speed to cover the downshift case */
-static void rtlgen_decode_speed(struct phy_device *phydev, int val)
+static void rtlgen_decode_physr(struct phy_device *phydev, int val)
{
- switch (val & RTLGEN_SPEED_MASK) {
+ /* bit 3
+ * 0: Half Duplex
+ * 1: Full Duplex
+ */
+ if (val & RTL_VND2_PHYSR_DUPLEX)
+ phydev->duplex = DUPLEX_FULL;
+ else
+ phydev->duplex = DUPLEX_HALF;
+
+ switch (val & RTL_VND2_PHYSR_SPEED_MASK) {
case 0x0000:
phydev->speed = SPEED_10;
break;
@@ -684,6 +696,19 @@ static void rtlgen_decode_speed(struct p
default:
break;
}
+
+ /* bit 11
+ * 0: Slave Mode
+ * 1: Master Mode
+ */
+ if (phydev->speed >= 1000) {
+ if (val & RTL_VND2_PHYSR_MASTER)
+ phydev->master_slave_state = MASTER_SLAVE_STATE_MASTER;
+ else
+ phydev->master_slave_state = MASTER_SLAVE_STATE_SLAVE;
+ } else {
+ phydev->master_slave_state = MASTER_SLAVE_STATE_UNSUPPORTED;
+ }
}
static int rtlgen_read_status(struct phy_device *phydev)
@@ -701,7 +726,7 @@ static int rtlgen_read_status(struct phy
if (val < 0)
return val;
- rtlgen_decode_speed(phydev, val);
+ rtlgen_decode_physr(phydev, val);
return 0;
}
@@ -1007,11 +1032,11 @@ static int rtl822x_c45_read_status(struc
return 0;
/* Read actual speed from vendor register. */
- val = phy_read_mmd(phydev, MDIO_MMD_VEND2, RTL822X_VND2_PHYSR);
+ val = phy_read_mmd(phydev, MDIO_MMD_VEND2, RTL_VND2_PHYSR);
if (val < 0)
return val;
- rtlgen_decode_speed(phydev, val);
+ rtlgen_decode_physr(phydev, val);
return 0;
}

View file

@ -0,0 +1,54 @@
From 68d5cd09e8919679ce13b85950debea4b2e98e04 Mon Sep 17 00:00:00 2001
From: Daniel Golle <daniel@makrotopia.org>
Date: Thu, 10 Oct 2024 14:07:26 +0100
Subject: [PATCH 4/5] net: phy: realtek: change order of calls in C22
read_status()
Always call rtlgen_read_status() first, so genphy_read_status() which
is called by it clears bits in case auto-negotiation has not completed.
Also clear 10GBT link-partner advertisement bits in case auto-negotiation
is disabled or has not completed.
Suggested-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
Link: https://patch.msgid.link/b15929a41621d215c6b2b57393368086589569ec.1728565530.git.daniel@makrotopia.org
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
---
drivers/net/phy/realtek.c | 22 +++++++++++++++-------
1 file changed, 15 insertions(+), 7 deletions(-)
--- a/drivers/net/phy/realtek.c
+++ b/drivers/net/phy/realtek.c
@@ -949,17 +949,25 @@ static void rtl822xb_update_interface(st
static int rtl822x_read_status(struct phy_device *phydev)
{
- if (phydev->autoneg == AUTONEG_ENABLE) {
- int lpadv = phy_read_paged(phydev, 0xa5d, 0x13);
+ int lpadv, ret;
- if (lpadv < 0)
- return lpadv;
+ ret = rtlgen_read_status(phydev);
+ if (ret < 0)
+ return ret;
- mii_10gbt_stat_mod_linkmode_lpa_t(phydev->lp_advertising,
- lpadv);
+ if (phydev->autoneg == AUTONEG_DISABLE ||
+ !phydev->autoneg_complete) {
+ mii_10gbt_stat_mod_linkmode_lpa_t(phydev->lp_advertising, 0);
+ return 0;
}
- return rtlgen_read_status(phydev);
+ lpadv = phy_read_paged(phydev, 0xa5d, 0x13);
+ if (lpadv < 0)
+ return lpadv;
+
+ mii_10gbt_stat_mod_linkmode_lpa_t(phydev->lp_advertising, lpadv);
+
+ return 0;
}
static int rtl822xb_read_status(struct phy_device *phydev)

View file

@ -0,0 +1,30 @@
From 5cb409b3960e75467cbb0a8e1e5596b4490570e3 Mon Sep 17 00:00:00 2001
From: Daniel Golle <daniel@makrotopia.org>
Date: Thu, 10 Oct 2024 14:07:39 +0100
Subject: [PATCH 5/5] net: phy: realtek: clear 1000Base-T link partner
advertisement
Clear 1000Base-T link partner advertisement bits in Clause-45
read_status() function in case auto-negotiation is disabled or has not
been completed.
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
Link: https://patch.msgid.link/9dc9b47b2d675708afef3ad366bfd78eb584d958.1728565530.git.daniel@makrotopia.org
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
---
drivers/net/phy/realtek.c | 4 ++++
1 file changed, 4 insertions(+)
--- a/drivers/net/phy/realtek.c
+++ b/drivers/net/phy/realtek.c
@@ -1026,6 +1026,10 @@ static int rtl822x_c45_read_status(struc
if (ret < 0)
return ret;
+ if (phydev->autoneg == AUTONEG_DISABLE ||
+ !genphy_c45_aneg_done(phydev))
+ mii_stat1000_mod_linkmode_lpa_t(phydev->lp_advertising, 0);
+
/* Vendor register as C45 has no standardized support for 1000BaseT */
if (phydev->autoneg == AUTONEG_ENABLE) {
val = phy_read_mmd(phydev, MDIO_MMD_VEND2,

View file

@ -0,0 +1,107 @@
From a2e1ba275eae96a8171deb19e9c7c2f5978fee7b Mon Sep 17 00:00:00 2001
From: Daniel Golle <daniel@makrotopia.org>
Date: Fri, 4 Oct 2024 17:18:16 +0100
Subject: [PATCH] net: phy: aquantia: allow forcing order of MDI pairs
Despite supporting Auto MDI-X, it looks like Aquantia only supports
swapping pair (1,2) with pair (3,6) like it used to be for MDI-X on
100MBit/s networks.
When all 4 pairs are in use (for 1000MBit/s or faster) the link does not
come up with pair order is not configured correctly, either using
MDI_CFG pin or using the "PMA Receive Reserved Vendor Provisioning 1"
register.
Normally, the order of MDI pairs being either ABCD or DCBA is configured
by pulling the MDI_CFG pin.
However, some hardware designs require overriding the value configured
by that bootstrap pin. The PHY allows doing that by setting a bit in
"PMA Receive Reserved Vendor Provisioning 1" register which allows
ignoring the state of the MDI_CFG pin and another bit configuring
whether the order of MDI pairs should be normal (ABCD) or reverse
(DCBA). Pair polarity is not affected and remains identical in both
settings.
Introduce property "marvell,mdi-cfg-order" which allows forcing either
normal or reverse order of the MDI pairs from DT.
If the property isn't present, the behavior is unchanged and MDI pair
order configuration is untouched (ie. either the result of MDI_CFG pin
pull-up/pull-down, or pair order override already configured by the
bootloader before Linux is started).
Forcing normal pair order is required on the Adtran SDG-8733A Wi-Fi 7
residential gateway.
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
Link: https://patch.msgid.link/9ed760ff87d5fc456f31e407ead548bbb754497d.1728058550.git.daniel@makrotopia.org
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
drivers/net/phy/aquantia/aquantia_main.c | 33 ++++++++++++++++++++++++
1 file changed, 33 insertions(+)
--- a/drivers/net/phy/aquantia/aquantia_main.c
+++ b/drivers/net/phy/aquantia/aquantia_main.c
@@ -11,6 +11,7 @@
#include <linux/module.h>
#include <linux/delay.h>
#include <linux/bitfield.h>
+#include <linux/of.h>
#include <linux/phy.h>
#include "aquantia.h"
@@ -71,6 +72,11 @@
#define MDIO_AN_TX_VEND_INT_MASK2 0xd401
#define MDIO_AN_TX_VEND_INT_MASK2_LINK BIT(0)
+#define PMAPMD_RSVD_VEND_PROV 0xe400
+#define PMAPMD_RSVD_VEND_PROV_MDI_CONF GENMASK(1, 0)
+#define PMAPMD_RSVD_VEND_PROV_MDI_REVERSE BIT(0)
+#define PMAPMD_RSVD_VEND_PROV_MDI_FORCE BIT(1)
+
#define MDIO_AN_RX_LP_STAT1 0xe820
#define MDIO_AN_RX_LP_STAT1_1000BASET_FULL BIT(15)
#define MDIO_AN_RX_LP_STAT1_1000BASET_HALF BIT(14)
@@ -485,6 +491,29 @@ static void aqr107_chip_info(struct phy_
fw_major, fw_minor, build_id, prov_id);
}
+static int aqr107_config_mdi(struct phy_device *phydev)
+{
+ struct device_node *np = phydev->mdio.dev.of_node;
+ u32 mdi_conf;
+ int ret;
+
+ ret = of_property_read_u32(np, "marvell,mdi-cfg-order", &mdi_conf);
+
+ /* Do nothing in case property "marvell,mdi-cfg-order" is not present */
+ if (ret == -ENOENT)
+ return 0;
+
+ if (ret)
+ return ret;
+
+ if (mdi_conf & ~PMAPMD_RSVD_VEND_PROV_MDI_REVERSE)
+ return -EINVAL;
+
+ return phy_modify_mmd(phydev, MDIO_MMD_PMAPMD, PMAPMD_RSVD_VEND_PROV,
+ PMAPMD_RSVD_VEND_PROV_MDI_CONF,
+ mdi_conf | PMAPMD_RSVD_VEND_PROV_MDI_FORCE);
+}
+
static int aqr107_config_init(struct phy_device *phydev)
{
struct aqr107_priv *priv = phydev->priv;
@@ -514,6 +543,10 @@ static int aqr107_config_init(struct phy
if (ret)
return ret;
+ ret = aqr107_config_mdi(phydev);
+ if (ret)
+ return ret;
+
/* Restore LED polarity state after reset */
for_each_set_bit(led_active_low, &priv->leds_active_low, AQR_MAX_LEDS) {
ret = aqr_phy_led_active_low_set(phydev, led_active_low, true);

View file

@ -0,0 +1,31 @@
From ce21b8fb255ebf0b49913fb4c62741d7eb05c6f6 Mon Sep 17 00:00:00 2001
From: Daniel Golle <daniel@makrotopia.org>
Date: Fri, 11 Oct 2024 22:28:43 +0100
Subject: [PATCH] net: phy: aquantia: fix return value check in
aqr107_config_mdi()
of_property_read_u32() returns -EINVAL in case the property cannot be
found rather than -ENOENT. Fix the check to not abort probing in case
of the property being missing, and also in case CONFIG_OF is not set
which will result in -ENOSYS.
Fixes: a2e1ba275eae ("net: phy: aquantia: allow forcing order of MDI pairs")
Reported-by: Jon Hunter <jonathanh@nvidia.com>
Closes: https://lore.kernel.org/all/114b4c03-5d16-42ed-945d-cf78eabea12b@nvidia.com/
Suggested-by: Hans-Frieder Vogt <hfdevel@gmx.net>
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
---
drivers/net/phy/aquantia/aquantia_main.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
--- a/drivers/net/phy/aquantia/aquantia_main.c
+++ b/drivers/net/phy/aquantia/aquantia_main.c
@@ -500,7 +500,7 @@ static int aqr107_config_mdi(struct phy_
ret = of_property_read_u32(np, "marvell,mdi-cfg-order", &mdi_conf);
/* Do nothing in case property "marvell,mdi-cfg-order" is not present */
- if (ret == -ENOENT)
+ if (ret == -EINVAL || ret == -ENOSYS)
return 0;
if (ret)

View file

@ -0,0 +1,53 @@
From a274465cc3bef2dfd9c9ea5100848dda0a8641e1 Mon Sep 17 00:00:00 2001
From: Daniel Golle <daniel@makrotopia.org>
Date: Thu, 10 Oct 2024 13:54:19 +0100
Subject: [PATCH 1/4] net: phy: support 'active-high' property for PHY LEDs
In addition to 'active-low' and 'inactive-high-impedance' also
support 'active-high' property for PHY LED pin configuration.
As only either 'active-high' or 'active-low' can be set at the
same time, WARN and return an error in case both are set.
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
Link: https://patch.msgid.link/91598487773d768f254d5faf06cf65b13e972f0e.1728558223.git.daniel@makrotopia.org
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
---
drivers/net/phy/phy_device.c | 6 ++++++
include/linux/phy.h | 5 +++--
2 files changed, 9 insertions(+), 2 deletions(-)
--- a/drivers/net/phy/phy_device.c
+++ b/drivers/net/phy/phy_device.c
@@ -3358,11 +3358,17 @@ static int of_phy_led(struct phy_device
if (index > U8_MAX)
return -EINVAL;
+ if (of_property_read_bool(led, "active-high"))
+ set_bit(PHY_LED_ACTIVE_HIGH, &modes);
if (of_property_read_bool(led, "active-low"))
set_bit(PHY_LED_ACTIVE_LOW, &modes);
if (of_property_read_bool(led, "inactive-high-impedance"))
set_bit(PHY_LED_INACTIVE_HIGH_IMPEDANCE, &modes);
+ if (WARN_ON(modes & BIT(PHY_LED_ACTIVE_LOW) &&
+ modes & BIT(PHY_LED_ACTIVE_HIGH)))
+ return -EINVAL;
+
if (modes) {
/* Return error if asked to set polarity modes but not supported */
if (!phydev->drv->led_polarity_set)
--- a/include/linux/phy.h
+++ b/include/linux/phy.h
@@ -877,8 +877,9 @@ struct phy_plca_status {
/* Modes for PHY LED configuration */
enum phy_led_modes {
- PHY_LED_ACTIVE_LOW = 0,
- PHY_LED_INACTIVE_HIGH_IMPEDANCE = 1,
+ PHY_LED_ACTIVE_HIGH = 0,
+ PHY_LED_ACTIVE_LOW = 1,
+ PHY_LED_INACTIVE_HIGH_IMPEDANCE = 2,
/* keep it last */
__PHY_LED_MODES_NUM,

View file

@ -0,0 +1,108 @@
From 9d55e68b19f222e6334ef4021c5527998f5ab537 Mon Sep 17 00:00:00 2001
From: Daniel Golle <daniel@makrotopia.org>
Date: Thu, 10 Oct 2024 13:55:00 +0100
Subject: [PATCH 2/4] net: phy: aquantia: correctly describe LED polarity
override
Use newly defined 'active-high' property to set the
VEND1_GLOBAL_LED_DRIVE_VDD bit and let 'active-low' clear that bit. This
reflects the technical reality which was inverted in the previous
description in which the 'active-low' property was used to actually set
the VEND1_GLOBAL_LED_DRIVE_VDD bit, which means that VDD (ie. supply
voltage) of the LED is driven rather than GND.
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
Link: https://patch.msgid.link/86a413b4387c42dcb54f587cc2433a06f16aae83.1728558223.git.daniel@makrotopia.org
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
---
drivers/net/phy/aquantia/aquantia.h | 1 +
drivers/net/phy/aquantia/aquantia_leds.c | 19 ++++++++++++++-----
drivers/net/phy/aquantia/aquantia_main.c | 12 +++++++++---
3 files changed, 24 insertions(+), 8 deletions(-)
--- a/drivers/net/phy/aquantia/aquantia.h
+++ b/drivers/net/phy/aquantia/aquantia.h
@@ -177,6 +177,7 @@ static const struct aqr107_hw_stat aqr10
struct aqr107_priv {
u64 sgmii_stats[AQR107_SGMII_STAT_SZ];
unsigned long leds_active_low;
+ unsigned long leds_active_high;
};
#if IS_REACHABLE(CONFIG_HWMON)
--- a/drivers/net/phy/aquantia/aquantia_leds.c
+++ b/drivers/net/phy/aquantia/aquantia_leds.c
@@ -121,13 +121,13 @@ int aqr_phy_led_active_low_set(struct ph
{
return phy_modify_mmd(phydev, MDIO_MMD_VEND1, AQR_LED_DRIVE(index),
VEND1_GLOBAL_LED_DRIVE_VDD,
- enable ? VEND1_GLOBAL_LED_DRIVE_VDD : 0);
+ enable ? 0 : VEND1_GLOBAL_LED_DRIVE_VDD);
}
int aqr_phy_led_polarity_set(struct phy_device *phydev, int index, unsigned long modes)
{
+ bool force_active_low = false, force_active_high = false;
struct aqr107_priv *priv = phydev->priv;
- bool active_low = false;
u32 mode;
if (index >= AQR_MAX_LEDS)
@@ -136,7 +136,10 @@ int aqr_phy_led_polarity_set(struct phy_
for_each_set_bit(mode, &modes, __PHY_LED_MODES_NUM) {
switch (mode) {
case PHY_LED_ACTIVE_LOW:
- active_low = true;
+ force_active_low = true;
+ break;
+ case PHY_LED_ACTIVE_HIGH:
+ force_active_high = true;
break;
default:
return -EINVAL;
@@ -144,8 +147,14 @@ int aqr_phy_led_polarity_set(struct phy_
}
/* Save LED driver vdd state to restore on SW reset */
- if (active_low)
+ if (force_active_low)
priv->leds_active_low |= BIT(index);
- return aqr_phy_led_active_low_set(phydev, index, active_low);
+ if (force_active_high)
+ priv->leds_active_high |= BIT(index);
+
+ if (force_active_high || force_active_low)
+ return aqr_phy_led_active_low_set(phydev, index, force_active_low);
+
+ unreachable();
}
--- a/drivers/net/phy/aquantia/aquantia_main.c
+++ b/drivers/net/phy/aquantia/aquantia_main.c
@@ -517,7 +517,7 @@ static int aqr107_config_mdi(struct phy_
static int aqr107_config_init(struct phy_device *phydev)
{
struct aqr107_priv *priv = phydev->priv;
- u32 led_active_low;
+ u32 led_idx;
int ret;
/* Check that the PHY interface type is compatible */
@@ -548,8 +548,14 @@ static int aqr107_config_init(struct phy
return ret;
/* Restore LED polarity state after reset */
- for_each_set_bit(led_active_low, &priv->leds_active_low, AQR_MAX_LEDS) {
- ret = aqr_phy_led_active_low_set(phydev, led_active_low, true);
+ for_each_set_bit(led_idx, &priv->leds_active_low, AQR_MAX_LEDS) {
+ ret = aqr_phy_led_active_low_set(phydev, led_idx, true);
+ if (ret)
+ return ret;
+ }
+
+ for_each_set_bit(led_idx, &priv->leds_active_high, AQR_MAX_LEDS) {
+ ret = aqr_phy_led_active_low_set(phydev, led_idx, false);
if (ret)
return ret;
}

View file

@ -0,0 +1,332 @@
From 78997e9a5e4d8a4df561e083a92c91ae23010e07 Mon Sep 17 00:00:00 2001
From: Daniel Golle <daniel@makrotopia.org>
Date: Tue, 1 Oct 2024 01:17:18 +0100
Subject: [PATCH] net: phy: mxl-gpy: add basic LED support
Add basic support for LEDs connected to MaxLinear GPY2xx and GPY115 PHYs.
The PHYs allow up to 4 LEDs to be connected.
Implement controlling LEDs in software as well as netdev trigger offloading
and LED polarity setup.
The hardware claims to support 16 PWM brightness levels but there is no
documentation on how to use that feature, hence this is not supported.
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
Link: https://patch.msgid.link/b6ec9050339f8244ff898898a1cecc33b13a48fc.1727741563.git.daniel@makrotopia.org
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
drivers/net/phy/mxl-gpy.c | 218 ++++++++++++++++++++++++++++++++++++++
1 file changed, 218 insertions(+)
--- a/drivers/net/phy/mxl-gpy.c
+++ b/drivers/net/phy/mxl-gpy.c
@@ -38,6 +38,7 @@
#define PHY_MIISTAT 0x18 /* MII state */
#define PHY_IMASK 0x19 /* interrupt mask */
#define PHY_ISTAT 0x1A /* interrupt status */
+#define PHY_LED 0x1B /* LEDs */
#define PHY_FWV 0x1E /* firmware version */
#define PHY_MIISTAT_SPD_MASK GENMASK(2, 0)
@@ -61,6 +62,11 @@
PHY_IMASK_ADSC | \
PHY_IMASK_ANC)
+#define GPY_MAX_LEDS 4
+#define PHY_LED_POLARITY(idx) BIT(12 + (idx))
+#define PHY_LED_HWCONTROL(idx) BIT(8 + (idx))
+#define PHY_LED_ON(idx) BIT(idx)
+
#define PHY_FWV_REL_MASK BIT(15)
#define PHY_FWV_MAJOR_MASK GENMASK(11, 8)
#define PHY_FWV_MINOR_MASK GENMASK(7, 0)
@@ -72,6 +78,23 @@
#define PHY_MDI_MDI_X_CD 0x1
#define PHY_MDI_MDI_X_CROSS 0x0
+/* LED */
+#define VSPEC1_LED(idx) (1 + (idx))
+#define VSPEC1_LED_BLINKS GENMASK(15, 12)
+#define VSPEC1_LED_PULSE GENMASK(11, 8)
+#define VSPEC1_LED_CON GENMASK(7, 4)
+#define VSPEC1_LED_BLINKF GENMASK(3, 0)
+
+#define VSPEC1_LED_LINK10 BIT(0)
+#define VSPEC1_LED_LINK100 BIT(1)
+#define VSPEC1_LED_LINK1000 BIT(2)
+#define VSPEC1_LED_LINK2500 BIT(3)
+
+#define VSPEC1_LED_TXACT BIT(0)
+#define VSPEC1_LED_RXACT BIT(1)
+#define VSPEC1_LED_COL BIT(2)
+#define VSPEC1_LED_NO_CON BIT(3)
+
/* SGMII */
#define VSPEC1_SGMII_CTRL 0x08
#define VSPEC1_SGMII_CTRL_ANEN BIT(12) /* Aneg enable */
@@ -835,6 +858,156 @@ static int gpy115_loopback(struct phy_de
return genphy_soft_reset(phydev);
}
+static int gpy_led_brightness_set(struct phy_device *phydev,
+ u8 index, enum led_brightness value)
+{
+ int ret;
+
+ if (index >= GPY_MAX_LEDS)
+ return -EINVAL;
+
+ /* clear HWCONTROL and set manual LED state */
+ ret = phy_modify(phydev, PHY_LED,
+ ((value == LED_OFF) ? PHY_LED_HWCONTROL(index) : 0) |
+ PHY_LED_ON(index),
+ (value == LED_OFF) ? 0 : PHY_LED_ON(index));
+ if (ret)
+ return ret;
+
+ /* ToDo: set PWM brightness */
+
+ /* clear HW LED setup */
+ if (value == LED_OFF)
+ return phy_write_mmd(phydev, MDIO_MMD_VEND1, VSPEC1_LED(index), 0);
+ else
+ return 0;
+}
+
+static const unsigned long supported_triggers = (BIT(TRIGGER_NETDEV_LINK) |
+ BIT(TRIGGER_NETDEV_LINK_100) |
+ BIT(TRIGGER_NETDEV_LINK_1000) |
+ BIT(TRIGGER_NETDEV_LINK_2500) |
+ BIT(TRIGGER_NETDEV_RX) |
+ BIT(TRIGGER_NETDEV_TX));
+
+static int gpy_led_hw_is_supported(struct phy_device *phydev, u8 index,
+ unsigned long rules)
+{
+ if (index >= GPY_MAX_LEDS)
+ return -EINVAL;
+
+ /* All combinations of the supported triggers are allowed */
+ if (rules & ~supported_triggers)
+ return -EOPNOTSUPP;
+
+ return 0;
+}
+
+static int gpy_led_hw_control_get(struct phy_device *phydev, u8 index,
+ unsigned long *rules)
+{
+ int val;
+
+ if (index >= GPY_MAX_LEDS)
+ return -EINVAL;
+
+ val = phy_read_mmd(phydev, MDIO_MMD_VEND1, VSPEC1_LED(index));
+ if (val < 0)
+ return val;
+
+ if (FIELD_GET(VSPEC1_LED_CON, val) & VSPEC1_LED_LINK10)
+ *rules |= BIT(TRIGGER_NETDEV_LINK_10);
+
+ if (FIELD_GET(VSPEC1_LED_CON, val) & VSPEC1_LED_LINK100)
+ *rules |= BIT(TRIGGER_NETDEV_LINK_100);
+
+ if (FIELD_GET(VSPEC1_LED_CON, val) & VSPEC1_LED_LINK1000)
+ *rules |= BIT(TRIGGER_NETDEV_LINK_1000);
+
+ if (FIELD_GET(VSPEC1_LED_CON, val) & VSPEC1_LED_LINK2500)
+ *rules |= BIT(TRIGGER_NETDEV_LINK_2500);
+
+ if (FIELD_GET(VSPEC1_LED_CON, val) == (VSPEC1_LED_LINK10 |
+ VSPEC1_LED_LINK100 |
+ VSPEC1_LED_LINK1000 |
+ VSPEC1_LED_LINK2500))
+ *rules |= BIT(TRIGGER_NETDEV_LINK);
+
+ if (FIELD_GET(VSPEC1_LED_PULSE, val) & VSPEC1_LED_TXACT)
+ *rules |= BIT(TRIGGER_NETDEV_TX);
+
+ if (FIELD_GET(VSPEC1_LED_PULSE, val) & VSPEC1_LED_RXACT)
+ *rules |= BIT(TRIGGER_NETDEV_RX);
+
+ return 0;
+}
+
+static int gpy_led_hw_control_set(struct phy_device *phydev, u8 index,
+ unsigned long rules)
+{
+ u16 val = 0;
+ int ret;
+
+ if (index >= GPY_MAX_LEDS)
+ return -EINVAL;
+
+ if (rules & BIT(TRIGGER_NETDEV_LINK) ||
+ rules & BIT(TRIGGER_NETDEV_LINK_10))
+ val |= FIELD_PREP(VSPEC1_LED_CON, VSPEC1_LED_LINK10);
+
+ if (rules & BIT(TRIGGER_NETDEV_LINK) ||
+ rules & BIT(TRIGGER_NETDEV_LINK_100))
+ val |= FIELD_PREP(VSPEC1_LED_CON, VSPEC1_LED_LINK100);
+
+ if (rules & BIT(TRIGGER_NETDEV_LINK) ||
+ rules & BIT(TRIGGER_NETDEV_LINK_1000))
+ val |= FIELD_PREP(VSPEC1_LED_CON, VSPEC1_LED_LINK1000);
+
+ if (rules & BIT(TRIGGER_NETDEV_LINK) ||
+ rules & BIT(TRIGGER_NETDEV_LINK_2500))
+ val |= FIELD_PREP(VSPEC1_LED_CON, VSPEC1_LED_LINK2500);
+
+ if (rules & BIT(TRIGGER_NETDEV_TX))
+ val |= FIELD_PREP(VSPEC1_LED_PULSE, VSPEC1_LED_TXACT);
+
+ if (rules & BIT(TRIGGER_NETDEV_RX))
+ val |= FIELD_PREP(VSPEC1_LED_PULSE, VSPEC1_LED_RXACT);
+
+ /* allow RX/TX pulse without link indication */
+ if ((rules & BIT(TRIGGER_NETDEV_TX) || rules & BIT(TRIGGER_NETDEV_RX)) &&
+ !(val & VSPEC1_LED_CON))
+ val |= FIELD_PREP(VSPEC1_LED_PULSE, VSPEC1_LED_NO_CON) | VSPEC1_LED_CON;
+
+ ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, VSPEC1_LED(index), val);
+ if (ret)
+ return ret;
+
+ return phy_set_bits(phydev, PHY_LED, PHY_LED_HWCONTROL(index));
+}
+
+static int gpy_led_polarity_set(struct phy_device *phydev, int index,
+ unsigned long modes)
+{
+ bool active_low = false;
+ u32 mode;
+
+ if (index >= GPY_MAX_LEDS)
+ return -EINVAL;
+
+ for_each_set_bit(mode, &modes, __PHY_LED_MODES_NUM) {
+ switch (mode) {
+ case PHY_LED_ACTIVE_LOW:
+ active_low = true;
+ break;
+ default:
+ return -EINVAL;
+ }
+ }
+
+ return phy_modify(phydev, PHY_LED, PHY_LED_POLARITY(index),
+ active_low ? 0 : PHY_LED_POLARITY(index));
+}
+
static struct phy_driver gpy_drivers[] = {
{
PHY_ID_MATCH_MODEL(PHY_ID_GPY2xx),
@@ -852,6 +1025,11 @@ static struct phy_driver gpy_drivers[] =
.set_wol = gpy_set_wol,
.get_wol = gpy_get_wol,
.set_loopback = gpy_loopback,
+ .led_brightness_set = gpy_led_brightness_set,
+ .led_hw_is_supported = gpy_led_hw_is_supported,
+ .led_hw_control_get = gpy_led_hw_control_get,
+ .led_hw_control_set = gpy_led_hw_control_set,
+ .led_polarity_set = gpy_led_polarity_set,
},
{
.phy_id = PHY_ID_GPY115B,
@@ -870,6 +1048,11 @@ static struct phy_driver gpy_drivers[] =
.set_wol = gpy_set_wol,
.get_wol = gpy_get_wol,
.set_loopback = gpy115_loopback,
+ .led_brightness_set = gpy_led_brightness_set,
+ .led_hw_is_supported = gpy_led_hw_is_supported,
+ .led_hw_control_get = gpy_led_hw_control_get,
+ .led_hw_control_set = gpy_led_hw_control_set,
+ .led_polarity_set = gpy_led_polarity_set,
},
{
PHY_ID_MATCH_MODEL(PHY_ID_GPY115C),
@@ -887,6 +1070,11 @@ static struct phy_driver gpy_drivers[] =
.set_wol = gpy_set_wol,
.get_wol = gpy_get_wol,
.set_loopback = gpy115_loopback,
+ .led_brightness_set = gpy_led_brightness_set,
+ .led_hw_is_supported = gpy_led_hw_is_supported,
+ .led_hw_control_get = gpy_led_hw_control_get,
+ .led_hw_control_set = gpy_led_hw_control_set,
+ .led_polarity_set = gpy_led_polarity_set,
},
{
.phy_id = PHY_ID_GPY211B,
@@ -905,6 +1093,11 @@ static struct phy_driver gpy_drivers[] =
.set_wol = gpy_set_wol,
.get_wol = gpy_get_wol,
.set_loopback = gpy_loopback,
+ .led_brightness_set = gpy_led_brightness_set,
+ .led_hw_is_supported = gpy_led_hw_is_supported,
+ .led_hw_control_get = gpy_led_hw_control_get,
+ .led_hw_control_set = gpy_led_hw_control_set,
+ .led_polarity_set = gpy_led_polarity_set,
},
{
PHY_ID_MATCH_MODEL(PHY_ID_GPY211C),
@@ -922,6 +1115,11 @@ static struct phy_driver gpy_drivers[] =
.set_wol = gpy_set_wol,
.get_wol = gpy_get_wol,
.set_loopback = gpy_loopback,
+ .led_brightness_set = gpy_led_brightness_set,
+ .led_hw_is_supported = gpy_led_hw_is_supported,
+ .led_hw_control_get = gpy_led_hw_control_get,
+ .led_hw_control_set = gpy_led_hw_control_set,
+ .led_polarity_set = gpy_led_polarity_set,
},
{
.phy_id = PHY_ID_GPY212B,
@@ -940,6 +1138,11 @@ static struct phy_driver gpy_drivers[] =
.set_wol = gpy_set_wol,
.get_wol = gpy_get_wol,
.set_loopback = gpy_loopback,
+ .led_brightness_set = gpy_led_brightness_set,
+ .led_hw_is_supported = gpy_led_hw_is_supported,
+ .led_hw_control_get = gpy_led_hw_control_get,
+ .led_hw_control_set = gpy_led_hw_control_set,
+ .led_polarity_set = gpy_led_polarity_set,
},
{
PHY_ID_MATCH_MODEL(PHY_ID_GPY212C),
@@ -957,6 +1160,11 @@ static struct phy_driver gpy_drivers[] =
.set_wol = gpy_set_wol,
.get_wol = gpy_get_wol,
.set_loopback = gpy_loopback,
+ .led_brightness_set = gpy_led_brightness_set,
+ .led_hw_is_supported = gpy_led_hw_is_supported,
+ .led_hw_control_get = gpy_led_hw_control_get,
+ .led_hw_control_set = gpy_led_hw_control_set,
+ .led_polarity_set = gpy_led_polarity_set,
},
{
.phy_id = PHY_ID_GPY215B,
@@ -975,6 +1183,11 @@ static struct phy_driver gpy_drivers[] =
.set_wol = gpy_set_wol,
.get_wol = gpy_get_wol,
.set_loopback = gpy_loopback,
+ .led_brightness_set = gpy_led_brightness_set,
+ .led_hw_is_supported = gpy_led_hw_is_supported,
+ .led_hw_control_get = gpy_led_hw_control_get,
+ .led_hw_control_set = gpy_led_hw_control_set,
+ .led_polarity_set = gpy_led_polarity_set,
},
{
PHY_ID_MATCH_MODEL(PHY_ID_GPY215C),
@@ -992,6 +1205,11 @@ static struct phy_driver gpy_drivers[] =
.set_wol = gpy_set_wol,
.get_wol = gpy_get_wol,
.set_loopback = gpy_loopback,
+ .led_brightness_set = gpy_led_brightness_set,
+ .led_hw_is_supported = gpy_led_hw_is_supported,
+ .led_hw_control_get = gpy_led_hw_control_get,
+ .led_hw_control_set = gpy_led_hw_control_set,
+ .led_polarity_set = gpy_led_polarity_set,
},
{
PHY_ID_MATCH_MODEL(PHY_ID_GPY241B),

View file

@ -0,0 +1,28 @@
From f95b4725e796b12e5f347a0d161e1d3843142aa8 Mon Sep 17 00:00:00 2001
From: Daniel Golle <daniel@makrotopia.org>
Date: Fri, 4 Oct 2024 16:56:35 +0100
Subject: [PATCH] net: phy: mxl-gpy: add missing support for
TRIGGER_NETDEV_LINK_10
The PHY also support 10MBit/s links as well as the corresponding link
indication trigger to be offloaded. Add TRIGGER_NETDEV_LINK_10 to the
supported triggers.
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
Link: https://patch.msgid.link/cc5da0a989af8b0d49d823656d88053c4de2ab98.1728057367.git.daniel@makrotopia.org
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
drivers/net/phy/mxl-gpy.c | 1 +
1 file changed, 1 insertion(+)
--- a/drivers/net/phy/mxl-gpy.c
+++ b/drivers/net/phy/mxl-gpy.c
@@ -884,6 +884,7 @@ static int gpy_led_brightness_set(struct
}
static const unsigned long supported_triggers = (BIT(TRIGGER_NETDEV_LINK) |
+ BIT(TRIGGER_NETDEV_LINK_10) |
BIT(TRIGGER_NETDEV_LINK_100) |
BIT(TRIGGER_NETDEV_LINK_1000) |
BIT(TRIGGER_NETDEV_LINK_2500) |

View file

@ -0,0 +1,58 @@
From eb89c79c1b8f17fc1611540768678e60df89ac42 Mon Sep 17 00:00:00 2001
From: Daniel Golle <daniel@makrotopia.org>
Date: Thu, 10 Oct 2024 13:55:17 +0100
Subject: [PATCH 3/4] net: phy: mxl-gpy: correctly describe LED polarity
According the datasheet covering the LED (0x1b) register:
0B Active High LEDx pin driven high when activated
1B Active Low LEDx pin driven low when activated
Make use of the now available 'active-high' property and correctly
reflect the polarity setting which was previously inverted.
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
Link: https://patch.msgid.link/180ccafa837f09908b852a8a874a3808c5ecd2d0.1728558223.git.daniel@makrotopia.org
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
---
drivers/net/phy/mxl-gpy.c | 16 ++++++++++++----
1 file changed, 12 insertions(+), 4 deletions(-)
--- a/drivers/net/phy/mxl-gpy.c
+++ b/drivers/net/phy/mxl-gpy.c
@@ -989,7 +989,7 @@ static int gpy_led_hw_control_set(struct
static int gpy_led_polarity_set(struct phy_device *phydev, int index,
unsigned long modes)
{
- bool active_low = false;
+ bool force_active_low = false, force_active_high = false;
u32 mode;
if (index >= GPY_MAX_LEDS)
@@ -998,15 +998,23 @@ static int gpy_led_polarity_set(struct p
for_each_set_bit(mode, &modes, __PHY_LED_MODES_NUM) {
switch (mode) {
case PHY_LED_ACTIVE_LOW:
- active_low = true;
+ force_active_low = true;
+ break;
+ case PHY_LED_ACTIVE_HIGH:
+ force_active_high = true;
break;
default:
return -EINVAL;
}
}
- return phy_modify(phydev, PHY_LED, PHY_LED_POLARITY(index),
- active_low ? 0 : PHY_LED_POLARITY(index));
+ if (force_active_low)
+ return phy_set_bits(phydev, PHY_LED, PHY_LED_POLARITY(index));
+
+ if (force_active_high)
+ return phy_clear_bits(phydev, PHY_LED, PHY_LED_POLARITY(index));
+
+ unreachable();
}
static struct phy_driver gpy_drivers[] = {

View file

@ -0,0 +1,379 @@
From 1758af47b98c17da464cb45f476875150955dd48 Mon Sep 17 00:00:00 2001
From: Daniel Golle <daniel@makrotopia.org>
Date: Thu, 10 Oct 2024 13:55:29 +0100
Subject: [PATCH 4/4] net: phy: intel-xway: add support for PHY LEDs
The intel-xway PHY driver predates the PHY LED framework and currently
initializes all LED pins to equal default values.
Add PHY LED functions to the drivers and don't set default values if
LEDs are defined in device tree.
According the datasheets 3 LEDs are supported on all Intel XWAY PHYs.
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
Link: https://patch.msgid.link/81f4717ab9acf38f3239727a4540ae96fd01109b.1728558223.git.daniel@makrotopia.org
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
---
drivers/net/phy/intel-xway.c | 253 +++++++++++++++++++++++++++++++++--
1 file changed, 244 insertions(+), 9 deletions(-)
--- a/drivers/net/phy/intel-xway.c
+++ b/drivers/net/phy/intel-xway.c
@@ -151,6 +151,13 @@
#define XWAY_MMD_LED3H 0x01E8
#define XWAY_MMD_LED3L 0x01E9
+#define XWAY_GPHY_MAX_LEDS 3
+#define XWAY_GPHY_LED_INV(idx) BIT(12 + (idx))
+#define XWAY_GPHY_LED_EN(idx) BIT(8 + (idx))
+#define XWAY_GPHY_LED_DA(idx) BIT(idx)
+#define XWAY_MMD_LEDxH(idx) (XWAY_MMD_LED0H + 2 * (idx))
+#define XWAY_MMD_LEDxL(idx) (XWAY_MMD_LED0L + 2 * (idx))
+
#define PHY_ID_PHY11G_1_3 0x030260D1
#define PHY_ID_PHY22F_1_3 0x030260E1
#define PHY_ID_PHY11G_1_4 0xD565A400
@@ -229,20 +236,12 @@ static int xway_gphy_rgmii_init(struct p
XWAY_MDIO_MIICTRL_TXSKEW_MASK, val);
}
-static int xway_gphy_config_init(struct phy_device *phydev)
+static int xway_gphy_init_leds(struct phy_device *phydev)
{
int err;
u32 ledxh;
u32 ledxl;
- /* Mask all interrupts */
- err = phy_write(phydev, XWAY_MDIO_IMASK, 0);
- if (err)
- return err;
-
- /* Clear all pending interrupts */
- phy_read(phydev, XWAY_MDIO_ISTAT);
-
/* Ensure that integrated led function is enabled for all leds */
err = phy_write(phydev, XWAY_MDIO_LED,
XWAY_MDIO_LED_LED0_EN |
@@ -276,6 +275,26 @@ static int xway_gphy_config_init(struct
phy_write_mmd(phydev, MDIO_MMD_VEND2, XWAY_MMD_LED2H, ledxh);
phy_write_mmd(phydev, MDIO_MMD_VEND2, XWAY_MMD_LED2L, ledxl);
+ return 0;
+}
+
+static int xway_gphy_config_init(struct phy_device *phydev)
+{
+ struct device_node *np = phydev->mdio.dev.of_node;
+ int err;
+
+ /* Mask all interrupts */
+ err = phy_write(phydev, XWAY_MDIO_IMASK, 0);
+ if (err)
+ return err;
+
+ /* Use default LED configuration if 'leds' node isn't defined */
+ if (!of_get_child_by_name(np, "leds"))
+ xway_gphy_init_leds(phydev);
+
+ /* Clear all pending interrupts */
+ phy_read(phydev, XWAY_MDIO_ISTAT);
+
err = xway_gphy_rgmii_init(phydev);
if (err)
return err;
@@ -347,6 +366,172 @@ static irqreturn_t xway_gphy_handle_inte
return IRQ_HANDLED;
}
+static int xway_gphy_led_brightness_set(struct phy_device *phydev,
+ u8 index, enum led_brightness value)
+{
+ int ret;
+
+ if (index >= XWAY_GPHY_MAX_LEDS)
+ return -EINVAL;
+
+ /* clear EN and set manual LED state */
+ ret = phy_modify(phydev, XWAY_MDIO_LED,
+ ((value == LED_OFF) ? XWAY_GPHY_LED_EN(index) : 0) |
+ XWAY_GPHY_LED_DA(index),
+ (value == LED_OFF) ? 0 : XWAY_GPHY_LED_DA(index));
+ if (ret)
+ return ret;
+
+ /* clear HW LED setup */
+ if (value == LED_OFF) {
+ ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, XWAY_MMD_LEDxH(index), 0);
+ if (ret)
+ return ret;
+
+ return phy_write_mmd(phydev, MDIO_MMD_VEND2, XWAY_MMD_LEDxL(index), 0);
+ } else {
+ return 0;
+ }
+}
+
+static const unsigned long supported_triggers = (BIT(TRIGGER_NETDEV_LINK) |
+ BIT(TRIGGER_NETDEV_LINK_10) |
+ BIT(TRIGGER_NETDEV_LINK_100) |
+ BIT(TRIGGER_NETDEV_LINK_1000) |
+ BIT(TRIGGER_NETDEV_RX) |
+ BIT(TRIGGER_NETDEV_TX));
+
+static int xway_gphy_led_hw_is_supported(struct phy_device *phydev, u8 index,
+ unsigned long rules)
+{
+ if (index >= XWAY_GPHY_MAX_LEDS)
+ return -EINVAL;
+
+ /* activity triggers are not possible without combination with a link
+ * trigger.
+ */
+ if (rules & (BIT(TRIGGER_NETDEV_RX) | BIT(TRIGGER_NETDEV_TX)) &&
+ !(rules & (BIT(TRIGGER_NETDEV_LINK) |
+ BIT(TRIGGER_NETDEV_LINK_10) |
+ BIT(TRIGGER_NETDEV_LINK_100) |
+ BIT(TRIGGER_NETDEV_LINK_1000))))
+ return -EOPNOTSUPP;
+
+ /* All other combinations of the supported triggers are allowed */
+ if (rules & ~supported_triggers)
+ return -EOPNOTSUPP;
+
+ return 0;
+}
+
+static int xway_gphy_led_hw_control_get(struct phy_device *phydev, u8 index,
+ unsigned long *rules)
+{
+ int lval, hval;
+
+ if (index >= XWAY_GPHY_MAX_LEDS)
+ return -EINVAL;
+
+ hval = phy_read_mmd(phydev, MDIO_MMD_VEND2, XWAY_MMD_LEDxH(index));
+ if (hval < 0)
+ return hval;
+
+ lval = phy_read_mmd(phydev, MDIO_MMD_VEND2, XWAY_MMD_LEDxL(index));
+ if (lval < 0)
+ return lval;
+
+ if (hval & XWAY_MMD_LEDxH_CON_LINK10)
+ *rules |= BIT(TRIGGER_NETDEV_LINK_10);
+
+ if (hval & XWAY_MMD_LEDxH_CON_LINK100)
+ *rules |= BIT(TRIGGER_NETDEV_LINK_100);
+
+ if (hval & XWAY_MMD_LEDxH_CON_LINK1000)
+ *rules |= BIT(TRIGGER_NETDEV_LINK_1000);
+
+ if ((hval & XWAY_MMD_LEDxH_CON_LINK10) &&
+ (hval & XWAY_MMD_LEDxH_CON_LINK100) &&
+ (hval & XWAY_MMD_LEDxH_CON_LINK1000))
+ *rules |= BIT(TRIGGER_NETDEV_LINK);
+
+ if (lval & XWAY_MMD_LEDxL_PULSE_TXACT)
+ *rules |= BIT(TRIGGER_NETDEV_TX);
+
+ if (lval & XWAY_MMD_LEDxL_PULSE_RXACT)
+ *rules |= BIT(TRIGGER_NETDEV_RX);
+
+ return 0;
+}
+
+static int xway_gphy_led_hw_control_set(struct phy_device *phydev, u8 index,
+ unsigned long rules)
+{
+ u16 hval = 0, lval = 0;
+ int ret;
+
+ if (index >= XWAY_GPHY_MAX_LEDS)
+ return -EINVAL;
+
+ if (rules & BIT(TRIGGER_NETDEV_LINK) ||
+ rules & BIT(TRIGGER_NETDEV_LINK_10))
+ hval |= XWAY_MMD_LEDxH_CON_LINK10;
+
+ if (rules & BIT(TRIGGER_NETDEV_LINK) ||
+ rules & BIT(TRIGGER_NETDEV_LINK_100))
+ hval |= XWAY_MMD_LEDxH_CON_LINK100;
+
+ if (rules & BIT(TRIGGER_NETDEV_LINK) ||
+ rules & BIT(TRIGGER_NETDEV_LINK_1000))
+ hval |= XWAY_MMD_LEDxH_CON_LINK1000;
+
+ if (rules & BIT(TRIGGER_NETDEV_TX))
+ lval |= XWAY_MMD_LEDxL_PULSE_TXACT;
+
+ if (rules & BIT(TRIGGER_NETDEV_RX))
+ lval |= XWAY_MMD_LEDxL_PULSE_RXACT;
+
+ ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, XWAY_MMD_LEDxH(index), hval);
+ if (ret)
+ return ret;
+
+ ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, XWAY_MMD_LEDxL(index), lval);
+ if (ret)
+ return ret;
+
+ return phy_set_bits(phydev, XWAY_MDIO_LED, XWAY_GPHY_LED_EN(index));
+}
+
+static int xway_gphy_led_polarity_set(struct phy_device *phydev, int index,
+ unsigned long modes)
+{
+ bool force_active_low = false, force_active_high = false;
+ u32 mode;
+
+ if (index >= XWAY_GPHY_MAX_LEDS)
+ return -EINVAL;
+
+ for_each_set_bit(mode, &modes, __PHY_LED_MODES_NUM) {
+ switch (mode) {
+ case PHY_LED_ACTIVE_LOW:
+ force_active_low = true;
+ break;
+ case PHY_LED_ACTIVE_HIGH:
+ force_active_high = true;
+ break;
+ default:
+ return -EINVAL;
+ }
+ }
+
+ if (force_active_low)
+ return phy_set_bits(phydev, XWAY_MDIO_LED, XWAY_GPHY_LED_INV(index));
+
+ if (force_active_high)
+ return phy_clear_bits(phydev, XWAY_MDIO_LED, XWAY_GPHY_LED_INV(index));
+
+ unreachable();
+}
+
static struct phy_driver xway_gphy[] = {
{
.phy_id = PHY_ID_PHY11G_1_3,
@@ -359,6 +544,11 @@ static struct phy_driver xway_gphy[] = {
.config_intr = xway_gphy_config_intr,
.suspend = genphy_suspend,
.resume = genphy_resume,
+ .led_brightness_set = xway_gphy_led_brightness_set,
+ .led_hw_is_supported = xway_gphy_led_hw_is_supported,
+ .led_hw_control_get = xway_gphy_led_hw_control_get,
+ .led_hw_control_set = xway_gphy_led_hw_control_set,
+ .led_polarity_set = xway_gphy_led_polarity_set,
}, {
.phy_id = PHY_ID_PHY22F_1_3,
.phy_id_mask = 0xffffffff,
@@ -370,6 +560,11 @@ static struct phy_driver xway_gphy[] = {
.config_intr = xway_gphy_config_intr,
.suspend = genphy_suspend,
.resume = genphy_resume,
+ .led_brightness_set = xway_gphy_led_brightness_set,
+ .led_hw_is_supported = xway_gphy_led_hw_is_supported,
+ .led_hw_control_get = xway_gphy_led_hw_control_get,
+ .led_hw_control_set = xway_gphy_led_hw_control_set,
+ .led_polarity_set = xway_gphy_led_polarity_set,
}, {
.phy_id = PHY_ID_PHY11G_1_4,
.phy_id_mask = 0xffffffff,
@@ -381,6 +576,11 @@ static struct phy_driver xway_gphy[] = {
.config_intr = xway_gphy_config_intr,
.suspend = genphy_suspend,
.resume = genphy_resume,
+ .led_brightness_set = xway_gphy_led_brightness_set,
+ .led_hw_is_supported = xway_gphy_led_hw_is_supported,
+ .led_hw_control_get = xway_gphy_led_hw_control_get,
+ .led_hw_control_set = xway_gphy_led_hw_control_set,
+ .led_polarity_set = xway_gphy_led_polarity_set,
}, {
.phy_id = PHY_ID_PHY22F_1_4,
.phy_id_mask = 0xffffffff,
@@ -392,6 +592,11 @@ static struct phy_driver xway_gphy[] = {
.config_intr = xway_gphy_config_intr,
.suspend = genphy_suspend,
.resume = genphy_resume,
+ .led_brightness_set = xway_gphy_led_brightness_set,
+ .led_hw_is_supported = xway_gphy_led_hw_is_supported,
+ .led_hw_control_get = xway_gphy_led_hw_control_get,
+ .led_hw_control_set = xway_gphy_led_hw_control_set,
+ .led_polarity_set = xway_gphy_led_polarity_set,
}, {
.phy_id = PHY_ID_PHY11G_1_5,
.phy_id_mask = 0xffffffff,
@@ -402,6 +607,11 @@ static struct phy_driver xway_gphy[] = {
.config_intr = xway_gphy_config_intr,
.suspend = genphy_suspend,
.resume = genphy_resume,
+ .led_brightness_set = xway_gphy_led_brightness_set,
+ .led_hw_is_supported = xway_gphy_led_hw_is_supported,
+ .led_hw_control_get = xway_gphy_led_hw_control_get,
+ .led_hw_control_set = xway_gphy_led_hw_control_set,
+ .led_polarity_set = xway_gphy_led_polarity_set,
}, {
.phy_id = PHY_ID_PHY22F_1_5,
.phy_id_mask = 0xffffffff,
@@ -412,6 +622,11 @@ static struct phy_driver xway_gphy[] = {
.config_intr = xway_gphy_config_intr,
.suspend = genphy_suspend,
.resume = genphy_resume,
+ .led_brightness_set = xway_gphy_led_brightness_set,
+ .led_hw_is_supported = xway_gphy_led_hw_is_supported,
+ .led_hw_control_get = xway_gphy_led_hw_control_get,
+ .led_hw_control_set = xway_gphy_led_hw_control_set,
+ .led_polarity_set = xway_gphy_led_polarity_set,
}, {
.phy_id = PHY_ID_PHY11G_VR9_1_1,
.phy_id_mask = 0xffffffff,
@@ -422,6 +637,11 @@ static struct phy_driver xway_gphy[] = {
.config_intr = xway_gphy_config_intr,
.suspend = genphy_suspend,
.resume = genphy_resume,
+ .led_brightness_set = xway_gphy_led_brightness_set,
+ .led_hw_is_supported = xway_gphy_led_hw_is_supported,
+ .led_hw_control_get = xway_gphy_led_hw_control_get,
+ .led_hw_control_set = xway_gphy_led_hw_control_set,
+ .led_polarity_set = xway_gphy_led_polarity_set,
}, {
.phy_id = PHY_ID_PHY22F_VR9_1_1,
.phy_id_mask = 0xffffffff,
@@ -432,6 +652,11 @@ static struct phy_driver xway_gphy[] = {
.config_intr = xway_gphy_config_intr,
.suspend = genphy_suspend,
.resume = genphy_resume,
+ .led_brightness_set = xway_gphy_led_brightness_set,
+ .led_hw_is_supported = xway_gphy_led_hw_is_supported,
+ .led_hw_control_get = xway_gphy_led_hw_control_get,
+ .led_hw_control_set = xway_gphy_led_hw_control_set,
+ .led_polarity_set = xway_gphy_led_polarity_set,
}, {
.phy_id = PHY_ID_PHY11G_VR9_1_2,
.phy_id_mask = 0xffffffff,
@@ -442,6 +667,11 @@ static struct phy_driver xway_gphy[] = {
.config_intr = xway_gphy_config_intr,
.suspend = genphy_suspend,
.resume = genphy_resume,
+ .led_brightness_set = xway_gphy_led_brightness_set,
+ .led_hw_is_supported = xway_gphy_led_hw_is_supported,
+ .led_hw_control_get = xway_gphy_led_hw_control_get,
+ .led_hw_control_set = xway_gphy_led_hw_control_set,
+ .led_polarity_set = xway_gphy_led_polarity_set,
}, {
.phy_id = PHY_ID_PHY22F_VR9_1_2,
.phy_id_mask = 0xffffffff,
@@ -452,6 +682,11 @@ static struct phy_driver xway_gphy[] = {
.config_intr = xway_gphy_config_intr,
.suspend = genphy_suspend,
.resume = genphy_resume,
+ .led_brightness_set = xway_gphy_led_brightness_set,
+ .led_hw_is_supported = xway_gphy_led_hw_is_supported,
+ .led_hw_control_get = xway_gphy_led_hw_control_get,
+ .led_hw_control_set = xway_gphy_led_hw_control_set,
+ .led_polarity_set = xway_gphy_led_polarity_set,
},
};
module_phy_driver(xway_gphy);

View file

@ -8489,13 +8489,3 @@ CONFIG_PROC_MEM_ALWAYS_FORCE=y
# CONFIG_NET_DSA_MV88E6XXX_LEDS is not set
# CONFIG_OF_PARTITION is not set
# CONFIG_HISILICON_ERRATUM_162100801 is not set
# CONFIG_VIDEO_TW9900 is not set
# CONFIG_SND_UTIMER is not set
# CONFIG_SND_SOC_AK4619 is not set
# CONFIG_SND_SOC_AW87390 is not set
# CONFIG_SND_SOC_AW88399 is not set
# CONFIG_SND_SOC_CS530X_I2C is not set
# CONFIG_SND_SOC_ES8311 is not set
# CONFIG_SND_SOC_PCM6240 is not set
# CONFIG_SND_SOC_RTQ9128 is not set
# CONFIG_SND_SOC_MT6357 is not set

View file

@ -0,0 +1,91 @@
# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
%YAML 1.2
---
$id: http://devicetree.org/schemas/mtd/partitions/openwrt,uimage.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: OpenWrt variations of U-Boot Image partitions
maintainers:
- Bjørn Mork <bjorn@mork.no>
description: |
The image format defined by the boot loader "Das U-Boot" is often
modified or extended by device vendors. This defines a few optional
properties which can be used to describe such modifications.
# partition.txt defines common properties, but has not yet been
# converted to YAML
#allOf:
# - $ref: ../partition.yaml#
properties:
compatible:
items:
- enum:
- openwrt,uimage
- const: denx,uimage
openwrt,padding:
description: Number of padding bytes between header and data
$ref: /schemas/types.yaml#/definitions/uint32
default: 0
openwrt,ih-magic:
description: U-Boot Image Header magic number.
$ref: /schemas/types.yaml#/definitions/uint32
default: 0x27051956 # IH_MAGIC
openwrt,ih-type:
description: U-Boot Image type
$ref: /schemas/types.yaml#/definitions/uint32
default: 2 # IH_TYPE_KERNEL
openwrt,offset:
description:
Offset between partition start and U-Boot Image in bytes
$ref: /schemas/types.yaml#/definitions/uint32
default: 0
openwrt,partition-magic:
description:
Magic number found at the start of the partition. Will only be
validated if both this property and openwrt,offset is non-zero
$ref: /schemas/types.yaml#/definitions/uint32
default: 0
required:
- compatible
- reg
#unevaluatedProperties: false
additionalProperties: false
examples:
- |
// device with non-default magic
partition@300000 {
compatible = "openwrt,uimage", "denx,uimage";
reg = <0x00300000 0xe80000>;
label = "firmware";
openwrt,ih-magic = <0x4e474520>;
};
- |
// device with U-Boot Image at an offset, with a partition magic value
partition@70000 {
compatible = "openwrt,uimage", "denx,uimage";
reg = <0x00070000 0x00790000>;
label = "firmware";
openwrt,offset = <20>;
openwrt,partition-magic = <0x43535953>;
};
- |
// device using a non-default image type
#include "dt-bindings/mtd/partitions/uimage.h"
partition@6c0000 {
compatible = "openwrt,uimage", "denx,uimage";
reg = <0x6c0000 0x1900000>;
label = "firmware";
openwrt,ih-magic = <0x33373033>;
openwrt,ih-type = <IH_TYPE_FILESYSTEM>;
};

View file

@ -0,0 +1,110 @@
-------
ADM6996FC / ADM6996M switch chip driver
1. General information
This driver supports the FC and M models only. The ADM6996F and L are
completely different chips.
Support for the FC model is extremely limited at the moment. There is no VLAN
support as of yet. The driver will not offer an swconfig interface for the FC
chip.
1.1 VLAN IDs
It is possible to define 16 different VLANs. Every VLAN has an identifier, its
VLAN ID. It is easiest if you use at most VLAN IDs 0-15. In that case, the
swconfig based configuration is very straightforward. To define two VLANs with
IDs 4 and 5, you can invoke, for example:
# swconfig dev ethX vlan 4 set ports '0 1t 2 5t'
# swconfig dev ethX vlan 5 set ports '0t 1t 5t'
The swconfig framework will automatically invoke 'port Y set pvid Z' for every
port that is an untagged member of VLAN Y, setting its Primary VLAN ID. In
this example, ports 0 and 2 would get "pvid 4". The Primary VLAN ID of a port
is the VLAN ID associated with untagged packets coming in on that port.
But if you wish to use VLAN IDs outside the range 0-15, this automatic
behaviour of the swconfig framework becomes a problem. The 16 VLANs that
swconfig can configure on the ADM6996 also have a "vid" setting. By default,
this is the same as the number of the VLAN entry, to make the simple behaviour
above possible. To still support a VLAN with a VLAN ID higher than 15
(presumably because you are in a network where such VLAN IDs are already in
use), you can change the "vid" setting of the VLAN to anything in the range
0-1023. But suppose you did the following:
# swconfig dev ethX vlan 0 set vid 998
# swconfig dev ethX vlan 0 set ports '0 2 5t'
Now the swconfig framework will issue 'port 0 set pvid 0' and 'port 2 set pvid
0'. But the "pvid" should be set to 998, so you are responsible for manually
fixing this!
1.2 VLAN filtering
The switch is configured to apply source port filtering. This means that
packets are only accepted when the port the packets came in on is a member of
the VLAN the packet should go to.
Only membership of a VLAN is tested, it does not matter whether it is a tagged
or untagged membership.
For untagged packets, the destination VLAN is the Primary VLAN ID of the
incoming port. So if the PVID of a port is 0, but that port is not a member of
the VLAN with ID 0, this means that untagged packets on that port are dropped.
This can be used as a roundabout way of dropping untagged packets from a port,
a mode often referred to as "Admit only tagged packets".
1.3 Reset
The two supported chip models do not have a sofware-initiated reset. When the
driver is initialised, as well as when the 'reset' swconfig option is invoked,
the driver will set those registers it knows about and supports to the correct
default value. But there are a lot of registers in the chip that the driver
does not support. If something changed those registers, invoking 'reset' or
performing a warm reboot might still leave the chip in a "broken" state. Only
a hardware reset will bring it back in the default state.
2. Technical details on PHYs and the ADM6996
From the viewpoint of the Linux kernel, it is common that an Ethernet adapter
can be seen as a separate MAC entity and a separate PHY entity. The PHY entity
can be queried and set through registers accessible via an MDIO bus. A PHY
normally has a single address on that bus, in the range 0 through 31.
The ADM6996 has special-purpose registers in the range of PHYs 0 through 10.
Even though all these registers control a single ADM6996 chip, the Linux
kernel treats this as 11 separate PHYs. The driver will bind to these
addresses to prevent a different PHY driver from binding and corrupting these
registers.
What Linux sees as the PHY on address 0 is meant for the Ethernet MAC
connected to the CPU port of the ADM6996 switch chip (port 5). This is the
Ethernet MAC you will use to send and receive data through the switch.
The PHYs at addresses 16 through 20 map to the PHYs on ports 0 through 4 of
the switch chip. These can be accessed with the Generic PHY driver, as the
registers have the common layout.
If a second Ethernet MAC on your board is wired to the port 4 PHY, that MAC
needs to bind to PHY address 20 for the port to work correctly.
The ADM6996 switch driver will reset the ports 0 through 3 on startup and when
'reset' is invoked. This could clash with a different PHY driver if the kernel
binds a PHY driver to address 16 through 19.
If Linux binds a PHY on addresses 1 through 10 to an Ethernet MAC, the ADM6996
driver will simply always report a connected 100 Mbit/s full-duplex link for
that PHY, and provide no other functionality. This is most likely not what you
want. So if you see a message in your log
ethX: PHY overlaps ADM6996, providing fixed PHY yy.
This is most likely an indication that ethX will not work properly, and your
kernel needs to be configured to attach a different PHY to that Ethernet MAC.
Controlling the mapping between MACs and PHYs is usually done in platform- or
board-specific fixup code. The ADM6996 driver has no influence over this.

View file

@ -0,0 +1,5 @@
#
# Makefile for the Compex's MyLoader support on MIPS architecture
#
lib-y += myloader.o

View file

@ -0,0 +1,63 @@
/*
* Compex's MyLoader specific prom routines
*
* Copyright (C) 2007-2008 Gabor Juhos <juhosg@openwrt.org>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 as published
* by the Free Software Foundation.
*
*/
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/types.h>
#include <linux/string.h>
#include <asm/addrspace.h>
#include <asm/fw/myloader/myloader.h>
#define SYS_PARAMS_ADDR KSEG1ADDR(0x80000800)
#define BOARD_PARAMS_ADDR KSEG1ADDR(0x80000A00)
#define PART_TABLE_ADDR KSEG1ADDR(0x80000C00)
#define BOOT_PARAMS_ADDR KSEG1ADDR(0x80000E00)
static struct myloader_info myloader_info __initdata;
static int myloader_found __initdata;
struct myloader_info * __init myloader_get_info(void)
{
struct mylo_system_params *sysp;
struct mylo_board_params *boardp;
struct mylo_partition_table *parts;
if (myloader_found)
return &myloader_info;
sysp = (struct mylo_system_params *)(SYS_PARAMS_ADDR);
boardp = (struct mylo_board_params *)(BOARD_PARAMS_ADDR);
parts = (struct mylo_partition_table *)(PART_TABLE_ADDR);
printk(KERN_DEBUG "MyLoader: sysp=%08x, boardp=%08x, parts=%08x\n",
sysp->magic, boardp->magic, parts->magic);
/* Check for some magic numbers */
if (sysp->magic != MYLO_MAGIC_SYS_PARAMS ||
boardp->magic != MYLO_MAGIC_BOARD_PARAMS ||
le32_to_cpu(parts->magic) != MYLO_MAGIC_PARTITIONS)
return NULL;
printk(KERN_DEBUG "MyLoader: id=%04x:%04x, sub_id=%04x:%04x\n",
sysp->vid, sysp->did, sysp->svid, sysp->sdid);
myloader_info.vid = sysp->vid;
myloader_info.did = sysp->did;
myloader_info.svid = sysp->svid;
myloader_info.sdid = sysp->sdid;
memcpy(myloader_info.macs, boardp->addr, sizeof(myloader_info.macs));
myloader_found = 1;
return &myloader_info;
}

View file

@ -0,0 +1,534 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* BCMA Fallback SPROM Driver
*
* Copyright (C) 2020 Álvaro Fernández Rojas <noltari@gmail.com>
* Copyright (C) 2014 Jonas Gorski <jonas.gorski@gmail.com>
* Copyright (C) 2008 Maxime Bizon <mbizon@freebox.fr>
* Copyright (C) 2008 Florian Fainelli <f.fainelli@gmail.com>
*/
#include <linux/bcma/bcma.h>
#include <linux/etherdevice.h>
#include <linux/firmware.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/mtd/mtd.h>
#include <linux/of_net.h>
#include <linux/of_platform.h>
#include "fallback-sprom.h"
#define BCMA_FBS_MAX_SIZE 468
/* SPROM Extraction */
#define SPOFF(offset) ((offset) / sizeof(u16))
#define SPEX(_outvar, _offset, _mask, _shift) \
out->_outvar = ((in[SPOFF(_offset)] & (_mask)) >> (_shift))
#define SPEX32(_outvar, _offset, _mask, _shift) \
out->_outvar = ((((u32)in[SPOFF((_offset)+2)] << 16 | \
in[SPOFF(_offset)]) & (_mask)) >> (_shift))
#define SPEX_ARRAY8(_field, _offset, _mask, _shift) \
do { \
SPEX(_field[0], _offset + 0, _mask, _shift); \
SPEX(_field[1], _offset + 2, _mask, _shift); \
SPEX(_field[2], _offset + 4, _mask, _shift); \
SPEX(_field[3], _offset + 6, _mask, _shift); \
SPEX(_field[4], _offset + 8, _mask, _shift); \
SPEX(_field[5], _offset + 10, _mask, _shift); \
SPEX(_field[6], _offset + 12, _mask, _shift); \
SPEX(_field[7], _offset + 14, _mask, _shift); \
} while (0)
struct bcma_fbs {
struct device *dev;
struct list_head list;
struct ssb_sprom sprom;
u32 pci_bus;
u32 pci_dev;
bool devid_override;
};
static DEFINE_SPINLOCK(bcma_fbs_lock);
static struct list_head bcma_fbs_list = LIST_HEAD_INIT(bcma_fbs_list);
int bcma_get_fallback_sprom(struct bcma_bus *bus, struct ssb_sprom *out)
{
struct bcma_fbs *pos;
u32 pci_bus, pci_dev;
if (bus->hosttype != BCMA_HOSTTYPE_PCI)
return -ENOENT;
pci_bus = bus->host_pci->bus->number;
pci_dev = PCI_SLOT(bus->host_pci->devfn);
list_for_each_entry(pos, &bcma_fbs_list, list) {
if (pos->pci_bus != pci_bus ||
pos->pci_dev != pci_dev)
continue;
if (pos->devid_override)
bus->host_pci->device = pos->sprom.dev_id;
memcpy(out, &pos->sprom, sizeof(struct ssb_sprom));
dev_info(pos->dev, "requested by [%x:%x]",
pos->pci_bus, pos->pci_dev);
return 0;
}
pr_err("unable to fill SPROM for [%x:%x]\n", pci_bus, pci_dev);
return -EINVAL;
}
static s8 sprom_extract_antgain(const u16 *in, u16 offset, u16 mask, u16 shift)
{
u16 v;
u8 gain;
v = in[SPOFF(offset)];
gain = (v & mask) >> shift;
if (gain == 0xFF) {
gain = 8; /* If unset use 2dBm */
} else {
/* Q5.2 Fractional part is stored in 0xC0 */
gain = ((gain & 0xC0) >> 6) | ((gain & 0x3F) << 2);
}
return (s8)gain;
}
static void sprom_extract_r8(struct ssb_sprom *out, const u16 *in)
{
static const u16 pwr_info_offset[] = {
SSB_SROM8_PWR_INFO_CORE0, SSB_SROM8_PWR_INFO_CORE1,
SSB_SROM8_PWR_INFO_CORE2, SSB_SROM8_PWR_INFO_CORE3
};
u16 o;
int i;
BUILD_BUG_ON(ARRAY_SIZE(pwr_info_offset) !=
ARRAY_SIZE(out->core_pwr_info));
SPEX(board_rev, SSB_SPROM8_BOARDREV, ~0, 0);
SPEX(board_type, SSB_SPROM1_SPID, ~0, 0);
SPEX(txpid2g[0], SSB_SPROM4_TXPID2G01, SSB_SPROM4_TXPID2G0,
SSB_SPROM4_TXPID2G0_SHIFT);
SPEX(txpid2g[1], SSB_SPROM4_TXPID2G01, SSB_SPROM4_TXPID2G1,
SSB_SPROM4_TXPID2G1_SHIFT);
SPEX(txpid2g[2], SSB_SPROM4_TXPID2G23, SSB_SPROM4_TXPID2G2,
SSB_SPROM4_TXPID2G2_SHIFT);
SPEX(txpid2g[3], SSB_SPROM4_TXPID2G23, SSB_SPROM4_TXPID2G3,
SSB_SPROM4_TXPID2G3_SHIFT);
SPEX(txpid5gl[0], SSB_SPROM4_TXPID5GL01, SSB_SPROM4_TXPID5GL0,
SSB_SPROM4_TXPID5GL0_SHIFT);
SPEX(txpid5gl[1], SSB_SPROM4_TXPID5GL01, SSB_SPROM4_TXPID5GL1,
SSB_SPROM4_TXPID5GL1_SHIFT);
SPEX(txpid5gl[2], SSB_SPROM4_TXPID5GL23, SSB_SPROM4_TXPID5GL2,
SSB_SPROM4_TXPID5GL2_SHIFT);
SPEX(txpid5gl[3], SSB_SPROM4_TXPID5GL23, SSB_SPROM4_TXPID5GL3,
SSB_SPROM4_TXPID5GL3_SHIFT);
SPEX(txpid5g[0], SSB_SPROM4_TXPID5G01, SSB_SPROM4_TXPID5G0,
SSB_SPROM4_TXPID5G0_SHIFT);
SPEX(txpid5g[1], SSB_SPROM4_TXPID5G01, SSB_SPROM4_TXPID5G1,
SSB_SPROM4_TXPID5G1_SHIFT);
SPEX(txpid5g[2], SSB_SPROM4_TXPID5G23, SSB_SPROM4_TXPID5G2,
SSB_SPROM4_TXPID5G2_SHIFT);
SPEX(txpid5g[3], SSB_SPROM4_TXPID5G23, SSB_SPROM4_TXPID5G3,
SSB_SPROM4_TXPID5G3_SHIFT);
SPEX(txpid5gh[0], SSB_SPROM4_TXPID5GH01, SSB_SPROM4_TXPID5GH0,
SSB_SPROM4_TXPID5GH0_SHIFT);
SPEX(txpid5gh[1], SSB_SPROM4_TXPID5GH01, SSB_SPROM4_TXPID5GH1,
SSB_SPROM4_TXPID5GH1_SHIFT);
SPEX(txpid5gh[2], SSB_SPROM4_TXPID5GH23, SSB_SPROM4_TXPID5GH2,
SSB_SPROM4_TXPID5GH2_SHIFT);
SPEX(txpid5gh[3], SSB_SPROM4_TXPID5GH23, SSB_SPROM4_TXPID5GH3,
SSB_SPROM4_TXPID5GH3_SHIFT);
SPEX(boardflags_lo, SSB_SPROM8_BFLLO, ~0, 0);
SPEX(boardflags_hi, SSB_SPROM8_BFLHI, ~0, 0);
SPEX(boardflags2_lo, SSB_SPROM8_BFL2LO, ~0, 0);
SPEX(boardflags2_hi, SSB_SPROM8_BFL2HI, ~0, 0);
SPEX(alpha2[0], SSB_SPROM8_CCODE, 0xff00, 8);
SPEX(alpha2[1], SSB_SPROM8_CCODE, 0x00ff, 0);
/* Extract core's power info */
for (i = 0; i < ARRAY_SIZE(pwr_info_offset); i++) {
o = pwr_info_offset[i];
SPEX(core_pwr_info[i].itssi_2g, o + SSB_SROM8_2G_MAXP_ITSSI,
SSB_SPROM8_2G_ITSSI, SSB_SPROM8_2G_ITSSI_SHIFT);
SPEX(core_pwr_info[i].maxpwr_2g, o + SSB_SROM8_2G_MAXP_ITSSI,
SSB_SPROM8_2G_MAXP, 0);
SPEX(core_pwr_info[i].pa_2g[0], o + SSB_SROM8_2G_PA_0, ~0, 0);
SPEX(core_pwr_info[i].pa_2g[1], o + SSB_SROM8_2G_PA_1, ~0, 0);
SPEX(core_pwr_info[i].pa_2g[2], o + SSB_SROM8_2G_PA_2, ~0, 0);
SPEX(core_pwr_info[i].itssi_5g, o + SSB_SROM8_5G_MAXP_ITSSI,
SSB_SPROM8_5G_ITSSI, SSB_SPROM8_5G_ITSSI_SHIFT);
SPEX(core_pwr_info[i].maxpwr_5g, o + SSB_SROM8_5G_MAXP_ITSSI,
SSB_SPROM8_5G_MAXP, 0);
SPEX(core_pwr_info[i].maxpwr_5gh, o + SSB_SPROM8_5GHL_MAXP,
SSB_SPROM8_5GH_MAXP, 0);
SPEX(core_pwr_info[i].maxpwr_5gl, o + SSB_SPROM8_5GHL_MAXP,
SSB_SPROM8_5GL_MAXP, SSB_SPROM8_5GL_MAXP_SHIFT);
SPEX(core_pwr_info[i].pa_5gl[0], o + SSB_SROM8_5GL_PA_0, ~0, 0);
SPEX(core_pwr_info[i].pa_5gl[1], o + SSB_SROM8_5GL_PA_1, ~0, 0);
SPEX(core_pwr_info[i].pa_5gl[2], o + SSB_SROM8_5GL_PA_2, ~0, 0);
SPEX(core_pwr_info[i].pa_5g[0], o + SSB_SROM8_5G_PA_0, ~0, 0);
SPEX(core_pwr_info[i].pa_5g[1], o + SSB_SROM8_5G_PA_1, ~0, 0);
SPEX(core_pwr_info[i].pa_5g[2], o + SSB_SROM8_5G_PA_2, ~0, 0);
SPEX(core_pwr_info[i].pa_5gh[0], o + SSB_SROM8_5GH_PA_0, ~0, 0);
SPEX(core_pwr_info[i].pa_5gh[1], o + SSB_SROM8_5GH_PA_1, ~0, 0);
SPEX(core_pwr_info[i].pa_5gh[2], o + SSB_SROM8_5GH_PA_2, ~0, 0);
}
SPEX(fem.ghz2.tssipos, SSB_SPROM8_FEM2G, SSB_SROM8_FEM_TSSIPOS,
SSB_SROM8_FEM_TSSIPOS_SHIFT);
SPEX(fem.ghz2.extpa_gain, SSB_SPROM8_FEM2G, SSB_SROM8_FEM_EXTPA_GAIN,
SSB_SROM8_FEM_EXTPA_GAIN_SHIFT);
SPEX(fem.ghz2.pdet_range, SSB_SPROM8_FEM2G, SSB_SROM8_FEM_PDET_RANGE,
SSB_SROM8_FEM_PDET_RANGE_SHIFT);
SPEX(fem.ghz2.tr_iso, SSB_SPROM8_FEM2G, SSB_SROM8_FEM_TR_ISO,
SSB_SROM8_FEM_TR_ISO_SHIFT);
SPEX(fem.ghz2.antswlut, SSB_SPROM8_FEM2G, SSB_SROM8_FEM_ANTSWLUT,
SSB_SROM8_FEM_ANTSWLUT_SHIFT);
SPEX(fem.ghz5.tssipos, SSB_SPROM8_FEM5G, SSB_SROM8_FEM_TSSIPOS,
SSB_SROM8_FEM_TSSIPOS_SHIFT);
SPEX(fem.ghz5.extpa_gain, SSB_SPROM8_FEM5G, SSB_SROM8_FEM_EXTPA_GAIN,
SSB_SROM8_FEM_EXTPA_GAIN_SHIFT);
SPEX(fem.ghz5.pdet_range, SSB_SPROM8_FEM5G, SSB_SROM8_FEM_PDET_RANGE,
SSB_SROM8_FEM_PDET_RANGE_SHIFT);
SPEX(fem.ghz5.tr_iso, SSB_SPROM8_FEM5G, SSB_SROM8_FEM_TR_ISO,
SSB_SROM8_FEM_TR_ISO_SHIFT);
SPEX(fem.ghz5.antswlut, SSB_SPROM8_FEM5G, SSB_SROM8_FEM_ANTSWLUT,
SSB_SROM8_FEM_ANTSWLUT_SHIFT);
SPEX(ant_available_a, SSB_SPROM8_ANTAVAIL, SSB_SPROM8_ANTAVAIL_A,
SSB_SPROM8_ANTAVAIL_A_SHIFT);
SPEX(ant_available_bg, SSB_SPROM8_ANTAVAIL, SSB_SPROM8_ANTAVAIL_BG,
SSB_SPROM8_ANTAVAIL_BG_SHIFT);
SPEX(maxpwr_bg, SSB_SPROM8_MAXP_BG, SSB_SPROM8_MAXP_BG_MASK, 0);
SPEX(itssi_bg, SSB_SPROM8_MAXP_BG, SSB_SPROM8_ITSSI_BG,
SSB_SPROM8_ITSSI_BG_SHIFT);
SPEX(maxpwr_a, SSB_SPROM8_MAXP_A, SSB_SPROM8_MAXP_A_MASK, 0);
SPEX(itssi_a, SSB_SPROM8_MAXP_A, SSB_SPROM8_ITSSI_A,
SSB_SPROM8_ITSSI_A_SHIFT);
SPEX(maxpwr_ah, SSB_SPROM8_MAXP_AHL, SSB_SPROM8_MAXP_AH_MASK, 0);
SPEX(maxpwr_al, SSB_SPROM8_MAXP_AHL, SSB_SPROM8_MAXP_AL_MASK,
SSB_SPROM8_MAXP_AL_SHIFT);
SPEX(gpio0, SSB_SPROM8_GPIOA, SSB_SPROM8_GPIOA_P0, 0);
SPEX(gpio1, SSB_SPROM8_GPIOA, SSB_SPROM8_GPIOA_P1,
SSB_SPROM8_GPIOA_P1_SHIFT);
SPEX(gpio2, SSB_SPROM8_GPIOB, SSB_SPROM8_GPIOB_P2, 0);
SPEX(gpio3, SSB_SPROM8_GPIOB, SSB_SPROM8_GPIOB_P3,
SSB_SPROM8_GPIOB_P3_SHIFT);
SPEX(tri2g, SSB_SPROM8_TRI25G, SSB_SPROM8_TRI2G, 0);
SPEX(tri5g, SSB_SPROM8_TRI25G, SSB_SPROM8_TRI5G,
SSB_SPROM8_TRI5G_SHIFT);
SPEX(tri5gl, SSB_SPROM8_TRI5GHL, SSB_SPROM8_TRI5GL, 0);
SPEX(tri5gh, SSB_SPROM8_TRI5GHL, SSB_SPROM8_TRI5GH,
SSB_SPROM8_TRI5GH_SHIFT);
SPEX(rxpo2g, SSB_SPROM8_RXPO, SSB_SPROM8_RXPO2G,
SSB_SPROM8_RXPO2G_SHIFT);
SPEX(rxpo5g, SSB_SPROM8_RXPO, SSB_SPROM8_RXPO5G,
SSB_SPROM8_RXPO5G_SHIFT);
SPEX(rssismf2g, SSB_SPROM8_RSSIPARM2G, SSB_SPROM8_RSSISMF2G, 0);
SPEX(rssismc2g, SSB_SPROM8_RSSIPARM2G, SSB_SPROM8_RSSISMC2G,
SSB_SPROM8_RSSISMC2G_SHIFT);
SPEX(rssisav2g, SSB_SPROM8_RSSIPARM2G, SSB_SPROM8_RSSISAV2G,
SSB_SPROM8_RSSISAV2G_SHIFT);
SPEX(bxa2g, SSB_SPROM8_RSSIPARM2G, SSB_SPROM8_BXA2G,
SSB_SPROM8_BXA2G_SHIFT);
SPEX(rssismf5g, SSB_SPROM8_RSSIPARM5G, SSB_SPROM8_RSSISMF5G, 0);
SPEX(rssismc5g, SSB_SPROM8_RSSIPARM5G, SSB_SPROM8_RSSISMC5G,
SSB_SPROM8_RSSISMC5G_SHIFT);
SPEX(rssisav5g, SSB_SPROM8_RSSIPARM5G, SSB_SPROM8_RSSISAV5G,
SSB_SPROM8_RSSISAV5G_SHIFT);
SPEX(bxa5g, SSB_SPROM8_RSSIPARM5G, SSB_SPROM8_BXA5G,
SSB_SPROM8_BXA5G_SHIFT);
SPEX(pa0b0, SSB_SPROM8_PA0B0, ~0, 0);
SPEX(pa0b1, SSB_SPROM8_PA0B1, ~0, 0);
SPEX(pa0b2, SSB_SPROM8_PA0B2, ~0, 0);
SPEX(pa1b0, SSB_SPROM8_PA1B0, ~0, 0);
SPEX(pa1b1, SSB_SPROM8_PA1B1, ~0, 0);
SPEX(pa1b2, SSB_SPROM8_PA1B2, ~0, 0);
SPEX(pa1lob0, SSB_SPROM8_PA1LOB0, ~0, 0);
SPEX(pa1lob1, SSB_SPROM8_PA1LOB1, ~0, 0);
SPEX(pa1lob2, SSB_SPROM8_PA1LOB2, ~0, 0);
SPEX(pa1hib0, SSB_SPROM8_PA1HIB0, ~0, 0);
SPEX(pa1hib1, SSB_SPROM8_PA1HIB1, ~0, 0);
SPEX(pa1hib2, SSB_SPROM8_PA1HIB2, ~0, 0);
SPEX(cck2gpo, SSB_SPROM8_CCK2GPO, ~0, 0);
SPEX32(ofdm2gpo, SSB_SPROM8_OFDM2GPO, ~0, 0);
SPEX32(ofdm5glpo, SSB_SPROM8_OFDM5GLPO, ~0, 0);
SPEX32(ofdm5gpo, SSB_SPROM8_OFDM5GPO, ~0, 0);
SPEX32(ofdm5ghpo, SSB_SPROM8_OFDM5GHPO, ~0, 0);
/* Extract the antenna gain values. */
out->antenna_gain.a0 = sprom_extract_antgain(in,
SSB_SPROM8_AGAIN01,
SSB_SPROM8_AGAIN0,
SSB_SPROM8_AGAIN0_SHIFT);
out->antenna_gain.a1 = sprom_extract_antgain(in,
SSB_SPROM8_AGAIN01,
SSB_SPROM8_AGAIN1,
SSB_SPROM8_AGAIN1_SHIFT);
out->antenna_gain.a2 = sprom_extract_antgain(in,
SSB_SPROM8_AGAIN23,
SSB_SPROM8_AGAIN2,
SSB_SPROM8_AGAIN2_SHIFT);
out->antenna_gain.a3 = sprom_extract_antgain(in,
SSB_SPROM8_AGAIN23,
SSB_SPROM8_AGAIN3,
SSB_SPROM8_AGAIN3_SHIFT);
SPEX(leddc_on_time, SSB_SPROM8_LEDDC, SSB_SPROM8_LEDDC_ON,
SSB_SPROM8_LEDDC_ON_SHIFT);
SPEX(leddc_off_time, SSB_SPROM8_LEDDC, SSB_SPROM8_LEDDC_OFF,
SSB_SPROM8_LEDDC_OFF_SHIFT);
SPEX(txchain, SSB_SPROM8_TXRXC, SSB_SPROM8_TXRXC_TXCHAIN,
SSB_SPROM8_TXRXC_TXCHAIN_SHIFT);
SPEX(rxchain, SSB_SPROM8_TXRXC, SSB_SPROM8_TXRXC_RXCHAIN,
SSB_SPROM8_TXRXC_RXCHAIN_SHIFT);
SPEX(antswitch, SSB_SPROM8_TXRXC, SSB_SPROM8_TXRXC_SWITCH,
SSB_SPROM8_TXRXC_SWITCH_SHIFT);
SPEX(opo, SSB_SPROM8_OFDM2GPO, 0x00ff, 0);
SPEX_ARRAY8(mcs2gpo, SSB_SPROM8_2G_MCSPO, ~0, 0);
SPEX_ARRAY8(mcs5gpo, SSB_SPROM8_5G_MCSPO, ~0, 0);
SPEX_ARRAY8(mcs5glpo, SSB_SPROM8_5GL_MCSPO, ~0, 0);
SPEX_ARRAY8(mcs5ghpo, SSB_SPROM8_5GH_MCSPO, ~0, 0);
SPEX(rawtempsense, SSB_SPROM8_RAWTS, SSB_SPROM8_RAWTS_RAWTEMP,
SSB_SPROM8_RAWTS_RAWTEMP_SHIFT);
SPEX(measpower, SSB_SPROM8_RAWTS, SSB_SPROM8_RAWTS_MEASPOWER,
SSB_SPROM8_RAWTS_MEASPOWER_SHIFT);
SPEX(tempsense_slope, SSB_SPROM8_OPT_CORRX,
SSB_SPROM8_OPT_CORRX_TEMP_SLOPE,
SSB_SPROM8_OPT_CORRX_TEMP_SLOPE_SHIFT);
SPEX(tempcorrx, SSB_SPROM8_OPT_CORRX, SSB_SPROM8_OPT_CORRX_TEMPCORRX,
SSB_SPROM8_OPT_CORRX_TEMPCORRX_SHIFT);
SPEX(tempsense_option, SSB_SPROM8_OPT_CORRX,
SSB_SPROM8_OPT_CORRX_TEMP_OPTION,
SSB_SPROM8_OPT_CORRX_TEMP_OPTION_SHIFT);
SPEX(freqoffset_corr, SSB_SPROM8_HWIQ_IQSWP,
SSB_SPROM8_HWIQ_IQSWP_FREQ_CORR,
SSB_SPROM8_HWIQ_IQSWP_FREQ_CORR_SHIFT);
SPEX(iqcal_swp_dis, SSB_SPROM8_HWIQ_IQSWP,
SSB_SPROM8_HWIQ_IQSWP_IQCAL_SWP,
SSB_SPROM8_HWIQ_IQSWP_IQCAL_SWP_SHIFT);
SPEX(hw_iqcal_en, SSB_SPROM8_HWIQ_IQSWP, SSB_SPROM8_HWIQ_IQSWP_HW_IQCAL,
SSB_SPROM8_HWIQ_IQSWP_HW_IQCAL_SHIFT);
SPEX(bw40po, SSB_SPROM8_BW40PO, ~0, 0);
SPEX(cddpo, SSB_SPROM8_CDDPO, ~0, 0);
SPEX(stbcpo, SSB_SPROM8_STBCPO, ~0, 0);
SPEX(bwduppo, SSB_SPROM8_BWDUPPO, ~0, 0);
SPEX(tempthresh, SSB_SPROM8_THERMAL, SSB_SPROM8_THERMAL_TRESH,
SSB_SPROM8_THERMAL_TRESH_SHIFT);
SPEX(tempoffset, SSB_SPROM8_THERMAL, SSB_SPROM8_THERMAL_OFFSET,
SSB_SPROM8_THERMAL_OFFSET_SHIFT);
SPEX(phycal_tempdelta, SSB_SPROM8_TEMPDELTA,
SSB_SPROM8_TEMPDELTA_PHYCAL,
SSB_SPROM8_TEMPDELTA_PHYCAL_SHIFT);
SPEX(temps_period, SSB_SPROM8_TEMPDELTA, SSB_SPROM8_TEMPDELTA_PERIOD,
SSB_SPROM8_TEMPDELTA_PERIOD_SHIFT);
SPEX(temps_hysteresis, SSB_SPROM8_TEMPDELTA,
SSB_SPROM8_TEMPDELTA_HYSTERESIS,
SSB_SPROM8_TEMPDELTA_HYSTERESIS_SHIFT);
}
static int sprom_extract(struct bcma_fbs *priv, const u16 *in, u16 size)
{
struct ssb_sprom *out = &priv->sprom;
memset(out, 0, sizeof(*out));
out->revision = in[size - 1] & 0x00FF;
if (out->revision < 8 || out->revision > 11) {
dev_warn(priv->dev,
"Unsupported SPROM revision %d detected."
" Will extract v8\n",
out->revision);
out->revision = 8;
}
sprom_extract_r8(out, in);
return 0;
}
static void bcma_fbs_fixup(struct bcma_fbs *priv, u16 *sprom)
{
struct device_node *node = priv->dev->of_node;
u32 fixups, off, val;
int i = 0;
if (!of_get_property(node, "brcm,sprom-fixups", &fixups))
return;
fixups /= sizeof(u32);
dev_info(priv->dev, "patching SPROM with %u fixups...\n", fixups >> 1);
while (i < fixups) {
if (of_property_read_u32_index(node, "brcm,sprom-fixups",
i++, &off)) {
dev_err(priv->dev, "error reading fixup[%u] offset\n",
i - 1);
return;
}
if (of_property_read_u32_index(node, "brcm,sprom-fixups",
i++, &val)) {
dev_err(priv->dev, "error reading fixup[%u] value\n",
i - 1);
return;
}
dev_dbg(priv->dev, "fixup[%d]=0x%04x\n", off, val);
sprom[off] = val;
}
}
static bool sprom_override_devid(struct bcma_fbs *priv, struct ssb_sprom *out,
const u16 *in)
{
SPEX(dev_id, 0x0060, 0xFFFF, 0);
return !!out->dev_id;
}
static void bcma_fbs_set(struct bcma_fbs *priv, struct device_node *node)
{
struct ssb_sprom *sprom = &priv->sprom;
const struct firmware *fw;
const char *sprom_name;
int err;
if (of_property_read_string(node, "brcm,sprom", &sprom_name))
sprom_name = NULL;
if (sprom_name) {
err = request_firmware_direct(&fw, sprom_name, priv->dev);
if (err)
dev_err(priv->dev, "%s load error\n", sprom_name);
} else {
err = -ENOENT;
}
if (err) {
sprom->revision = 0x02;
sprom->board_rev = 0x0017;
sprom->country_code = 0x00;
sprom->ant_available_bg = 0x03;
sprom->pa0b0 = 0x15ae;
sprom->pa0b1 = 0xfa85;
sprom->pa0b2 = 0xfe8d;
sprom->pa1b0 = 0xffff;
sprom->pa1b1 = 0xffff;
sprom->pa1b2 = 0xffff;
sprom->gpio0 = 0xff;
sprom->gpio1 = 0xff;
sprom->gpio2 = 0xff;
sprom->gpio3 = 0xff;
sprom->maxpwr_bg = 0x4c;
sprom->itssi_bg = 0x00;
sprom->boardflags_lo = 0x2848;
sprom->boardflags_hi = 0x0000;
priv->devid_override = false;
dev_warn(priv->dev, "using basic SPROM\n");
} else {
size_t size = min(fw->size, (size_t) BCMA_FBS_MAX_SIZE);
u16 tmp_sprom[BCMA_FBS_MAX_SIZE >> 1];
u32 i, j;
for (i = 0, j = 0; i < size; i += 2, j++)
tmp_sprom[j] = (fw->data[i] << 8) | fw->data[i + 1];
release_firmware(fw);
bcma_fbs_fixup(priv, tmp_sprom);
sprom_extract(priv, tmp_sprom, size >> 1);
priv->devid_override = sprom_override_devid(priv, sprom,
tmp_sprom);
}
}
static int bcma_fbs_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *node = dev->of_node;
struct bcma_fbs *priv;
unsigned long flags;
u8 mac[ETH_ALEN];
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->dev = dev;
bcma_fbs_set(priv, node);
of_property_read_u32(node, "pci-bus", &priv->pci_bus);
of_property_read_u32(node, "pci-dev", &priv->pci_dev);
of_get_mac_address(node, mac);
if (is_valid_ether_addr(mac)) {
dev_info(dev, "mtd mac %pM\n", mac);
} else {
eth_random_addr(mac);
dev_info(dev, "random mac %pM\n", mac);
}
memcpy(priv->sprom.il0mac, mac, ETH_ALEN);
memcpy(priv->sprom.et0mac, mac, ETH_ALEN);
memcpy(priv->sprom.et1mac, mac, ETH_ALEN);
memcpy(priv->sprom.et2mac, mac, ETH_ALEN);
spin_lock_irqsave(&bcma_fbs_lock, flags);
list_add(&priv->list, &bcma_fbs_list);
spin_unlock_irqrestore(&bcma_fbs_lock, flags);
dev_info(dev, "registered SPROM for [%x:%x]\n",
priv->pci_bus, priv->pci_dev);
return 0;
}
static const struct of_device_id bcma_fbs_of_match[] = {
{ .compatible = "brcm,bcma-sprom", },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, bcma_fbs_of_match);
static struct platform_driver bcma_fbs_driver = {
.probe = bcma_fbs_probe,
.driver = {
.name = "bcma-sprom",
.of_match_table = bcma_fbs_of_match,
},
};
int __init bcma_fbs_register(void)
{
return platform_driver_register(&bcma_fbs_driver);
}

View file

@ -0,0 +1,7 @@
#ifndef _FALLBACK_SPROM_H
#define _FALLBACK_SPROM_H
int __init bcma_fbs_register(void);
int bcma_get_fallback_sprom(struct bcma_bus *dev, struct ssb_sprom *out);
#endif /* _FALLBACK_SPROM_H */

View file

@ -0,0 +1,112 @@
config MTD_SPLIT
def_bool n
help
Generic MTD split support.
config MTD_SPLIT_SUPPORT
def_bool MTD = y
comment "Rootfs partition parsers"
config MTD_SPLIT_SQUASHFS_ROOT
bool "Squashfs based root partition parser"
depends on MTD_SPLIT_SUPPORT
select MTD_SPLIT
help
This provides a parsing function which allows to detect the
offset and size of the unused portion of a rootfs partition
containing a squashfs.
comment "Firmware partition parsers"
config MTD_SPLIT_BCM63XX_FW
bool "BCM63xx firmware parser"
depends on MTD_SPLIT_SUPPORT
select MTD_SPLIT
config MTD_SPLIT_BCM_WFI_FW
bool "Broadcom Whole Flash Image parser"
depends on MTD_SPLIT_SUPPORT
select MTD_SPLIT
config MTD_SPLIT_CFE_BOOTFS
bool "Parser finding rootfs appended to the CFE bootfs"
depends on MTD_SPLIT_SUPPORT && (ARCH_BCM4908 || ARCH_BCMBCA)
select MTD_SPLIT
help
cferom on BCM4908 (and bcm63xx) uses JFFS2 bootfs partition
for storing kernel, cferam and some device specific files.
There isn't any straight way of storing rootfs so it gets
appended to the JFFS2 bootfs partition. Kernel needs to find
it and run init from it. This parser is responsible for
finding appended rootfs.
config MTD_SPLIT_SEAMA_FW
bool "Seama firmware parser"
depends on MTD_SPLIT_SUPPORT
select MTD_SPLIT
config MTD_SPLIT_WRGG_FW
bool "WRGG firmware parser"
depends on MTD_SPLIT_SUPPORT
select MTD_SPLIT
config MTD_SPLIT_UIMAGE_FW
bool "uImage based firmware partition parser"
depends on MTD_SPLIT_SUPPORT
select MTD_SPLIT
config MTD_SPLIT_FIT_FW
bool "FIT based firmware partition parser"
depends on MTD_SPLIT_SUPPORT
select MTD_SPLIT
config MTD_SPLIT_LZMA_FW
bool "LZMA compressed kernel based firmware partition parser"
depends on MTD_SPLIT_SUPPORT
select MTD_SPLIT
config MTD_SPLIT_TPLINK_FW
bool "TP-Link firmware parser"
depends on MTD_SPLIT_SUPPORT
select MTD_SPLIT
config MTD_SPLIT_TRX_FW
bool "TRX image based firmware partition parser"
depends on MTD_SPLIT_SUPPORT
select MTD_SPLIT
config MTD_SPLIT_BRNIMAGE_FW
bool "brnImage (brnboot image) firmware parser"
depends on MTD_SPLIT_SUPPORT
select MTD_SPLIT
config MTD_SPLIT_EVA_FW
bool "EVA image based firmware partition parser"
depends on MTD_SPLIT_SUPPORT
select MTD_SPLIT
config MTD_SPLIT_MINOR_FW
bool "Mikrotik NOR image based firmware partition parser"
depends on MTD_SPLIT_SUPPORT
select MTD_SPLIT
config MTD_SPLIT_JIMAGE_FW
bool "JBOOT Image based firmware partition parser"
depends on MTD_SPLIT_SUPPORT
select MTD_SPLIT
config MTD_SPLIT_ELF_FW
bool "ELF loader firmware partition parser"
depends on MTD_SPLIT_SUPPORT
select MTD_SPLIT
config MTD_SPLIT_H3C_VFS
bool "Parser finding rootfs appended to H3C VFS"
depends on MTD_SPLIT_SUPPORT
select MTD_SPLIT
config MTD_SPLIT_SEIL_FW
bool "IIJ SEIL firmware parser"
depends on MTD_SPLIT_SUPPORT
select MTD_SPLIT

View file

@ -0,0 +1,19 @@
obj-$(CONFIG_MTD_SPLIT) += mtdsplit.o
obj-$(CONFIG_MTD_SPLIT_BCM63XX_FW) += mtdsplit_bcm63xx.o
obj-$(CONFIG_MTD_SPLIT_BCM_WFI_FW) += mtdsplit_bcm_wfi.o
obj-$(CONFIG_MTD_SPLIT_CFE_BOOTFS) += mtdsplit_cfe_bootfs.o
obj-$(CONFIG_MTD_SPLIT_SEAMA_FW) += mtdsplit_seama.o
obj-$(CONFIG_MTD_SPLIT_SEIL_FW) += mtdsplit_seil.o
obj-$(CONFIG_MTD_SPLIT_SQUASHFS_ROOT) += mtdsplit_squashfs.o
obj-$(CONFIG_MTD_SPLIT_UIMAGE_FW) += mtdsplit_uimage.o
obj-$(CONFIG_MTD_SPLIT_FIT_FW) += mtdsplit_fit.o
obj-$(CONFIG_MTD_SPLIT_LZMA_FW) += mtdsplit_lzma.o
obj-$(CONFIG_MTD_SPLIT_TPLINK_FW) += mtdsplit_tplink.o
obj-$(CONFIG_MTD_SPLIT_TRX_FW) += mtdsplit_trx.o
obj-$(CONFIG_MTD_SPLIT_BRNIMAGE_FW) += mtdsplit_brnimage.o
obj-$(CONFIG_MTD_SPLIT_EVA_FW) += mtdsplit_eva.o
obj-$(CONFIG_MTD_SPLIT_WRGG_FW) += mtdsplit_wrgg.o
obj-$(CONFIG_MTD_SPLIT_MINOR_FW) += mtdsplit_minor.o
obj-$(CONFIG_MTD_SPLIT_JIMAGE_FW) += mtdsplit_jimage.o
obj-$(CONFIG_MTD_SPLIT_ELF_FW) += mtdsplit_elf.o
obj-$(CONFIG_MTD_SPLIT_H3C_VFS) += mtdsplit_h3c_vfs.o

View file

@ -0,0 +1,130 @@
/*
* Copyright (C) 2009-2013 Felix Fietkau <nbd@nbd.name>
* Copyright (C) 2009-2013 Gabor Juhos <juhosg@openwrt.org>
* Copyright (C) 2012 Jonas Gorski <jogo@openwrt.org>
* Copyright (C) 2013 Hauke Mehrtens <hauke@hauke-m.de>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 as published
* by the Free Software Foundation.
*
*/
#define pr_fmt(fmt) "mtdsplit: " fmt
#include <linux/export.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/magic.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/partitions.h>
#include <linux/byteorder/generic.h>
#include "mtdsplit.h"
#define UBI_EC_MAGIC 0x55424923 /* UBI# */
struct squashfs_super_block {
__le32 s_magic;
__le32 pad0[9];
__le64 bytes_used;
};
int mtd_get_squashfs_len(struct mtd_info *master,
size_t offset,
size_t *squashfs_len)
{
struct squashfs_super_block sb;
size_t retlen;
int err;
err = mtd_read(master, offset, sizeof(sb), &retlen, (void *)&sb);
if (err || (retlen != sizeof(sb))) {
pr_alert("error occured while reading from \"%s\"\n",
master->name);
return -EIO;
}
if (le32_to_cpu(sb.s_magic) != SQUASHFS_MAGIC) {
pr_alert("no squashfs found in \"%s\"\n", master->name);
return -EINVAL;
}
retlen = le64_to_cpu(sb.bytes_used);
if (retlen <= 0) {
pr_alert("squashfs is empty in \"%s\"\n", master->name);
return -ENODEV;
}
if (offset + retlen > master->size) {
pr_alert("squashfs has invalid size in \"%s\"\n",
master->name);
return -EINVAL;
}
*squashfs_len = retlen;
return 0;
}
EXPORT_SYMBOL_GPL(mtd_get_squashfs_len);
static ssize_t mtd_next_eb(struct mtd_info *mtd, size_t offset)
{
return mtd_rounddown_to_eb(offset, mtd) + mtd->erasesize;
}
int mtd_check_rootfs_magic(struct mtd_info *mtd, size_t offset,
enum mtdsplit_part_type *type)
{
u32 magic;
size_t retlen;
int ret;
ret = mtd_read(mtd, offset, sizeof(magic), &retlen,
(unsigned char *) &magic);
if (ret)
return ret;
if (retlen != sizeof(magic))
return -EIO;
if (le32_to_cpu(magic) == SQUASHFS_MAGIC) {
if (type)
*type = MTDSPLIT_PART_TYPE_SQUASHFS;
return 0;
} else if (magic == 0x19852003) {
if (type)
*type = MTDSPLIT_PART_TYPE_JFFS2;
return 0;
} else if (be32_to_cpu(magic) == UBI_EC_MAGIC) {
if (type)
*type = MTDSPLIT_PART_TYPE_UBI;
return 0;
}
return -EINVAL;
}
EXPORT_SYMBOL_GPL(mtd_check_rootfs_magic);
int mtd_find_rootfs_from(struct mtd_info *mtd,
size_t from,
size_t limit,
size_t *ret_offset,
enum mtdsplit_part_type *type)
{
size_t offset;
int err;
for (offset = from; offset < limit;
offset = mtd_next_eb(mtd, offset)) {
err = mtd_check_rootfs_magic(mtd, offset, type);
if (err)
continue;
*ret_offset = offset;
return 0;
}
return -ENODEV;
}
EXPORT_SYMBOL_GPL(mtd_find_rootfs_from);

View file

@ -0,0 +1,67 @@
/*
* Copyright (C) 2009-2013 Felix Fietkau <nbd@nbd.name>
* Copyright (C) 2009-2013 Gabor Juhos <juhosg@openwrt.org>
* Copyright (C) 2012 Jonas Gorski <jogo@openwrt.org>
* Copyright (C) 2013 Hauke Mehrtens <hauke@hauke-m.de>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 as published
* by the Free Software Foundation.
*
*/
#ifndef _MTDSPLIT_H
#define _MTDSPLIT_H
#define KERNEL_PART_NAME "kernel"
#define ROOTFS_PART_NAME "rootfs"
#define UBI_PART_NAME "ubi"
#define ROOTFS_SPLIT_NAME "rootfs_data"
enum mtdsplit_part_type {
MTDSPLIT_PART_TYPE_UNK = 0,
MTDSPLIT_PART_TYPE_SQUASHFS,
MTDSPLIT_PART_TYPE_JFFS2,
MTDSPLIT_PART_TYPE_UBI,
};
#ifdef CONFIG_MTD_SPLIT
int mtd_get_squashfs_len(struct mtd_info *master,
size_t offset,
size_t *squashfs_len);
int mtd_check_rootfs_magic(struct mtd_info *mtd, size_t offset,
enum mtdsplit_part_type *type);
int mtd_find_rootfs_from(struct mtd_info *mtd,
size_t from,
size_t limit,
size_t *ret_offset,
enum mtdsplit_part_type *type);
#else
static inline int mtd_get_squashfs_len(struct mtd_info *master,
size_t offset,
size_t *squashfs_len)
{
return -ENODEV;
}
static inline int mtd_check_rootfs_magic(struct mtd_info *mtd, size_t offset,
enum mtdsplit_part_type *type)
{
return -EINVAL;
}
static inline int mtd_find_rootfs_from(struct mtd_info *mtd,
size_t from,
size_t limit,
size_t *ret_offset,
enum mtdsplit_part_type *type)
{
return -ENODEV;
}
#endif /* CONFIG_MTD_SPLIT */
#endif /* _MTDSPLIT_H */

View file

@ -0,0 +1,186 @@
/*
* Firmware MTD split for BCM63XX, based on bcm63xxpart.c
*
* Copyright (C) 2006-2008 Florian Fainelli <florian@openwrt.org>
* Copyright (C) 2006-2008 Mike Albon <malbon@openwrt.org>
* Copyright (C) 2009-2010 Daniel Dickinson <openwrt@cshore.neomailbox.net>
* Copyright (C) 2011-2013 Jonas Gorski <jonas.gorski@gmail.com>
* Copyright (C) 2015 Simon Arlott <simon@fire.lp0.eu>
* Copyright (C) 2017 Álvaro Fernández Rojas <noltari@gmail.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 as published
* by the Free Software Foundation.
*
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/bcm963xx_tag.h>
#include <linux/crc32.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/byteorder/generic.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/partitions.h>
#include "mtdsplit.h"
/* Ensure strings read from flash structs are null terminated */
#define STR_NULL_TERMINATE(x) \
do { char *_str = (x); _str[sizeof(x) - 1] = 0; } while (0)
#define BCM63XX_NR_PARTS 2
static int bcm63xx_read_image_tag(struct mtd_info *master, loff_t offset,
struct bcm_tag *hdr)
{
int ret;
size_t retlen;
u32 computed_crc;
ret = mtd_read(master, offset, sizeof(*hdr), &retlen, (void *) hdr);
if (ret)
return ret;
if (retlen != sizeof(*hdr))
return -EIO;
computed_crc = crc32_le(IMAGETAG_CRC_START, (u8 *)hdr,
offsetof(struct bcm_tag, header_crc));
if (computed_crc == hdr->header_crc) {
STR_NULL_TERMINATE(hdr->board_id);
STR_NULL_TERMINATE(hdr->tag_version);
pr_info("CFE image tag found at 0x%llx with version %s, "
"board type %s\n", offset, hdr->tag_version,
hdr->board_id);
return 0;
} else {
pr_err("CFE image tag at 0x%llx CRC invalid "
"(expected %08x, actual %08x)\n",
offset, hdr->header_crc, computed_crc);
return 1;
}
}
static int bcm63xx_parse_partitions(struct mtd_info *master,
const struct mtd_partition **pparts,
struct bcm_tag *hdr)
{
struct mtd_partition *parts;
unsigned int flash_image_start;
unsigned int kernel_address;
unsigned int kernel_length;
size_t kernel_offset = 0, kernel_size = 0;
size_t rootfs_offset = 0, rootfs_size = 0;
int kernel_part, rootfs_part;
STR_NULL_TERMINATE(hdr->flash_image_start);
if (kstrtouint(hdr->flash_image_start, 10, &flash_image_start) ||
flash_image_start < BCM963XX_EXTENDED_SIZE) {
pr_err("invalid rootfs address: %*ph\n",
(int) sizeof(hdr->flash_image_start),
hdr->flash_image_start);
return -EINVAL;
}
STR_NULL_TERMINATE(hdr->kernel_address);
if (kstrtouint(hdr->kernel_address, 10, &kernel_address) ||
kernel_address < BCM963XX_EXTENDED_SIZE) {
pr_err("invalid kernel address: %*ph\n",
(int) sizeof(hdr->kernel_address), hdr->kernel_address);
return -EINVAL;
}
STR_NULL_TERMINATE(hdr->kernel_length);
if (kstrtouint(hdr->kernel_length, 10, &kernel_length) ||
!kernel_length) {
pr_err("invalid kernel length: %*ph\n",
(int) sizeof(hdr->kernel_length), hdr->kernel_length);
return -EINVAL;
}
kernel_offset = kernel_address - BCM963XX_EXTENDED_SIZE -
mtdpart_get_offset(master);
kernel_size = kernel_length;
if (flash_image_start < kernel_address) {
/* rootfs first */
rootfs_part = 0;
kernel_part = 1;
rootfs_offset = flash_image_start - BCM963XX_EXTENDED_SIZE -
mtdpart_get_offset(master);
rootfs_size = kernel_offset - rootfs_offset;
} else {
/* kernel first */
kernel_part = 0;
rootfs_part = 1;
rootfs_offset = kernel_offset + kernel_size;
rootfs_size = master->size - rootfs_offset;
}
if (mtd_check_rootfs_magic(master, rootfs_offset, NULL))
pr_warn("rootfs magic not found\n");
parts = kzalloc(BCM63XX_NR_PARTS * sizeof(*parts), GFP_KERNEL);
if (!parts)
return -ENOMEM;
parts[kernel_part].name = KERNEL_PART_NAME;
parts[kernel_part].offset = kernel_offset;
parts[kernel_part].size = kernel_size;
parts[rootfs_part].name = ROOTFS_PART_NAME;
parts[rootfs_part].offset = rootfs_offset;
parts[rootfs_part].size = rootfs_size;
*pparts = parts;
return BCM63XX_NR_PARTS;
}
static int mtdsplit_parse_bcm63xx(struct mtd_info *master,
const struct mtd_partition **pparts,
struct mtd_part_parser_data *data)
{
struct bcm_tag hdr;
loff_t offset;
if (mtd_type_is_nand(master))
return -EINVAL;
/* find bcm63xx_cfe image on erase block boundaries */
for (offset = 0; offset < master->size; offset += master->erasesize) {
if (!bcm63xx_read_image_tag(master, offset, (void *) &hdr))
return bcm63xx_parse_partitions(master, pparts,
(void *) &hdr);
}
return -EINVAL;
}
static const struct of_device_id mtdsplit_fit_of_match_table[] = {
{ .compatible = "brcm,bcm963xx-imagetag" },
{ },
};
static struct mtd_part_parser mtdsplit_bcm63xx_parser = {
.owner = THIS_MODULE,
.name = "bcm63xx-fw",
.of_match_table = mtdsplit_fit_of_match_table,
.parse_fn = mtdsplit_parse_bcm63xx,
.type = MTD_PARSER_TYPE_FIRMWARE,
};
static int __init mtdsplit_bcm63xx_init(void)
{
register_mtd_parser(&mtdsplit_bcm63xx_parser);
return 0;
}
module_init(mtdsplit_bcm63xx_init);

View file

@ -0,0 +1,535 @@
/*
* MTD split for Broadcom Whole Flash Image
*
* Copyright (C) 2020 Álvaro Fernández Rojas <noltari@gmail.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 as published
* by the Free Software Foundation.
*
*/
#define je16_to_cpu(x) ((x).v16)
#define je32_to_cpu(x) ((x).v32)
#include <linux/crc32.h>
#include <linux/init.h>
#include <linux/jffs2.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/byteorder/generic.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/partitions.h>
#include "mtdsplit.h"
#define char_to_num(c) ((c >= '0' && c <= '9') ? (c - '0') : (0))
#define BCM_WFI_PARTS 3
#define BCM_WFI_SPLIT_PARTS 2
#define CFERAM_NAME "cferam"
#define CFERAM_NAME_LEN (sizeof(CFERAM_NAME) - 1)
#define CFERAM_NAME_MAX_LEN 32
#define KERNEL_NAME "vmlinux.lz"
#define KERNEL_NAME_LEN (sizeof(KERNEL_NAME) - 1)
#define OPENWRT_NAME "1-openwrt"
#define OPENWRT_NAME_LEN (sizeof(OPENWRT_NAME) - 1)
#define UBI_MAGIC 0x55424923
#define CFE_MAGIC_PFX "cferam."
#define CFE_MAGIC_PFX_LEN (sizeof(CFE_MAGIC_PFX) - 1)
#define CFE_MAGIC "cferam.000"
#define CFE_MAGIC_LEN (sizeof(CFE_MAGIC) - 1)
#define SERCOMM_MAGIC_PFX "eRcOmM."
#define SERCOMM_MAGIC_PFX_LEN (sizeof(SERCOMM_MAGIC_PFX) - 1)
#define SERCOMM_MAGIC "eRcOmM.000"
#define SERCOMM_MAGIC_LEN (sizeof(SERCOMM_MAGIC) - 1)
#define PART_CFERAM "cferam"
#define PART_FIRMWARE "firmware"
#define PART_IMAGE_1 "img1"
#define PART_IMAGE_2 "img2"
static u32 jffs2_dirent_crc(struct jffs2_raw_dirent *node)
{
return crc32(0, node, sizeof(struct jffs2_raw_dirent) - 8);
}
static bool jffs2_dirent_valid(struct jffs2_raw_dirent *node)
{
return ((je16_to_cpu(node->magic) == JFFS2_MAGIC_BITMASK) &&
(je16_to_cpu(node->nodetype) == JFFS2_NODETYPE_DIRENT) &&
je32_to_cpu(node->ino) &&
je32_to_cpu(node->node_crc) == jffs2_dirent_crc(node));
}
static int jffs2_find_file(struct mtd_info *mtd, uint8_t *buf,
const char *name, size_t name_len,
loff_t *offs, loff_t size,
char **out_name, size_t *out_name_len)
{
const loff_t end = *offs + size;
struct jffs2_raw_dirent *node;
bool valid = false;
size_t retlen;
uint16_t magic;
int rc;
for (; *offs < end; *offs += mtd->erasesize) {
unsigned int block_offs = 0;
/* Skip CFE erased blocks */
rc = mtd_read(mtd, *offs, sizeof(magic), &retlen,
(void *) &magic);
if (rc || retlen != sizeof(magic)) {
continue;
}
/* Skip blocks not starting with JFFS2 magic */
if (magic != JFFS2_MAGIC_BITMASK)
continue;
/* Read full block */
rc = mtd_read(mtd, *offs, mtd->erasesize, &retlen,
(void *) buf);
if (rc)
return rc;
if (retlen != mtd->erasesize)
return -EINVAL;
while (block_offs < mtd->erasesize) {
node = (struct jffs2_raw_dirent *) &buf[block_offs];
if (!jffs2_dirent_valid(node)) {
block_offs += 4;
continue;
}
if (!memcmp(node->name, OPENWRT_NAME,
OPENWRT_NAME_LEN)) {
valid = true;
} else if (!memcmp(node->name, name, name_len)) {
if (!valid)
return -EINVAL;
if (out_name)
*out_name = kstrndup(node->name,
node->nsize,
GFP_KERNEL);
if (out_name_len)
*out_name_len = node->nsize;
return 0;
}
block_offs += je32_to_cpu(node->totlen);
block_offs = (block_offs + 0x3) & ~0x3;
}
}
return -ENOENT;
}
static int ubifs_find(struct mtd_info *mtd, loff_t *offs, loff_t size)
{
const loff_t end = *offs + size;
uint32_t magic;
size_t retlen;
int rc;
for (; *offs < end; *offs += mtd->erasesize) {
rc = mtd_read(mtd, *offs, sizeof(magic), &retlen,
(unsigned char *) &magic);
if (rc || retlen != sizeof(magic))
continue;
if (be32_to_cpu(magic) == UBI_MAGIC)
return 0;
}
return -ENOENT;
}
static int parse_bcm_wfi(struct mtd_info *master,
const struct mtd_partition **pparts,
uint8_t *buf, loff_t off, loff_t size, bool cfe_part)
{
struct device_node *mtd_node;
struct mtd_partition *parts;
loff_t cfe_off, kernel_off, rootfs_off;
unsigned int num_parts = BCM_WFI_PARTS, cur_part = 0;
const char *cferam_name = CFERAM_NAME;
size_t cferam_name_len;
int ret;
mtd_node = mtd_get_of_node(master);
if (mtd_node)
of_property_read_string(mtd_node, "brcm,cferam", &cferam_name);
cferam_name_len = strnlen(cferam_name, CFERAM_NAME_MAX_LEN);
if (cferam_name_len > 0)
cferam_name_len--;
if (cfe_part) {
num_parts++;
cfe_off = off;
ret = jffs2_find_file(master, buf, cferam_name,
cferam_name_len, &cfe_off,
size - (cfe_off - off), NULL, NULL);
if (ret)
return ret;
kernel_off = cfe_off + master->erasesize;
} else {
kernel_off = off;
}
ret = jffs2_find_file(master, buf, KERNEL_NAME, KERNEL_NAME_LEN,
&kernel_off, size - (kernel_off - off),
NULL, NULL);
if (ret)
return ret;
rootfs_off = kernel_off + master->erasesize;
ret = ubifs_find(master, &rootfs_off, size - (rootfs_off - off));
if (ret)
return ret;
parts = kzalloc(num_parts * sizeof(*parts), GFP_KERNEL);
if (!parts)
return -ENOMEM;
if (cfe_part) {
parts[cur_part].name = PART_CFERAM;
parts[cur_part].mask_flags = MTD_WRITEABLE;
parts[cur_part].offset = cfe_off;
parts[cur_part].size = kernel_off - cfe_off;
cur_part++;
}
parts[cur_part].name = PART_FIRMWARE;
parts[cur_part].offset = kernel_off;
parts[cur_part].size = size - (kernel_off - off);
cur_part++;
parts[cur_part].name = KERNEL_PART_NAME;
parts[cur_part].offset = kernel_off;
parts[cur_part].size = rootfs_off - kernel_off;
cur_part++;
parts[cur_part].name = UBI_PART_NAME;
parts[cur_part].offset = rootfs_off;
parts[cur_part].size = size - (rootfs_off - off);
cur_part++;
*pparts = parts;
return num_parts;
}
static int mtdsplit_parse_bcm_wfi(struct mtd_info *master,
const struct mtd_partition **pparts,
struct mtd_part_parser_data *data)
{
struct device_node *mtd_node;
bool cfe_part = true;
uint8_t *buf;
int ret;
mtd_node = mtd_get_of_node(master);
if (!mtd_node)
return -EINVAL;
buf = kzalloc(master->erasesize, GFP_KERNEL);
if (!buf)
return -ENOMEM;
if (of_property_read_bool(mtd_node, "brcm,no-cferam"))
cfe_part = false;
ret = parse_bcm_wfi(master, pparts, buf, 0, master->size, cfe_part);
kfree(buf);
return ret;
}
static const struct of_device_id mtdsplit_bcm_wfi_of_match[] = {
{ .compatible = "brcm,wfi" },
{ },
};
static struct mtd_part_parser mtdsplit_bcm_wfi_parser = {
.owner = THIS_MODULE,
.name = "bcm-wfi-fw",
.of_match_table = mtdsplit_bcm_wfi_of_match,
.parse_fn = mtdsplit_parse_bcm_wfi,
.type = MTD_PARSER_TYPE_FIRMWARE,
};
static int cferam_bootflag_value(const char *name, size_t name_len)
{
int rc = -ENOENT;
if (name &&
(name_len >= CFE_MAGIC_LEN) &&
!memcmp(name, CFE_MAGIC_PFX, CFE_MAGIC_PFX_LEN)) {
rc = char_to_num(name[CFE_MAGIC_PFX_LEN + 0]) * 100;
rc += char_to_num(name[CFE_MAGIC_PFX_LEN + 1]) * 10;
rc += char_to_num(name[CFE_MAGIC_PFX_LEN + 2]) * 1;
}
return rc;
}
static int mtdsplit_parse_bcm_wfi_split(struct mtd_info *master,
const struct mtd_partition **pparts,
struct mtd_part_parser_data *data)
{
struct mtd_partition *parts;
loff_t cfe_off;
loff_t img1_off = 0;
loff_t img2_off = master->size / 2;
loff_t img1_size = (img2_off - img1_off);
loff_t img2_size = (master->size - img2_off);
loff_t active_off, inactive_off;
loff_t active_size, inactive_size;
const char *inactive_name;
uint8_t *buf;
char *cfe1_name = NULL, *cfe2_name = NULL;
size_t cfe1_size = 0, cfe2_size = 0;
int ret;
int bf1, bf2;
buf = kzalloc(master->erasesize, GFP_KERNEL);
if (!buf)
return -ENOMEM;
cfe_off = img1_off;
ret = jffs2_find_file(master, buf, CFERAM_NAME, CFERAM_NAME_LEN,
&cfe_off, img1_size, &cfe1_name, &cfe1_size);
cfe_off = img2_off;
ret = jffs2_find_file(master, buf, CFERAM_NAME, CFERAM_NAME_LEN,
&cfe_off, img2_size, &cfe2_name, &cfe2_size);
bf1 = cferam_bootflag_value(cfe1_name, cfe1_size);
if (bf1 >= 0)
printk("cferam: bootflag1=%d\n", bf1);
bf2 = cferam_bootflag_value(cfe2_name, cfe2_size);
if (bf2 >= 0)
printk("cferam: bootflag2=%d\n", bf2);
kfree(cfe1_name);
kfree(cfe2_name);
if (bf1 >= bf2) {
active_off = img1_off;
active_size = img1_size;
inactive_off = img2_off;
inactive_size = img2_size;
inactive_name = PART_IMAGE_2;
} else {
active_off = img2_off;
active_size = img2_size;
inactive_off = img1_off;
inactive_size = img1_size;
inactive_name = PART_IMAGE_1;
}
ret = parse_bcm_wfi(master, pparts, buf, active_off, active_size, true);
kfree(buf);
if (ret > 0) {
parts = kzalloc((ret + 1) * sizeof(*parts), GFP_KERNEL);
if (!parts)
return -ENOMEM;
memcpy(parts, *pparts, ret * sizeof(*parts));
kfree(*pparts);
parts[ret].name = inactive_name;
parts[ret].offset = inactive_off;
parts[ret].size = inactive_size;
ret++;
*pparts = parts;
} else {
parts = kzalloc(BCM_WFI_SPLIT_PARTS * sizeof(*parts), GFP_KERNEL);
parts[0].name = PART_IMAGE_1;
parts[0].offset = img1_off;
parts[0].size = img1_size;
parts[1].name = PART_IMAGE_2;
parts[1].offset = img2_off;
parts[1].size = img2_size;
*pparts = parts;
}
return ret;
}
static const struct of_device_id mtdsplit_bcm_wfi_split_of_match[] = {
{ .compatible = "brcm,wfi-split" },
{ },
};
static struct mtd_part_parser mtdsplit_bcm_wfi_split_parser = {
.owner = THIS_MODULE,
.name = "bcm-wfi-split-fw",
.of_match_table = mtdsplit_bcm_wfi_split_of_match,
.parse_fn = mtdsplit_parse_bcm_wfi_split,
.type = MTD_PARSER_TYPE_FIRMWARE,
};
static int sercomm_bootflag_value(struct mtd_info *mtd, uint8_t *buf)
{
size_t retlen;
loff_t offs;
int rc;
for (offs = 0; offs < mtd->size; offs += mtd->erasesize) {
rc = mtd_read(mtd, offs, SERCOMM_MAGIC_LEN, &retlen, buf);
if (rc || retlen != SERCOMM_MAGIC_LEN)
continue;
if (memcmp(buf, SERCOMM_MAGIC_PFX, SERCOMM_MAGIC_PFX_LEN))
continue;
rc = char_to_num(buf[SERCOMM_MAGIC_PFX_LEN + 0]) * 100;
rc += char_to_num(buf[SERCOMM_MAGIC_PFX_LEN + 1]) * 10;
rc += char_to_num(buf[SERCOMM_MAGIC_PFX_LEN + 2]) * 1;
return rc;
}
return -ENOENT;
}
static int mtdsplit_parse_ser_wfi(struct mtd_info *master,
const struct mtd_partition **pparts,
struct mtd_part_parser_data *data)
{
struct mtd_partition *parts;
struct mtd_info *mtd_bf1, *mtd_bf2;
loff_t img1_off = 0;
loff_t img2_off = master->size / 2;
loff_t img1_size = (img2_off - img1_off);
loff_t img2_size = (master->size - img2_off);
loff_t active_off, inactive_off;
loff_t active_size, inactive_size;
const char *inactive_name;
uint8_t *buf;
int bf1, bf2;
int ret;
mtd_bf1 = get_mtd_device_nm("bootflag1");
if (IS_ERR(mtd_bf1))
return -ENOENT;
mtd_bf2 = get_mtd_device_nm("bootflag2");
if (IS_ERR(mtd_bf2))
return -ENOENT;
buf = kzalloc(master->erasesize, GFP_KERNEL);
if (!buf)
return -ENOMEM;
bf1 = sercomm_bootflag_value(mtd_bf1, buf);
if (bf1 >= 0)
printk("sercomm: bootflag1=%d\n", bf1);
bf2 = sercomm_bootflag_value(mtd_bf2, buf);
if (bf2 >= 0)
printk("sercomm: bootflag2=%d\n", bf2);
if (bf1 == bf2 && bf2 >= 0) {
struct erase_info bf_erase;
bf2 = -ENOENT;
bf_erase.addr = 0;
bf_erase.len = mtd_bf2->size;
mtd_erase(mtd_bf2, &bf_erase);
}
if (bf1 >= bf2) {
active_off = img1_off;
active_size = img1_size;
inactive_off = img2_off;
inactive_size = img2_size;
inactive_name = PART_IMAGE_2;
} else {
active_off = img2_off;
active_size = img2_size;
inactive_off = img1_off;
inactive_size = img1_size;
inactive_name = PART_IMAGE_1;
}
ret = parse_bcm_wfi(master, pparts, buf, active_off, active_size, false);
kfree(buf);
if (ret > 0) {
parts = kzalloc((ret + 1) * sizeof(*parts), GFP_KERNEL);
if (!parts)
return -ENOMEM;
memcpy(parts, *pparts, ret * sizeof(*parts));
kfree(*pparts);
parts[ret].name = inactive_name;
parts[ret].offset = inactive_off;
parts[ret].size = inactive_size;
ret++;
*pparts = parts;
} else {
parts = kzalloc(BCM_WFI_SPLIT_PARTS * sizeof(*parts), GFP_KERNEL);
parts[0].name = PART_IMAGE_1;
parts[0].offset = img1_off;
parts[0].size = img1_size;
parts[1].name = PART_IMAGE_2;
parts[1].offset = img2_off;
parts[1].size = img2_size;
*pparts = parts;
}
return ret;
}
static const struct of_device_id mtdsplit_ser_wfi_of_match[] = {
{ .compatible = "sercomm,wfi" },
{ },
};
static struct mtd_part_parser mtdsplit_ser_wfi_parser = {
.owner = THIS_MODULE,
.name = "ser-wfi-fw",
.of_match_table = mtdsplit_ser_wfi_of_match,
.parse_fn = mtdsplit_parse_ser_wfi,
.type = MTD_PARSER_TYPE_FIRMWARE,
};
static int __init mtdsplit_bcm_wfi_init(void)
{
register_mtd_parser(&mtdsplit_bcm_wfi_parser);
register_mtd_parser(&mtdsplit_bcm_wfi_split_parser);
register_mtd_parser(&mtdsplit_ser_wfi_parser);
return 0;
}
module_init(mtdsplit_bcm_wfi_init);

View file

@ -0,0 +1,104 @@
/*
* Copyright (C) 2012 John Crispin <blogic@openwrt.org>
* Copyright (C) 2015 Martin Blumenstingl <martin.blumenstingl@googlemail.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 as published
* by the Free Software Foundation.
*
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/partitions.h>
#include <linux/byteorder/generic.h>
#include "mtdsplit.h"
#define BRNIMAGE_NR_PARTS 2
#define BRNIMAGE_ALIGN_BYTES 0x400
#define BRNIMAGE_FOOTER_SIZE 12
#define BRNIMAGE_MIN_OVERHEAD (BRNIMAGE_FOOTER_SIZE)
#define BRNIMAGE_MAX_OVERHEAD (BRNIMAGE_ALIGN_BYTES + BRNIMAGE_FOOTER_SIZE)
static int mtdsplit_parse_brnimage(struct mtd_info *master,
const struct mtd_partition **pparts,
struct mtd_part_parser_data *data)
{
struct mtd_partition *parts;
uint32_t buf;
unsigned long rootfs_offset, rootfs_size, kernel_size;
size_t len;
int ret = 0;
for (rootfs_offset = 0; rootfs_offset < master->size;
rootfs_offset += BRNIMAGE_ALIGN_BYTES) {
ret = mtd_check_rootfs_magic(master, rootfs_offset, NULL);
if (!ret)
break;
}
if (ret)
return ret;
if (rootfs_offset >= master->size)
return -EINVAL;
ret = mtd_read(master, rootfs_offset - BRNIMAGE_FOOTER_SIZE, 4, &len,
(void *)&buf);
if (ret)
return ret;
if (len != 4)
return -EIO;
kernel_size = le32_to_cpu(buf);
if (kernel_size > (rootfs_offset - BRNIMAGE_MIN_OVERHEAD))
return -EINVAL;
if (kernel_size < (rootfs_offset - BRNIMAGE_MAX_OVERHEAD))
return -EINVAL;
/*
* The footer must be untouched as it contains the checksum of the
* original brnImage (kernel + squashfs)!
*/
rootfs_size = master->size - rootfs_offset - BRNIMAGE_FOOTER_SIZE;
parts = kzalloc(BRNIMAGE_NR_PARTS * sizeof(*parts), GFP_KERNEL);
if (!parts)
return -ENOMEM;
parts[0].name = KERNEL_PART_NAME;
parts[0].offset = 0;
parts[0].size = kernel_size;
parts[1].name = ROOTFS_PART_NAME;
parts[1].offset = rootfs_offset;
parts[1].size = rootfs_size;
*pparts = parts;
return BRNIMAGE_NR_PARTS;
}
static struct mtd_part_parser mtdsplit_brnimage_parser = {
.owner = THIS_MODULE,
.name = "brnimage-fw",
.parse_fn = mtdsplit_parse_brnimage,
.type = MTD_PARSER_TYPE_FIRMWARE,
};
static int __init mtdsplit_brnimage_init(void)
{
register_mtd_parser(&mtdsplit_brnimage_parser);
return 0;
}
subsys_initcall(mtdsplit_brnimage_init);

View file

@ -0,0 +1,90 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2021 Rafał Miłecki <rafal@milecki.pl>
*/
#include <linux/init.h>
#include <linux/jffs2.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/partitions.h>
#include <linux/of.h>
#include <linux/slab.h>
#include "mtdsplit.h"
#define je16_to_cpu(x) ((x).v16)
#define je32_to_cpu(x) ((x).v32)
#define NR_PARTS 2
static int mtdsplit_cfe_bootfs_parse(struct mtd_info *mtd,
const struct mtd_partition **pparts,
struct mtd_part_parser_data *data)
{
struct jffs2_raw_dirent node;
enum mtdsplit_part_type type;
struct mtd_partition *parts;
size_t rootfs_offset;
size_t retlen;
size_t offset;
int err;
/* Don't parse backup partitions */
if (strcmp(mtd->name, "firmware"))
return -EINVAL;
/* Find the end of JFFS2 bootfs partition */
offset = 0;
do {
err = mtd_read(mtd, offset, sizeof(node), &retlen, (void *)&node);
if (err || retlen != sizeof(node))
break;
if (je16_to_cpu(node.magic) != JFFS2_MAGIC_BITMASK)
break;
offset += je32_to_cpu(node.totlen);
offset = (offset + 0x3) & ~0x3;
} while (offset < mtd->size);
/* Find rootfs partition that follows the bootfs */
err = mtd_find_rootfs_from(mtd, mtd->erasesize, mtd->size, &rootfs_offset, &type);
if (err)
return err;
parts = kzalloc(NR_PARTS * sizeof(*parts), GFP_KERNEL);
if (!parts)
return -ENOMEM;
parts[0].name = "bootfs";
parts[0].offset = 0;
parts[0].size = rootfs_offset;
if (type == MTDSPLIT_PART_TYPE_UBI)
parts[1].name = UBI_PART_NAME;
else
parts[1].name = ROOTFS_PART_NAME;
parts[1].offset = rootfs_offset;
parts[1].size = mtd->size - rootfs_offset;
*pparts = parts;
return NR_PARTS;
}
static const struct of_device_id mtdsplit_cfe_bootfs_of_match_table[] = {
{ .compatible = "brcm,bcm4908-firmware" },
{},
};
MODULE_DEVICE_TABLE(of, mtdsplit_cfe_bootfs_of_match_table);
static struct mtd_part_parser mtdsplit_cfe_bootfs_parser = {
.owner = THIS_MODULE,
.name = "cfe-bootfs",
.of_match_table = mtdsplit_cfe_bootfs_of_match_table,
.parse_fn = mtdsplit_cfe_bootfs_parse,
};
module_mtd_part_parser(mtdsplit_cfe_bootfs_parser);

View file

@ -0,0 +1,287 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* MTD splitter for ELF loader firmware partitions
*
* Copyright (C) 2020 Sander Vanheule <sander@svanheule.net>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; version 2.
*
* To parse the ELF kernel loader, a small ELF parser is used that can
* handle both ELF32 or ELF64 class loaders. The splitter assumes that the
* kernel is always located before the rootfs, whether it is embedded in the
* loader or not.
*
* The kernel image is preferably embedded inside the ELF loader, so the end
* of the loader equals the end of the kernel partition. This is due to the
* way mtd_find_rootfs_from searches for the the rootfs:
* - if the kernel image is embedded in the loader, the appended rootfs may
* follow the loader immediately, within the same erase block.
* - if the kernel image is not embedded in the loader, but placed at some
* offset behind the loader (OKLI-style loader), the rootfs must be
* aligned to an erase-block after the loader and kernel image.
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/partitions.h>
#include <linux/of.h>
#include <linux/byteorder/generic.h>
#include "mtdsplit.h"
#define ELF_NR_PARTS 2
#define ELF_MAGIC 0x7f454c46 /* 0x7f E L F */
#define ELF_CLASS_32 1
#define ELF_CLASS_64 2
struct elf_header_ident {
uint32_t magic;
uint8_t class;
uint8_t data;
uint8_t version;
uint8_t osabi;
uint8_t abiversion;
uint8_t pad[7];
};
struct elf_header_32 {
uint16_t type;
uint16_t machine;
uint32_t version;
uint32_t entry;
uint32_t phoff;
uint32_t shoff;
uint32_t flags;
uint16_t ehsize;
uint16_t phentsize;
uint16_t phnum;
uint16_t shentsize;
uint16_t shnum;
uint16_t shstrndx;
};
struct elf_header_64 {
uint16_t type;
uint16_t machine;
uint32_t version;
uint64_t entry;
uint64_t phoff;
uint64_t shoff;
uint32_t flags;
uint16_t ehsize;
uint16_t phentsize;
uint16_t phnum;
uint16_t shentsize;
uint16_t shnum;
uint16_t shstrndx;
};
struct elf_header {
struct elf_header_ident ident;
union {
struct elf_header_32 elf32;
struct elf_header_64 elf64;
};
};
struct elf_program_header_32 {
uint32_t type;
uint32_t offset;
uint32_t vaddr;
uint32_t paddr;
uint32_t filesize;
uint32_t memsize;
uint32_t flags;
};
struct elf_program_header_64 {
uint32_t type;
uint32_t flags;
uint64_t offset;
uint64_t vaddr;
uint64_t paddr;
uint64_t filesize;
uint64_t memsize;
};
static int mtdsplit_elf_read_mtd(struct mtd_info *mtd, size_t offset,
uint8_t *dst, size_t len)
{
size_t retlen;
int ret;
ret = mtd_read(mtd, offset, len, &retlen, dst);
if (ret) {
pr_debug("read error in \"%s\"\n", mtd->name);
return ret;
}
if (retlen != len) {
pr_debug("short read in \"%s\"\n", mtd->name);
return -EIO;
}
return 0;
}
static int elf32_determine_size(struct mtd_info *mtd, struct elf_header *hdr,
size_t *size)
{
struct elf_header_32 *hdr32 = &(hdr->elf32);
int err;
size_t section_end, ph_table_end, ph_entry;
struct elf_program_header_32 ph;
*size = 0;
if (hdr32->shoff > 0) {
*size = hdr32->shoff + hdr32->shentsize * hdr32->shnum;
return 0;
}
ph_entry = hdr32->phoff;
ph_table_end = hdr32->phoff + hdr32->phentsize * hdr32->phnum;
while (ph_entry < ph_table_end) {
err = mtdsplit_elf_read_mtd(mtd, ph_entry, (uint8_t *)(&ph),
sizeof(ph));
if (err)
return err;
section_end = ph.offset + ph.filesize;
if (section_end > *size)
*size = section_end;
ph_entry += hdr32->phentsize;
}
return 0;
}
static int elf64_determine_size(struct mtd_info *mtd, struct elf_header *hdr,
size_t *size)
{
struct elf_header_64 *hdr64 = &(hdr->elf64);
int err;
size_t section_end, ph_table_end, ph_entry;
struct elf_program_header_64 ph;
*size = 0;
if (hdr64->shoff > 0) {
*size = hdr64->shoff + hdr64->shentsize * hdr64->shnum;
return 0;
}
ph_entry = hdr64->phoff;
ph_table_end = hdr64->phoff + hdr64->phentsize * hdr64->phnum;
while (ph_entry < ph_table_end) {
err = mtdsplit_elf_read_mtd(mtd, ph_entry, (uint8_t *)(&ph),
sizeof(ph));
if (err)
return err;
section_end = ph.offset + ph.filesize;
if (section_end > *size)
*size = section_end;
ph_entry += hdr64->phentsize;
}
return 0;
}
static int mtdsplit_parse_elf(struct mtd_info *mtd,
const struct mtd_partition **pparts,
struct mtd_part_parser_data *data)
{
struct elf_header hdr;
size_t loader_size, rootfs_offset;
enum mtdsplit_part_type type;
struct mtd_partition *parts;
int err;
err = mtdsplit_elf_read_mtd(mtd, 0, (uint8_t *)&hdr, sizeof(hdr));
if (err)
return err;
if (be32_to_cpu(hdr.ident.magic) != ELF_MAGIC) {
pr_debug("invalid ELF magic %08x\n",
be32_to_cpu(hdr.ident.magic));
return -EINVAL;
}
switch (hdr.ident.class) {
case ELF_CLASS_32:
err = elf32_determine_size(mtd, &hdr, &loader_size);
break;
case ELF_CLASS_64:
err = elf64_determine_size(mtd, &hdr, &loader_size);
break;
default:
pr_debug("invalid ELF class %i\n", hdr.ident.class);
err = -EINVAL;
}
if (err)
return err;
err = mtd_find_rootfs_from(mtd, loader_size, mtd->size,
&rootfs_offset, &type);
if (err)
return err;
if (rootfs_offset == mtd->size) {
pr_debug("no rootfs found in \"%s\"\n", mtd->name);
return -ENODEV;
}
parts = kzalloc(ELF_NR_PARTS * sizeof(*parts), GFP_KERNEL);
if (!parts)
return -ENOMEM;
parts[0].name = KERNEL_PART_NAME;
parts[0].offset = 0;
parts[0].size = rootfs_offset;
if (type == MTDSPLIT_PART_TYPE_UBI)
parts[1].name = UBI_PART_NAME;
else
parts[1].name = ROOTFS_PART_NAME;
parts[1].offset = rootfs_offset;
parts[1].size = mtd->size - rootfs_offset;
*pparts = parts;
return ELF_NR_PARTS;
}
static const struct of_device_id mtdsplit_elf_of_match_table[] = {
{ .compatible = "openwrt,elf" },
{},
};
MODULE_DEVICE_TABLE(of, mtdsplit_elf_of_match_table);
static struct mtd_part_parser mtdsplit_elf_parser = {
.owner = THIS_MODULE,
.name = "elf-loader-fw",
.of_match_table = mtdsplit_elf_of_match_table,
.parse_fn = mtdsplit_parse_elf,
.type = MTD_PARSER_TYPE_FIRMWARE,
};
static int __init mtdsplit_elf_init(void)
{
register_mtd_parser(&mtdsplit_elf_parser);
return 0;
}
subsys_initcall(mtdsplit_elf_init);

View file

@ -0,0 +1,103 @@
/*
* Copyright (C) 2012 John Crispin <blogic@openwrt.org>
* Copyright (C) 2015 Martin Blumenstingl <martin.blumenstingl@googlemail.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 as published
* by the Free Software Foundation.
*
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/partitions.h>
#include <linux/byteorder/generic.h>
#include <linux/of.h>
#include "mtdsplit.h"
#define EVA_NR_PARTS 2
#define EVA_MAGIC 0xfeed1281
#define EVA_FOOTER_SIZE 0x18
#define EVA_DUMMY_SQUASHFS_SIZE 0x100
struct eva_image_header {
uint32_t magic;
uint32_t size;
};
static int mtdsplit_parse_eva(struct mtd_info *master,
const struct mtd_partition **pparts,
struct mtd_part_parser_data *data)
{
struct mtd_partition *parts;
struct eva_image_header hdr;
size_t retlen;
unsigned long kernel_size, rootfs_offset;
int err;
err = mtd_read(master, 0, sizeof(hdr), &retlen, (void *) &hdr);
if (err)
return err;
if (retlen != sizeof(hdr))
return -EIO;
if (le32_to_cpu(hdr.magic) != EVA_MAGIC)
return -EINVAL;
kernel_size = le32_to_cpu(hdr.size) + EVA_FOOTER_SIZE;
/* rootfs starts at the next 0x10000 boundary: */
rootfs_offset = round_up(kernel_size, 0x10000);
/* skip the dummy EVA squashfs partition (with wrong endianness): */
rootfs_offset += EVA_DUMMY_SQUASHFS_SIZE;
if (rootfs_offset >= master->size)
return -EINVAL;
err = mtd_check_rootfs_magic(master, rootfs_offset, NULL);
if (err)
return err;
parts = kzalloc(EVA_NR_PARTS * sizeof(*parts), GFP_KERNEL);
if (!parts)
return -ENOMEM;
parts[0].name = KERNEL_PART_NAME;
parts[0].offset = 0;
parts[0].size = kernel_size;
parts[1].name = ROOTFS_PART_NAME;
parts[1].offset = rootfs_offset;
parts[1].size = master->size - rootfs_offset;
*pparts = parts;
return EVA_NR_PARTS;
}
static const struct of_device_id mtdsplit_eva_of_match_table[] = {
{ .compatible = "avm,eva-firmware" },
{},
};
static struct mtd_part_parser mtdsplit_eva_parser = {
.owner = THIS_MODULE,
.name = "eva-fw",
.of_match_table = mtdsplit_eva_of_match_table,
.parse_fn = mtdsplit_parse_eva,
.type = MTD_PARSER_TYPE_FIRMWARE,
};
static int __init mtdsplit_eva_init(void)
{
register_mtd_parser(&mtdsplit_eva_parser);
return 0;
}
subsys_initcall(mtdsplit_eva_init);

View file

@ -0,0 +1,365 @@
/*
* Copyright (c) 2015 The Linux Foundation
* Copyright (C) 2014 Gabor Juhos <juhosg@openwrt.org>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <linux/module.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/partitions.h>
#include <linux/types.h>
#include <linux/byteorder/generic.h>
#include <linux/slab.h>
#include <linux/libfdt.h>
#include <linux/of_fdt.h>
#include "mtdsplit.h"
// string macros from git://git.denx.de/u-boot.git/include/image.h
#define FIT_IMAGES_PATH "/images"
#define FIT_DATA_PROP "data"
#define FIT_DATA_POSITION_PROP "data-position"
#define FIT_DATA_OFFSET_PROP "data-offset"
#define FIT_DATA_SIZE_PROP "data-size"
// functions from git://git.denx.de/u-boot.git/common/image-fit.c
/**
* fit_image_get_data - get data property and its size for a given component image node
* @fit: pointer to the FIT format image header
* @noffset: component image node offset
* @data: double pointer to void, will hold data property's data address
* @size: pointer to size_t, will hold data property's data size
*
* fit_image_get_data() finds data property in a given component image node.
* If the property is found its data start address and size are returned to
* the caller.
*
* returns:
* 0, on success
* -1, on failure
*/
static int fit_image_get_data(const void *fit, int noffset,
const void **data, size_t *size)
{
int len;
*data = fdt_getprop(fit, noffset, FIT_DATA_PROP, &len);
if (*data == NULL) {
*size = 0;
return -1;
}
*size = len;
return 0;
}
/**
* Get 'data-offset' property from a given image node.
*
* @fit: pointer to the FIT image header
* @noffset: component image node offset
* @data_offset: holds the data-offset property
*
* returns:
* 0, on success
* -ENOENT if the property could not be found
*/
static int fit_image_get_data_offset(const void *fit, int noffset, int *data_offset)
{
const fdt32_t *val;
val = fdt_getprop(fit, noffset, FIT_DATA_OFFSET_PROP, NULL);
if (!val)
return -ENOENT;
*data_offset = fdt32_to_cpu(*val);
return 0;
}
/**
* Get 'data-position' property from a given image node.
*
* @fit: pointer to the FIT image header
* @noffset: component image node offset
* @data_position: holds the data-position property
*
* returns:
* 0, on success
* -ENOENT if the property could not be found
*/
static int fit_image_get_data_position(const void *fit, int noffset,
int *data_position)
{
const fdt32_t *val;
val = fdt_getprop(fit, noffset, FIT_DATA_POSITION_PROP, NULL);
if (!val)
return -ENOENT;
*data_position = fdt32_to_cpu(*val);
return 0;
}
/**
* Get 'data-size' property from a given image node.
*
* @fit: pointer to the FIT image header
* @noffset: component image node offset
* @data_size: holds the data-size property
*
* returns:
* 0, on success
* -ENOENT if the property could not be found
*/
static int fit_image_get_data_size(const void *fit, int noffset, int *data_size)
{
const fdt32_t *val;
val = fdt_getprop(fit, noffset, FIT_DATA_SIZE_PROP, NULL);
if (!val)
return -ENOENT;
*data_size = fdt32_to_cpu(*val);
return 0;
}
/**
* fit_image_get_data_and_size - get data and its size including
* both embedded and external data
* @fit: pointer to the FIT format image header
* @noffset: component image node offset
* @data: double pointer to void, will hold data property's data address
* @size: pointer to size_t, will hold data property's data size
*
* fit_image_get_data_and_size() finds data and its size including
* both embedded and external data. If the property is found
* its data start address and size are returned to the caller.
*
* returns:
* 0, on success
* otherwise, on failure
*/
static int fit_image_get_data_and_size(const void *fit, int noffset,
const void **data, size_t *size)
{
bool external_data = false;
int offset;
int len;
int ret;
if (!fit_image_get_data_position(fit, noffset, &offset)) {
external_data = true;
} else if (!fit_image_get_data_offset(fit, noffset, &offset)) {
external_data = true;
/*
* For FIT with external data, figure out where
* the external images start. This is the base
* for the data-offset properties in each image.
*/
offset += ((fdt_totalsize(fit) + 3) & ~3);
}
if (external_data) {
ret = fit_image_get_data_size(fit, noffset, &len);
if (!ret) {
*data = fit + offset;
*size = len;
}
} else {
ret = fit_image_get_data(fit, noffset, data, size);
}
return ret;
}
static int
mtdsplit_fit_parse(struct mtd_info *mtd,
const struct mtd_partition **pparts,
struct mtd_part_parser_data *data)
{
struct device_node *np = mtd_get_of_node(mtd);
const char *cmdline_match = NULL;
struct fdt_header hdr;
size_t hdr_len, retlen;
size_t offset;
u32 offset_start = 0;
size_t fit_offset, fit_size;
size_t rootfs_offset, rootfs_size;
size_t data_size, img_total, max_size = 0;
struct mtd_partition *parts;
int ret, ndepth, noffset, images_noffset;
const void *img_data;
void *fit;
of_property_read_string(np, "openwrt,cmdline-match", &cmdline_match);
if (cmdline_match && !strstr(saved_command_line, cmdline_match))
return -ENODEV;
of_property_read_u32(np, "openwrt,fit-offset", &offset_start);
hdr_len = sizeof(struct fdt_header);
/* Parse the MTD device & search for the FIT image location */
for(offset = 0; offset + hdr_len <= mtd->size; offset += mtd->erasesize) {
ret = mtd_read(mtd, offset + offset_start, hdr_len, &retlen, (void*) &hdr);
if (ret) {
pr_err("read error in \"%s\" at offset 0x%llx\n",
mtd->name, (unsigned long long) offset);
return ret;
}
if (retlen != hdr_len) {
pr_err("short read in \"%s\"\n", mtd->name);
return -EIO;
}
/* Check the magic - see if this is a FIT image */
if (be32_to_cpu(hdr.magic) != OF_DT_HEADER) {
pr_debug("no valid FIT image found in \"%s\" at offset %llx\n",
mtd->name, (unsigned long long) offset);
continue;
}
/* We found a FIT image. Let's keep going */
break;
}
fit_offset = offset;
fit_size = be32_to_cpu(hdr.totalsize);
if (fit_size == 0) {
pr_err("FIT image in \"%s\" at offset %llx has null size\n",
mtd->name, (unsigned long long) fit_offset);
return -ENODEV;
}
/*
* Classic uImage.FIT has all data embedded into the FDT
* data structure. Hence the total size of the image equals
* the total size of the FDT structure.
* Modern uImage.FIT may have only references to data in FDT,
* hence we need to parse FDT structure to find the end of the
* last external data refernced.
*/
if (fit_size > 0x1000) {
enum mtdsplit_part_type type;
/* Search for the rootfs partition after the FIT image */
ret = mtd_find_rootfs_from(mtd, fit_offset + fit_size + offset_start, mtd->size,
&rootfs_offset, &type);
if (ret) {
pr_info("no rootfs found after FIT image in \"%s\"\n",
mtd->name);
return ret;
}
rootfs_size = mtd->size - rootfs_offset;
parts = kzalloc(2 * sizeof(*parts), GFP_KERNEL);
if (!parts)
return -ENOMEM;
parts[0].name = KERNEL_PART_NAME;
parts[0].offset = fit_offset;
parts[0].size = mtd_roundup_to_eb(fit_size + offset_start, mtd);
if (type == MTDSPLIT_PART_TYPE_UBI)
parts[1].name = UBI_PART_NAME;
else
parts[1].name = ROOTFS_PART_NAME;
parts[1].offset = rootfs_offset;
parts[1].size = rootfs_size;
*pparts = parts;
return 2;
} else {
/* Search for rootfs_data after FIT external data */
fit = kzalloc(fit_size, GFP_KERNEL);
ret = mtd_read(mtd, offset, fit_size + offset_start, &retlen, fit);
if (ret) {
pr_err("read error in \"%s\" at offset 0x%llx\n",
mtd->name, (unsigned long long) offset);
return ret;
}
images_noffset = fdt_path_offset(fit, FIT_IMAGES_PATH);
if (images_noffset < 0) {
pr_err("Can't find images parent node '%s' (%s)\n",
FIT_IMAGES_PATH, fdt_strerror(images_noffset));
return -ENODEV;
}
for (ndepth = 0,
noffset = fdt_next_node(fit, images_noffset, &ndepth);
(noffset >= 0) && (ndepth > 0);
noffset = fdt_next_node(fit, noffset, &ndepth)) {
if (ndepth == 1) {
ret = fit_image_get_data_and_size(fit, noffset, &img_data, &data_size);
if (ret)
return 0;
img_total = data_size + (img_data - fit);
max_size = (max_size > img_total) ? max_size : img_total;
}
}
parts = kzalloc(sizeof(*parts), GFP_KERNEL);
if (!parts)
return -ENOMEM;
parts[0].name = ROOTFS_SPLIT_NAME;
parts[0].offset = fit_offset + mtd_roundup_to_eb(max_size, mtd);
parts[0].size = mtd->size - parts[0].offset;
*pparts = parts;
kfree(fit);
return 1;
}
}
static const struct of_device_id mtdsplit_fit_of_match_table[] = {
{ .compatible = "denx,fit" },
{},
};
static struct mtd_part_parser uimage_parser = {
.owner = THIS_MODULE,
.name = "fit-fw",
.of_match_table = mtdsplit_fit_of_match_table,
.parse_fn = mtdsplit_fit_parse,
.type = MTD_PARSER_TYPE_FIRMWARE,
};
/**************************************************
* Init
**************************************************/
static int __init mtdsplit_fit_init(void)
{
register_mtd_parser(&uimage_parser);
return 0;
}
module_init(mtdsplit_fit_init);

View file

@ -0,0 +1,170 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Some devices made by H3C use a "VFS" filesystem to store firmware images.
* This parses the start of the filesystem to read the length of the first
* file (the kernel image). It then searches for the rootfs after the end of
* the file data. This driver assumes that the filesystem was generated by
* mkh3cvfs, and only works if the filesystem matches the expected layout,
* which includes the file name of the kernel image.
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/of.h>
#include <linux/types.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/partitions.h>
#include "mtdsplit.h"
#define VFS_ERASEBLOCK_SIZE 0x10000
#define VFS_BLOCK_SIZE 0x400
#define VFS_BLOCKS_PER_ERASEBLOCK (VFS_ERASEBLOCK_SIZE / VFS_BLOCK_SIZE)
#define FORMAT_FLAG_OFFSET 0x0
#define FORMAT_FLAG (VFS_ERASEBLOCK_SIZE << 12 | VFS_BLOCK_SIZE)
#define FILE_ENTRY_OFFSET 0x800
#define FILE_ENTRY_FLAGS 0x3f
#define FILE_ENTRY_PARENT_BLOCK 0
#define FILE_ENTRY_PARENT_INDEX 0
#define FILE_ENTRY_DATA_BLOCK 2
#define FILE_ENTRY_NAME "openwrt-kernel.bin"
#define NR_PARTS 2
struct file_entry {
uint8_t flags;
uint8_t res0[5];
uint16_t year;
uint8_t month;
uint8_t day;
uint8_t hour;
uint8_t minute;
uint8_t second;
uint8_t res1[3];
uint32_t length;
uint32_t parent_block;
uint16_t parent_index;
uint8_t res2[2];
uint32_t data_block;
char name[96];
} __attribute__ ((packed));
static inline size_t block_offset(int block)
{
return VFS_ERASEBLOCK_SIZE * (block / (VFS_BLOCKS_PER_ERASEBLOCK-1))
+ VFS_BLOCK_SIZE * (1 + (block % (VFS_BLOCKS_PER_ERASEBLOCK-1)));
}
static inline int block_count(size_t size)
{
return (size + VFS_BLOCK_SIZE - 1) / VFS_BLOCK_SIZE;
}
static int mtdsplit_h3c_vfs_parse(struct mtd_info *mtd,
const struct mtd_partition **pparts,
struct mtd_part_parser_data *data)
{
struct mtd_partition *parts;
uint32_t format_flag;
struct file_entry file_entry;
size_t retlen;
int err;
size_t kernel_size;
size_t expected_offset;
size_t rootfs_offset;
if (mtd->erasesize != VFS_ERASEBLOCK_SIZE)
return -EINVAL;
/* Check format flag */
err = mtd_read(mtd, FORMAT_FLAG_OFFSET, sizeof(format_flag), &retlen,
(void *) &format_flag);
if (err)
return err;
if (retlen != sizeof(format_flag))
return -EIO;
if (format_flag != FORMAT_FLAG)
return -EINVAL;
/* Check file entry */
err = mtd_read(mtd, FILE_ENTRY_OFFSET, sizeof(file_entry), &retlen,
(void *) &file_entry);
if (err)
return err;
if (retlen != sizeof(file_entry))
return -EIO;
if (file_entry.flags != FILE_ENTRY_FLAGS)
return -EINVAL;
if (file_entry.parent_block != FILE_ENTRY_PARENT_BLOCK)
return -EINVAL;
if (file_entry.parent_index != FILE_ENTRY_PARENT_INDEX)
return -EINVAL;
if (file_entry.data_block != FILE_ENTRY_DATA_BLOCK)
return -EINVAL;
if (strncmp(file_entry.name, FILE_ENTRY_NAME, sizeof(file_entry.name)) != 0)
return -EINVAL;
/* Find rootfs offset */
kernel_size = block_offset(file_entry.data_block +
block_count(file_entry.length) - 1) +
VFS_BLOCK_SIZE;
expected_offset = mtd_roundup_to_eb(kernel_size, mtd);
err = mtd_find_rootfs_from(mtd, expected_offset, mtd->size,
&rootfs_offset, NULL);
if (err)
return err;
parts = kzalloc(NR_PARTS * sizeof(*parts), GFP_KERNEL);
if (!parts)
return -ENOMEM;
parts[0].name = KERNEL_PART_NAME;
parts[0].offset = 0;
parts[0].size = rootfs_offset;
parts[1].name = ROOTFS_PART_NAME;
parts[1].offset = rootfs_offset;
parts[1].size = mtd->size - rootfs_offset;
*pparts = parts;
return NR_PARTS;
}
static const struct of_device_id mtdsplit_h3c_vfs_of_match_table[] = {
{ .compatible = "h3c,vfs-firmware" },
{},
};
MODULE_DEVICE_TABLE(of, mtdsplit_h3c_vfs_of_match_table);
static struct mtd_part_parser mtdsplit_h3c_vfs_parser = {
.owner = THIS_MODULE,
.name = "h3c-vfs",
.of_match_table = mtdsplit_h3c_vfs_of_match_table,
.parse_fn = mtdsplit_h3c_vfs_parse,
.type = MTD_PARSER_TYPE_FIRMWARE,
};
module_mtd_part_parser(mtdsplit_h3c_vfs_parser);

View file

@ -0,0 +1,284 @@
/*
* Copyright (C) 2018 Paweł Dembicki <paweldembicki@gmail.com>
*
* Based on: mtdsplit_uimage.c
* Copyright (C) 2013 Gabor Juhos <juhosg@openwrt.org>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 as published
* by the Free Software Foundation.
*
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/partitions.h>
#include <linux/byteorder/generic.h>
#include <linux/of.h>
#include "mtdsplit.h"
#define MAX_HEADER_LEN ( STAG_SIZE + SCH2_SIZE )
#define STAG_SIZE 16
#define STAG_ID 0x04
#define STAG_MAGIC 0x2B24
#define SCH2_SIZE 40
#define SCH2_MAGIC 0x2124
#define SCH2_VER 0x02
/*
* Jboot image header,
* all data in little endian.
*/
struct jimage_header //stag + sch2 jboot joined headers
{
uint8_t stag_cmark; // in factory 0xFF , in sysupgrade must be the same as stag_id
uint8_t stag_id; // 0x04
uint16_t stag_magic; //magic 0x2B24
uint32_t stag_time_stamp; // timestamp calculated in jboot way
uint32_t stag_image_length; // lentgh of kernel + sch2 header
uint16_t stag_image_checksum; // negated jboot_checksum of sch2 + kernel
uint16_t stag_tag_checksum; // negated jboot_checksum of stag header data
uint16_t sch2_magic; // magic 0x2124
uint8_t sch2_cp_type; // 0x00 for flat, 0x01 for jz, 0x02 for gzip, 0x03 for lzma
uint8_t sch2_version; // 0x02 for sch2
uint32_t sch2_ram_addr; // ram entry address
uint32_t sch2_image_len; // kernel image length
uint32_t sch2_image_crc32; // kernel image crc
uint32_t sch2_start_addr; // ram start address
uint32_t sch2_rootfs_addr; // rootfs flash address
uint32_t sch2_rootfs_len; // rootfls length
uint32_t sch2_rootfs_crc32; // rootfs crc32
uint32_t sch2_header_crc32; // sch2 header crc32, durring calculation this area is replaced by zero
uint16_t sch2_header_length; // sch2 header length: 0x28
uint16_t sch2_cmd_line_length; // cmd line length, known zeros
};
static int
read_jimage_header(struct mtd_info *mtd, size_t offset, u_char *buf,
size_t header_len)
{
size_t retlen;
int ret;
ret = mtd_read(mtd, offset, header_len, &retlen, buf);
if (ret) {
pr_debug("read error in \"%s\"\n", mtd->name);
return ret;
}
if (retlen != header_len) {
pr_debug("short read in \"%s\"\n", mtd->name);
return -EIO;
}
return 0;
}
/**
* __mtdsplit_parse_jimage - scan partition and create kernel + rootfs parts
*
* @find_header: function to call for a block of data that will return offset
* of a valid jImage header if found
*/
static int __mtdsplit_parse_jimage(struct mtd_info *master,
const struct mtd_partition **pparts,
struct mtd_part_parser_data *data,
ssize_t (*find_header)(u_char *buf, size_t len))
{
struct mtd_partition *parts;
u_char *buf;
int nr_parts;
size_t offset;
size_t jimage_offset;
size_t jimage_size = 0;
size_t rootfs_offset;
size_t rootfs_size = 0;
int jimage_part, rf_part;
int ret;
enum mtdsplit_part_type type;
nr_parts = 2;
parts = kzalloc(nr_parts * sizeof(*parts), GFP_KERNEL);
if (!parts)
return -ENOMEM;
buf = vmalloc(MAX_HEADER_LEN);
if (!buf) {
ret = -ENOMEM;
goto err_free_parts;
}
/* find jImage on erase block boundaries */
for (offset = 0; offset < master->size; offset += master->erasesize) {
struct jimage_header *header;
jimage_size = 0;
ret = read_jimage_header(master, offset, buf, MAX_HEADER_LEN);
if (ret)
continue;
ret = find_header(buf, MAX_HEADER_LEN);
if (ret < 0) {
pr_debug("no valid jImage found in \"%s\" at offset %llx\n",
master->name, (unsigned long long) offset);
continue;
}
header = (struct jimage_header *)(buf + ret);
jimage_size = sizeof(*header) + header->sch2_image_len + ret;
if ((offset + jimage_size) > master->size) {
pr_debug("jImage exceeds MTD device \"%s\"\n",
master->name);
continue;
}
break;
}
if (jimage_size == 0) {
pr_debug("no jImage found in \"%s\"\n", master->name);
ret = -ENODEV;
goto err_free_buf;
}
jimage_offset = offset;
if (jimage_offset == 0) {
jimage_part = 0;
rf_part = 1;
/* find the roots after the jImage */
ret = mtd_find_rootfs_from(master, jimage_offset + jimage_size,
master->size, &rootfs_offset, &type);
if (ret) {
pr_debug("no rootfs after jImage in \"%s\"\n",
master->name);
goto err_free_buf;
}
rootfs_size = master->size - rootfs_offset;
jimage_size = rootfs_offset - jimage_offset;
} else {
rf_part = 0;
jimage_part = 1;
/* check rootfs presence at offset 0 */
ret = mtd_check_rootfs_magic(master, 0, &type);
if (ret) {
pr_debug("no rootfs before jImage in \"%s\"\n",
master->name);
goto err_free_buf;
}
rootfs_offset = 0;
rootfs_size = jimage_offset;
}
if (rootfs_size == 0) {
pr_debug("no rootfs found in \"%s\"\n", master->name);
ret = -ENODEV;
goto err_free_buf;
}
parts[jimage_part].name = KERNEL_PART_NAME;
parts[jimage_part].offset = jimage_offset;
parts[jimage_part].size = jimage_size;
if (type == MTDSPLIT_PART_TYPE_UBI)
parts[rf_part].name = UBI_PART_NAME;
else
parts[rf_part].name = ROOTFS_PART_NAME;
parts[rf_part].offset = rootfs_offset;
parts[rf_part].size = rootfs_size;
vfree(buf);
*pparts = parts;
return nr_parts;
err_free_buf:
vfree(buf);
err_free_parts:
kfree(parts);
return ret;
}
static ssize_t jimage_verify_default(u_char *buf, size_t len)
{
struct jimage_header *header = (struct jimage_header *)buf;
/* default sanity checks */
if (header->stag_magic != STAG_MAGIC) {
pr_debug("invalid jImage stag header magic: %04x\n",
header->stag_magic);
return -EINVAL;
}
if (header->sch2_magic != SCH2_MAGIC) {
pr_debug("invalid jImage sch2 header magic: %04x\n",
header->stag_magic);
return -EINVAL;
}
if (header->stag_cmark != header->stag_id) {
pr_debug("invalid jImage stag header cmark: %02x\n",
header->stag_magic);
return -EINVAL;
}
if (header->stag_id != STAG_ID) {
pr_debug("invalid jImage stag header id: %02x\n",
header->stag_magic);
return -EINVAL;
}
if (header->sch2_version != SCH2_VER) {
pr_debug("invalid jImage sch2 header version: %02x\n",
header->stag_magic);
return -EINVAL;
}
return 0;
}
static int
mtdsplit_jimage_parse_generic(struct mtd_info *master,
const struct mtd_partition **pparts,
struct mtd_part_parser_data *data)
{
return __mtdsplit_parse_jimage(master, pparts, data,
jimage_verify_default);
}
static const struct of_device_id mtdsplit_jimage_of_match_table[] = {
{ .compatible = "amit,jimage" },
{},
};
static struct mtd_part_parser jimage_generic_parser = {
.owner = THIS_MODULE,
.name = "jimage-fw",
.of_match_table = mtdsplit_jimage_of_match_table,
.parse_fn = mtdsplit_jimage_parse_generic,
.type = MTD_PARSER_TYPE_FIRMWARE,
};
/**************************************************
* Init
**************************************************/
static int __init mtdsplit_jimage_init(void)
{
register_mtd_parser(&jimage_generic_parser);
return 0;
}
module_init(mtdsplit_jimage_init);

View file

@ -0,0 +1,104 @@
/*
* Copyright (C) 2014 Gabor Juhos <juhosg@openwrt.org>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 as published
* by the Free Software Foundation.
*
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/partitions.h>
#include <linux/of.h>
#include <asm/unaligned.h>
#include "mtdsplit.h"
#define LZMA_NR_PARTS 2
#define LZMA_PROPERTIES_SIZE 5
struct lzma_header {
u8 props[LZMA_PROPERTIES_SIZE];
u8 size_low[4];
u8 size_high[4];
};
static int mtdsplit_parse_lzma(struct mtd_info *master,
const struct mtd_partition **pparts,
struct mtd_part_parser_data *data)
{
struct lzma_header hdr;
size_t hdr_len, retlen;
size_t rootfs_offset;
u32 t;
struct mtd_partition *parts;
int err;
hdr_len = sizeof(hdr);
err = mtd_read(master, 0, hdr_len, &retlen, (void *) &hdr);
if (err)
return err;
if (retlen != hdr_len)
return -EIO;
/* verify LZMA properties */
if (hdr.props[0] >= (9 * 5 * 5))
return -EINVAL;
t = get_unaligned_le32(&hdr.props[1]);
if (!is_power_of_2(t))
return -EINVAL;
t = get_unaligned_le32(&hdr.size_high);
if (t)
return -EINVAL;
err = mtd_find_rootfs_from(master, master->erasesize, master->size,
&rootfs_offset, NULL);
if (err)
return err;
parts = kzalloc(LZMA_NR_PARTS * sizeof(*parts), GFP_KERNEL);
if (!parts)
return -ENOMEM;
parts[0].name = KERNEL_PART_NAME;
parts[0].offset = 0;
parts[0].size = rootfs_offset;
parts[1].name = ROOTFS_PART_NAME;
parts[1].offset = rootfs_offset;
parts[1].size = master->size - rootfs_offset;
*pparts = parts;
return LZMA_NR_PARTS;
}
static const struct of_device_id mtdsplit_lzma_of_match_table[] = {
{ .compatible = "lzma" },
{},
};
MODULE_DEVICE_TABLE(of, mtdsplit_lzma_of_match_table);
static struct mtd_part_parser mtdsplit_lzma_parser = {
.owner = THIS_MODULE,
.name = "lzma-fw",
.of_match_table = mtdsplit_lzma_of_match_table,
.parse_fn = mtdsplit_parse_lzma,
.type = MTD_PARSER_TYPE_FIRMWARE,
};
static int __init mtdsplit_lzma_init(void)
{
register_mtd_parser(&mtdsplit_lzma_parser);
return 0;
}
subsys_initcall(mtdsplit_lzma_init);

View file

@ -0,0 +1,142 @@
/*
* MTD splitter for MikroTik NOR devices
*
* Copyright (C) 2017 Thibaut VARENE <varenet@parisc-linux.org>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 as published
* by the Free Software Foundation.
*
* The rootfs is expected at erase-block boundary due to the use of
* mtd_find_rootfs_from(). We use a trimmed down version of the yaffs header
* for two main reasons:
* - the original header uses weakly defined types (int, enum...) which can
* vary in length depending on build host (and the struct is not packed),
* and the name field can have a different total length depending on
* whether or not the yaffs code was _built_ with unicode support.
* - the only field that could be of real use here (file_size_low) contains
* invalid data in the header generated by kernel2minor, so we cannot use
* it to infer the exact position of the rootfs and do away with
* mtd_find_rootfs_from() (and thus have non-EB-aligned rootfs).
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/partitions.h>
#include <linux/string.h>
#include <linux/of.h>
#include "mtdsplit.h"
#define YAFFS_OBJECT_TYPE_FILE 0x1
#define YAFFS_OBJECTID_ROOT 0x1
#define YAFFS_SUM_UNUSED 0xFFFF
#define YAFFS_MAX_NAME_LENGTH 127
#define YAFFS_NAME_KERNEL "kernel"
#define YAFFS_NAME_BOOTIMAGE "bootimage"
#define MINOR_NR_PARTS 2
/*
* This structure is based on yaffs_obj_hdr from yaffs_guts.h
* The weak types match upstream. The fields have cpu-endianness
*/
struct minor_header {
int yaffs_type;
int yaffs_obj_id;
u16 yaffs_sum_unused;
char yaffs_name[YAFFS_MAX_NAME_LENGTH];
};
static int mtdsplit_parse_minor(struct mtd_info *master,
const struct mtd_partition **pparts,
struct mtd_part_parser_data *data)
{
struct minor_header hdr;
size_t hdr_len, retlen;
size_t rootfs_offset;
struct mtd_partition *parts;
int err;
hdr_len = sizeof(hdr);
err = mtd_read(master, 0, hdr_len, &retlen, (void *) &hdr);
if (err) {
pr_err("MiNOR mtd_read error: %d\n", err);
return err;
}
if (retlen != hdr_len) {
pr_err("MiNOR mtd_read too short\n");
return -EIO;
}
/* match header */
if (hdr.yaffs_type != YAFFS_OBJECT_TYPE_FILE) {
pr_info("MiNOR YAFFS first type not matched\n");
return 0;
}
if (hdr.yaffs_obj_id != YAFFS_OBJECTID_ROOT) {
pr_info("MiNOR YAFFS first objectid not matched\n");
return 0;
}
if (hdr.yaffs_sum_unused != YAFFS_SUM_UNUSED) {
pr_info("MiNOR YAFFS first sum not matched\n");
return 0;
}
if ((memcmp(hdr.yaffs_name, YAFFS_NAME_KERNEL, sizeof(YAFFS_NAME_KERNEL))) &&
(memcmp(hdr.yaffs_name, YAFFS_NAME_BOOTIMAGE, sizeof(YAFFS_NAME_BOOTIMAGE)))) {
pr_info("MiNOR YAFFS first name not matched\n");
return 0;
}
err = mtd_find_rootfs_from(master, master->erasesize, master->size,
&rootfs_offset, NULL);
if (err) {
pr_info("MiNOR mtd_find_rootfs_from error: %d\n", err);
return 0;
}
parts = kzalloc(MINOR_NR_PARTS * sizeof(*parts), GFP_KERNEL);
if (!parts)
return -ENOMEM;
parts[0].name = KERNEL_PART_NAME;
parts[0].offset = 0;
parts[0].size = rootfs_offset;
parts[1].name = ROOTFS_PART_NAME;
parts[1].offset = rootfs_offset;
parts[1].size = master->size - rootfs_offset;
*pparts = parts;
return MINOR_NR_PARTS;
}
static const struct of_device_id mtdsplit_minor_of_match_table[] = {
{ .compatible = "mikrotik,minor" },
{},
};
MODULE_DEVICE_TABLE(of, mtdsplit_minor_of_match_table);
static struct mtd_part_parser mtdsplit_minor_parser = {
.owner = THIS_MODULE,
.name = "minor-fw",
.of_match_table = mtdsplit_minor_of_match_table,
.parse_fn = mtdsplit_parse_minor,
.type = MTD_PARSER_TYPE_FIRMWARE,
};
static int __init mtdsplit_minor_init(void)
{
register_mtd_parser(&mtdsplit_minor_parser);
return 0;
}
subsys_initcall(mtdsplit_minor_init);

View file

@ -0,0 +1,118 @@
/*
* Copyright (C) 2013 Gabor Juhos <juhosg@openwrt.org>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 as published
* by the Free Software Foundation.
*
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/partitions.h>
#include <linux/byteorder/generic.h>
#include <linux/of.h>
#include "mtdsplit.h"
#define SEAMA_MAGIC 0x5EA3A417
#define SEAMA_NR_PARTS 2
#define SEAMA_MIN_ROOTFS_OFFS 0x80000 /* 512KiB */
struct seama_header {
__be32 magic; /* should always be SEAMA_MAGIC. */
__be16 reserved; /* reserved for */
__be16 metasize; /* size of the META data */
__be32 size; /* size of the image */
u8 md5[16]; /* digest */
};
static int mtdsplit_parse_seama(struct mtd_info *master,
const struct mtd_partition **pparts,
struct mtd_part_parser_data *data)
{
struct seama_header hdr;
size_t hdr_len, retlen, kernel_ent_size;
size_t rootfs_offset;
struct mtd_partition *parts;
enum mtdsplit_part_type type;
int err;
hdr_len = sizeof(hdr);
err = mtd_read(master, 0, hdr_len, &retlen, (void *) &hdr);
if (err)
return err;
if (retlen != hdr_len)
return -EIO;
/* sanity checks */
if (be32_to_cpu(hdr.magic) != SEAMA_MAGIC)
return -EINVAL;
kernel_ent_size = hdr_len + be32_to_cpu(hdr.size) +
be16_to_cpu(hdr.metasize);
if (kernel_ent_size > master->size)
return -EINVAL;
/* Check for the rootfs right after Seama entity with a kernel. */
err = mtd_check_rootfs_magic(master, kernel_ent_size, &type);
if (!err) {
rootfs_offset = kernel_ent_size;
} else {
/*
* On some devices firmware entity might contain both: kernel
* and rootfs. We can't determine kernel size so we just have to
* look for rootfs magic.
* Start the search from an arbitrary offset.
*/
err = mtd_find_rootfs_from(master, SEAMA_MIN_ROOTFS_OFFS,
master->size, &rootfs_offset, &type);
if (err)
return err;
}
parts = kzalloc(SEAMA_NR_PARTS * sizeof(*parts), GFP_KERNEL);
if (!parts)
return -ENOMEM;
parts[0].name = KERNEL_PART_NAME;
parts[0].offset = sizeof hdr + be16_to_cpu(hdr.metasize);
parts[0].size = rootfs_offset - parts[0].offset;
if (type == MTDSPLIT_PART_TYPE_UBI)
parts[1].name = UBI_PART_NAME;
else
parts[1].name = ROOTFS_PART_NAME;
parts[1].offset = rootfs_offset;
parts[1].size = master->size - rootfs_offset;
*pparts = parts;
return SEAMA_NR_PARTS;
}
static const struct of_device_id mtdsplit_seama_of_match_table[] = {
{ .compatible = "seama" },
{},
};
MODULE_DEVICE_TABLE(of, mtdsplit_seama_of_match_table);
static struct mtd_part_parser mtdsplit_seama_parser = {
.owner = THIS_MODULE,
.name = "seama-fw",
.of_match_table = mtdsplit_seama_of_match_table,
.parse_fn = mtdsplit_parse_seama,
.type = MTD_PARSER_TYPE_FIRMWARE,
};
static int __init mtdsplit_seama_init(void)
{
register_mtd_parser(&mtdsplit_seama_parser);
return 0;
}
subsys_initcall(mtdsplit_seama_init);

View file

@ -0,0 +1,191 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/* a mtdsplit driver for IIJ SEIL devices */
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
#include <linux/of.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/partitions.h>
#include <linux/byteorder/generic.h>
#include "mtdsplit.h"
#define NR_PARTS 2
#define SEIL_VFMT 1
#define LDR_ENV_PART_NAME "bootloader-env"
#define LDR_ENV_KEY_BOOTDEV "BOOTDEV"
struct seil_header {
uint64_t id; /* Identifier */
uint8_t copy[80]; /* Copyright */
uint32_t dcrc; /* Data CRC Checksum */
uint32_t vfmt; /* Image Version Format */
uint32_t vmjr; /* Image Version Major */
uint32_t vmnr; /* Image Version Minor */
uint8_t vrel[32]; /* Image Version Release */
uint32_t dxor; /* xor value for Data? */
uint32_t dlen; /* Data Length */
};
/*
* check whether the current mtd device is active or not
*
* example of BOOTDEV value (IIJ SA-W2):
* - "flash" : primary image on flash
* - "rescue" : secondary image on flash
* - "usb" : usb storage
* - "lan0/1" : network
*/
static bool seil_bootdev_is_active(struct device_node *np)
{
struct mtd_info *env_mtd;
char *buf, *var, *value, *eq;
const char *devnm;
size_t rdlen;
int ret;
/*
* read bootdev name of the partition
* if doesn't exist, return true and skip checking of active device
*/
ret = of_property_read_string(np, "iij,bootdev-name", &devnm);
if (ret == -EINVAL)
return true;
else if (ret < 0)
return false;
env_mtd = get_mtd_device_nm(LDR_ENV_PART_NAME);
if (IS_ERR(env_mtd)) {
pr_err("failed to get mtd device \"%s\"", LDR_ENV_PART_NAME);
return false;
}
buf = vmalloc(env_mtd->size);
if (!buf)
return false;
ret = mtd_read(env_mtd, 0, env_mtd->size, &rdlen, buf);
if (ret || rdlen != env_mtd->size) {
pr_err("failed to read from mtd (%d)\n", ret);
ret = 0;
goto exit_vfree;
}
for (var = buf, ret = false;
var < buf + env_mtd->size && *var;
var = value + strlen(value) + 1) {
eq = strchr(var, '=');
if (!eq)
break;
*eq = '\0';
value = eq + 1;
pr_debug("ENV: %s=%s\n", var, value);
if (!strcmp(var, LDR_ENV_KEY_BOOTDEV)) {
ret = !strcmp(devnm, value);
break;
}
}
exit_vfree:
vfree(buf);
return ret;
}
static int mtdsplit_parse_seil_fw(struct mtd_info *master,
const struct mtd_partition **pparts,
struct mtd_part_parser_data *data)
{
struct device_node *np = mtd_get_of_node(master);
struct mtd_partition *parts;
struct seil_header header;
size_t image_size = 0;
size_t rootfs_offset;
size_t hdrlen = sizeof(header);
size_t retlen;
int ret;
u64 id;
if (!seil_bootdev_is_active(np))
return -ENODEV;
ret = of_property_read_u64(np, "iij,seil-id", &id);
if (ret) {
pr_err("failed to get iij,seil-id from dt\n");
return ret;
}
pr_debug("got seil-id=0x%016llx from dt\n", id);
parts = kcalloc(NR_PARTS, sizeof(*parts), GFP_KERNEL);
if (!parts)
return -ENOMEM;
ret = mtd_read(master, 0, hdrlen, &retlen, (void *)&header);
if (ret)
goto err_free_parts;
if (retlen != hdrlen) {
ret = -EIO;
goto err_free_parts;
}
if (be64_to_cpu(header.id) != id ||
be32_to_cpu(header.vfmt) != SEIL_VFMT) {
pr_debug("no valid seil image found in \"%s\"\n", master->name);
ret = -ENODEV;
goto err_free_parts;
}
image_size = hdrlen + be32_to_cpu(header.dlen);
if (image_size > master->size) {
pr_err("seil image exceeds MTD device \"%s\"\n", master->name);
ret = -EINVAL;
goto err_free_parts;
}
/* find the roots after the seil image */
ret = mtd_find_rootfs_from(master, image_size,
master->size, &rootfs_offset, NULL);
if (ret || (master->size - rootfs_offset) == 0) {
pr_debug("no rootfs after seil image in \"%s\"\n",
master->name);
ret = -ENODEV;
goto err_free_parts;
}
parts[0].name = KERNEL_PART_NAME;
parts[0].offset = 0;
parts[0].size = rootfs_offset;
parts[1].name = ROOTFS_PART_NAME;
parts[1].offset = rootfs_offset;
parts[1].size = master->size - rootfs_offset;
*pparts = parts;
return NR_PARTS;
err_free_parts:
kfree(parts);
return ret;
}
static const struct of_device_id mtdsplit_seil_fw_of_match_table[] = {
{ .compatible = "iij,seil-firmware" },
{},
};
MODULE_DEVICE_TABLE(of, mtdsplit_seil_fw_of_match_table);
static struct mtd_part_parser mtdsplit_seil_fw_parser = {
.owner = THIS_MODULE,
.name = "seil-fw",
.of_match_table = mtdsplit_seil_fw_of_match_table,
.parse_fn = mtdsplit_parse_seil_fw,
.type = MTD_PARSER_TYPE_FIRMWARE,
};
module_mtd_part_parser(mtdsplit_seil_fw_parser);

View file

@ -0,0 +1,72 @@
/*
* Copyright (C) 2013 Felix Fietkau <nbd@nbd.name>
* Copyright (C) 2013 Gabor Juhos <juhosg@openwrt.org>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 as published
* by the Free Software Foundation.
*
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/magic.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/partitions.h>
#include <linux/byteorder/generic.h>
#include "mtdsplit.h"
static int
mtdsplit_parse_squashfs(struct mtd_info *master,
const struct mtd_partition **pparts,
struct mtd_part_parser_data *data)
{
struct mtd_partition *part;
struct mtd_info *parent_mtd;
size_t part_offset;
size_t squashfs_len;
int err;
err = mtd_get_squashfs_len(master, 0, &squashfs_len);
if (err)
return err;
parent_mtd = mtd_get_master(master);
part_offset = mtdpart_get_offset(master);
part = kzalloc(sizeof(*part), GFP_KERNEL);
if (!part) {
pr_alert("unable to allocate memory for \"%s\" partition\n",
ROOTFS_SPLIT_NAME);
return -ENOMEM;
}
part->name = ROOTFS_SPLIT_NAME;
part->offset = mtd_roundup_to_eb(part_offset + squashfs_len,
parent_mtd) - part_offset;
part->size = mtd_rounddown_to_eb(master->size - part->offset, master);
*pparts = part;
return 1;
}
static struct mtd_part_parser mtdsplit_squashfs_parser = {
.owner = THIS_MODULE,
.name = "squashfs-split",
.parse_fn = mtdsplit_parse_squashfs,
.type = MTD_PARSER_TYPE_ROOTFS,
};
static int __init mtdsplit_squashfs_init(void)
{
register_mtd_parser(&mtdsplit_squashfs_parser);
return 0;
}
subsys_initcall(mtdsplit_squashfs_init);

View file

@ -0,0 +1,176 @@
/*
* Copyright (C) 2013 Gabor Juhos <juhosg@openwrt.org>
* Copyright (C) 2014 Felix Fietkau <nbd@nbd.name>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 as published
* by the Free Software Foundation.
*
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/partitions.h>
#include <linux/byteorder/generic.h>
#include <linux/of.h>
#include "mtdsplit.h"
#define TPLINK_NR_PARTS 2
#define TPLINK_MIN_ROOTFS_OFFS 0x80000 /* 512KiB */
#define MD5SUM_LEN 16
struct fw_v1 {
char vendor_name[24];
char fw_version[36];
uint32_t hw_id; /* hardware id */
uint32_t hw_rev; /* hardware revision */
uint32_t unk1;
uint8_t md5sum1[MD5SUM_LEN];
uint32_t unk2;
uint8_t md5sum2[MD5SUM_LEN];
uint32_t unk3;
uint32_t kernel_la; /* kernel load address */
uint32_t kernel_ep; /* kernel entry point */
uint32_t fw_length; /* total length of the firmware */
uint32_t kernel_ofs; /* kernel data offset */
uint32_t kernel_len; /* kernel data length */
uint32_t rootfs_ofs; /* rootfs data offset */
uint32_t rootfs_len; /* rootfs data length */
uint32_t boot_ofs; /* bootloader data offset */
uint32_t boot_len; /* bootloader data length */
uint8_t pad[360];
} __attribute__ ((packed));
struct fw_v2 {
char fw_version[48]; /* 0x04: fw version string */
uint32_t hw_id; /* 0x34: hardware id */
uint32_t hw_rev; /* 0x38: FIXME: hardware revision? */
uint32_t unk1; /* 0x3c: 0x00000000 */
uint8_t md5sum1[MD5SUM_LEN]; /* 0x40 */
uint32_t unk2; /* 0x50: 0x00000000 */
uint8_t md5sum2[MD5SUM_LEN]; /* 0x54 */
uint32_t unk3; /* 0x64: 0xffffffff */
uint32_t kernel_la; /* 0x68: kernel load address */
uint32_t kernel_ep; /* 0x6c: kernel entry point */
uint32_t fw_length; /* 0x70: total length of the image */
uint32_t kernel_ofs; /* 0x74: kernel data offset */
uint32_t kernel_len; /* 0x78: kernel data length */
uint32_t rootfs_ofs; /* 0x7c: rootfs data offset */
uint32_t rootfs_len; /* 0x80: rootfs data length */
uint32_t boot_ofs; /* 0x84: FIXME: seems to be unused */
uint32_t boot_len; /* 0x88: FIXME: seems to be unused */
uint16_t unk4; /* 0x8c: 0x55aa */
uint8_t sver_hi; /* 0x8e */
uint8_t sver_lo; /* 0x8f */
uint8_t unk5; /* 0x90: magic: 0xa5 */
uint8_t ver_hi; /* 0x91 */
uint8_t ver_mid; /* 0x92 */
uint8_t ver_lo; /* 0x93 */
uint8_t pad[364];
} __attribute__ ((packed));
struct tplink_fw_header {
uint32_t version;
union {
struct fw_v1 v1;
struct fw_v2 v2;
};
};
static int mtdsplit_parse_tplink(struct mtd_info *master,
const struct mtd_partition **pparts,
struct mtd_part_parser_data *data)
{
struct tplink_fw_header hdr;
size_t hdr_len, retlen, kernel_size;
size_t rootfs_offset;
struct mtd_partition *parts;
int err;
hdr_len = sizeof(hdr);
err = mtd_read(master, 0, hdr_len, &retlen, (void *) &hdr);
if (err)
return err;
if (retlen != hdr_len)
return -EIO;
switch (le32_to_cpu(hdr.version)) {
case 1:
if (be32_to_cpu(hdr.v1.kernel_ofs) != sizeof(hdr))
return -EINVAL;
kernel_size = sizeof(hdr) + be32_to_cpu(hdr.v1.kernel_len);
rootfs_offset = be32_to_cpu(hdr.v1.rootfs_ofs);
break;
case 2:
case 3:
if (be32_to_cpu(hdr.v2.kernel_ofs) != sizeof(hdr))
return -EINVAL;
kernel_size = sizeof(hdr) + be32_to_cpu(hdr.v2.kernel_len);
rootfs_offset = be32_to_cpu(hdr.v2.rootfs_ofs);
break;
default:
return -EINVAL;
}
if (kernel_size > master->size)
return -EINVAL;
/* Find the rootfs */
err = mtd_check_rootfs_magic(master, rootfs_offset, NULL);
if (err) {
/*
* The size in the header might cover the rootfs as well.
* Start the search from an arbitrary offset.
*/
err = mtd_find_rootfs_from(master, TPLINK_MIN_ROOTFS_OFFS,
master->size, &rootfs_offset, NULL);
if (err)
return err;
}
parts = kzalloc(TPLINK_NR_PARTS * sizeof(*parts), GFP_KERNEL);
if (!parts)
return -ENOMEM;
parts[0].name = KERNEL_PART_NAME;
parts[0].offset = 0;
parts[0].size = kernel_size;
parts[1].name = ROOTFS_PART_NAME;
parts[1].offset = rootfs_offset;
parts[1].size = master->size - rootfs_offset;
*pparts = parts;
return TPLINK_NR_PARTS;
}
static const struct of_device_id mtdsplit_tplink_of_match_table[] = {
{ .compatible = "tplink,firmware" },
{},
};
static struct mtd_part_parser mtdsplit_tplink_parser = {
.owner = THIS_MODULE,
.name = "tplink-fw",
.of_match_table = mtdsplit_tplink_of_match_table,
.parse_fn = mtdsplit_parse_tplink,
.type = MTD_PARSER_TYPE_FIRMWARE,
};
static int __init mtdsplit_tplink_init(void)
{
register_mtd_parser(&mtdsplit_tplink_parser);
return 0;
}
subsys_initcall(mtdsplit_tplink_init);

View file

@ -0,0 +1,155 @@
/*
* Copyright (C) 2013 Gabor Juhos <juhosg@openwrt.org>
* Copyright (C) 2014 Felix Fietkau <nbd@nbd.name>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 as published
* by the Free Software Foundation.
*
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/partitions.h>
#include <linux/byteorder/generic.h>
#include <linux/of.h>
#include "mtdsplit.h"
#define TRX_MAGIC 0x30524448 /* "HDR0" */
struct trx_header {
__le32 magic;
__le32 len;
__le32 crc32;
__le32 flag_version;
__le32 offset[4];
};
static int
read_trx_header(struct mtd_info *mtd, size_t offset,
struct trx_header *header)
{
size_t header_len;
size_t retlen;
int ret;
header_len = sizeof(*header);
ret = mtd_read(mtd, offset, header_len, &retlen,
(unsigned char *) header);
if (ret) {
pr_debug("read error in \"%s\"\n", mtd->name);
return ret;
}
if (retlen != header_len) {
pr_debug("short read in \"%s\"\n", mtd->name);
return -EIO;
}
return 0;
}
static int
mtdsplit_parse_trx(struct mtd_info *master,
const struct mtd_partition **pparts,
struct mtd_part_parser_data *data)
{
struct mtd_partition *parts;
struct trx_header hdr;
int nr_parts;
size_t offset;
size_t trx_offset;
size_t trx_size = 0;
size_t rootfs_offset;
size_t rootfs_size = 0;
int ret;
nr_parts = 2;
parts = kzalloc(nr_parts * sizeof(*parts), GFP_KERNEL);
if (!parts)
return -ENOMEM;
/* find trx image on erase block boundaries */
for (offset = 0; offset < master->size; offset += master->erasesize) {
trx_size = 0;
ret = read_trx_header(master, offset, &hdr);
if (ret)
continue;
if (hdr.magic != cpu_to_le32(TRX_MAGIC)) {
pr_debug("no valid trx header found in \"%s\" at offset %llx\n",
master->name, (unsigned long long) offset);
continue;
}
trx_size = le32_to_cpu(hdr.len);
if ((offset + trx_size) > master->size) {
pr_debug("trx image exceeds MTD device \"%s\"\n",
master->name);
continue;
}
break;
}
if (trx_size == 0) {
pr_debug("no trx header found in \"%s\"\n", master->name);
ret = -ENODEV;
goto err;
}
trx_offset = offset + hdr.offset[0];
rootfs_offset = offset + hdr.offset[1];
rootfs_size = master->size - rootfs_offset;
trx_size = rootfs_offset - trx_offset;
if (rootfs_size == 0) {
pr_debug("no rootfs found in \"%s\"\n", master->name);
ret = -ENODEV;
goto err;
}
parts[0].name = KERNEL_PART_NAME;
parts[0].offset = trx_offset;
parts[0].size = trx_size;
parts[1].name = ROOTFS_PART_NAME;
parts[1].offset = rootfs_offset;
parts[1].size = rootfs_size;
*pparts = parts;
return nr_parts;
err:
kfree(parts);
return ret;
}
static const struct of_device_id trx_parser_of_match_table[] = {
{ .compatible = "openwrt,trx" },
{},
};
MODULE_DEVICE_TABLE(of, trx_parser_of_match_table);
static struct mtd_part_parser trx_parser = {
.owner = THIS_MODULE,
.name = "trx-fw",
.of_match_table = trx_parser_of_match_table,
.parse_fn = mtdsplit_parse_trx,
.type = MTD_PARSER_TYPE_FIRMWARE,
};
static int __init mtdsplit_trx_init(void)
{
register_mtd_parser(&trx_parser);
return 0;
}
module_init(mtdsplit_trx_init);

View file

@ -0,0 +1,282 @@
/*
* Copyright (C) 2013 Gabor Juhos <juhosg@openwrt.org>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 as published
* by the Free Software Foundation.
*
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/partitions.h>
#include <linux/version.h>
#include <linux/byteorder/generic.h>
#include <linux/of.h>
#include <dt-bindings/mtd/partitions/uimage.h>
#include "mtdsplit.h"
/*
* Legacy format image header,
* all data in network byte order (aka natural aka bigendian).
*/
struct uimage_header {
uint32_t ih_magic; /* Image Header Magic Number */
uint32_t ih_hcrc; /* Image Header CRC Checksum */
uint32_t ih_time; /* Image Creation Timestamp */
uint32_t ih_size; /* Image Data Size */
uint32_t ih_load; /* Data Load Address */
uint32_t ih_ep; /* Entry Point Address */
uint32_t ih_dcrc; /* Image Data CRC Checksum */
uint8_t ih_os; /* Operating System */
uint8_t ih_arch; /* CPU architecture */
uint8_t ih_type; /* Image Type */
uint8_t ih_comp; /* Compression Type */
uint8_t ih_name[IH_NMLEN]; /* Image Name */
};
static int
read_uimage_header(struct mtd_info *mtd, size_t offset, u_char *buf,
size_t header_len)
{
size_t retlen;
int ret;
ret = mtd_read(mtd, offset, header_len, &retlen, buf);
if (ret) {
pr_debug("read error in \"%s\"\n", mtd->name);
return ret;
}
if (retlen != header_len) {
pr_debug("short read in \"%s\"\n", mtd->name);
return -EIO;
}
return 0;
}
static void uimage_parse_dt(struct mtd_info *master, int *extralen,
u32 *ih_magic, u32 *ih_type,
u32 *header_offset, u32 *part_magic)
{
struct device_node *np = mtd_get_of_node(master);
if (!np || !of_device_is_compatible(np, "openwrt,uimage"))
return;
if (!of_property_read_u32(np, "openwrt,padding", extralen))
pr_debug("got openwrt,padding=%d from device-tree\n", *extralen);
if (!of_property_read_u32(np, "openwrt,ih-magic", ih_magic))
pr_debug("got openwrt,ih-magic=%08x from device-tree\n", *ih_magic);
if (!of_property_read_u32(np, "openwrt,ih-type", ih_type))
pr_debug("got openwrt,ih-type=%08x from device-tree\n", *ih_type);
if (!of_property_read_u32(np, "openwrt,offset", header_offset))
pr_debug("got ih-start=%u from device-tree\n", *header_offset);
if (!of_property_read_u32(np, "openwrt,partition-magic", part_magic))
pr_debug("got openwrt,partition-magic=%08x from device-tree\n", *part_magic);
}
static ssize_t uimage_verify_default(u_char *buf, u32 ih_magic, u32 ih_type)
{
struct uimage_header *header = (struct uimage_header *)buf;
/* default sanity checks */
if (be32_to_cpu(header->ih_magic) != ih_magic) {
pr_debug("invalid uImage magic: %08x != %08x\n",
be32_to_cpu(header->ih_magic), ih_magic);
return -EINVAL;
}
if (header->ih_os != IH_OS_LINUX) {
pr_debug("invalid uImage OS: %08x != %08x\n",
be32_to_cpu(header->ih_os), IH_OS_LINUX);
return -EINVAL;
}
if (header->ih_type != ih_type) {
pr_debug("invalid uImage type: %08x != %08x\n",
be32_to_cpu(header->ih_type), ih_type);
return -EINVAL;
}
return 0;
}
/**
* __mtdsplit_parse_uimage - scan partition and create kernel + rootfs parts
*
* @find_header: function to call for a block of data that will return offset
* and tail padding length of a valid uImage header if found
*/
static int __mtdsplit_parse_uimage(struct mtd_info *master,
const struct mtd_partition **pparts,
struct mtd_part_parser_data *data)
{
struct mtd_partition *parts;
u_char *buf;
int nr_parts;
size_t offset;
size_t uimage_offset;
size_t uimage_size = 0;
size_t rootfs_offset;
size_t rootfs_size = 0;
size_t buflen;
int uimage_part, rf_part;
int ret;
int extralen = 0;
u32 ih_magic = IH_MAGIC;
u32 ih_type = IH_TYPE_KERNEL;
u32 header_offset = 0;
u32 part_magic = 0;
enum mtdsplit_part_type type;
nr_parts = 2;
parts = kzalloc(nr_parts * sizeof(*parts), GFP_KERNEL);
if (!parts)
return -ENOMEM;
uimage_parse_dt(master, &extralen, &ih_magic, &ih_type, &header_offset, &part_magic);
buflen = sizeof(struct uimage_header) + header_offset;
buf = vmalloc(buflen);
if (!buf) {
ret = -ENOMEM;
goto err_free_parts;
}
/* find uImage on erase block boundaries */
for (offset = 0; offset < master->size; offset += master->erasesize) {
struct uimage_header *header;
uimage_size = 0;
ret = read_uimage_header(master, offset, buf, buflen);
if (ret)
continue;
/* verify optional partition magic before uimage header */
if (header_offset && part_magic && (be32_to_cpu(*(u32 *)buf) != part_magic))
continue;
ret = uimage_verify_default(buf + header_offset, ih_magic, ih_type);
if (ret < 0) {
pr_debug("no valid uImage found in \"%s\" at offset %llx\n",
master->name, (unsigned long long) offset);
continue;
}
header = (struct uimage_header *)(buf + header_offset);
uimage_size = sizeof(*header) +
be32_to_cpu(header->ih_size) + header_offset + extralen;
if ((offset + uimage_size) > master->size) {
pr_debug("uImage exceeds MTD device \"%s\"\n",
master->name);
continue;
}
break;
}
if (uimage_size == 0) {
pr_debug("no uImage found in \"%s\"\n", master->name);
ret = -ENODEV;
goto err_free_buf;
}
uimage_offset = offset;
if (uimage_offset == 0) {
uimage_part = 0;
rf_part = 1;
/* find the roots after the uImage */
ret = mtd_find_rootfs_from(master, uimage_offset + uimage_size,
master->size, &rootfs_offset, &type);
if (ret) {
pr_debug("no rootfs after uImage in \"%s\"\n",
master->name);
goto err_free_buf;
}
rootfs_size = master->size - rootfs_offset;
uimage_size = rootfs_offset - uimage_offset;
} else {
rf_part = 0;
uimage_part = 1;
/* check rootfs presence at offset 0 */
ret = mtd_check_rootfs_magic(master, 0, &type);
if (ret) {
pr_debug("no rootfs before uImage in \"%s\"\n",
master->name);
goto err_free_buf;
}
rootfs_offset = 0;
rootfs_size = uimage_offset;
}
if (rootfs_size == 0) {
pr_debug("no rootfs found in \"%s\"\n", master->name);
ret = -ENODEV;
goto err_free_buf;
}
parts[uimage_part].name = KERNEL_PART_NAME;
parts[uimage_part].offset = uimage_offset;
parts[uimage_part].size = uimage_size;
if (type == MTDSPLIT_PART_TYPE_UBI)
parts[rf_part].name = UBI_PART_NAME;
else
parts[rf_part].name = ROOTFS_PART_NAME;
parts[rf_part].offset = rootfs_offset;
parts[rf_part].size = rootfs_size;
vfree(buf);
*pparts = parts;
return nr_parts;
err_free_buf:
vfree(buf);
err_free_parts:
kfree(parts);
return ret;
}
static const struct of_device_id mtdsplit_uimage_of_match_table[] = {
{ .compatible = "denx,uimage" },
{ .compatible = "openwrt,uimage" },
{},
};
static struct mtd_part_parser uimage_generic_parser = {
.owner = THIS_MODULE,
.name = "uimage-fw",
.of_match_table = mtdsplit_uimage_of_match_table,
.parse_fn = __mtdsplit_parse_uimage,
.type = MTD_PARSER_TYPE_FIRMWARE,
};
/**************************************************
* Init
**************************************************/
static int __init mtdsplit_uimage_init(void)
{
register_mtd_parser(&uimage_generic_parser);
return 0;
}
module_init(mtdsplit_uimage_init);

View file

@ -0,0 +1,142 @@
/*
* Copyright (C) 2013 Gabor Juhos <juhosg@openwrt.org>
* Copyright (C) 2014 Felix Fietkau <nbd@nbd.name>
* Copyright (C) 2016 Stijn Tintel <stijn@linux-ipv6.be>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 as published
* by the Free Software Foundation.
*
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/partitions.h>
#include <linux/byteorder/generic.h>
#include <linux/of.h>
#include "mtdsplit.h"
#define WRGG_NR_PARTS 2
#define WRGG_MIN_ROOTFS_OFFS 0x80000 /* 512KiB */
#define WRGG03_MAGIC 0x20080321
#define WRG_MAGIC 0x20040220
struct wrgg03_header {
char signature[32];
uint32_t magic1;
uint32_t magic2;
char version[16];
char model[16];
uint32_t flag[2];
uint32_t reserve[2];
char buildno[16];
uint32_t size;
uint32_t offset;
char devname[32];
char digest[16];
} __attribute__ ((packed));
struct wrg_header {
char signature[32];
uint32_t magic1;
uint32_t magic2;
uint32_t size;
uint32_t offset;
char devname[32];
char digest[16];
} __attribute__ ((packed));
static int mtdsplit_parse_wrgg(struct mtd_info *master,
const struct mtd_partition **pparts,
struct mtd_part_parser_data *data)
{
struct wrgg03_header hdr;
size_t hdr_len, retlen, kernel_ent_size;
size_t rootfs_offset;
struct mtd_partition *parts;
enum mtdsplit_part_type type;
int err;
hdr_len = sizeof(hdr);
err = mtd_read(master, 0, hdr_len, &retlen, (void *) &hdr);
if (err)
return err;
if (retlen != hdr_len)
return -EIO;
/* sanity checks */
if (le32_to_cpu(hdr.magic1) == WRGG03_MAGIC) {
kernel_ent_size = hdr_len + be32_to_cpu(hdr.size);
/*
* If this becomes silly big it's probably because the
* WRGG image is little-endian.
*/
if (kernel_ent_size > master->size)
kernel_ent_size = hdr_len + le32_to_cpu(hdr.size);
/* Now what ?! It's neither */
if (kernel_ent_size > master->size)
return -EINVAL;
} else if (le32_to_cpu(hdr.magic1) == WRG_MAGIC) {
kernel_ent_size = sizeof(struct wrg_header) + le32_to_cpu(
((struct wrg_header*)&hdr)->size);
} else {
return -EINVAL;
}
if (kernel_ent_size > master->size)
return -EINVAL;
/*
* The size in the header covers the rootfs as well.
* Start the search from an arbitrary offset.
*/
err = mtd_find_rootfs_from(master, WRGG_MIN_ROOTFS_OFFS,
master->size, &rootfs_offset, &type);
if (err)
return err;
parts = kzalloc(WRGG_NR_PARTS * sizeof(*parts), GFP_KERNEL);
if (!parts)
return -ENOMEM;
parts[0].name = KERNEL_PART_NAME;
parts[0].offset = 0;
parts[0].size = rootfs_offset;
parts[1].name = ROOTFS_PART_NAME;
parts[1].offset = rootfs_offset;
parts[1].size = master->size - rootfs_offset;
*pparts = parts;
return WRGG_NR_PARTS;
}
static const struct of_device_id mtdsplit_wrgg_of_match_table[] = {
{ .compatible = "wrg" },
{},
};
MODULE_DEVICE_TABLE(of, mtdsplit_wrgg_of_match_table);
static struct mtd_part_parser mtdsplit_wrgg_parser = {
.owner = THIS_MODULE,
.name = "wrgg-fw",
.of_match_table = mtdsplit_wrgg_of_match_table,
.parse_fn = mtdsplit_parse_wrgg,
.type = MTD_PARSER_TYPE_FIRMWARE,
};
static int __init mtdsplit_wrgg_init(void)
{
register_mtd_parser(&mtdsplit_wrgg_parser);
return 0;
}
subsys_initcall(mtdsplit_wrgg_init);

View file

@ -0,0 +1,465 @@
/*
* Copyright (c) 2017 MediaTek Inc.
* Author: Xiangsheng Hou <xiangsheng.hou@mediatek.com>
* Copyright (c) 2020-2022 Felix Fietkau <nbd@nbd.name>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/module.h>
#include <linux/gfp.h>
#include <linux/slab.h>
#include <linux/bits.h>
#include "mtk_bmt.h"
struct bmt_desc bmtd = {};
/* -------- Nand operations wrapper -------- */
int bbt_nand_copy(u16 dest_blk, u16 src_blk, loff_t max_offset)
{
int pages = bmtd.blk_size >> bmtd.pg_shift;
loff_t src = (loff_t)src_blk << bmtd.blk_shift;
loff_t dest = (loff_t)dest_blk << bmtd.blk_shift;
loff_t offset = 0;
uint8_t oob[64];
int i, ret;
for (i = 0; i < pages; i++) {
struct mtd_oob_ops rd_ops = {
.mode = MTD_OPS_PLACE_OOB,
.oobbuf = oob,
.ooblen = min_t(int, bmtd.mtd->oobsize / pages, sizeof(oob)),
.datbuf = bmtd.data_buf,
.len = bmtd.pg_size,
};
struct mtd_oob_ops wr_ops = {
.mode = MTD_OPS_PLACE_OOB,
.oobbuf = oob,
.datbuf = bmtd.data_buf,
.len = bmtd.pg_size,
};
if (offset >= max_offset)
break;
ret = bmtd._read_oob(bmtd.mtd, src + offset, &rd_ops);
if (ret < 0 && !mtd_is_bitflip(ret))
return ret;
if (!rd_ops.retlen)
break;
ret = bmtd._write_oob(bmtd.mtd, dest + offset, &wr_ops);
if (ret < 0)
return ret;
wr_ops.ooblen = rd_ops.oobretlen;
offset += rd_ops.retlen;
}
return 0;
}
/* -------- Bad Blocks Management -------- */
bool mapping_block_in_range(int block, int *start, int *end)
{
const __be32 *cur = bmtd.remap_range;
u32 addr = block << bmtd.blk_shift;
int i;
if (!cur || !bmtd.remap_range_len) {
*start = 0;
*end = bmtd.total_blks;
return true;
}
for (i = 0; i < bmtd.remap_range_len; i++, cur += 2) {
if (addr < be32_to_cpu(cur[0]) || addr >= be32_to_cpu(cur[1]))
continue;
*start = be32_to_cpu(cur[0]);
*end = be32_to_cpu(cur[1]);
return true;
}
return false;
}
static bool
mtk_bmt_remap_block(u32 block, u32 mapped_block, int copy_len)
{
int start, end;
if (!mapping_block_in_range(block, &start, &end))
return false;
return bmtd.ops->remap_block(block, mapped_block, copy_len);
}
static int
mtk_bmt_read(struct mtd_info *mtd, loff_t from,
struct mtd_oob_ops *ops)
{
struct mtd_oob_ops cur_ops = *ops;
int retry_count = 0;
loff_t cur_from;
int ret = 0;
int max_bitflips = 0;
ops->retlen = 0;
ops->oobretlen = 0;
while (ops->retlen < ops->len || ops->oobretlen < ops->ooblen) {
int cur_ret;
u32 offset = from & (bmtd.blk_size - 1);
u32 block = from >> bmtd.blk_shift;
int cur_block;
cur_block = bmtd.ops->get_mapping_block(block);
if (cur_block < 0)
return -EIO;
cur_from = ((loff_t)cur_block << bmtd.blk_shift) + offset;
cur_ops.oobretlen = 0;
cur_ops.retlen = 0;
cur_ops.len = min_t(u32, mtd->erasesize - offset,
ops->len - ops->retlen);
cur_ret = bmtd._read_oob(mtd, cur_from, &cur_ops);
if (cur_ret < 0)
ret = cur_ret;
else
max_bitflips = max_t(int, max_bitflips, cur_ret);
if (cur_ret < 0 && !mtd_is_bitflip(cur_ret)) {
if (mtk_bmt_remap_block(block, cur_block, mtd->erasesize) &&
retry_count++ < 10)
continue;
goto out;
}
if (mtd->bitflip_threshold && cur_ret >= mtd->bitflip_threshold)
mtk_bmt_remap_block(block, cur_block, mtd->erasesize);
ops->retlen += cur_ops.retlen;
ops->oobretlen += cur_ops.oobretlen;
cur_ops.ooboffs = 0;
cur_ops.datbuf += cur_ops.retlen;
cur_ops.oobbuf += cur_ops.oobretlen;
cur_ops.ooblen -= cur_ops.oobretlen;
if (!cur_ops.len)
cur_ops.len = mtd->erasesize - offset;
from += cur_ops.len;
retry_count = 0;
}
out:
if (ret < 0)
return ret;
return max_bitflips;
}
static int
mtk_bmt_write(struct mtd_info *mtd, loff_t to,
struct mtd_oob_ops *ops)
{
struct mtd_oob_ops cur_ops = *ops;
int retry_count = 0;
loff_t cur_to;
int ret;
ops->retlen = 0;
ops->oobretlen = 0;
while (ops->retlen < ops->len || ops->oobretlen < ops->ooblen) {
u32 offset = to & (bmtd.blk_size - 1);
u32 block = to >> bmtd.blk_shift;
int cur_block;
cur_block = bmtd.ops->get_mapping_block(block);
if (cur_block < 0)
return -EIO;
cur_to = ((loff_t)cur_block << bmtd.blk_shift) + offset;
cur_ops.oobretlen = 0;
cur_ops.retlen = 0;
cur_ops.len = min_t(u32, bmtd.blk_size - offset,
ops->len - ops->retlen);
ret = bmtd._write_oob(mtd, cur_to, &cur_ops);
if (ret < 0) {
if (mtk_bmt_remap_block(block, cur_block, offset) &&
retry_count++ < 10)
continue;
return ret;
}
ops->retlen += cur_ops.retlen;
ops->oobretlen += cur_ops.oobretlen;
cur_ops.ooboffs = 0;
cur_ops.datbuf += cur_ops.retlen;
cur_ops.oobbuf += cur_ops.oobretlen;
cur_ops.ooblen -= cur_ops.oobretlen;
if (!cur_ops.len)
cur_ops.len = mtd->erasesize - offset;
to += cur_ops.len;
retry_count = 0;
}
return 0;
}
static int
mtk_bmt_mtd_erase(struct mtd_info *mtd, struct erase_info *instr)
{
struct erase_info mapped_instr = {
.len = bmtd.blk_size,
};
int retry_count = 0;
u64 start_addr, end_addr;
int ret;
u16 orig_block;
int block;
start_addr = instr->addr & (~mtd->erasesize_mask);
end_addr = instr->addr + instr->len;
while (start_addr < end_addr) {
orig_block = start_addr >> bmtd.blk_shift;
block = bmtd.ops->get_mapping_block(orig_block);
if (block < 0)
return -EIO;
mapped_instr.addr = (loff_t)block << bmtd.blk_shift;
ret = bmtd._erase(mtd, &mapped_instr);
if (ret) {
if (mtk_bmt_remap_block(orig_block, block, 0) &&
retry_count++ < 10)
continue;
instr->fail_addr = start_addr;
break;
}
start_addr += mtd->erasesize;
retry_count = 0;
}
return ret;
}
static int
mtk_bmt_block_isbad(struct mtd_info *mtd, loff_t ofs)
{
int retry_count = 0;
u16 orig_block = ofs >> bmtd.blk_shift;
u16 block;
int ret;
retry:
block = bmtd.ops->get_mapping_block(orig_block);
ret = bmtd._block_isbad(mtd, (loff_t)block << bmtd.blk_shift);
if (ret) {
if (mtk_bmt_remap_block(orig_block, block, bmtd.blk_size) &&
retry_count++ < 10)
goto retry;
}
return ret;
}
static int
mtk_bmt_block_markbad(struct mtd_info *mtd, loff_t ofs)
{
u16 orig_block = ofs >> bmtd.blk_shift;
int block;
block = bmtd.ops->get_mapping_block(orig_block);
if (block < 0)
return -EIO;
mtk_bmt_remap_block(orig_block, block, bmtd.blk_size);
return bmtd._block_markbad(mtd, (loff_t)block << bmtd.blk_shift);
}
static void
mtk_bmt_replace_ops(struct mtd_info *mtd)
{
bmtd._read_oob = mtd->_read_oob;
bmtd._write_oob = mtd->_write_oob;
bmtd._erase = mtd->_erase;
bmtd._block_isbad = mtd->_block_isbad;
bmtd._block_markbad = mtd->_block_markbad;
mtd->_read_oob = mtk_bmt_read;
mtd->_write_oob = mtk_bmt_write;
mtd->_erase = mtk_bmt_mtd_erase;
mtd->_block_isbad = mtk_bmt_block_isbad;
mtd->_block_markbad = mtk_bmt_block_markbad;
}
static int mtk_bmt_debug_repair(void *data, u64 val)
{
int block = val >> bmtd.blk_shift;
int prev_block, new_block;
prev_block = bmtd.ops->get_mapping_block(block);
if (prev_block < 0)
return -EIO;
bmtd.ops->unmap_block(block);
new_block = bmtd.ops->get_mapping_block(block);
if (new_block < 0)
return -EIO;
if (prev_block == new_block)
return 0;
bbt_nand_erase(new_block);
bbt_nand_copy(new_block, prev_block, bmtd.blk_size);
return 0;
}
static int mtk_bmt_debug_mark_good(void *data, u64 val)
{
bmtd.ops->unmap_block(val >> bmtd.blk_shift);
return 0;
}
static int mtk_bmt_debug_mark_bad(void *data, u64 val)
{
u32 block = val >> bmtd.blk_shift;
int cur_block;
cur_block = bmtd.ops->get_mapping_block(block);
if (cur_block < 0)
return -EIO;
mtk_bmt_remap_block(block, cur_block, bmtd.blk_size);
return 0;
}
static int mtk_bmt_debug(void *data, u64 val)
{
return bmtd.ops->debug(data, val);
}
DEFINE_DEBUGFS_ATTRIBUTE(fops_repair, NULL, mtk_bmt_debug_repair, "%llu\n");
DEFINE_DEBUGFS_ATTRIBUTE(fops_mark_good, NULL, mtk_bmt_debug_mark_good, "%llu\n");
DEFINE_DEBUGFS_ATTRIBUTE(fops_mark_bad, NULL, mtk_bmt_debug_mark_bad, "%llu\n");
DEFINE_DEBUGFS_ATTRIBUTE(fops_debug, NULL, mtk_bmt_debug, "%llu\n");
static void
mtk_bmt_add_debugfs(void)
{
struct dentry *dir;
dir = bmtd.debugfs_dir = debugfs_create_dir("mtk-bmt", NULL);
if (!dir)
return;
debugfs_create_file_unsafe("repair", S_IWUSR, dir, NULL, &fops_repair);
debugfs_create_file_unsafe("mark_good", S_IWUSR, dir, NULL, &fops_mark_good);
debugfs_create_file_unsafe("mark_bad", S_IWUSR, dir, NULL, &fops_mark_bad);
debugfs_create_file_unsafe("debug", S_IWUSR, dir, NULL, &fops_debug);
}
void mtk_bmt_detach(struct mtd_info *mtd)
{
if (bmtd.mtd != mtd)
return;
if (bmtd.debugfs_dir)
debugfs_remove_recursive(bmtd.debugfs_dir);
bmtd.debugfs_dir = NULL;
kfree(bmtd.bbt_buf);
kfree(bmtd.data_buf);
mtd->_read_oob = bmtd._read_oob;
mtd->_write_oob = bmtd._write_oob;
mtd->_erase = bmtd._erase;
mtd->_block_isbad = bmtd._block_isbad;
mtd->_block_markbad = bmtd._block_markbad;
mtd->size = bmtd.total_blks << bmtd.blk_shift;
memset(&bmtd, 0, sizeof(bmtd));
}
int mtk_bmt_attach(struct mtd_info *mtd)
{
struct device_node *np;
int ret = 0;
if (bmtd.mtd)
return -ENOSPC;
np = mtd_get_of_node(mtd);
if (!np)
return 0;
if (of_property_read_bool(np, "mediatek,bmt-v2"))
bmtd.ops = &mtk_bmt_v2_ops;
else if (of_property_read_bool(np, "mediatek,nmbm"))
bmtd.ops = &mtk_bmt_nmbm_ops;
else if (of_property_read_bool(np, "mediatek,bbt"))
bmtd.ops = &mtk_bmt_bbt_ops;
else
return 0;
bmtd.remap_range = of_get_property(np, "mediatek,bmt-remap-range",
&bmtd.remap_range_len);
bmtd.remap_range_len /= 8;
bmtd.mtd = mtd;
mtk_bmt_replace_ops(mtd);
bmtd.blk_size = mtd->erasesize;
bmtd.blk_shift = ffs(bmtd.blk_size) - 1;
bmtd.pg_size = mtd->writesize;
bmtd.pg_shift = ffs(bmtd.pg_size) - 1;
bmtd.total_blks = mtd->size >> bmtd.blk_shift;
bmtd.data_buf = kzalloc(bmtd.pg_size + bmtd.mtd->oobsize, GFP_KERNEL);
if (!bmtd.data_buf) {
pr_info("nand: FATAL ERR: allocate buffer failed!\n");
ret = -1;
goto error;
}
memset(bmtd.data_buf, 0xff, bmtd.pg_size + bmtd.mtd->oobsize);
ret = bmtd.ops->init(np);
if (ret)
goto error;
mtk_bmt_add_debugfs();
return 0;
error:
mtk_bmt_detach(mtd);
return ret;
}
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Xiangsheng Hou <xiangsheng.hou@mediatek.com>, Felix Fietkau <nbd@nbd.name>");
MODULE_DESCRIPTION("Bad Block mapping management v2 for MediaTek NAND Flash Driver");

View file

@ -0,0 +1,137 @@
#ifndef __MTK_BMT_PRIV_H
#define __MTK_BMT_PRIV_H
#include <linux/kernel.h>
#include <linux/of.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/partitions.h>
#include <linux/mtd/mtk_bmt.h>
#include <linux/debugfs.h>
#define MAIN_SIGNATURE_OFFSET 0
#define OOB_SIGNATURE_OFFSET 1
#define BBT_LOG(fmt, ...) pr_debug("[BBT][%s|%d] "fmt"\n", __func__, __LINE__, ##__VA_ARGS__)
struct mtk_bmt_ops {
char *sig;
unsigned int sig_len;
int (*init)(struct device_node *np);
bool (*remap_block)(u16 block, u16 mapped_block, int copy_len);
void (*unmap_block)(u16 block);
int (*get_mapping_block)(int block);
int (*debug)(void *data, u64 val);
};
struct bbbt;
struct nmbm_instance;
struct bmt_desc {
struct mtd_info *mtd;
unsigned char *bbt_buf;
unsigned char *data_buf;
int (*_read_oob) (struct mtd_info *mtd, loff_t from,
struct mtd_oob_ops *ops);
int (*_write_oob) (struct mtd_info *mtd, loff_t to,
struct mtd_oob_ops *ops);
int (*_erase) (struct mtd_info *mtd, struct erase_info *instr);
int (*_block_isbad) (struct mtd_info *mtd, loff_t ofs);
int (*_block_markbad) (struct mtd_info *mtd, loff_t ofs);
const struct mtk_bmt_ops *ops;
union {
struct bbbt *bbt;
struct nmbm_instance *ni;
};
struct dentry *debugfs_dir;
u32 table_size;
u32 pg_size;
u32 blk_size;
u16 pg_shift;
u16 blk_shift;
/* bbt logical address */
u16 pool_lba;
/* bbt physical address */
u16 pool_pba;
/* Maximum count of bad blocks that the vendor guaranteed */
u16 bb_max;
/* Total blocks of the Nand Chip */
u16 total_blks;
/* The block(n) BMT is located at (bmt_tbl[n]) */
u16 bmt_blk_idx;
/* How many pages needs to store 'struct bbbt' */
u32 bmt_pgs;
const __be32 *remap_range;
int remap_range_len;
/* to compensate for driver level remapping */
u8 oob_offset;
};
extern struct bmt_desc bmtd;
extern const struct mtk_bmt_ops mtk_bmt_v2_ops;
extern const struct mtk_bmt_ops mtk_bmt_bbt_ops;
extern const struct mtk_bmt_ops mtk_bmt_nmbm_ops;
static inline u32 blk_pg(u16 block)
{
return (u32)(block << (bmtd.blk_shift - bmtd.pg_shift));
}
static inline int
bbt_nand_read(u32 page, unsigned char *dat, int dat_len,
unsigned char *fdm, int fdm_len)
{
struct mtd_oob_ops ops = {
.mode = MTD_OPS_PLACE_OOB,
.ooboffs = bmtd.oob_offset,
.oobbuf = fdm,
.ooblen = fdm_len,
.datbuf = dat,
.len = dat_len,
};
int ret;
ret = bmtd._read_oob(bmtd.mtd, page << bmtd.pg_shift, &ops);
if (ret < 0)
return ret;
if (ret)
pr_info("%s: %d bitflips\n", __func__, ret);
return 0;
}
static inline int bbt_nand_erase(u16 block)
{
struct mtd_info *mtd = bmtd.mtd;
struct erase_info instr = {
.addr = (loff_t)block << bmtd.blk_shift,
.len = bmtd.blk_size,
};
return bmtd._erase(mtd, &instr);
}
static inline int write_bmt(u16 block, unsigned char *dat)
{
struct mtd_oob_ops ops = {
.mode = MTD_OPS_PLACE_OOB,
.ooboffs = OOB_SIGNATURE_OFFSET + bmtd.oob_offset,
.oobbuf = bmtd.ops->sig,
.ooblen = bmtd.ops->sig_len,
.datbuf = dat,
.len = bmtd.bmt_pgs << bmtd.pg_shift,
};
loff_t addr = (loff_t)block << bmtd.blk_shift;
return bmtd._write_oob(bmtd.mtd, addr, &ops);
}
int bbt_nand_copy(u16 dest_blk, u16 src_blk, loff_t max_offset);
bool mapping_block_in_range(int block, int *start, int *end);
#endif

View file

@ -0,0 +1,203 @@
/*
* Copyright (c) 2017 MediaTek Inc.
* Author: Xiangsheng Hou <xiangsheng.hou@mediatek.com>
* Copyright (c) 2020-2022 Felix Fietkau <nbd@nbd.name>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/kernel.h>
#include <linux/slab.h>
#include "mtk_bmt.h"
static bool
bbt_block_is_bad(u16 block)
{
u8 cur = bmtd.bbt_buf[block / 4];
return cur & (3 << ((block % 4) * 2));
}
static void
bbt_set_block_state(u16 block, bool bad)
{
u8 mask = (3 << ((block % 4) * 2));
if (bad)
bmtd.bbt_buf[block / 4] |= mask;
else
bmtd.bbt_buf[block / 4] &= ~mask;
bbt_nand_erase(bmtd.bmt_blk_idx);
write_bmt(bmtd.bmt_blk_idx, bmtd.bbt_buf);
}
static int
get_mapping_block_index_bbt(int block)
{
int start, end, ofs;
int bad_blocks = 0;
int i;
if (!mapping_block_in_range(block, &start, &end))
return block;
start >>= bmtd.blk_shift;
end >>= bmtd.blk_shift;
/* skip bad blocks within the mapping range */
ofs = block - start;
for (i = start; i < end; i++) {
if (bbt_block_is_bad(i))
bad_blocks++;
else if (ofs)
ofs--;
else
break;
}
if (i < end)
return i;
/* when overflowing, remap remaining blocks to bad ones */
for (i = end - 1; bad_blocks > 0; i--) {
if (!bbt_block_is_bad(i))
continue;
bad_blocks--;
if (bad_blocks <= ofs)
return i;
}
return block;
}
static bool remap_block_bbt(u16 block, u16 mapped_blk, int copy_len)
{
int start, end;
u16 new_blk;
if (!mapping_block_in_range(block, &start, &end))
return false;
bbt_set_block_state(mapped_blk, true);
new_blk = get_mapping_block_index_bbt(block);
bbt_nand_erase(new_blk);
if (copy_len > 0)
bbt_nand_copy(new_blk, mapped_blk, copy_len);
return true;
}
static void
unmap_block_bbt(u16 block)
{
bbt_set_block_state(block, false);
}
static int
mtk_bmt_read_bbt(void)
{
u8 oob_buf[8];
int i;
for (i = bmtd.total_blks - 1; i >= bmtd.total_blks - 5; i--) {
u32 page = i << (bmtd.blk_shift - bmtd.pg_shift);
if (bbt_nand_read(page, bmtd.bbt_buf, bmtd.pg_size,
oob_buf, sizeof(oob_buf))) {
pr_info("read_bbt: could not read block %d\n", i);
continue;
}
if (oob_buf[0] != 0xff) {
pr_info("read_bbt: bad block at %d\n", i);
continue;
}
if (memcmp(&oob_buf[1], "mtknand", 7) != 0) {
pr_info("read_bbt: signature mismatch in block %d\n", i);
print_hex_dump(KERN_INFO, "", DUMP_PREFIX_OFFSET, 16, 1, oob_buf, 8, 1);
continue;
}
pr_info("read_bbt: found bbt at block %d\n", i);
bmtd.bmt_blk_idx = i;
return 0;
}
return -EIO;
}
static int
mtk_bmt_init_bbt(struct device_node *np)
{
int buf_size = round_up(bmtd.total_blks >> 2, bmtd.blk_size);
int ret;
bmtd.bbt_buf = kmalloc(buf_size, GFP_KERNEL);
if (!bmtd.bbt_buf)
return -ENOMEM;
memset(bmtd.bbt_buf, 0xff, buf_size);
bmtd.mtd->size -= 4 * bmtd.mtd->erasesize;
ret = mtk_bmt_read_bbt();
if (ret)
return ret;
bmtd.bmt_pgs = buf_size / bmtd.pg_size;
return 0;
}
static int mtk_bmt_debug_bbt(void *data, u64 val)
{
char buf[5];
int i, k;
switch (val) {
case 0:
for (i = 0; i < bmtd.total_blks; i += 4) {
u8 cur = bmtd.bbt_buf[i / 4];
for (k = 0; k < 4; k++, cur >>= 2)
buf[k] = (cur & 3) ? 'B' : '.';
buf[4] = 0;
printk("[%06x] %s\n", i * bmtd.blk_size, buf);
}
break;
case 100:
#if 0
for (i = bmtd.bmt_blk_idx; i < bmtd.total_blks - 1; i++)
bbt_nand_erase(bmtd.bmt_blk_idx);
#endif
bmtd.bmt_blk_idx = bmtd.total_blks - 1;
bbt_nand_erase(bmtd.bmt_blk_idx);
write_bmt(bmtd.bmt_blk_idx, bmtd.bbt_buf);
break;
default:
break;
}
return 0;
}
const struct mtk_bmt_ops mtk_bmt_bbt_ops = {
.sig = "mtknand",
.sig_len = 7,
.init = mtk_bmt_init_bbt,
.remap_block = remap_block_bbt,
.unmap_block = unmap_block_bbt,
.get_mapping_block = get_mapping_block_index_bbt,
.debug = mtk_bmt_debug_bbt,
};

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,506 @@
/*
* Copyright (c) 2017 MediaTek Inc.
* Author: Xiangsheng Hou <xiangsheng.hou@mediatek.com>
* Copyright (c) 2020-2022 Felix Fietkau <nbd@nbd.name>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/kernel.h>
#include <linux/slab.h>
#include "mtk_bmt.h"
struct bbbt {
char signature[3];
/* This version is used to distinguish the legacy and new algorithm */
#define BBMT_VERSION 2
unsigned char version;
/* Below 2 tables will be written in SLC */
u16 bb_tbl[];
};
struct bbmt {
u16 block;
#define NO_MAPPED 0
#define NORMAL_MAPPED 1
#define BMT_MAPPED 2
u16 mapped;
};
/* Maximum 8k blocks */
#define BBPOOL_RATIO 2
#define BB_TABLE_MAX bmtd.table_size
#define BMT_TABLE_MAX (BB_TABLE_MAX * BBPOOL_RATIO / 100)
#define BMT_TBL_DEF_VAL 0x0
static inline struct bbmt *bmt_tbl(struct bbbt *bbbt)
{
return (struct bbmt *)&bbbt->bb_tbl[bmtd.table_size];
}
static u16 find_valid_block(u16 block)
{
u8 fdm[4];
int ret;
int loop = 0;
retry:
if (block >= bmtd.total_blks)
return 0;
ret = bbt_nand_read(blk_pg(block), bmtd.data_buf, bmtd.pg_size,
fdm, sizeof(fdm));
/* Read the 1st byte of FDM to judge whether it's a bad
* or not
*/
if (ret || fdm[0] != 0xff) {
pr_info("nand: found bad block 0x%x\n", block);
if (loop >= bmtd.bb_max) {
pr_info("nand: FATAL ERR: too many bad blocks!!\n");
return 0;
}
loop++;
block++;
goto retry;
}
return block;
}
/* Find out all bad blocks, and fill in the mapping table */
static int scan_bad_blocks(struct bbbt *bbt)
{
int i;
u16 block = 0;
/* First time download, the block0 MUST NOT be a bad block,
* this is guaranteed by vendor
*/
bbt->bb_tbl[0] = 0;
/*
* Construct the mapping table of Normal data area(non-PMT/BMTPOOL)
* G - Good block; B - Bad block
* ---------------------------
* physical |G|G|B|G|B|B|G|G|G|G|B|G|B|
* ---------------------------
* What bb_tbl[i] looks like:
* physical block(i):
* 0 1 2 3 4 5 6 7 8 9 a b c
* mapped block(bb_tbl[i]):
* 0 1 3 6 7 8 9 b ......
* ATTENTION:
* If new bad block ocurred(n), search bmt_tbl to find
* a available block(x), and fill in the bb_tbl[n] = x;
*/
for (i = 1; i < bmtd.pool_lba; i++) {
bbt->bb_tbl[i] = find_valid_block(bbt->bb_tbl[i - 1] + 1);
BBT_LOG("bb_tbl[0x%x] = 0x%x", i, bbt->bb_tbl[i]);
if (bbt->bb_tbl[i] == 0)
return -1;
}
/* Physical Block start Address of BMT pool */
bmtd.pool_pba = bbt->bb_tbl[i - 1] + 1;
if (bmtd.pool_pba >= bmtd.total_blks - 2) {
pr_info("nand: FATAL ERR: Too many bad blocks!!\n");
return -1;
}
BBT_LOG("pool_pba=0x%x", bmtd.pool_pba);
i = 0;
block = bmtd.pool_pba;
/*
* The bmt table is used for runtime bad block mapping
* G - Good block; B - Bad block
* ---------------------------
* physical |G|G|B|G|B|B|G|G|G|G|B|G|B|
* ---------------------------
* block: 0 1 2 3 4 5 6 7 8 9 a b c
* What bmt_tbl[i] looks like in initial state:
* i:
* 0 1 2 3 4 5 6 7
* bmt_tbl[i].block:
* 0 1 3 6 7 8 9 b
* bmt_tbl[i].mapped:
* N N N N N N N B
* N - Not mapped(Available)
* M - Mapped
* B - BMT
* ATTENTION:
* BMT always in the last valid block in pool
*/
while ((block = find_valid_block(block)) != 0) {
bmt_tbl(bbt)[i].block = block;
bmt_tbl(bbt)[i].mapped = NO_MAPPED;
BBT_LOG("bmt_tbl[%d].block = 0x%x", i, block);
block++;
i++;
}
/* i - How many available blocks in pool, which is the length of bmt_tbl[]
* bmtd.bmt_blk_idx - bmt_tbl[bmtd.bmt_blk_idx].block => the BMT block
*/
bmtd.bmt_blk_idx = i - 1;
bmt_tbl(bbt)[bmtd.bmt_blk_idx].mapped = BMT_MAPPED;
if (i < 1) {
pr_info("nand: FATAL ERR: no space to store BMT!!\n");
return -1;
}
pr_info("[BBT] %d available blocks in BMT pool\n", i);
return 0;
}
static bool is_valid_bmt(unsigned char *buf, unsigned char *fdm)
{
struct bbbt *bbt = (struct bbbt *)buf;
u8 *sig = (u8*)bbt->signature + MAIN_SIGNATURE_OFFSET;
if (memcmp(bbt->signature + MAIN_SIGNATURE_OFFSET, "BMT", 3) == 0 &&
memcmp(fdm + OOB_SIGNATURE_OFFSET, "bmt", 3) == 0) {
if (bbt->version == BBMT_VERSION)
return true;
}
BBT_LOG("[BBT] BMT Version not match,upgrage preloader and uboot please! sig=%02x%02x%02x, fdm=%02x%02x%02x",
sig[0], sig[1], sig[2],
fdm[1], fdm[2], fdm[3]);
return false;
}
static u16 get_bmt_index(struct bbmt *bmt)
{
int i = 0;
while (bmt[i].block != BMT_TBL_DEF_VAL) {
if (bmt[i].mapped == BMT_MAPPED)
return i;
i++;
}
return 0;
}
/* Write the Burner Bad Block Table to Nand Flash
* n - write BMT to bmt_tbl[n]
*/
static u16 upload_bmt(struct bbbt *bbt, int n)
{
u16 block;
retry:
if (n < 0 || bmt_tbl(bbt)[n].mapped == NORMAL_MAPPED) {
pr_info("nand: FATAL ERR: no space to store BMT!\n");
return (u16)-1;
}
block = bmt_tbl(bbt)[n].block;
BBT_LOG("n = 0x%x, block = 0x%x", n, block);
if (bbt_nand_erase(block)) {
bmt_tbl(bbt)[n].block = 0;
/* erase failed, try the previous block: bmt_tbl[n - 1].block */
n--;
goto retry;
}
/* The signature offset is fixed set to 0,
* oob signature offset is fixed set to 1
*/
memcpy(bbt->signature + MAIN_SIGNATURE_OFFSET, "BMT", 3);
bbt->version = BBMT_VERSION;
if (write_bmt(block, (unsigned char *)bbt)) {
bmt_tbl(bbt)[n].block = 0;
/* write failed, try the previous block in bmt_tbl[n - 1] */
n--;
goto retry;
}
/* Return the current index(n) of BMT pool (bmt_tbl[n]) */
return n;
}
static u16 find_valid_block_in_pool(struct bbbt *bbt)
{
int i;
if (bmtd.bmt_blk_idx == 0)
goto error;
for (i = 0; i < bmtd.bmt_blk_idx; i++) {
if (bmt_tbl(bbt)[i].block != 0 && bmt_tbl(bbt)[i].mapped == NO_MAPPED) {
bmt_tbl(bbt)[i].mapped = NORMAL_MAPPED;
return bmt_tbl(bbt)[i].block;
}
}
error:
pr_info("nand: FATAL ERR: BMT pool is run out!\n");
return 0;
}
/* We met a bad block, mark it as bad and map it to a valid block in pool,
* if it's a write failure, we need to write the data to mapped block
*/
static bool remap_block_v2(u16 block, u16 mapped_block, int copy_len)
{
u16 new_block;
struct bbbt *bbt;
bbt = bmtd.bbt;
new_block = find_valid_block_in_pool(bbt);
if (new_block == 0)
return false;
/* Map new bad block to available block in pool */
bbt->bb_tbl[block] = new_block;
/* Erase new block */
bbt_nand_erase(new_block);
if (copy_len > 0)
bbt_nand_copy(new_block, mapped_block, copy_len);
bmtd.bmt_blk_idx = upload_bmt(bbt, bmtd.bmt_blk_idx);
return true;
}
static int get_mapping_block_index_v2(int block)
{
int start, end;
if (block >= bmtd.pool_lba)
return block;
if (!mapping_block_in_range(block, &start, &end))
return block;
return bmtd.bbt->bb_tbl[block];
}
static void
unmap_block_v2(u16 block)
{
bmtd.bbt->bb_tbl[block] = block;
bmtd.bmt_blk_idx = upload_bmt(bmtd.bbt, bmtd.bmt_blk_idx);
}
static unsigned long *
mtk_bmt_get_mapping_mask(void)
{
struct bbmt *bbmt = bmt_tbl(bmtd.bbt);
int main_blocks = bmtd.mtd->size >> bmtd.blk_shift;
unsigned long *used;
int i, k;
used = kcalloc(sizeof(unsigned long), BIT_WORD(bmtd.bmt_blk_idx) + 1, GFP_KERNEL);
if (!used)
return NULL;
for (i = 1; i < main_blocks; i++) {
if (bmtd.bbt->bb_tbl[i] == i)
continue;
for (k = 0; k < bmtd.bmt_blk_idx; k++) {
if (bmtd.bbt->bb_tbl[i] != bbmt[k].block)
continue;
set_bit(k, used);
break;
}
}
return used;
}
static int mtk_bmt_debug_v2(void *data, u64 val)
{
struct bbmt *bbmt = bmt_tbl(bmtd.bbt);
struct mtd_info *mtd = bmtd.mtd;
unsigned long *used;
int main_blocks = mtd->size >> bmtd.blk_shift;
int n_remap = 0;
int i;
used = mtk_bmt_get_mapping_mask();
if (!used)
return -ENOMEM;
switch (val) {
case 0:
for (i = 1; i < main_blocks; i++) {
if (bmtd.bbt->bb_tbl[i] == i)
continue;
printk("remap [%x->%x]\n", i, bmtd.bbt->bb_tbl[i]);
n_remap++;
}
for (i = 0; i <= bmtd.bmt_blk_idx; i++) {
char c;
switch (bbmt[i].mapped) {
case NO_MAPPED:
continue;
case NORMAL_MAPPED:
c = 'm';
if (test_bit(i, used))
c = 'M';
break;
case BMT_MAPPED:
c = 'B';
break;
default:
c = 'X';
break;
}
printk("[%x:%c] = 0x%x\n", i, c, bbmt[i].block);
}
break;
case 100:
for (i = 0; i <= bmtd.bmt_blk_idx; i++) {
if (bbmt[i].mapped != NORMAL_MAPPED)
continue;
if (test_bit(i, used))
continue;
n_remap++;
bbmt[i].mapped = NO_MAPPED;
printk("free block [%d:%x]\n", i, bbmt[i].block);
}
if (n_remap)
bmtd.bmt_blk_idx = upload_bmt(bmtd.bbt, bmtd.bmt_blk_idx);
break;
}
kfree(used);
return 0;
}
static int mtk_bmt_init_v2(struct device_node *np)
{
u32 bmt_pool_size, bmt_table_size;
u32 bufsz, block;
u16 pmt_block;
if (of_property_read_u32(np, "mediatek,bmt-pool-size",
&bmt_pool_size) != 0)
bmt_pool_size = 80;
if (of_property_read_u8(np, "mediatek,bmt-oob-offset",
&bmtd.oob_offset) != 0)
bmtd.oob_offset = 0;
if (of_property_read_u32(np, "mediatek,bmt-table-size",
&bmt_table_size) != 0)
bmt_table_size = 0x2000U;
bmtd.table_size = bmt_table_size;
pmt_block = bmtd.total_blks - bmt_pool_size - 2;
bmtd.mtd->size = pmt_block << bmtd.blk_shift;
/*
* ---------------------------------------
* | PMT(2blks) | BMT POOL(totalblks * 2%) |
* ---------------------------------------
* ^ ^
* | |
* pmt_block pmt_block + 2blocks(pool_lba)
*
* ATTETION!!!!!!
* The blocks ahead of the boundary block are stored in bb_tbl
* and blocks behind are stored in bmt_tbl
*/
bmtd.pool_lba = (u16)(pmt_block + 2);
bmtd.bb_max = bmtd.total_blks * BBPOOL_RATIO / 100;
bufsz = round_up(sizeof(struct bbbt) +
bmt_table_size * sizeof(struct bbmt), bmtd.pg_size);
bmtd.bmt_pgs = bufsz >> bmtd.pg_shift;
bmtd.bbt_buf = kzalloc(bufsz, GFP_KERNEL);
if (!bmtd.bbt_buf)
return -ENOMEM;
memset(bmtd.bbt_buf, 0xff, bufsz);
/* Scanning start from the first page of the last block
* of whole flash
*/
bmtd.bbt = NULL;
for (u16 block = bmtd.total_blks - 1; !bmtd.bbt && block >= bmtd.pool_lba; block--) {
u8 fdm[4];
if (bbt_nand_read(blk_pg(block), bmtd.bbt_buf, bufsz, fdm, sizeof(fdm))) {
/* Read failed, try the previous block */
continue;
}
if (!is_valid_bmt(bmtd.bbt_buf, fdm)) {
/* No valid BMT found, try the previous block */
continue;
}
bmtd.bmt_blk_idx = get_bmt_index(bmt_tbl((struct bbbt *)bmtd.bbt_buf));
if (bmtd.bmt_blk_idx == 0) {
pr_info("[BBT] FATAL ERR: bmt block index is wrong!\n");
break;
}
pr_info("[BBT] BMT.v2 is found at 0x%x\n", block);
bmtd.bbt = (struct bbbt *)bmtd.bbt_buf;
}
if (!bmtd.bbt) {
/* BMT not found */
if (bmtd.total_blks > BB_TABLE_MAX + BMT_TABLE_MAX) {
pr_info("nand: FATAL: Too many blocks, can not support!\n");
return -1;
}
bmtd.bbt = (struct bbbt *)bmtd.bbt_buf;
memset(bmt_tbl(bmtd.bbt), BMT_TBL_DEF_VAL,
bmtd.table_size * sizeof(struct bbmt));
if (scan_bad_blocks(bmtd.bbt))
return -1;
/* BMT always in the last valid block in pool */
bmtd.bmt_blk_idx = upload_bmt(bmtd.bbt, bmtd.bmt_blk_idx);
block = bmt_tbl(bmtd.bbt)[bmtd.bmt_blk_idx].block;
pr_notice("[BBT] BMT.v2 is written into PBA:0x%x\n", block);
if (bmtd.bmt_blk_idx == 0)
pr_info("nand: Warning: no available block in BMT pool!\n");
else if (bmtd.bmt_blk_idx == (u16)-1)
return -1;
}
return 0;
}
const struct mtk_bmt_ops mtk_bmt_v2_ops = {
.sig = "bmt",
.sig_len = 3,
.init = mtk_bmt_init_v2,
.remap_block = remap_block_v2,
.unmap_block = unmap_block_v2,
.get_mapping_block = get_mapping_block_index_v2,
.debug = mtk_bmt_debug_v2,
};

View file

@ -0,0 +1,365 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Parser for MikroTik RouterBoot partitions.
*
* Copyright (C) 2020 Thibaut VARÈNE <hacks+kernel@slashdirt.org>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 as published
* by the Free Software Foundation.
*
* This parser builds from the "fixed-partitions" one (see ofpart.c), but it can
* handle dynamic partitions as found on routerboot devices.
*
* DTS nodes are defined as follows:
* For fixed partitions:
* node-name@unit-address {
* reg = <prop-encoded-array>;
* label = <string>;
* read-only;
* lock;
* };
*
* reg property is mandatory; other properties are optional.
* reg format is <address length>. length can be 0 if the next partition is
* another fixed partition or a "well-known" partition as defined below: in that
* case the partition will extend up to the next one.
*
* For dynamic partitions:
* node-name {
* size = <prop-encoded-array>;
* label = <string>;
* read-only;
* lock;
* };
*
* size property is normally mandatory. It can only be omitted (or set to 0) if:
* - the partition is a "well-known" one (as defined below), in which case
* the partition size will be automatically adjusted; or
* - the next partition is a fixed one or a "well-known" one, in which case
* the current partition will extend up to the next one.
* Other properties are optional.
* size format is <length>.
* By default dynamic partitions are appended after the preceding one, except
* for "well-known" ones which are automatically located on flash.
*
* Well-known partitions (matched via label or node-name):
* - "hard_config"
* - "soft_config"
* - "dtb_config"
*
* Note: this parser will happily register 0-sized partitions if misused.
*
* This parser requires the DTS to list partitions in ascending order as
* expected on the MTD device.
*
* Since only the "hard_config" and "soft_config" partitions are used in OpenWRT,
* a minimal working DTS could define only these two partitions dynamically (in
* the right order, usually hard_config then soft_config).
*
* Note: some mips RB devices encode the hard_config offset and length in two
* consecutive u32 located at offset 0x14 (for ramips) or 0x24 (for ath79) on
* the SPI NOR flash. Unfortunately this seems inconsistent across machines and
* does not apply to e.g. ipq-based ones, so we ignore that information.
*
* Note: To find well-known partitions, this parser will go through the entire
* top mtd partition parsed, _before_ the DTS nodes are processed. This works
* well in the current state of affairs, and is a simpler implementation than
* searching for known partitions in the "holes" left between fixed-partition,
* _after_ processing DTS nodes.
*/
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/partitions.h>
#include <linux/of.h>
#include <linux/of_fdt.h>
#include <linux/libfdt_env.h>
#include <linux/string.h>
#define RB_MAGIC_HARD (('H') | ('a' << 8) | ('r' << 16) | ('d' << 24))
#define RB_MAGIC_SOFT (('S') | ('o' << 8) | ('f' << 16) | ('t' << 24))
#define RB_BLOCK_SIZE 0x1000
struct routerboot_dynpart {
const char * const name;
const u32 magic;
int (* const size_fixup)(struct mtd_info *, struct routerboot_dynpart *);
size_t offset;
size_t size;
bool found;
};
static int routerboot_dtbsfixup(struct mtd_info *, struct routerboot_dynpart *);
static struct routerboot_dynpart rb_dynparts[] = {
{
.name = "hard_config",
.magic = RB_MAGIC_HARD, // stored in CPU-endianness on flash
.size_fixup = NULL,
.offset = 0x0,
.size = RB_BLOCK_SIZE,
.found = false,
}, {
.name = "soft_config",
.magic = RB_MAGIC_SOFT, // stored in CPU-endianness on flash
.size_fixup = NULL,
.offset = 0x0,
.size = RB_BLOCK_SIZE,
.found = false,
}, {
.name = "dtb_config",
.magic = fdt32_to_cpu(OF_DT_HEADER), // stored BE on flash
.size_fixup = routerboot_dtbsfixup,
.offset = 0x0,
.size = 0x0,
.found = false,
}
};
static int routerboot_dtbsfixup(struct mtd_info *master, struct routerboot_dynpart *rbdpart)
{
int err;
size_t bytes_read, psize;
struct {
fdt32_t magic;
fdt32_t totalsize;
fdt32_t off_dt_struct;
fdt32_t off_dt_strings;
fdt32_t off_mem_rsvmap;
fdt32_t version;
fdt32_t last_comp_version;
fdt32_t boot_cpuid_phys;
fdt32_t size_dt_strings;
fdt32_t size_dt_struct;
} fdt_header;
err = mtd_read(master, rbdpart->offset, sizeof(fdt_header),
&bytes_read, (u8 *)&fdt_header);
if (err)
return err;
if (bytes_read != sizeof(fdt_header))
return -EIO;
psize = fdt32_to_cpu(fdt_header.totalsize);
if (!psize)
return -EINVAL;
rbdpart->size = psize;
return 0;
}
static void routerboot_find_dynparts(struct mtd_info *master)
{
size_t bytes_read, offset;
bool allfound;
int err, i;
u32 buf;
/*
* Dynamic RouterBoot partitions offsets are aligned to RB_BLOCK_SIZE:
* read the whole partition at RB_BLOCK_SIZE intervals to find sigs.
* Skip partition content when possible.
*/
offset = 0;
while (offset < master->size) {
err = mtd_read(master, offset, sizeof(buf), &bytes_read, (u8 *)&buf);
if (err) {
pr_err("%s: mtd_read error while parsing (offset: 0x%zX): %d\n",
master->name, offset, err);
continue;
}
allfound = true;
for (i = 0; i < ARRAY_SIZE(rb_dynparts); i++) {
if (rb_dynparts[i].found)
continue;
allfound = false;
if (rb_dynparts[i].magic == buf) {
rb_dynparts[i].offset = offset;
if (rb_dynparts[i].size_fixup) {
err = rb_dynparts[i].size_fixup(master, &rb_dynparts[i]);
if (err) {
pr_err("%s: size fixup error while parsing \"%s\": %d\n",
master->name, rb_dynparts[i].name, err);
continue;
}
}
rb_dynparts[i].found = true;
/*
* move offset to skip the whole partition on
* next iteration if size > RB_BLOCK_SIZE.
*/
if (rb_dynparts[i].size > RB_BLOCK_SIZE)
offset += ALIGN_DOWN((rb_dynparts[i].size - RB_BLOCK_SIZE), RB_BLOCK_SIZE);
break;
}
}
offset += RB_BLOCK_SIZE;
if (allfound)
break;
}
}
static int routerboot_partitions_parse(struct mtd_info *master,
const struct mtd_partition **pparts,
struct mtd_part_parser_data *data)
{
struct device_node *rbpart_node, *pp;
struct mtd_partition *parts;
const char *partname;
size_t master_ofs;
int np;
/* Pull of_node from the master device node */
rbpart_node = mtd_get_of_node(master);
if (!rbpart_node)
return 0;
/* First count the subnodes */
np = 0;
for_each_child_of_node(rbpart_node, pp)
np++;
if (!np)
return 0;
parts = kcalloc(np, sizeof(*parts), GFP_KERNEL);
if (!parts)
return -ENOMEM;
/* Preemptively look for known parts in flash */
routerboot_find_dynparts(master);
np = 0;
master_ofs = 0;
for_each_child_of_node(rbpart_node, pp) {
const __be32 *reg, *sz;
size_t offset, size;
int i, len, a_cells, s_cells;
partname = of_get_property(pp, "label", &len);
/* Allow deprecated use of "name" instead of "label" */
if (!partname)
partname = of_get_property(pp, "name", &len);
/* Fallback to node name per spec if all else fails: partname is always set */
if (!partname)
partname = pp->name;
parts[np].name = partname;
reg = of_get_property(pp, "reg", &len);
if (reg) {
/* Fixed partition */
a_cells = of_n_addr_cells(pp);
s_cells = of_n_size_cells(pp);
if ((len / 4) != (a_cells + s_cells)) {
pr_debug("%s: routerboot partition %pOF (%pOF) error parsing reg property.\n",
master->name, pp, rbpart_node);
goto rbpart_fail;
}
offset = of_read_number(reg, a_cells);
size = of_read_number(reg + a_cells, s_cells);
} else {
/* Dynamic partition */
/* Default: part starts at current offset, 0 size */
offset = master_ofs;
size = 0;
/* Check if well-known partition */
for (i = 0; i < ARRAY_SIZE(rb_dynparts); i++) {
if (!strcmp(partname, rb_dynparts[i].name) && rb_dynparts[i].found) {
offset = rb_dynparts[i].offset;
size = rb_dynparts[i].size;
break;
}
}
/* Standalone 'size' property? Override size */
sz = of_get_property(pp, "size", &len);
if (sz) {
s_cells = of_n_size_cells(pp);
if ((len / 4) != s_cells) {
pr_debug("%s: routerboot partition %pOF (%pOF) error parsing size property.\n",
master->name, pp, rbpart_node);
goto rbpart_fail;
}
size = of_read_number(sz, s_cells);
}
}
if (np > 0) {
/* Minor sanity check for overlaps */
if (offset < (parts[np-1].offset + parts[np-1].size)) {
pr_err("%s: routerboot partition %pOF (%pOF) \"%s\" overlaps with previous partition \"%s\".\n",
master->name, pp, rbpart_node,
partname, parts[np-1].name);
goto rbpart_fail;
}
/* Fixup end of previous partition if necessary */
if (!parts[np-1].size)
parts[np-1].size = (offset - parts[np-1].offset);
}
if ((offset + size) > master->size) {
pr_err("%s: routerboot partition %pOF (%pOF) \"%s\" extends past end of segment.\n",
master->name, pp, rbpart_node, partname);
goto rbpart_fail;
}
parts[np].offset = offset;
parts[np].size = size;
parts[np].of_node = pp;
if (of_get_property(pp, "read-only", &len))
parts[np].mask_flags |= MTD_WRITEABLE;
if (of_get_property(pp, "lock", &len))
parts[np].mask_flags |= MTD_POWERUP_LOCK;
/* Keep master offset aligned to RB_BLOCK_SIZE */
master_ofs = ALIGN(offset + size, RB_BLOCK_SIZE);
np++;
}
*pparts = parts;
return np;
rbpart_fail:
pr_err("%s: error parsing routerboot partition %pOF (%pOF)\n",
master->name, pp, rbpart_node);
of_node_put(pp);
kfree(parts);
return -EINVAL;
}
static const struct of_device_id parse_routerbootpart_match_table[] = {
{ .compatible = "mikrotik,routerboot-partitions" },
{},
};
MODULE_DEVICE_TABLE(of, parse_routerbootpart_match_table);
static struct mtd_part_parser routerbootpart_parser = {
.parse_fn = routerboot_partitions_parse,
.name = "routerbootpart",
.of_match_table = parse_routerbootpart_match_table,
};
module_mtd_part_parser(routerbootpart_parser);
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("MTD partitioning for RouterBoot");
MODULE_AUTHOR("Thibaut VARENE");

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,186 @@
/*
* ADM6996 switch driver
*
* Copyright (c) 2008 Felix Fietkau <nbd@nbd.name>
* Copyright (c) 2010,2011 Peter Lebbing <peter@digitalbrains.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License v2 as published by the
* Free Software Foundation
*/
#ifndef __ADM6996_H
#define __ADM6996_H
/*
* ADM_PHY_PORTS: Number of ports with a PHY.
* We only control ports 0 to 3, because if 4 is connected, it is most likely
* not connected to the switch but to a separate MII and MAC for the WAN port.
*/
#define ADM_PHY_PORTS 4
#define ADM_NUM_PORTS 6
#define ADM_CPU_PORT 5
#define ADM_NUM_VLANS 16
#define ADM_VLAN_MAX_ID 4094
enum admreg {
ADM_EEPROM_BASE = 0x0,
ADM_P0_CFG = ADM_EEPROM_BASE + 1,
ADM_P1_CFG = ADM_EEPROM_BASE + 3,
ADM_P2_CFG = ADM_EEPROM_BASE + 5,
ADM_P3_CFG = ADM_EEPROM_BASE + 7,
ADM_P4_CFG = ADM_EEPROM_BASE + 8,
ADM_P5_CFG = ADM_EEPROM_BASE + 9,
ADM_SYSC0 = ADM_EEPROM_BASE + 0xa,
ADM_VLAN_PRIOMAP = ADM_EEPROM_BASE + 0xe,
ADM_SYSC3 = ADM_EEPROM_BASE + 0x11,
/* Input Force No Tag Enable */
ADM_IFNTE = ADM_EEPROM_BASE + 0x20,
ADM_VID_CHECK = ADM_EEPROM_BASE + 0x26,
ADM_P0_PVID = ADM_EEPROM_BASE + 0x28,
ADM_P1_PVID = ADM_EEPROM_BASE + 0x29,
/* Output Tag Bypass Enable and P2 PVID */
ADM_OTBE_P2_PVID = ADM_EEPROM_BASE + 0x2a,
ADM_P3_P4_PVID = ADM_EEPROM_BASE + 0x2b,
ADM_P5_PVID = ADM_EEPROM_BASE + 0x2c,
ADM_EEPROM_EXT_BASE = 0x40,
#define ADM_VLAN_FILT_L(n) (ADM_EEPROM_EXT_BASE + 2 * (n))
#define ADM_VLAN_FILT_H(n) (ADM_EEPROM_EXT_BASE + 1 + 2 * (n))
#define ADM_VLAN_MAP(n) (ADM_EEPROM_BASE + 0x13 + n)
ADM_COUNTER_BASE = 0xa0,
ADM_SIG0 = ADM_COUNTER_BASE + 0,
ADM_SIG1 = ADM_COUNTER_BASE + 1,
ADM_PS0 = ADM_COUNTER_BASE + 2,
ADM_PS1 = ADM_COUNTER_BASE + 3,
ADM_PS2 = ADM_COUNTER_BASE + 4,
ADM_CL0 = ADM_COUNTER_BASE + 8, /* RxPacket */
ADM_CL6 = ADM_COUNTER_BASE + 0x1a, /* RxByte */
ADM_CL12 = ADM_COUNTER_BASE + 0x2c, /* TxPacket */
ADM_CL18 = ADM_COUNTER_BASE + 0x3e, /* TxByte */
ADM_CL24 = ADM_COUNTER_BASE + 0x50, /* Coll */
ADM_CL30 = ADM_COUNTER_BASE + 0x62, /* Err */
#define ADM_OFFSET_PORT(n) ((n * 4) - (n / 4) * 2 - (n / 5) * 2)
ADM_PHY_BASE = 0x200,
#define ADM_PHY_PORT(n) (ADM_PHY_BASE + (0x20 * n))
};
/* Chip identification patterns */
#define ADM_SIG0_MASK 0xffff
#define ADM_SIG0_VAL 0x1023
#define ADM_SIG1_MASK 0xffff
#define ADM_SIG1_VAL 0x0007
enum {
ADM_PHYCFG_COLTST = (1 << 7), /* Enable collision test */
ADM_PHYCFG_DPLX = (1 << 8), /* Enable full duplex */
ADM_PHYCFG_ANEN_RST = (1 << 9), /* Restart auto negotiation (self clear) */
ADM_PHYCFG_ISO = (1 << 10), /* Isolate PHY */
ADM_PHYCFG_PDN = (1 << 11), /* Power down PHY */
ADM_PHYCFG_ANEN = (1 << 12), /* Enable auto negotiation */
ADM_PHYCFG_SPEED_100 = (1 << 13), /* Enable 100 Mbit/s */
ADM_PHYCFG_LPBK = (1 << 14), /* Enable loopback operation */
ADM_PHYCFG_RST = (1 << 15), /* Reset the port (self clear) */
ADM_PHYCFG_INIT = (
ADM_PHYCFG_RST |
ADM_PHYCFG_SPEED_100 |
ADM_PHYCFG_ANEN |
ADM_PHYCFG_ANEN_RST
)
};
enum {
ADM_PORTCFG_FC = (1 << 0), /* Enable 802.x flow control */
ADM_PORTCFG_AN = (1 << 1), /* Enable auto-negotiation */
ADM_PORTCFG_SPEED_100 = (1 << 2), /* Enable 100 Mbit/s */
ADM_PORTCFG_DPLX = (1 << 3), /* Enable full duplex */
ADM_PORTCFG_OT = (1 << 4), /* Output tagged packets */
ADM_PORTCFG_PD = (1 << 5), /* Port disable */
ADM_PORTCFG_TV_PRIO = (1 << 6), /* 0 = VLAN based priority
* 1 = TOS based priority */
ADM_PORTCFG_PPE = (1 << 7), /* Port based priority enable */
ADM_PORTCFG_PP_S = (1 << 8), /* Port based priority, 2 bits */
ADM_PORTCFG_PVID_BASE = (1 << 10), /* Primary VLAN id, 4 bits */
ADM_PORTCFG_FSE = (1 << 14), /* Fx select enable */
ADM_PORTCFG_CAM = (1 << 15), /* Crossover Auto MDIX */
ADM_PORTCFG_INIT = (
ADM_PORTCFG_FC |
ADM_PORTCFG_AN |
ADM_PORTCFG_SPEED_100 |
ADM_PORTCFG_DPLX |
ADM_PORTCFG_CAM
),
ADM_PORTCFG_CPU = (
ADM_PORTCFG_FC |
ADM_PORTCFG_SPEED_100 |
ADM_PORTCFG_OT |
ADM_PORTCFG_DPLX
),
};
#define ADM_PORTCFG_PPID(n) ((n & 0x3) << 8)
#define ADM_PORTCFG_PVID(n) ((n & 0xf) << 10)
#define ADM_PORTCFG_PVID_MASK (0xf << 10)
#define ADM_IFNTE_MASK (0x3f << 9)
#define ADM_VID_CHECK_MASK (0x3f << 6)
#define ADM_P0_PVID_VAL(n) ((((n) & 0xff0) >> 4) << 0)
#define ADM_P1_PVID_VAL(n) ((((n) & 0xff0) >> 4) << 0)
#define ADM_P2_PVID_VAL(n) ((((n) & 0xff0) >> 4) << 0)
#define ADM_P3_PVID_VAL(n) ((((n) & 0xff0) >> 4) << 0)
#define ADM_P4_PVID_VAL(n) ((((n) & 0xff0) >> 4) << 8)
#define ADM_P5_PVID_VAL(n) ((((n) & 0xff0) >> 4) << 0)
#define ADM_P2_PVID_MASK 0xff
#define ADM_OTBE(n) (((n) & 0x3f) << 8)
#define ADM_OTBE_MASK (0x3f << 8)
/* ADM_SYSC0 */
enum {
ADM_NTTE = (1 << 2), /* New Tag Transmit Enable */
ADM_RVID1 = (1 << 8) /* Replace VLAN ID 1 */
};
/* Tag Based VLAN in ADM_SYSC3 */
#define ADM_MAC_CLONE BIT(4)
#define ADM_TBV BIT(5)
static const u8 adm_portcfg[] = {
[0] = ADM_P0_CFG,
[1] = ADM_P1_CFG,
[2] = ADM_P2_CFG,
[3] = ADM_P3_CFG,
[4] = ADM_P4_CFG,
[5] = ADM_P5_CFG,
};
/* Fields in ADM_VLAN_FILT_L(x) */
#define ADM_VLAN_FILT_FID(n) (((n) & 0xf) << 12)
#define ADM_VLAN_FILT_TAGGED(n) (((n) & 0x3f) << 6)
#define ADM_VLAN_FILT_MEMBER(n) (((n) & 0x3f) << 0)
#define ADM_VLAN_FILT_MEMBER_MASK 0x3f
/* Fields in ADM_VLAN_FILT_H(x) */
#define ADM_VLAN_FILT_VALID (1 << 15)
#define ADM_VLAN_FILT_VID(n) (((n) & 0xfff) << 0)
/* Convert ports to a form for ADM6996L VLAN map */
#define ADM_VLAN_FILT(ports) ((ports & 0x01) | ((ports & 0x02) << 1) | \
((ports & 0x04) << 2) | ((ports & 0x08) << 3) | \
((ports & 0x10) << 3) | ((ports & 0x20) << 3))
/* Port status register */
enum {
ADM_PS_LS = (1 << 0), /* Link status */
ADM_PS_SS = (1 << 1), /* Speed status */
ADM_PS_DS = (1 << 2), /* Duplex status */
ADM_PS_FCS = (1 << 3) /* Flow control status */
};
/*
* Split the register address in phy id and register
* it will get combined again by the mdio bus op
*/
#define PHYADDR(_reg) ((_reg >> 5) & 0xff), (_reg & 0x1f)
#endif

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,725 @@
/*
* ar8216.h: AR8216 switch driver
*
* Copyright (C) 2009 Felix Fietkau <nbd@nbd.name>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#ifndef __AR8216_H
#define __AR8216_H
#define BITS(_s, _n) (((1UL << (_n)) - 1) << _s)
#define AR8XXX_CAP_GIGE BIT(0)
#define AR8XXX_CAP_MIB_COUNTERS BIT(1)
#define AR8XXX_NUM_PHYS 5
#define AR8216_PORT_CPU 0
#define AR8216_NUM_PORTS 6
#define AR8216_NUM_VLANS 16
#define AR7240SW_NUM_PORTS 5
#define AR8316_NUM_VLANS 4096
/* size of the vlan table */
#define AR8X16_MAX_VLANS 128
#define AR83X7_MAX_VLANS 4096
#define AR8XXX_MAX_VLANS AR83X7_MAX_VLANS
#define AR8X16_PROBE_RETRIES 10
#define AR8X16_MAX_PORTS 8
#define AR8XXX_REG_ARL_CTRL_AGE_TIME_SECS 7
#define AR8XXX_DEFAULT_ARL_AGE_TIME 300
/* Atheros specific MII registers */
#define MII_ATH_MMD_ADDR 0x0d
#define MII_ATH_MMD_DATA 0x0e
#define MII_ATH_DBG_ADDR 0x1d
#define MII_ATH_DBG_DATA 0x1e
#define AR8216_REG_CTRL 0x0000
#define AR8216_CTRL_REVISION BITS(0, 8)
#define AR8216_CTRL_REVISION_S 0
#define AR8216_CTRL_VERSION BITS(8, 8)
#define AR8216_CTRL_VERSION_S 8
#define AR8216_CTRL_RESET BIT(31)
#define AR8216_REG_FLOOD_MASK 0x002C
#define AR8216_FM_UNI_DEST_PORTS BITS(0, 6)
#define AR8216_FM_MULTI_DEST_PORTS BITS(16, 6)
#define AR8216_FM_CPU_BROADCAST_EN BIT(26)
#define AR8229_FLOOD_MASK_UC_DP(_p) BIT(_p)
#define AR8229_FLOOD_MASK_MC_DP(_p) BIT(16 + (_p))
#define AR8229_FLOOD_MASK_BC_DP(_p) BIT(25 + (_p))
#define AR8216_REG_GLOBAL_CTRL 0x0030
#define AR8216_GCTRL_MTU BITS(0, 11)
#define AR8236_GCTRL_MTU BITS(0, 14)
#define AR8316_GCTRL_MTU BITS(0, 14)
#define AR8216_REG_VTU 0x0040
#define AR8216_VTU_OP BITS(0, 3)
#define AR8216_VTU_OP_NOOP 0x0
#define AR8216_VTU_OP_FLUSH 0x1
#define AR8216_VTU_OP_LOAD 0x2
#define AR8216_VTU_OP_PURGE 0x3
#define AR8216_VTU_OP_REMOVE_PORT 0x4
#define AR8216_VTU_ACTIVE BIT(3)
#define AR8216_VTU_FULL BIT(4)
#define AR8216_VTU_PORT BITS(8, 4)
#define AR8216_VTU_PORT_S 8
#define AR8216_VTU_VID BITS(16, 12)
#define AR8216_VTU_VID_S 16
#define AR8216_VTU_PRIO BITS(28, 3)
#define AR8216_VTU_PRIO_S 28
#define AR8216_VTU_PRIO_EN BIT(31)
#define AR8216_REG_VTU_DATA 0x0044
#define AR8216_VTUDATA_MEMBER BITS(0, 10)
#define AR8236_VTUDATA_MEMBER BITS(0, 7)
#define AR8216_VTUDATA_VALID BIT(11)
#define AR8216_REG_ATU_FUNC0 0x0050
#define AR8216_ATU_OP BITS(0, 3)
#define AR8216_ATU_OP_NOOP 0x0
#define AR8216_ATU_OP_FLUSH 0x1
#define AR8216_ATU_OP_LOAD 0x2
#define AR8216_ATU_OP_PURGE 0x3
#define AR8216_ATU_OP_FLUSH_UNLOCKED 0x4
#define AR8216_ATU_OP_FLUSH_PORT 0x5
#define AR8216_ATU_OP_GET_NEXT 0x6
#define AR8216_ATU_ACTIVE BIT(3)
#define AR8216_ATU_PORT_NUM BITS(8, 4)
#define AR8216_ATU_PORT_NUM_S 8
#define AR8216_ATU_FULL_VIO BIT(12)
#define AR8216_ATU_ADDR5 BITS(16, 8)
#define AR8216_ATU_ADDR5_S 16
#define AR8216_ATU_ADDR4 BITS(24, 8)
#define AR8216_ATU_ADDR4_S 24
#define AR8216_REG_ATU_FUNC1 0x0054
#define AR8216_ATU_ADDR3 BITS(0, 8)
#define AR8216_ATU_ADDR3_S 0
#define AR8216_ATU_ADDR2 BITS(8, 8)
#define AR8216_ATU_ADDR2_S 8
#define AR8216_ATU_ADDR1 BITS(16, 8)
#define AR8216_ATU_ADDR1_S 16
#define AR8216_ATU_ADDR0 BITS(24, 8)
#define AR8216_ATU_ADDR0_S 24
#define AR8216_REG_ATU_FUNC2 0x0058
#define AR8216_ATU_PORTS BITS(0, 6)
#define AR8216_ATU_PORTS_S 0
#define AR8216_ATU_PORT0 BIT(0)
#define AR8216_ATU_PORT1 BIT(1)
#define AR8216_ATU_PORT2 BIT(2)
#define AR8216_ATU_PORT3 BIT(3)
#define AR8216_ATU_PORT4 BIT(4)
#define AR8216_ATU_PORT5 BIT(5)
#define AR8216_ATU_STATUS BITS(16, 4)
#define AR8216_ATU_STATUS_S 16
#define AR8216_REG_ATU_CTRL 0x005C
#define AR8216_ATU_CTRL_AGE_EN BIT(17)
#define AR8216_ATU_CTRL_AGE_TIME BITS(0, 16)
#define AR8216_ATU_CTRL_AGE_TIME_S 0
#define AR8236_ATU_CTRL_RES BIT(20)
#define AR8216_ATU_CTRL_LEARN_CHANGE BIT(18)
#define AR8216_ATU_CTRL_RESERVED BIT(19)
#define AR8216_ATU_CTRL_ARP_EN BIT(20)
#define AR8216_REG_TAG_PRIORITY 0x0070
#define AR8216_REG_SERVICE_TAG 0x0074
#define AR8216_SERVICE_TAG_M BITS(0, 16)
#define AR8216_REG_MIB_FUNC 0x0080
#define AR8216_MIB_TIMER BITS(0, 16)
#define AR8216_MIB_AT_HALF_EN BIT(16)
#define AR8216_MIB_BUSY BIT(17)
#define AR8216_MIB_FUNC BITS(24, 3)
#define AR8216_MIB_FUNC_S 24
#define AR8216_MIB_FUNC_NO_OP 0x0
#define AR8216_MIB_FUNC_FLUSH 0x1
#define AR8216_MIB_FUNC_CAPTURE 0x3
#define AR8236_MIB_EN BIT(30)
#define AR8216_REG_GLOBAL_CPUPORT 0x0078
#define AR8216_GLOBAL_CPUPORT_MIRROR_PORT BITS(4, 4)
#define AR8216_GLOBAL_CPUPORT_MIRROR_PORT_S 4
#define AR8216_GLOBAL_CPUPORT_EN BIT(8)
#define AR8216_REG_MDIO_CTRL 0x98
#define AR8216_MDIO_CTRL_DATA_M BITS(0, 16)
#define AR8216_MDIO_CTRL_REG_ADDR_S 16
#define AR8216_MDIO_CTRL_PHY_ADDR_S 21
#define AR8216_MDIO_CTRL_CMD_WRITE 0
#define AR8216_MDIO_CTRL_CMD_READ BIT(27)
#define AR8216_MDIO_CTRL_MASTER_EN BIT(30)
#define AR8216_MDIO_CTRL_BUSY BIT(31)
#define AR8216_PORT_OFFSET(_i) (0x0100 * (_i + 1))
#define AR8216_REG_PORT_STATUS(_i) (AR8216_PORT_OFFSET(_i) + 0x0000)
#define AR8216_PORT_STATUS_SPEED BITS(0,2)
#define AR8216_PORT_STATUS_SPEED_S 0
#define AR8216_PORT_STATUS_TXMAC BIT(2)
#define AR8216_PORT_STATUS_RXMAC BIT(3)
#define AR8216_PORT_STATUS_TXFLOW BIT(4)
#define AR8216_PORT_STATUS_RXFLOW BIT(5)
#define AR8216_PORT_STATUS_DUPLEX BIT(6)
#define AR8216_PORT_STATUS_LINK_UP BIT(8)
#define AR8216_PORT_STATUS_LINK_AUTO BIT(9)
#define AR8216_PORT_STATUS_LINK_PAUSE BIT(10)
#define AR8216_PORT_STATUS_FLOW_CONTROL BIT(12)
#define AR8216_REG_PORT_CTRL(_i) (AR8216_PORT_OFFSET(_i) + 0x0004)
/* port forwarding state */
#define AR8216_PORT_CTRL_STATE BITS(0, 3)
#define AR8216_PORT_CTRL_STATE_S 0
#define AR8216_PORT_CTRL_LEARN_LOCK BIT(7)
/* egress 802.1q mode */
#define AR8216_PORT_CTRL_VLAN_MODE BITS(8, 2)
#define AR8216_PORT_CTRL_VLAN_MODE_S 8
#define AR8216_PORT_CTRL_IGMP_SNOOP BIT(10)
#define AR8216_PORT_CTRL_HEADER BIT(11)
#define AR8216_PORT_CTRL_MAC_LOOP BIT(12)
#define AR8216_PORT_CTRL_SINGLE_VLAN BIT(13)
#define AR8216_PORT_CTRL_LEARN BIT(14)
#define AR8216_PORT_CTRL_MIRROR_TX BIT(16)
#define AR8216_PORT_CTRL_MIRROR_RX BIT(17)
#define AR8216_REG_PORT_VLAN(_i) (AR8216_PORT_OFFSET(_i) + 0x0008)
#define AR8216_PORT_VLAN_DEFAULT_ID BITS(0, 12)
#define AR8216_PORT_VLAN_DEFAULT_ID_S 0
#define AR8216_PORT_VLAN_DEST_PORTS BITS(16, 9)
#define AR8216_PORT_VLAN_DEST_PORTS_S 16
/* bit0 added to the priority field of egress frames */
#define AR8216_PORT_VLAN_TX_PRIO BIT(27)
/* port default priority */
#define AR8216_PORT_VLAN_PRIORITY BITS(28, 2)
#define AR8216_PORT_VLAN_PRIORITY_S 28
/* ingress 802.1q mode */
#define AR8216_PORT_VLAN_MODE BITS(30, 2)
#define AR8216_PORT_VLAN_MODE_S 30
#define AR8216_REG_PORT_RATE(_i) (AR8216_PORT_OFFSET(_i) + 0x000c)
#define AR8216_REG_PORT_PRIO(_i) (AR8216_PORT_OFFSET(_i) + 0x0010)
#define AR8216_STATS_RXBROAD 0x00
#define AR8216_STATS_RXPAUSE 0x04
#define AR8216_STATS_RXMULTI 0x08
#define AR8216_STATS_RXFCSERR 0x0c
#define AR8216_STATS_RXALIGNERR 0x10
#define AR8216_STATS_RXRUNT 0x14
#define AR8216_STATS_RXFRAGMENT 0x18
#define AR8216_STATS_RX64BYTE 0x1c
#define AR8216_STATS_RX128BYTE 0x20
#define AR8216_STATS_RX256BYTE 0x24
#define AR8216_STATS_RX512BYTE 0x28
#define AR8216_STATS_RX1024BYTE 0x2c
#define AR8216_STATS_RXMAXBYTE 0x30
#define AR8216_STATS_RXTOOLONG 0x34
#define AR8216_STATS_RXGOODBYTE 0x38
#define AR8216_STATS_RXBADBYTE 0x40
#define AR8216_STATS_RXOVERFLOW 0x48
#define AR8216_STATS_FILTERED 0x4c
#define AR8216_STATS_TXBROAD 0x50
#define AR8216_STATS_TXPAUSE 0x54
#define AR8216_STATS_TXMULTI 0x58
#define AR8216_STATS_TXUNDERRUN 0x5c
#define AR8216_STATS_TX64BYTE 0x60
#define AR8216_STATS_TX128BYTE 0x64
#define AR8216_STATS_TX256BYTE 0x68
#define AR8216_STATS_TX512BYTE 0x6c
#define AR8216_STATS_TX1024BYTE 0x70
#define AR8216_STATS_TXMAXBYTE 0x74
#define AR8216_STATS_TXOVERSIZE 0x78
#define AR8216_STATS_TXBYTE 0x7c
#define AR8216_STATS_TXCOLLISION 0x84
#define AR8216_STATS_TXABORTCOL 0x88
#define AR8216_STATS_TXMULTICOL 0x8c
#define AR8216_STATS_TXSINGLECOL 0x90
#define AR8216_STATS_TXEXCDEFER 0x94
#define AR8216_STATS_TXDEFER 0x98
#define AR8216_STATS_TXLATECOL 0x9c
#define AR8216_MIB_RXB_ID 14 /* RxGoodByte */
#define AR8216_MIB_TXB_ID 29 /* TxByte */
#define AR8229_REG_OPER_MODE0 0x04
#define AR8229_OPER_MODE0_MAC_GMII_EN BIT(6)
#define AR8229_OPER_MODE0_PHY_MII_EN BIT(10)
#define AR8229_REG_OPER_MODE1 0x08
#define AR8229_REG_OPER_MODE1_PHY4_MII_EN BIT(28)
#define AR8229_REG_QM_CTRL 0x3c
#define AR8229_QM_CTRL_ARP_EN BIT(15)
#define AR8236_REG_PORT_VLAN(_i) (AR8216_PORT_OFFSET((_i)) + 0x0008)
#define AR8236_PORT_VLAN_DEFAULT_ID BITS(16, 12)
#define AR8236_PORT_VLAN_DEFAULT_ID_S 16
#define AR8236_PORT_VLAN_PRIORITY BITS(29, 3)
#define AR8236_PORT_VLAN_PRIORITY_S 28
#define AR8236_REG_PORT_VLAN2(_i) (AR8216_PORT_OFFSET((_i)) + 0x000c)
#define AR8236_PORT_VLAN2_MEMBER BITS(16, 7)
#define AR8236_PORT_VLAN2_MEMBER_S 16
#define AR8236_PORT_VLAN2_TX_PRIO BIT(23)
#define AR8236_PORT_VLAN2_VLAN_MODE BITS(30, 2)
#define AR8236_PORT_VLAN2_VLAN_MODE_S 30
#define AR8236_STATS_RXBROAD 0x00
#define AR8236_STATS_RXPAUSE 0x04
#define AR8236_STATS_RXMULTI 0x08
#define AR8236_STATS_RXFCSERR 0x0c
#define AR8236_STATS_RXALIGNERR 0x10
#define AR8236_STATS_RXRUNT 0x14
#define AR8236_STATS_RXFRAGMENT 0x18
#define AR8236_STATS_RX64BYTE 0x1c
#define AR8236_STATS_RX128BYTE 0x20
#define AR8236_STATS_RX256BYTE 0x24
#define AR8236_STATS_RX512BYTE 0x28
#define AR8236_STATS_RX1024BYTE 0x2c
#define AR8236_STATS_RX1518BYTE 0x30
#define AR8236_STATS_RXMAXBYTE 0x34
#define AR8236_STATS_RXTOOLONG 0x38
#define AR8236_STATS_RXGOODBYTE 0x3c
#define AR8236_STATS_RXBADBYTE 0x44
#define AR8236_STATS_RXOVERFLOW 0x4c
#define AR8236_STATS_FILTERED 0x50
#define AR8236_STATS_TXBROAD 0x54
#define AR8236_STATS_TXPAUSE 0x58
#define AR8236_STATS_TXMULTI 0x5c
#define AR8236_STATS_TXUNDERRUN 0x60
#define AR8236_STATS_TX64BYTE 0x64
#define AR8236_STATS_TX128BYTE 0x68
#define AR8236_STATS_TX256BYTE 0x6c
#define AR8236_STATS_TX512BYTE 0x70
#define AR8236_STATS_TX1024BYTE 0x74
#define AR8236_STATS_TX1518BYTE 0x78
#define AR8236_STATS_TXMAXBYTE 0x7c
#define AR8236_STATS_TXOVERSIZE 0x80
#define AR8236_STATS_TXBYTE 0x84
#define AR8236_STATS_TXCOLLISION 0x8c
#define AR8236_STATS_TXABORTCOL 0x90
#define AR8236_STATS_TXMULTICOL 0x94
#define AR8236_STATS_TXSINGLECOL 0x98
#define AR8236_STATS_TXEXCDEFER 0x9c
#define AR8236_STATS_TXDEFER 0xa0
#define AR8236_STATS_TXLATECOL 0xa4
#define AR8236_MIB_RXB_ID 15 /* RxGoodByte */
#define AR8236_MIB_TXB_ID 31 /* TxByte */
#define AR8316_REG_POSTRIP 0x0008
#define AR8316_POSTRIP_MAC0_GMII_EN BIT(0)
#define AR8316_POSTRIP_MAC0_RGMII_EN BIT(1)
#define AR8316_POSTRIP_PHY4_GMII_EN BIT(2)
#define AR8316_POSTRIP_PHY4_RGMII_EN BIT(3)
#define AR8316_POSTRIP_MAC0_MAC_MODE BIT(4)
#define AR8316_POSTRIP_RTL_MODE BIT(5)
#define AR8316_POSTRIP_RGMII_RXCLK_DELAY_EN BIT(6)
#define AR8316_POSTRIP_RGMII_TXCLK_DELAY_EN BIT(7)
#define AR8316_POSTRIP_SERDES_EN BIT(8)
#define AR8316_POSTRIP_SEL_ANA_RST BIT(9)
#define AR8316_POSTRIP_GATE_25M_EN BIT(10)
#define AR8316_POSTRIP_SEL_CLK25M BIT(11)
#define AR8316_POSTRIP_HIB_PULSE_HW BIT(12)
#define AR8316_POSTRIP_DBG_MODE_I BIT(13)
#define AR8316_POSTRIP_MAC5_MAC_MODE BIT(14)
#define AR8316_POSTRIP_MAC5_PHY_MODE BIT(15)
#define AR8316_POSTRIP_POWER_DOWN_HW BIT(16)
#define AR8316_POSTRIP_LPW_STATE_EN BIT(17)
#define AR8316_POSTRIP_MAN_EN BIT(18)
#define AR8316_POSTRIP_PHY_PLL_ON BIT(19)
#define AR8316_POSTRIP_LPW_EXIT BIT(20)
#define AR8316_POSTRIP_TXDELAY_S0 BIT(21)
#define AR8316_POSTRIP_TXDELAY_S1 BIT(22)
#define AR8316_POSTRIP_RXDELAY_S0 BIT(23)
#define AR8316_POSTRIP_LED_OPEN_EN BIT(24)
#define AR8316_POSTRIP_SPI_EN BIT(25)
#define AR8316_POSTRIP_RXDELAY_S1 BIT(26)
#define AR8316_POSTRIP_POWER_ON_SEL BIT(31)
/* port speed */
enum {
AR8216_PORT_SPEED_10M = 0,
AR8216_PORT_SPEED_100M = 1,
AR8216_PORT_SPEED_1000M = 2,
AR8216_PORT_SPEED_ERR = 3,
};
/* ingress 802.1q mode */
enum {
AR8216_IN_PORT_ONLY = 0,
AR8216_IN_PORT_FALLBACK = 1,
AR8216_IN_VLAN_ONLY = 2,
AR8216_IN_SECURE = 3
};
/* egress 802.1q mode */
enum {
AR8216_OUT_KEEP = 0,
AR8216_OUT_STRIP_VLAN = 1,
AR8216_OUT_ADD_VLAN = 2
};
/* port forwarding state */
enum {
AR8216_PORT_STATE_DISABLED = 0,
AR8216_PORT_STATE_BLOCK = 1,
AR8216_PORT_STATE_LISTEN = 2,
AR8216_PORT_STATE_LEARN = 3,
AR8216_PORT_STATE_FORWARD = 4
};
/* mib counter type */
enum {
AR8XXX_MIB_BASIC = 0,
AR8XXX_MIB_EXTENDED = 1
};
enum {
AR8XXX_VER_AR8216 = 0x01,
AR8XXX_VER_AR8236 = 0x03,
AR8XXX_VER_AR8316 = 0x10,
AR8XXX_VER_AR8327 = 0x12,
AR8XXX_VER_AR8337 = 0x13,
};
#define AR8XXX_NUM_ARL_RECORDS 100
enum arl_op {
AR8XXX_ARL_INITIALIZE,
AR8XXX_ARL_GET_NEXT
};
struct arl_entry {
u16 portmap;
u8 mac[6];
};
struct ar8xxx_priv;
struct ar8xxx_mib_desc {
unsigned int size;
unsigned int offset;
const char *name;
u8 type;
};
struct ar8xxx_chip {
unsigned long caps;
bool config_at_probe;
bool mii_lo_first;
/* parameters to calculate REG_PORT_STATS_BASE */
unsigned reg_port_stats_start;
unsigned reg_port_stats_length;
unsigned reg_arl_ctrl;
int (*hw_init)(struct ar8xxx_priv *priv);
void (*cleanup)(struct ar8xxx_priv *priv);
const char *name;
int vlans;
int ports;
const struct switch_dev_ops *swops;
void (*init_globals)(struct ar8xxx_priv *priv);
void (*init_port)(struct ar8xxx_priv *priv, int port);
void (*setup_port)(struct ar8xxx_priv *priv, int port, u32 members);
u32 (*read_port_status)(struct ar8xxx_priv *priv, int port);
u32 (*read_port_eee_status)(struct ar8xxx_priv *priv, int port);
int (*atu_flush)(struct ar8xxx_priv *priv);
int (*atu_flush_port)(struct ar8xxx_priv *priv, int port);
void (*vtu_flush)(struct ar8xxx_priv *priv);
void (*vtu_load_vlan)(struct ar8xxx_priv *priv, u32 vid, u32 port_mask);
void (*phy_fixup)(struct ar8xxx_priv *priv, int phy);
void (*set_mirror_regs)(struct ar8xxx_priv *priv);
void (*get_arl_entry)(struct ar8xxx_priv *priv, struct arl_entry *a,
u32 *status, enum arl_op op);
int (*sw_hw_apply)(struct switch_dev *dev);
void (*phy_rgmii_set)(struct ar8xxx_priv *priv, struct phy_device *phydev);
int (*phy_read)(struct ar8xxx_priv *priv, int addr, int regnum);
int (*phy_write)(struct ar8xxx_priv *priv, int addr, int regnum, u16 val);
const struct ar8xxx_mib_desc *mib_decs;
unsigned num_mibs;
unsigned mib_func;
int mib_rxb_id;
int mib_txb_id;
};
struct ar8xxx_priv {
struct switch_dev dev;
struct mii_bus *mii_bus;
struct mii_bus *sw_mii_bus;
struct phy_device *phy;
struct device *pdev;
int (*get_port_link)(unsigned port);
const struct net_device_ops *ndo_old;
struct net_device_ops ndo;
struct mutex reg_mutex;
u8 chip_ver;
u8 chip_rev;
const struct ar8xxx_chip *chip;
void *chip_data;
bool initialized;
bool port4_phy;
char buf[2048];
struct arl_entry arl_table[AR8XXX_NUM_ARL_RECORDS];
char arl_buf[AR8XXX_NUM_ARL_RECORDS * 32 + 256];
bool link_up[AR8X16_MAX_PORTS];
bool init;
struct mutex mib_lock;
struct delayed_work mib_work;
u64 *mib_stats;
u32 mib_poll_interval;
u8 mib_type;
struct list_head list;
unsigned int use_count;
/* all fields below are cleared on reset */
struct_group(ar8xxx_priv_volatile,
bool vlan;
u16 vlan_id[AR8XXX_MAX_VLANS];
u8 vlan_table[AR8XXX_MAX_VLANS];
u8 vlan_tagged;
u16 pvid[AR8X16_MAX_PORTS];
int arl_age_time;
/* mirroring */
bool mirror_rx;
bool mirror_tx;
int source_port;
int monitor_port;
u8 port_vlan_prio[AR8X16_MAX_PORTS];
);
};
u32
ar8xxx_mii_read32(struct ar8xxx_priv *priv, int phy_id, int regnum);
void
ar8xxx_mii_write32(struct ar8xxx_priv *priv, int phy_id, int regnum, u32 val);
u32
ar8xxx_read(struct ar8xxx_priv *priv, int reg);
void
ar8xxx_write(struct ar8xxx_priv *priv, int reg, u32 val);
u32
ar8xxx_rmw(struct ar8xxx_priv *priv, int reg, u32 mask, u32 val);
void
ar8xxx_phy_dbg_read(struct ar8xxx_priv *priv, int phy_addr,
u16 dbg_addr, u16 *dbg_data);
void
ar8xxx_phy_dbg_write(struct ar8xxx_priv *priv, int phy_addr,
u16 dbg_addr, u16 dbg_data);
void
ar8xxx_phy_mmd_write(struct ar8xxx_priv *priv, int phy_addr, u16 addr, u16 reg, u16 data);
u16
ar8xxx_phy_mmd_read(struct ar8xxx_priv *priv, int phy_addr, u16 addr, u16 reg);
void
ar8xxx_phy_init(struct ar8xxx_priv *priv);
int
ar8xxx_sw_set_vlan(struct switch_dev *dev, const struct switch_attr *attr,
struct switch_val *val);
int
ar8xxx_sw_get_vlan(struct switch_dev *dev, const struct switch_attr *attr,
struct switch_val *val);
int
ar8xxx_sw_set_reset_mibs(struct switch_dev *dev,
const struct switch_attr *attr,
struct switch_val *val);
int
ar8xxx_sw_set_mib_poll_interval(struct switch_dev *dev,
const struct switch_attr *attr,
struct switch_val *val);
int
ar8xxx_sw_get_mib_poll_interval(struct switch_dev *dev,
const struct switch_attr *attr,
struct switch_val *val);
int
ar8xxx_sw_set_mib_type(struct switch_dev *dev,
const struct switch_attr *attr,
struct switch_val *val);
int
ar8xxx_sw_get_mib_type(struct switch_dev *dev,
const struct switch_attr *attr,
struct switch_val *val);
int
ar8xxx_sw_set_mirror_rx_enable(struct switch_dev *dev,
const struct switch_attr *attr,
struct switch_val *val);
int
ar8xxx_sw_get_mirror_rx_enable(struct switch_dev *dev,
const struct switch_attr *attr,
struct switch_val *val);
int
ar8xxx_sw_set_mirror_tx_enable(struct switch_dev *dev,
const struct switch_attr *attr,
struct switch_val *val);
int
ar8xxx_sw_get_mirror_tx_enable(struct switch_dev *dev,
const struct switch_attr *attr,
struct switch_val *val);
int
ar8xxx_sw_set_mirror_monitor_port(struct switch_dev *dev,
const struct switch_attr *attr,
struct switch_val *val);
int
ar8xxx_sw_get_mirror_monitor_port(struct switch_dev *dev,
const struct switch_attr *attr,
struct switch_val *val);
int
ar8xxx_sw_set_mirror_source_port(struct switch_dev *dev,
const struct switch_attr *attr,
struct switch_val *val);
int
ar8xxx_sw_get_mirror_source_port(struct switch_dev *dev,
const struct switch_attr *attr,
struct switch_val *val);
int
ar8xxx_sw_set_pvid(struct switch_dev *dev, int port, int vlan);
int
ar8xxx_sw_get_pvid(struct switch_dev *dev, int port, int *vlan);
int
ar8xxx_sw_hw_apply(struct switch_dev *dev);
int
ar8xxx_sw_reset_switch(struct switch_dev *dev);
int
ar8xxx_sw_get_port_link(struct switch_dev *dev, int port,
struct switch_port_link *link);
int
ar8xxx_sw_set_port_reset_mib(struct switch_dev *dev,
const struct switch_attr *attr,
struct switch_val *val);
int
ar8xxx_sw_get_port_mib(struct switch_dev *dev,
const struct switch_attr *attr,
struct switch_val *val);
int
ar8xxx_sw_get_arl_age_time(struct switch_dev *dev,
const struct switch_attr *attr,
struct switch_val *val);
int
ar8xxx_sw_set_arl_age_time(struct switch_dev *dev,
const struct switch_attr *attr,
struct switch_val *val);
int
ar8xxx_sw_get_arl_table(struct switch_dev *dev,
const struct switch_attr *attr,
struct switch_val *val);
int
ar8xxx_sw_set_flush_arl_table(struct switch_dev *dev,
const struct switch_attr *attr,
struct switch_val *val);
int
ar8xxx_sw_set_flush_port_arl_table(struct switch_dev *dev,
const struct switch_attr *attr,
struct switch_val *val);
int
ar8xxx_sw_get_port_stats(struct switch_dev *dev, int port,
struct switch_port_stats *stats);
int
ar8216_wait_bit(struct ar8xxx_priv *priv, int reg, u32 mask, u32 val);
static inline struct ar8xxx_priv *
swdev_to_ar8xxx(struct switch_dev *swdev)
{
return container_of(swdev, struct ar8xxx_priv, dev);
}
static inline bool ar8xxx_has_gige(struct ar8xxx_priv *priv)
{
return priv->chip->caps & AR8XXX_CAP_GIGE;
}
static inline bool ar8xxx_has_mib_counters(struct ar8xxx_priv *priv)
{
return priv->chip->caps & AR8XXX_CAP_MIB_COUNTERS;
}
static inline bool chip_is_ar8216(struct ar8xxx_priv *priv)
{
return priv->chip_ver == AR8XXX_VER_AR8216;
}
static inline bool chip_is_ar8236(struct ar8xxx_priv *priv)
{
return priv->chip_ver == AR8XXX_VER_AR8236;
}
static inline bool chip_is_ar8316(struct ar8xxx_priv *priv)
{
return priv->chip_ver == AR8XXX_VER_AR8316;
}
static inline bool chip_is_ar8327(struct ar8xxx_priv *priv)
{
return priv->chip_ver == AR8XXX_VER_AR8327;
}
static inline bool chip_is_ar8337(struct ar8xxx_priv *priv)
{
return priv->chip_ver == AR8XXX_VER_AR8337;
}
static inline void
ar8xxx_reg_set(struct ar8xxx_priv *priv, int reg, u32 val)
{
ar8xxx_rmw(priv, reg, 0, val);
}
static inline void
ar8xxx_reg_clear(struct ar8xxx_priv *priv, int reg, u32 val)
{
ar8xxx_rmw(priv, reg, val, 0);
}
static inline void
split_addr(u32 regaddr, u16 *r1, u16 *r2, u16 *page)
{
regaddr >>= 1;
*r1 = regaddr & 0x1e;
regaddr >>= 5;
*r2 = regaddr & 0x7;
regaddr >>= 3;
*page = regaddr & 0x1ff;
}
static inline void
wait_for_page_switch(void)
{
udelay(5);
}
#endif

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,333 @@
/*
* ar8327.h: AR8216 switch driver
*
* Copyright (C) 2009 Felix Fietkau <nbd@nbd.name>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#ifndef __AR8327_H
#define __AR8327_H
#define AR8327_NUM_PORTS 7
#define AR8327_NUM_LEDS 15
#define AR8327_PORTS_ALL 0x7f
#define AR8327_NUM_LED_CTRL_REGS 4
#define AR8327_REG_MASK 0x000
#define AR8327_REG_PAD0_MODE 0x004
#define AR8327_REG_PAD5_MODE 0x008
#define AR8327_REG_PAD6_MODE 0x00c
#define AR8327_PAD_MAC_MII_RXCLK_SEL BIT(0)
#define AR8327_PAD_MAC_MII_TXCLK_SEL BIT(1)
#define AR8327_PAD_MAC_MII_EN BIT(2)
#define AR8327_PAD_MAC_GMII_RXCLK_SEL BIT(4)
#define AR8327_PAD_MAC_GMII_TXCLK_SEL BIT(5)
#define AR8327_PAD_MAC_GMII_EN BIT(6)
#define AR8327_PAD_SGMII_EN BIT(7)
#define AR8327_PAD_PHY_MII_RXCLK_SEL BIT(8)
#define AR8327_PAD_PHY_MII_TXCLK_SEL BIT(9)
#define AR8327_PAD_PHY_MII_EN BIT(10)
#define AR8327_PAD_PHY_GMII_PIPE_RXCLK_SEL BIT(11)
#define AR8327_PAD_PHY_GMII_RXCLK_SEL BIT(12)
#define AR8327_PAD_PHY_GMII_TXCLK_SEL BIT(13)
#define AR8327_PAD_PHY_GMII_EN BIT(14)
#define AR8327_PAD_PHYX_GMII_EN BIT(16)
#define AR8327_PAD_PHYX_RGMII_EN BIT(17)
#define AR8327_PAD_PHYX_MII_EN BIT(18)
#define AR8327_PAD_SGMII_DELAY_EN BIT(19)
#define AR8327_PAD_RGMII_RXCLK_DELAY_SEL BITS(20, 2)
#define AR8327_PAD_RGMII_RXCLK_DELAY_SEL_S 20
#define AR8327_PAD_RGMII_TXCLK_DELAY_SEL BITS(22, 2)
#define AR8327_PAD_RGMII_TXCLK_DELAY_SEL_S 22
#define AR8327_PAD_RGMII_RXCLK_DELAY_EN BIT(24)
#define AR8327_PAD_RGMII_TXCLK_DELAY_EN BIT(25)
#define AR8327_PAD_RGMII_EN BIT(26)
#define AR8327_REG_POWER_ON_STRAP 0x010
#define AR8327_POWER_ON_STRAP_POWER_ON_SEL BIT(31)
#define AR8327_POWER_ON_STRAP_LED_OPEN_EN BIT(24)
#define AR8327_POWER_ON_STRAP_SERDES_AEN BIT(7)
#define AR8327_REG_INT_STATUS0 0x020
#define AR8327_INT0_VT_DONE BIT(20)
#define AR8327_REG_INT_STATUS1 0x024
#define AR8327_REG_INT_MASK0 0x028
#define AR8327_REG_INT_MASK1 0x02c
#define AR8327_REG_MODULE_EN 0x030
#define AR8327_MODULE_EN_MIB BIT(0)
#define AR8327_REG_MIB_FUNC 0x034
#define AR8327_MIB_CPU_KEEP BIT(20)
#define AR8327_REG_SERVICE_TAG 0x048
#define AR8327_REG_LED_CTRL(_i) (0x050 + (_i) * 4)
#define AR8327_REG_LED_CTRL0 0x050
#define AR8327_REG_LED_CTRL1 0x054
#define AR8327_REG_LED_CTRL2 0x058
#define AR8327_REG_LED_CTRL3 0x05c
#define AR8327_REG_MAC_ADDR0 0x060
#define AR8327_REG_MAC_ADDR1 0x064
#define AR8327_REG_MAX_FRAME_SIZE 0x078
#define AR8327_MAX_FRAME_SIZE_MTU BITS(0, 14)
#define AR8327_REG_PORT_STATUS(_i) (0x07c + (_i) * 4)
#define AR8327_PORT_STATUS_TXFLOW_AUTO BIT(10)
#define AR8327_PORT_STATUS_RXFLOW_AUTO BIT(11)
#define AR8327_REG_HEADER_CTRL 0x098
#define AR8327_REG_PORT_HEADER(_i) (0x09c + (_i) * 4)
#define AR8327_REG_SGMII_CTRL 0x0e0
#define AR8327_SGMII_CTRL_EN_PLL BIT(1)
#define AR8327_SGMII_CTRL_EN_RX BIT(2)
#define AR8327_SGMII_CTRL_EN_TX BIT(3)
#define AR8327_REG_EEE_CTRL 0x100
#define AR8327_EEE_CTRL_DISABLE_PHY(_i) BIT(4 + (_i) * 2)
#define AR8327_REG_FRAME_ACK_CTRL0 0x210
#define AR8327_FRAME_ACK_CTRL_IGMP_MLD_EN0 BIT(0)
#define AR8327_FRAME_ACK_CTRL_IGMP_JOIN_EN0 BIT(1)
#define AR8327_FRAME_ACK_CTRL_IGMP_LEAVE_EN0 BIT(2)
#define AR8327_FRAME_ACK_CTRL_EAPOL_EN0 BIT(3)
#define AR8327_FRAME_ACK_CTRL_DHCP_EN0 BIT(4)
#define AR8327_FRAME_ACK_CTRL_ARP_ACK_EN0 BIT(5)
#define AR8327_FRAME_ACK_CTRL_ARP_REQ_EN0 BIT(6)
#define AR8327_FRAME_ACK_CTRL_IGMP_MLD_EN1 BIT(8)
#define AR8327_FRAME_ACK_CTRL_IGMP_JOIN_EN1 BIT(9)
#define AR8327_FRAME_ACK_CTRL_IGMP_LEAVE_EN1 BIT(10)
#define AR8327_FRAME_ACK_CTRL_EAPOL_EN1 BIT(11)
#define AR8327_FRAME_ACK_CTRL_DHCP_EN1 BIT(12)
#define AR8327_FRAME_ACK_CTRL_ARP_ACK_EN1 BIT(13)
#define AR8327_FRAME_ACK_CTRL_ARP_REQ_EN1 BIT(14)
#define AR8327_FRAME_ACK_CTRL_IGMP_MLD_EN2 BIT(16)
#define AR8327_FRAME_ACK_CTRL_IGMP_JOIN_EN2 BIT(17)
#define AR8327_FRAME_ACK_CTRL_IGMP_LEAVE_EN2 BIT(18)
#define AR8327_FRAME_ACK_CTRL_EAPOL_EN2 BIT(19)
#define AR8327_FRAME_ACK_CTRL_DHCP_EN2 BIT(20)
#define AR8327_FRAME_ACK_CTRL_ARP_ACK_EN2 BIT(21)
#define AR8327_FRAME_ACK_CTRL_ARP_REQ_EN2 BIT(22)
#define AR8327_FRAME_ACK_CTRL_IGMP_MLD_EN3 BIT(24)
#define AR8327_FRAME_ACK_CTRL_IGMP_JOIN_EN3 BIT(25)
#define AR8327_FRAME_ACK_CTRL_IGMP_LEAVE_EN3 BIT(26)
#define AR8327_FRAME_ACK_CTRL_EAPOL_EN3 BIT(27)
#define AR8327_FRAME_ACK_CTRL_DHCP_EN3 BIT(28)
#define AR8327_FRAME_ACK_CTRL_ARP_ACK_EN3 BIT(29)
#define AR8327_FRAME_ACK_CTRL_ARP_REQ_EN3 BIT(30)
#define AR8327_REG_FRAME_ACK_CTRL1 0x214
#define AR8327_FRAME_ACK_CTRL_IGMP_MLD_EN4 BIT(0)
#define AR8327_FRAME_ACK_CTRL_IGMP_JOIN_EN4 BIT(1)
#define AR8327_FRAME_ACK_CTRL_IGMP_LEAVE_EN4 BIT(2)
#define AR8327_FRAME_ACK_CTRL_EAPOL_EN4 BIT(3)
#define AR8327_FRAME_ACK_CTRL_DHCP_EN4 BIT(4)
#define AR8327_FRAME_ACK_CTRL_ARP_ACK_EN4 BIT(5)
#define AR8327_FRAME_ACK_CTRL_ARP_REQ_EN4 BIT(6)
#define AR8327_FRAME_ACK_CTRL_IGMP_MLD_EN5 BIT(8)
#define AR8327_FRAME_ACK_CTRL_IGMP_JOIN_EN5 BIT(9)
#define AR8327_FRAME_ACK_CTRL_IGMP_LEAVE_EN5 BIT(10)
#define AR8327_FRAME_ACK_CTRL_EAPOL_EN5 BIT(11)
#define AR8327_FRAME_ACK_CTRL_DHCP_EN5 BIT(12)
#define AR8327_FRAME_ACK_CTRL_ARP_ACK_EN5 BIT(13)
#define AR8327_FRAME_ACK_CTRL_ARP_REQ_EN5 BIT(14)
#define AR8327_FRAME_ACK_CTRL_IGMP_MLD_EN6 BIT(16)
#define AR8327_FRAME_ACK_CTRL_IGMP_JOIN_EN6 BIT(17)
#define AR8327_FRAME_ACK_CTRL_IGMP_LEAVE_EN6 BIT(18)
#define AR8327_FRAME_ACK_CTRL_EAPOL_EN6 BIT(19)
#define AR8327_FRAME_ACK_CTRL_DHCP_EN6 BIT(20)
#define AR8327_FRAME_ACK_CTRL_ARP_ACK_EN6 BIT(21)
#define AR8327_FRAME_ACK_CTRL_ARP_REQ_EN6 BIT(22)
#define AR8327_FRAME_ACK_CTRL_IGMP_V3_EN BIT(24)
#define AR8327_FRAME_ACK_CTRL_PPPOE_EN BIT(25)
#define AR8327_REG_FRAME_ACK_CTRL(_i) (0x210 + ((_i) / 4) * 0x4)
#define AR8327_FRAME_ACK_CTRL_IGMP_MLD BIT(0)
#define AR8327_FRAME_ACK_CTRL_IGMP_JOIN BIT(1)
#define AR8327_FRAME_ACK_CTRL_IGMP_LEAVE BIT(2)
#define AR8327_FRAME_ACK_CTRL_EAPOL BIT(3)
#define AR8327_FRAME_ACK_CTRL_DHCP BIT(4)
#define AR8327_FRAME_ACK_CTRL_ARP_ACK BIT(5)
#define AR8327_FRAME_ACK_CTRL_ARP_REQ BIT(6)
#define AR8327_FRAME_ACK_CTRL_S(_i) (((_i) % 4) * 8)
#define AR8327_REG_PORT_VLAN0(_i) (0x420 + (_i) * 0x8)
#define AR8327_PORT_VLAN0_DEF_PRI_MASK BITS(0, 3)
#define AR8327_PORT_VLAN0_DEF_SVID BITS(0, 12)
#define AR8327_PORT_VLAN0_DEF_SVID_S 0
#define AR8327_PORT_VLAN0_DEF_SPRI BITS(13, 3)
#define AR8327_PORT_VLAN0_DEF_SPRI_S 13
#define AR8327_PORT_VLAN0_DEF_CVID BITS(16, 12)
#define AR8327_PORT_VLAN0_DEF_CVID_S 16
#define AR8327_PORT_VLAN0_DEF_CPRI BITS(29, 3)
#define AR8327_PORT_VLAN0_DEF_CPRI_S 29
#define AR8327_REG_PORT_VLAN1(_i) (0x424 + (_i) * 0x8)
#define AR8327_PORT_VLAN1_VLAN_PRI_PROP BIT(4)
#define AR8327_PORT_VLAN1_PORT_VLAN_PROP BIT(6)
#define AR8327_PORT_VLAN1_OUT_MODE BITS(12, 2)
#define AR8327_PORT_VLAN1_OUT_MODE_S 12
#define AR8327_PORT_VLAN1_OUT_MODE_UNMOD 0
#define AR8327_PORT_VLAN1_OUT_MODE_UNTAG 1
#define AR8327_PORT_VLAN1_OUT_MODE_TAG 2
#define AR8327_PORT_VLAN1_OUT_MODE_UNTOUCH 3
#define AR8327_REG_ATU_DATA0 0x600
#define AR8327_ATU_ADDR0 BITS(0, 8)
#define AR8327_ATU_ADDR0_S 0
#define AR8327_ATU_ADDR1 BITS(8, 8)
#define AR8327_ATU_ADDR1_S 8
#define AR8327_ATU_ADDR2 BITS(16, 8)
#define AR8327_ATU_ADDR2_S 16
#define AR8327_ATU_ADDR3 BITS(24, 8)
#define AR8327_ATU_ADDR3_S 24
#define AR8327_REG_ATU_DATA1 0x604
#define AR8327_ATU_ADDR4 BITS(0, 8)
#define AR8327_ATU_ADDR4_S 0
#define AR8327_ATU_ADDR5 BITS(8, 8)
#define AR8327_ATU_ADDR5_S 8
#define AR8327_ATU_PORTS BITS(16, 7)
#define AR8327_ATU_PORTS_S 16
#define AR8327_ATU_PORT0 BIT(16)
#define AR8327_ATU_PORT1 BIT(17)
#define AR8327_ATU_PORT2 BIT(18)
#define AR8327_ATU_PORT3 BIT(19)
#define AR8327_ATU_PORT4 BIT(20)
#define AR8327_ATU_PORT5 BIT(21)
#define AR8327_ATU_PORT6 BIT(22)
#define AR8327_REG_ATU_DATA2 0x608
#define AR8327_ATU_STATUS BITS(0, 4)
#define AR8327_REG_ATU_FUNC 0x60c
#define AR8327_ATU_FUNC_OP BITS(0, 4)
#define AR8327_ATU_FUNC_OP_NOOP 0x0
#define AR8327_ATU_FUNC_OP_FLUSH 0x1
#define AR8327_ATU_FUNC_OP_LOAD 0x2
#define AR8327_ATU_FUNC_OP_PURGE 0x3
#define AR8327_ATU_FUNC_OP_FLUSH_UNLOCKED 0x4
#define AR8327_ATU_FUNC_OP_FLUSH_PORT 0x5
#define AR8327_ATU_FUNC_OP_GET_NEXT 0x6
#define AR8327_ATU_FUNC_OP_SEARCH_MAC 0x7
#define AR8327_ATU_FUNC_OP_CHANGE_TRUNK 0x8
#define AR8327_ATU_PORT_NUM BITS(8, 4)
#define AR8327_ATU_PORT_NUM_S 8
#define AR8327_ATU_FUNC_BUSY BIT(31)
#define AR8327_REG_VTU_FUNC0 0x0610
#define AR8327_VTU_FUNC0_EG_MODE BITS(4, 14)
#define AR8327_VTU_FUNC0_EG_MODE_S(_i) (4 + (_i) * 2)
#define AR8327_VTU_FUNC0_EG_MODE_KEEP 0
#define AR8327_VTU_FUNC0_EG_MODE_UNTAG 1
#define AR8327_VTU_FUNC0_EG_MODE_TAG 2
#define AR8327_VTU_FUNC0_EG_MODE_NOT 3
#define AR8327_VTU_FUNC0_IVL BIT(19)
#define AR8327_VTU_FUNC0_VALID BIT(20)
#define AR8327_REG_VTU_FUNC1 0x0614
#define AR8327_VTU_FUNC1_OP BITS(0, 3)
#define AR8327_VTU_FUNC1_OP_NOOP 0
#define AR8327_VTU_FUNC1_OP_FLUSH 1
#define AR8327_VTU_FUNC1_OP_LOAD 2
#define AR8327_VTU_FUNC1_OP_PURGE 3
#define AR8327_VTU_FUNC1_OP_REMOVE_PORT 4
#define AR8327_VTU_FUNC1_OP_GET_NEXT 5
#define AR8327_VTU_FUNC1_OP_GET_ONE 6
#define AR8327_VTU_FUNC1_FULL BIT(4)
#define AR8327_VTU_FUNC1_PORT BIT(8, 4)
#define AR8327_VTU_FUNC1_PORT_S 8
#define AR8327_VTU_FUNC1_VID BIT(16, 12)
#define AR8327_VTU_FUNC1_VID_S 16
#define AR8327_VTU_FUNC1_BUSY BIT(31)
#define AR8327_REG_ARL_CTRL 0x0618
#define AR8327_REG_FWD_CTRL0 0x620
#define AR8327_FWD_CTRL0_CPU_PORT_EN BIT(10)
#define AR8327_FWD_CTRL0_MIRROR_PORT BITS(4, 4)
#define AR8327_FWD_CTRL0_MIRROR_PORT_S 4
#define AR8327_REG_FWD_CTRL1 0x624
#define AR8327_FWD_CTRL1_UC_FLOOD BITS(0, 7)
#define AR8327_FWD_CTRL1_UC_FLOOD_S 0
#define AR8327_FWD_CTRL1_MC_FLOOD BITS(8, 7)
#define AR8327_FWD_CTRL1_MC_FLOOD_S 8
#define AR8327_FWD_CTRL1_BC_FLOOD BITS(16, 7)
#define AR8327_FWD_CTRL1_BC_FLOOD_S 16
#define AR8327_FWD_CTRL1_IGMP BITS(24, 7)
#define AR8327_FWD_CTRL1_IGMP_S 24
#define AR8327_REG_PORT_LOOKUP(_i) (0x660 + (_i) * 0xc)
#define AR8327_PORT_LOOKUP_MEMBER BITS(0, 7)
#define AR8327_PORT_LOOKUP_IN_MODE BITS(8, 2)
#define AR8327_PORT_LOOKUP_IN_MODE_S 8
#define AR8327_PORT_LOOKUP_STATE BITS(16, 3)
#define AR8327_PORT_LOOKUP_STATE_S 16
#define AR8327_PORT_LOOKUP_LEARN BIT(20)
#define AR8327_PORT_LOOKUP_ING_MIRROR_EN BIT(25)
#define AR8327_REG_PORT_PRIO(_i) (0x664 + (_i) * 0xc)
#define AR8327_REG_PORT_HOL_CTRL1(_i) (0x974 + (_i) * 0x8)
#define AR8327_PORT_HOL_CTRL1_EG_MIRROR_EN BIT(16)
#define AR8337_PAD_MAC06_EXCHANGE_EN BIT(31)
#define AR8327_PHY_MODE_SEL 0x12
#define AR8327_PHY_MODE_SEL_RGMII BIT(3)
#define AR8327_PHY_TEST_CTRL 0x0
#define AR8327_PHY_TEST_CTRL_RGMII_RX_DELAY BIT(15)
#define AR8327_PHY_SYS_CTRL 0x5
#define AR8327_PHY_SYS_CTRL_RGMII_TX_DELAY BIT(8)
enum ar8327_led_pattern {
AR8327_LED_PATTERN_OFF = 0,
AR8327_LED_PATTERN_BLINK,
AR8327_LED_PATTERN_ON,
AR8327_LED_PATTERN_RULE,
};
struct ar8327_led_entry {
unsigned reg;
unsigned shift;
};
struct ar8327_led {
struct led_classdev cdev;
struct ar8xxx_priv *sw_priv;
char *name;
bool active_low;
u8 led_num;
enum ar8327_led_mode mode;
struct mutex mutex;
spinlock_t lock;
struct work_struct led_work;
bool enable_hw_mode;
enum ar8327_led_pattern pattern;
};
struct ar8327_data {
u32 port0_status;
u32 port6_status;
struct ar8327_led **leds;
unsigned int num_leds;
/* all fields below are cleared on reset */
bool eee[AR8XXX_NUM_PHYS];
};
#endif

View file

@ -0,0 +1,37 @@
menuconfig SWCONFIG_B53
tristate "Broadcom bcm53xx managed switch support"
depends on SWCONFIG
help
This driver adds support for Broadcom managed switch chips. It supports
BCM5325E, BCM5365, BCM539x, BCM53115 and BCM53125 as well as BCM63XX
integrated switches.
config SWCONFIG_B53_SPI_DRIVER
tristate "B53 SPI connected switch driver"
depends on SWCONFIG_B53 && SPI
help
Select to enable support for registering switches configured through SPI.
config SWCONFIG_B53_PHY_DRIVER
tristate "B53 MDIO connected switch driver"
depends on SWCONFIG_B53
select SWCONFIG_B53_PHY_FIXUP
help
Select to enable support for registering switches configured through MDIO.
config SWCONFIG_B53_MMAP_DRIVER
tristate "B53 MMAP connected switch driver"
depends on SWCONFIG_B53
help
Select to enable support for memory-mapped switches like the BCM63XX
integrated switches.
config SWCONFIG_B53_SRAB_DRIVER
tristate "B53 SRAB connected switch driver"
depends on SWCONFIG_B53
help
Select to enable support for memory-mapped Switch Register Access
Bridge Registers (SRAB) like it is found on the BCM53010
config SWCONFIG_B53_PHY_FIXUP
bool

View file

@ -0,0 +1,10 @@
obj-$(CONFIG_SWCONFIG_B53) += b53_common.o
obj-$(CONFIG_SWCONFIG_B53_PHY_FIXUP) += b53_phy_fixup.o
obj-$(CONFIG_SWCONFIG_B53_MMAP_DRIVER) += b53_mmap.o
obj-$(CONFIG_SWCONFIG_B53_SRAB_DRIVER) += b53_srab.o
obj-$(CONFIG_SWCONFIG_B53_PHY_DRIVER) += b53_mdio.o
obj-$(CONFIG_SWCONFIG_B53_SPI_DRIVER) += b53_spi.o
ccflags-y += -Werror

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,436 @@
/*
* B53 register access through MII registers
*
* Copyright (C) 2011-2013 Jonas Gorski <jogo@openwrt.org>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <linux/kernel.h>
#include <linux/phy.h>
#include <linux/module.h>
#include "b53_priv.h"
#define B53_PSEUDO_PHY 0x1e /* Register Access Pseudo PHY */
/* MII registers */
#define REG_MII_PAGE 0x10 /* MII Page register */
#define REG_MII_ADDR 0x11 /* MII Address register */
#define REG_MII_DATA0 0x18 /* MII Data register 0 */
#define REG_MII_DATA1 0x19 /* MII Data register 1 */
#define REG_MII_DATA2 0x1a /* MII Data register 2 */
#define REG_MII_DATA3 0x1b /* MII Data register 3 */
#define REG_MII_PAGE_ENABLE BIT(0)
#define REG_MII_ADDR_WRITE BIT(0)
#define REG_MII_ADDR_READ BIT(1)
static int b53_mdio_op(struct b53_device *dev, u8 page, u8 reg, u16 op)
{
int i;
u16 v;
int ret;
struct mii_bus *bus = dev->priv;
if (dev->current_page != page) {
/* set page number */
v = (page << 8) | REG_MII_PAGE_ENABLE;
ret = mdiobus_write(bus, B53_PSEUDO_PHY, REG_MII_PAGE, v);
if (ret)
return ret;
dev->current_page = page;
}
/* set register address */
v = (reg << 8) | op;
ret = mdiobus_write(bus, B53_PSEUDO_PHY, REG_MII_ADDR, v);
if (ret)
return ret;
/* check if operation completed */
for (i = 0; i < 5; ++i) {
v = mdiobus_read(bus, B53_PSEUDO_PHY, REG_MII_ADDR);
if (!(v & (REG_MII_ADDR_WRITE | REG_MII_ADDR_READ)))
break;
usleep_range(10, 100);
}
if (WARN_ON(i == 5))
return -EIO;
return 0;
}
static int b53_mdio_read8(struct b53_device *dev, u8 page, u8 reg, u8 *val)
{
struct mii_bus *bus = dev->priv;
int ret;
ret = b53_mdio_op(dev, page, reg, REG_MII_ADDR_READ);
if (ret)
return ret;
*val = mdiobus_read(bus, B53_PSEUDO_PHY, REG_MII_DATA0) & 0xff;
return 0;
}
static int b53_mdio_read16(struct b53_device *dev, u8 page, u8 reg, u16 *val)
{
struct mii_bus *bus = dev->priv;
int ret;
ret = b53_mdio_op(dev, page, reg, REG_MII_ADDR_READ);
if (ret)
return ret;
*val = mdiobus_read(bus, B53_PSEUDO_PHY, REG_MII_DATA0);
return 0;
}
static int b53_mdio_read32(struct b53_device *dev, u8 page, u8 reg, u32 *val)
{
struct mii_bus *bus = dev->priv;
int ret;
ret = b53_mdio_op(dev, page, reg, REG_MII_ADDR_READ);
if (ret)
return ret;
*val = mdiobus_read(bus, B53_PSEUDO_PHY, REG_MII_DATA0);
*val |= mdiobus_read(bus, B53_PSEUDO_PHY, REG_MII_DATA1) << 16;
return 0;
}
static int b53_mdio_read48(struct b53_device *dev, u8 page, u8 reg, u64 *val)
{
struct mii_bus *bus = dev->priv;
u64 temp = 0;
int i;
int ret;
ret = b53_mdio_op(dev, page, reg, REG_MII_ADDR_READ);
if (ret)
return ret;
for (i = 2; i >= 0; i--) {
temp <<= 16;
temp |= mdiobus_read(bus, B53_PSEUDO_PHY, REG_MII_DATA0 + i);
}
*val = temp;
return 0;
}
static int b53_mdio_read64(struct b53_device *dev, u8 page, u8 reg, u64 *val)
{
struct mii_bus *bus = dev->priv;
u64 temp = 0;
int i;
int ret;
ret = b53_mdio_op(dev, page, reg, REG_MII_ADDR_READ);
if (ret)
return ret;
for (i = 3; i >= 0; i--) {
temp <<= 16;
temp |= mdiobus_read(bus, B53_PSEUDO_PHY, REG_MII_DATA0 + i);
}
*val = temp;
return 0;
}
static int b53_mdio_write8(struct b53_device *dev, u8 page, u8 reg, u8 value)
{
struct mii_bus *bus = dev->priv;
int ret;
ret = mdiobus_write(bus, B53_PSEUDO_PHY, REG_MII_DATA0, value);
if (ret)
return ret;
return b53_mdio_op(dev, page, reg, REG_MII_ADDR_WRITE);
}
static int b53_mdio_write16(struct b53_device *dev, u8 page, u8 reg,
u16 value)
{
struct mii_bus *bus = dev->priv;
int ret;
ret = mdiobus_write(bus, B53_PSEUDO_PHY, REG_MII_DATA0, value);
if (ret)
return ret;
return b53_mdio_op(dev, page, reg, REG_MII_ADDR_WRITE);
}
static int b53_mdio_write32(struct b53_device *dev, u8 page, u8 reg,
u32 value)
{
struct mii_bus *bus = dev->priv;
unsigned int i;
u32 temp = value;
for (i = 0; i < 2; i++) {
int ret = mdiobus_write(bus, B53_PSEUDO_PHY, REG_MII_DATA0 + i,
temp & 0xffff);
if (ret)
return ret;
temp >>= 16;
}
return b53_mdio_op(dev, page, reg, REG_MII_ADDR_WRITE);
}
static int b53_mdio_write48(struct b53_device *dev, u8 page, u8 reg,
u64 value)
{
struct mii_bus *bus = dev->priv;
unsigned i;
u64 temp = value;
for (i = 0; i < 3; i++) {
int ret = mdiobus_write(bus, B53_PSEUDO_PHY, REG_MII_DATA0 + i,
temp & 0xffff);
if (ret)
return ret;
temp >>= 16;
}
return b53_mdio_op(dev, page, reg, REG_MII_ADDR_WRITE);
}
static int b53_mdio_write64(struct b53_device *dev, u8 page, u8 reg,
u64 value)
{
struct mii_bus *bus = dev->priv;
unsigned i;
u64 temp = value;
for (i = 0; i < 4; i++) {
int ret = mdiobus_write(bus, B53_PSEUDO_PHY, REG_MII_DATA0 + i,
temp & 0xffff);
if (ret)
return ret;
temp >>= 16;
}
return b53_mdio_op(dev, page, reg, REG_MII_ADDR_WRITE);
}
static int b53_mdio_phy_read16(struct b53_device *dev, int addr, u8 reg,
u16 *value)
{
struct mii_bus *bus = dev->priv;
*value = mdiobus_read(bus, addr, reg);
return 0;
}
static int b53_mdio_phy_write16(struct b53_device *dev, int addr, u8 reg,
u16 value)
{
struct mii_bus *bus = dev->priv;
return mdiobus_write(bus, addr, reg, value);
}
static struct b53_io_ops b53_mdio_ops = {
.read8 = b53_mdio_read8,
.read16 = b53_mdio_read16,
.read32 = b53_mdio_read32,
.read48 = b53_mdio_read48,
.read64 = b53_mdio_read64,
.write8 = b53_mdio_write8,
.write16 = b53_mdio_write16,
.write32 = b53_mdio_write32,
.write48 = b53_mdio_write48,
.write64 = b53_mdio_write64,
.phy_read16 = b53_mdio_phy_read16,
.phy_write16 = b53_mdio_phy_write16,
};
static int b53_phy_probe(struct phy_device *phydev)
{
struct b53_device *dev;
int ret;
/* allow the generic phy driver to take over */
if (phydev->mdio.addr != B53_PSEUDO_PHY && phydev->mdio.addr != 0)
return -ENODEV;
dev = b53_swconfig_switch_alloc(&phydev->mdio.dev, &b53_mdio_ops, phydev->mdio.bus);
if (!dev)
return -ENOMEM;
dev->current_page = 0xff;
dev->priv = phydev->mdio.bus;
dev->ops = &b53_mdio_ops;
dev->pdata = NULL;
mutex_init(&dev->reg_mutex);
ret = b53_swconfig_switch_detect(dev);
if (ret)
return ret;
linkmode_zero(phydev->supported);
if (is5325(dev) || is5365(dev))
linkmode_set_bit(ETHTOOL_LINK_MODE_100baseT_Full_BIT, phydev->supported);
else
linkmode_set_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT, phydev->supported);
linkmode_copy(phydev->advertising, phydev->supported);
ret = b53_swconfig_switch_register(dev);
if (ret) {
dev_err(dev->dev, "failed to register switch: %i\n", ret);
return ret;
}
phydev->priv = dev;
return 0;
}
static int b53_phy_config_init(struct phy_device *phydev)
{
struct b53_device *dev = phydev->priv;
/* we don't use page 0xff, so force a page set */
dev->current_page = 0xff;
/* force the ethX as alias */
dev->sw_dev.alias = phydev->attached_dev->name;
return 0;
}
static void b53_phy_remove(struct phy_device *phydev)
{
struct b53_device *priv = phydev->priv;
if (!priv)
return;
b53_switch_remove(priv);
phydev->priv = NULL;
}
static int b53_phy_config_aneg(struct phy_device *phydev)
{
return 0;
}
static int b53_phy_read_status(struct phy_device *phydev)
{
struct b53_device *priv = phydev->priv;
if (is5325(priv) || is5365(priv))
phydev->speed = 100;
else
phydev->speed = 1000;
phydev->duplex = DUPLEX_FULL;
phydev->link = 1;
phydev->state = PHY_RUNNING;
netif_carrier_on(phydev->attached_dev);
phydev->adjust_link(phydev->attached_dev);
return 0;
}
/* BCM5325, BCM539x */
static struct phy_driver b53_phy_driver_id1 = {
.phy_id = 0x0143bc00,
.name = "Broadcom B53 (1)",
.phy_id_mask = 0x1ffffc00,
.features = 0,
.probe = b53_phy_probe,
.remove = b53_phy_remove,
.config_aneg = b53_phy_config_aneg,
.config_init = b53_phy_config_init,
.read_status = b53_phy_read_status,
};
/* BCM53125, BCM53128 */
static struct phy_driver b53_phy_driver_id2 = {
.phy_id = 0x03625c00,
.name = "Broadcom B53 (2)",
.phy_id_mask = 0x1ffffc00,
.features = 0,
.probe = b53_phy_probe,
.remove = b53_phy_remove,
.config_aneg = b53_phy_config_aneg,
.config_init = b53_phy_config_init,
.read_status = b53_phy_read_status,
};
/* BCM5365 */
static struct phy_driver b53_phy_driver_id3 = {
.phy_id = 0x00406300,
.name = "Broadcom B53 (3)",
.phy_id_mask = 0x1fffff00,
.features = 0,
.probe = b53_phy_probe,
.remove = b53_phy_remove,
.config_aneg = b53_phy_config_aneg,
.config_init = b53_phy_config_init,
.read_status = b53_phy_read_status,
};
int __init b53_phy_driver_register(void)
{
int ret;
ret = phy_driver_register(&b53_phy_driver_id1, THIS_MODULE);
if (ret)
return ret;
ret = phy_driver_register(&b53_phy_driver_id2, THIS_MODULE);
if (ret)
goto err1;
ret = phy_driver_register(&b53_phy_driver_id3, THIS_MODULE);
if (!ret)
return 0;
phy_driver_unregister(&b53_phy_driver_id2);
err1:
phy_driver_unregister(&b53_phy_driver_id1);
return ret;
}
void __exit b53_phy_driver_unregister(void)
{
phy_driver_unregister(&b53_phy_driver_id3);
phy_driver_unregister(&b53_phy_driver_id2);
phy_driver_unregister(&b53_phy_driver_id1);
}
module_init(b53_phy_driver_register);
module_exit(b53_phy_driver_unregister);
MODULE_DESCRIPTION("B53 MDIO access driver");
MODULE_LICENSE("Dual BSD/GPL");

View file

@ -0,0 +1,248 @@
/*
* B53 register access through memory mapped registers
*
* Copyright (C) 2012-2013 Jonas Gorski <jogo@openwrt.org>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/platform_data/b53.h>
#include <linux/version.h>
#include "b53_priv.h"
static int b53_mmap_read8(struct b53_device *dev, u8 page, u8 reg, u8 *val)
{
u8 __iomem *regs = dev->priv;
*val = readb(regs + (page << 8) + reg);
return 0;
}
static int b53_mmap_read16(struct b53_device *dev, u8 page, u8 reg, u16 *val)
{
u8 __iomem *regs = dev->priv;
if (WARN_ON(reg % 2))
return -EINVAL;
if (dev->pdata && dev->pdata->big_endian)
*val = readw_be(regs + (page << 8) + reg);
else
*val = readw(regs + (page << 8) + reg);
return 0;
}
static int b53_mmap_read32(struct b53_device *dev, u8 page, u8 reg, u32 *val)
{
u8 __iomem *regs = dev->priv;
if (WARN_ON(reg % 4))
return -EINVAL;
if (dev->pdata && dev->pdata->big_endian)
*val = readl_be(regs + (page << 8) + reg);
else
*val = readl(regs + (page << 8) + reg);
return 0;
}
static int b53_mmap_read48(struct b53_device *dev, u8 page, u8 reg, u64 *val)
{
if (WARN_ON(reg % 2))
return -EINVAL;
if (reg % 4) {
u16 lo;
u32 hi;
b53_mmap_read16(dev, page, reg, &lo);
b53_mmap_read32(dev, page, reg + 2, &hi);
*val = ((u64)hi << 16) | lo;
} else {
u32 lo;
u16 hi;
b53_mmap_read32(dev, page, reg, &lo);
b53_mmap_read16(dev, page, reg + 4, &hi);
*val = ((u64)hi << 32) | lo;
}
return 0;
}
static int b53_mmap_read64(struct b53_device *dev, u8 page, u8 reg, u64 *val)
{
u32 hi, lo;
if (WARN_ON(reg % 4))
return -EINVAL;
b53_mmap_read32(dev, page, reg, &lo);
b53_mmap_read32(dev, page, reg + 4, &hi);
*val = ((u64)hi << 32) | lo;
return 0;
}
static int b53_mmap_write8(struct b53_device *dev, u8 page, u8 reg, u8 value)
{
u8 __iomem *regs = dev->priv;
writeb(value, regs + (page << 8) + reg);
return 0;
}
static int b53_mmap_write16(struct b53_device *dev, u8 page, u8 reg,
u16 value)
{
u8 __iomem *regs = dev->priv;
if (WARN_ON(reg % 2))
return -EINVAL;
if (dev->pdata && dev->pdata->big_endian)
writew_be(value, regs + (page << 8) + reg);
else
writew(value, regs + (page << 8) + reg);
return 0;
}
static int b53_mmap_write32(struct b53_device *dev, u8 page, u8 reg,
u32 value)
{
u8 __iomem *regs = dev->priv;
if (WARN_ON(reg % 4))
return -EINVAL;
if (dev->pdata && dev->pdata->big_endian)
writel_be(value, regs + (page << 8) + reg);
else
writel(value, regs + (page << 8) + reg);
return 0;
}
static int b53_mmap_write48(struct b53_device *dev, u8 page, u8 reg,
u64 value)
{
if (WARN_ON(reg % 2))
return -EINVAL;
if (reg % 4) {
u32 hi = (u32)(value >> 16);
u16 lo = (u16)value;
b53_mmap_write16(dev, page, reg, lo);
b53_mmap_write32(dev, page, reg + 2, hi);
} else {
u16 hi = (u16)(value >> 32);
u32 lo = (u32)value;
b53_mmap_write32(dev, page, reg, lo);
b53_mmap_write16(dev, page, reg + 4, hi);
}
return 0;
}
static int b53_mmap_write64(struct b53_device *dev, u8 page, u8 reg,
u64 value)
{
u32 hi, lo;
hi = (u32)(value >> 32);
lo = (u32)value;
if (WARN_ON(reg % 4))
return -EINVAL;
b53_mmap_write32(dev, page, reg, lo);
b53_mmap_write32(dev, page, reg + 4, hi);
return 0;
}
static struct b53_io_ops b53_mmap_ops = {
.read8 = b53_mmap_read8,
.read16 = b53_mmap_read16,
.read32 = b53_mmap_read32,
.read48 = b53_mmap_read48,
.read64 = b53_mmap_read64,
.write8 = b53_mmap_write8,
.write16 = b53_mmap_write16,
.write32 = b53_mmap_write32,
.write48 = b53_mmap_write48,
.write64 = b53_mmap_write64,
};
static int b53_mmap_probe(struct platform_device *pdev)
{
struct b53_platform_data *pdata = pdev->dev.platform_data;
struct b53_device *dev;
if (!pdata)
return -EINVAL;
dev = b53_swconfig_switch_alloc(&pdev->dev, &b53_mmap_ops, pdata->regs);
if (!dev)
return -ENOMEM;
if (pdata)
dev->pdata = pdata;
platform_set_drvdata(pdev, dev);
return b53_swconfig_switch_register(dev);
}
#if LINUX_VERSION_CODE < KERNEL_VERSION(6,11,0)
static int b53_mmap_remove(struct platform_device *pdev)
#else
static void b53_mmap_remove(struct platform_device *pdev)
#endif
{
struct b53_device *dev = platform_get_drvdata(pdev);
if (dev)
b53_switch_remove(dev);
#if LINUX_VERSION_CODE < KERNEL_VERSION(6,11,0)
return 0;
#endif
}
static struct platform_driver b53_mmap_driver = {
.probe = b53_mmap_probe,
.remove = b53_mmap_remove,
.driver = {
.name = "b53-switch",
},
};
module_platform_driver(b53_mmap_driver);
MODULE_AUTHOR("Jonas Gorski <jogo@openwrt.org>");
MODULE_DESCRIPTION("B53 MMAP access driver");
MODULE_LICENSE("Dual BSD/GPL");

View file

@ -0,0 +1,55 @@
/*
* B53 PHY Fixup call
*
* Copyright (C) 2013 Jonas Gorski <jogo@openwrt.org>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/phy.h>
#define B53_PSEUDO_PHY 0x1e /* Register Access Pseudo PHY */
#define B53_BRCM_OUI_1 0x0143bc00
#define B53_BRCM_OUI_2 0x03625c00
#define B53_BRCM_OUI_3 0x00406300
static int b53_phy_fixup(struct phy_device *dev)
{
struct mii_bus *bus = dev->mdio.bus;
u32 phy_id;
if (dev->mdio.addr != B53_PSEUDO_PHY)
return 0;
/* read the first port's id */
phy_id = mdiobus_read(bus, 0, 2) << 16;
phy_id |= mdiobus_read(bus, 0, 3);
if ((phy_id & 0xfffffc00) == B53_BRCM_OUI_1 ||
(phy_id & 0xfffffc00) == B53_BRCM_OUI_2 ||
(phy_id & 0xffffff00) == B53_BRCM_OUI_3) {
dev->phy_id = phy_id;
}
return 0;
}
int __init b53_phy_fixup_register(void)
{
return phy_register_fixup_for_id(PHY_ANY_ID, b53_phy_fixup);
}
subsys_initcall(b53_phy_fixup_register);

View file

@ -0,0 +1,336 @@
/*
* B53 common definitions
*
* Copyright (C) 2011-2013 Jonas Gorski <jogo@openwrt.org>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#ifndef __B53_PRIV_H
#define __B53_PRIV_H
#include <linux/kernel.h>
#include <linux/mutex.h>
#include <linux/switch.h>
struct b53_device;
struct b53_io_ops {
int (*read8)(struct b53_device *dev, u8 page, u8 reg, u8 *value);
int (*read16)(struct b53_device *dev, u8 page, u8 reg, u16 *value);
int (*read32)(struct b53_device *dev, u8 page, u8 reg, u32 *value);
int (*read48)(struct b53_device *dev, u8 page, u8 reg, u64 *value);
int (*read64)(struct b53_device *dev, u8 page, u8 reg, u64 *value);
int (*write8)(struct b53_device *dev, u8 page, u8 reg, u8 value);
int (*write16)(struct b53_device *dev, u8 page, u8 reg, u16 value);
int (*write32)(struct b53_device *dev, u8 page, u8 reg, u32 value);
int (*write48)(struct b53_device *dev, u8 page, u8 reg, u64 value);
int (*write64)(struct b53_device *dev, u8 page, u8 reg, u64 value);
int (*phy_read16)(struct b53_device *dev, int addr, u8 reg, u16 *value);
int (*phy_write16)(struct b53_device *dev, int addr, u8 reg, u16 value);
};
enum {
BCM5325_DEVICE_ID = 0x25,
BCM5365_DEVICE_ID = 0x65,
BCM5395_DEVICE_ID = 0x95,
BCM5397_DEVICE_ID = 0x97,
BCM5398_DEVICE_ID = 0x98,
BCM53115_DEVICE_ID = 0x53115,
BCM53125_DEVICE_ID = 0x53125,
BCM53128_DEVICE_ID = 0x53128,
BCM63XX_DEVICE_ID = 0x6300,
BCM53010_DEVICE_ID = 0x53010,
BCM53011_DEVICE_ID = 0x53011,
BCM53012_DEVICE_ID = 0x53012,
BCM53018_DEVICE_ID = 0x53018,
BCM53019_DEVICE_ID = 0x53019,
};
#define B53_N_PORTS 9
#define B53_N_PORTS_25 6
struct b53_vlan {
unsigned int members:B53_N_PORTS;
unsigned int untag:B53_N_PORTS;
};
struct b53_port {
unsigned int pvid:12;
};
struct b53_device {
struct switch_dev sw_dev;
struct b53_platform_data *pdata;
struct mutex reg_mutex;
const struct b53_io_ops *ops;
/* chip specific data */
u32 chip_id;
u8 core_rev;
u8 vta_regs[3];
u8 duplex_reg;
u8 jumbo_pm_reg;
u8 jumbo_size_reg;
int reset_gpio;
/* used ports mask */
u16 enabled_ports;
/* connect specific data */
u8 current_page;
struct device *dev;
void *priv;
/* run time configuration */
unsigned enable_vlan:1;
unsigned enable_jumbo:1;
unsigned allow_vid_4095:1;
struct b53_port *ports;
struct b53_vlan *vlans;
char *buf;
};
#define b53_for_each_port(dev, i) \
for (i = 0; i < B53_N_PORTS; i++) \
if (dev->enabled_ports & BIT(i))
static inline int is5325(struct b53_device *dev)
{
return dev->chip_id == BCM5325_DEVICE_ID;
}
static inline int is5365(struct b53_device *dev)
{
#ifdef CONFIG_BCM47XX
return dev->chip_id == BCM5365_DEVICE_ID;
#else
return 0;
#endif
}
static inline int is5397_98(struct b53_device *dev)
{
return dev->chip_id == BCM5397_DEVICE_ID ||
dev->chip_id == BCM5398_DEVICE_ID;
}
static inline int is539x(struct b53_device *dev)
{
return dev->chip_id == BCM5395_DEVICE_ID ||
dev->chip_id == BCM5397_DEVICE_ID ||
dev->chip_id == BCM5398_DEVICE_ID;
}
static inline int is531x5(struct b53_device *dev)
{
return dev->chip_id == BCM53115_DEVICE_ID ||
dev->chip_id == BCM53125_DEVICE_ID ||
dev->chip_id == BCM53128_DEVICE_ID;
}
static inline int is63xx(struct b53_device *dev)
{
#ifdef CONFIG_BCM63XX
return dev->chip_id == BCM63XX_DEVICE_ID;
#else
return 0;
#endif
}
static inline int is5301x(struct b53_device *dev)
{
return dev->chip_id == BCM53010_DEVICE_ID ||
dev->chip_id == BCM53011_DEVICE_ID ||
dev->chip_id == BCM53012_DEVICE_ID ||
dev->chip_id == BCM53018_DEVICE_ID ||
dev->chip_id == BCM53019_DEVICE_ID;
}
#define B53_CPU_PORT_25 5
#define B53_CPU_PORT 8
static inline int is_cpu_port(struct b53_device *dev, int port)
{
return dev->sw_dev.cpu_port == port;
}
static inline int is_imp_port(struct b53_device *dev, int port)
{
if (is5325(dev) || is5365(dev))
return port == B53_CPU_PORT_25;
else
return port == B53_CPU_PORT;
}
static inline struct b53_device *sw_to_b53(struct switch_dev *sw)
{
return container_of(sw, struct b53_device, sw_dev);
}
struct b53_device *b53_swconfig_switch_alloc(struct device *base, struct b53_io_ops *ops,
void *priv);
int b53_swconfig_switch_detect(struct b53_device *dev);
int b53_swconfig_switch_register(struct b53_device *dev);
static inline void b53_switch_remove(struct b53_device *dev)
{
unregister_switch(&dev->sw_dev);
}
static inline int b53_read8(struct b53_device *dev, u8 page, u8 reg, u8 *val)
{
int ret;
mutex_lock(&dev->reg_mutex);
ret = dev->ops->read8(dev, page, reg, val);
mutex_unlock(&dev->reg_mutex);
return ret;
}
static inline int b53_read16(struct b53_device *dev, u8 page, u8 reg, u16 *val)
{
int ret;
mutex_lock(&dev->reg_mutex);
ret = dev->ops->read16(dev, page, reg, val);
mutex_unlock(&dev->reg_mutex);
return ret;
}
static inline int b53_read32(struct b53_device *dev, u8 page, u8 reg, u32 *val)
{
int ret;
mutex_lock(&dev->reg_mutex);
ret = dev->ops->read32(dev, page, reg, val);
mutex_unlock(&dev->reg_mutex);
return ret;
}
static inline int b53_read48(struct b53_device *dev, u8 page, u8 reg, u64 *val)
{
int ret;
mutex_lock(&dev->reg_mutex);
ret = dev->ops->read48(dev, page, reg, val);
mutex_unlock(&dev->reg_mutex);
return ret;
}
static inline int b53_read64(struct b53_device *dev, u8 page, u8 reg, u64 *val)
{
int ret;
mutex_lock(&dev->reg_mutex);
ret = dev->ops->read64(dev, page, reg, val);
mutex_unlock(&dev->reg_mutex);
return ret;
}
static inline int b53_write8(struct b53_device *dev, u8 page, u8 reg, u8 value)
{
int ret;
mutex_lock(&dev->reg_mutex);
ret = dev->ops->write8(dev, page, reg, value);
mutex_unlock(&dev->reg_mutex);
return ret;
}
static inline int b53_write16(struct b53_device *dev, u8 page, u8 reg,
u16 value)
{
int ret;
mutex_lock(&dev->reg_mutex);
ret = dev->ops->write16(dev, page, reg, value);
mutex_unlock(&dev->reg_mutex);
return ret;
}
static inline int b53_write32(struct b53_device *dev, u8 page, u8 reg,
u32 value)
{
int ret;
mutex_lock(&dev->reg_mutex);
ret = dev->ops->write32(dev, page, reg, value);
mutex_unlock(&dev->reg_mutex);
return ret;
}
static inline int b53_write48(struct b53_device *dev, u8 page, u8 reg,
u64 value)
{
int ret;
mutex_lock(&dev->reg_mutex);
ret = dev->ops->write48(dev, page, reg, value);
mutex_unlock(&dev->reg_mutex);
return ret;
}
static inline int b53_write64(struct b53_device *dev, u8 page, u8 reg,
u64 value)
{
int ret;
mutex_lock(&dev->reg_mutex);
ret = dev->ops->write64(dev, page, reg, value);
mutex_unlock(&dev->reg_mutex);
return ret;
}
#ifdef CONFIG_BCM47XX
#include <bcm47xx_board.h>
#endif
#include <linux/version.h>
#include <linux/bcm47xx_nvram.h>
static inline int b53_switch_get_reset_gpio(struct b53_device *dev)
{
#ifdef CONFIG_BCM47XX
enum bcm47xx_board board = bcm47xx_board_get();
switch (board) {
case BCM47XX_BOARD_LINKSYS_WRT300NV11:
case BCM47XX_BOARD_LINKSYS_WRT310NV1:
return 8;
default:
break;
}
#endif
return bcm47xx_nvram_gpio_pin("robo_reset");
}
#endif

View file

@ -0,0 +1,348 @@
/*
* B53 register definitions
*
* Copyright (C) 2004 Broadcom Corporation
* Copyright (C) 2011-2013 Jonas Gorski <jogo@openwrt.org>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#ifndef __B53_REGS_H
#define __B53_REGS_H
/* Management Port (SMP) Page offsets */
#define B53_CTRL_PAGE 0x00 /* Control */
#define B53_STAT_PAGE 0x01 /* Status */
#define B53_MGMT_PAGE 0x02 /* Management Mode */
#define B53_MIB_AC_PAGE 0x03 /* MIB Autocast */
#define B53_ARLCTRL_PAGE 0x04 /* ARL Control */
#define B53_ARLIO_PAGE 0x05 /* ARL Access */
#define B53_FRAMEBUF_PAGE 0x06 /* Management frame access */
#define B53_MEM_ACCESS_PAGE 0x08 /* Memory access */
/* PHY Registers */
#define B53_PORT_MII_PAGE(i) (0x10 + (i)) /* Port i MII Registers */
#define B53_IM_PORT_PAGE 0x18 /* Inverse MII Port (to EMAC) */
#define B53_ALL_PORT_PAGE 0x19 /* All ports MII (broadcast) */
/* MIB registers */
#define B53_MIB_PAGE(i) (0x20 + (i))
/* Quality of Service (QoS) Registers */
#define B53_QOS_PAGE 0x30
/* Port VLAN Page */
#define B53_PVLAN_PAGE 0x31
/* VLAN Registers */
#define B53_VLAN_PAGE 0x34
/* Jumbo Frame Registers */
#define B53_JUMBO_PAGE 0x40
/* CFP Configuration Registers Page */
#define B53_CFP_PAGE 0xa1
/*************************************************************************
* Control Page registers
*************************************************************************/
/* Port Control Register (8 bit) */
#define B53_PORT_CTRL(i) (0x00 + (i))
#define PORT_CTRL_RX_DISABLE BIT(0)
#define PORT_CTRL_TX_DISABLE BIT(1)
#define PORT_CTRL_RX_BCST_EN BIT(2) /* Broadcast RX (P8 only) */
#define PORT_CTRL_RX_MCST_EN BIT(3) /* Multicast RX (P8 only) */
#define PORT_CTRL_RX_UCST_EN BIT(4) /* Unicast RX (P8 only) */
#define PORT_CTRL_STP_STATE_S 5
#define PORT_CTRL_STP_STATE_MASK (0x7 << PORT_CTRL_STP_STATE_S)
/* SMP Control Register (8 bit) */
#define B53_SMP_CTRL 0x0a
/* Switch Mode Control Register (8 bit) */
#define B53_SWITCH_MODE 0x0b
#define SM_SW_FWD_MODE BIT(0) /* 1 = Managed Mode */
#define SM_SW_FWD_EN BIT(1) /* Forwarding Enable */
/* IMP Port state override register (8 bit) */
#define B53_PORT_OVERRIDE_CTRL 0x0e
#define PORT_OVERRIDE_LINK BIT(0)
#define PORT_OVERRIDE_FULL_DUPLEX BIT(1) /* 0 = Half Duplex */
#define PORT_OVERRIDE_SPEED_S 2
#define PORT_OVERRIDE_SPEED_10M (0 << PORT_OVERRIDE_SPEED_S)
#define PORT_OVERRIDE_SPEED_100M (1 << PORT_OVERRIDE_SPEED_S)
#define PORT_OVERRIDE_SPEED_1000M (2 << PORT_OVERRIDE_SPEED_S)
#define PORT_OVERRIDE_RV_MII_25 BIT(4) /* BCM5325 only */
#define PORT_OVERRIDE_RX_FLOW BIT(4)
#define PORT_OVERRIDE_TX_FLOW BIT(5)
#define PORT_OVERRIDE_SPEED_2000M BIT(6) /* BCM5301X only, requires setting 1000M */
#define PORT_OVERRIDE_EN BIT(7) /* Use the register contents */
/* Power-down mode control */
#define B53_PD_MODE_CTRL_25 0x0f
/* IP Multicast control (8 bit) */
#define B53_IP_MULTICAST_CTRL 0x21
#define B53_IPMC_FWD_EN BIT(1)
#define B53_UC_FWD_EN BIT(6)
#define B53_MC_FWD_EN BIT(7)
/* (16 bit) */
#define B53_UC_FLOOD_MASK 0x32
#define B53_MC_FLOOD_MASK 0x34
#define B53_IPMC_FLOOD_MASK 0x36
/*
* Override Ports 0-7 State on devices with xMII interfaces (8 bit)
*
* For port 8 still use B53_PORT_OVERRIDE_CTRL
* Please note that not all ports are available on every hardware, e.g. BCM5301X
* don't include overriding port 6, BCM63xx also have some limitations.
*/
#define B53_GMII_PORT_OVERRIDE_CTRL(i) (0x58 + (i))
#define GMII_PO_LINK BIT(0)
#define GMII_PO_FULL_DUPLEX BIT(1) /* 0 = Half Duplex */
#define GMII_PO_SPEED_S 2
#define GMII_PO_SPEED_10M (0 << GMII_PO_SPEED_S)
#define GMII_PO_SPEED_100M (1 << GMII_PO_SPEED_S)
#define GMII_PO_SPEED_1000M (2 << GMII_PO_SPEED_S)
#define GMII_PO_RX_FLOW BIT(4)
#define GMII_PO_TX_FLOW BIT(5)
#define GMII_PO_EN BIT(6) /* Use the register contents */
#define GMII_PO_SPEED_2000M BIT(7) /* BCM5301X only, requires setting 1000M */
/* Software reset register (8 bit) */
#define B53_SOFTRESET 0x79
/* Fast Aging Control register (8 bit) */
#define B53_FAST_AGE_CTRL 0x88
#define FAST_AGE_STATIC BIT(0)
#define FAST_AGE_DYNAMIC BIT(1)
#define FAST_AGE_PORT BIT(2)
#define FAST_AGE_VLAN BIT(3)
#define FAST_AGE_STP BIT(4)
#define FAST_AGE_MC BIT(5)
#define FAST_AGE_DONE BIT(7)
/*************************************************************************
* Status Page registers
*************************************************************************/
/* Link Status Summary Register (16bit) */
#define B53_LINK_STAT 0x00
/* Link Status Change Register (16 bit) */
#define B53_LINK_STAT_CHANGE 0x02
/* Port Speed Summary Register (16 bit for FE, 32 bit for GE) */
#define B53_SPEED_STAT 0x04
#define SPEED_PORT_FE(reg, port) (((reg) >> (port)) & 1)
#define SPEED_PORT_GE(reg, port) (((reg) >> 2 * (port)) & 3)
#define SPEED_STAT_10M 0
#define SPEED_STAT_100M 1
#define SPEED_STAT_1000M 2
/* Duplex Status Summary (16 bit) */
#define B53_DUPLEX_STAT_FE 0x06
#define B53_DUPLEX_STAT_GE 0x08
#define B53_DUPLEX_STAT_63XX 0x0c
/* Revision ID register for BCM5325 */
#define B53_REV_ID_25 0x50
/* Strap Value (48 bit) */
#define B53_STRAP_VALUE 0x70
#define SV_GMII_CTRL_115 BIT(27)
/*************************************************************************
* Management Mode Page Registers
*************************************************************************/
/* Global Management Config Register (8 bit) */
#define B53_GLOBAL_CONFIG 0x00
#define GC_RESET_MIB 0x01
#define GC_RX_BPDU_EN 0x02
#define GC_MIB_AC_HDR_EN 0x10
#define GC_MIB_AC_EN 0x20
#define GC_FRM_MGMT_PORT_M 0xC0
#define GC_FRM_MGMT_PORT_04 0x00
#define GC_FRM_MGMT_PORT_MII 0x80
/* Broadcom Header control register (8 bit) */
#define B53_BRCM_HDR 0x03
#define BRCM_HDR_P8_EN BIT(0) /* Enable tagging on port 8 */
#define BRCM_HDR_P5_EN BIT(1) /* Enable tagging on port 5 */
/* Device ID register (8 or 32 bit) */
#define B53_DEVICE_ID 0x30
/* Revision ID register (8 bit) */
#define B53_REV_ID 0x40
/*************************************************************************
* ARL Access Page Registers
*************************************************************************/
/* VLAN Table Access Register (8 bit) */
#define B53_VT_ACCESS 0x80
#define B53_VT_ACCESS_9798 0x60 /* for BCM5397/BCM5398 */
#define B53_VT_ACCESS_63XX 0x60 /* for BCM6328/62/68 */
#define VTA_CMD_WRITE 0
#define VTA_CMD_READ 1
#define VTA_CMD_CLEAR 2
#define VTA_START_CMD BIT(7)
/* VLAN Table Index Register (16 bit) */
#define B53_VT_INDEX 0x81
#define B53_VT_INDEX_9798 0x61
#define B53_VT_INDEX_63XX 0x62
/* VLAN Table Entry Register (32 bit) */
#define B53_VT_ENTRY 0x83
#define B53_VT_ENTRY_9798 0x63
#define B53_VT_ENTRY_63XX 0x64
#define VTE_MEMBERS 0x1ff
#define VTE_UNTAG_S 9
#define VTE_UNTAG (0x1ff << 9)
/*************************************************************************
* Port VLAN Registers
*************************************************************************/
/* Port VLAN mask (16 bit) IMP port is always 8, also on 5325 & co */
#define B53_PVLAN_PORT_MASK(i) ((i) * 2)
/*************************************************************************
* 802.1Q Page Registers
*************************************************************************/
/* Global QoS Control (8 bit) */
#define B53_QOS_GLOBAL_CTL 0x00
/* Enable 802.1Q for individual Ports (16 bit) */
#define B53_802_1P_EN 0x04
/*************************************************************************
* VLAN Page Registers
*************************************************************************/
/* VLAN Control 0 (8 bit) */
#define B53_VLAN_CTRL0 0x00
#define VC0_8021PF_CTRL_MASK 0x3
#define VC0_8021PF_CTRL_NONE 0x0
#define VC0_8021PF_CTRL_CHANGE_PRI 0x1
#define VC0_8021PF_CTRL_CHANGE_VID 0x2
#define VC0_8021PF_CTRL_CHANGE_BOTH 0x3
#define VC0_8021QF_CTRL_MASK 0xc
#define VC0_8021QF_CTRL_CHANGE_PRI 0x1
#define VC0_8021QF_CTRL_CHANGE_VID 0x2
#define VC0_8021QF_CTRL_CHANGE_BOTH 0x3
#define VC0_RESERVED_1 BIT(1)
#define VC0_DROP_VID_MISS BIT(4)
#define VC0_VID_HASH_VID BIT(5)
#define VC0_VID_CHK_EN BIT(6) /* Use VID,DA or VID,SA */
#define VC0_VLAN_EN BIT(7) /* 802.1Q VLAN Enabled */
/* VLAN Control 1 (8 bit) */
#define B53_VLAN_CTRL1 0x01
#define VC1_RX_MCST_TAG_EN BIT(1)
#define VC1_RX_MCST_FWD_EN BIT(2)
#define VC1_RX_MCST_UNTAG_EN BIT(3)
/* VLAN Control 2 (8 bit) */
#define B53_VLAN_CTRL2 0x02
/* VLAN Control 3 (8 bit when BCM5325, 16 bit else) */
#define B53_VLAN_CTRL3 0x03
#define B53_VLAN_CTRL3_63XX 0x04
#define VC3_MAXSIZE_1532 BIT(6) /* 5325 only */
#define VC3_HIGH_8BIT_EN BIT(7) /* 5325 only */
/* VLAN Control 4 (8 bit) */
#define B53_VLAN_CTRL4 0x05
#define B53_VLAN_CTRL4_25 0x04
#define B53_VLAN_CTRL4_63XX 0x06
#define VC4_ING_VID_CHECK_S 6
#define VC4_ING_VID_CHECK_MASK (0x3 << VC4_ING_VID_CHECK_S)
#define VC4_ING_VID_VIO_FWD 0 /* forward, but do not learn */
#define VC4_ING_VID_VIO_DROP 1 /* drop VID violations */
#define VC4_NO_ING_VID_CHK 2 /* do not check */
#define VC4_ING_VID_VIO_TO_IMP 3 /* redirect to MII port */
/* VLAN Control 5 (8 bit) */
#define B53_VLAN_CTRL5 0x06
#define B53_VLAN_CTRL5_25 0x05
#define B53_VLAN_CTRL5_63XX 0x07
#define VC5_VID_FFF_EN BIT(2)
#define VC5_DROP_VTABLE_MISS BIT(3)
/* VLAN Control 6 (8 bit) */
#define B53_VLAN_CTRL6 0x07
#define B53_VLAN_CTRL6_63XX 0x08
/* VLAN Table Access Register (16 bit) */
#define B53_VLAN_TABLE_ACCESS_25 0x06 /* BCM5325E/5350 */
#define B53_VLAN_TABLE_ACCESS_65 0x08 /* BCM5365 */
#define VTA_VID_LOW_MASK_25 0xf
#define VTA_VID_LOW_MASK_65 0xff
#define VTA_VID_HIGH_S_25 4
#define VTA_VID_HIGH_S_65 8
#define VTA_VID_HIGH_MASK_25 (0xff << VTA_VID_HIGH_S_25E)
#define VTA_VID_HIGH_MASK_65 (0xf << VTA_VID_HIGH_S_65)
#define VTA_RW_STATE BIT(12)
#define VTA_RW_STATE_RD 0
#define VTA_RW_STATE_WR BIT(12)
#define VTA_RW_OP_EN BIT(13)
/* VLAN Read/Write Registers for (16/32 bit) */
#define B53_VLAN_WRITE_25 0x08
#define B53_VLAN_WRITE_65 0x0a
#define B53_VLAN_READ 0x0c
#define VA_MEMBER_MASK 0x3f
#define VA_UNTAG_S_25 6
#define VA_UNTAG_MASK_25 0x3f
#define VA_UNTAG_S_65 7
#define VA_UNTAG_MASK_65 0x1f
#define VA_VID_HIGH_S 12
#define VA_VID_HIGH_MASK (0xffff << VA_VID_HIGH_S)
#define VA_VALID_25 BIT(20)
#define VA_VALID_25_R4 BIT(24)
#define VA_VALID_65 BIT(14)
/* VLAN Port Default Tag (16 bit) */
#define B53_VLAN_PORT_DEF_TAG(i) (0x10 + 2 * (i))
/*************************************************************************
* Jumbo Frame Page Registers
*************************************************************************/
/* Jumbo Enable Port Mask (bit i == port i enabled) (32 bit) */
#define B53_JUMBO_PORT_MASK 0x01
#define B53_JUMBO_PORT_MASK_63XX 0x04
#define JPM_10_100_JUMBO_EN BIT(24) /* GigE always enabled */
/* Good Frame Max Size without 802.1Q TAG (16 bit) */
#define B53_JUMBO_MAX_SIZE 0x05
#define B53_JUMBO_MAX_SIZE_63XX 0x08
#define JMS_MIN_SIZE 1518
#define JMS_MAX_SIZE 9724
/*************************************************************************
* CFP Configuration Page Registers
*************************************************************************/
/* CFP Control Register with ports map (8 bit) */
#define B53_CFP_CTRL 0x00
#endif /* !__B53_REGS_H */

View file

@ -0,0 +1,344 @@
/*
* B53 register access through SPI
*
* Copyright (C) 2011-2013 Jonas Gorski <jogo@openwrt.org>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <asm/unaligned.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/spi/spi.h>
#include <linux/of.h>
#include <linux/platform_data/b53.h>
#include "b53_priv.h"
#define B53_SPI_DATA 0xf0
#define B53_SPI_STATUS 0xfe
#define B53_SPI_CMD_SPIF BIT(7)
#define B53_SPI_CMD_RACK BIT(5)
#define B53_SPI_CMD_READ 0x00
#define B53_SPI_CMD_WRITE 0x01
#define B53_SPI_CMD_NORMAL 0x60
#define B53_SPI_CMD_FAST 0x10
#define B53_SPI_PAGE_SELECT 0xff
static inline int b53_spi_read_reg(struct spi_device *spi, u8 reg, u8 *val,
unsigned len)
{
u8 txbuf[2];
txbuf[0] = B53_SPI_CMD_NORMAL | B53_SPI_CMD_READ;
txbuf[1] = reg;
return spi_write_then_read(spi, txbuf, 2, val, len);
}
static inline int b53_spi_clear_status(struct spi_device *spi)
{
unsigned int i;
u8 rxbuf;
int ret;
for (i = 0; i < 10; i++) {
ret = b53_spi_read_reg(spi, B53_SPI_STATUS, &rxbuf, 1);
if (ret)
return ret;
if (!(rxbuf & B53_SPI_CMD_SPIF))
break;
mdelay(1);
}
if (i == 10)
return -EIO;
return 0;
}
static inline int b53_spi_set_page(struct spi_device *spi, u8 page)
{
u8 txbuf[3];
txbuf[0] = B53_SPI_CMD_NORMAL | B53_SPI_CMD_WRITE;
txbuf[1] = B53_SPI_PAGE_SELECT;
txbuf[2] = page;
return spi_write(spi, txbuf, sizeof(txbuf));
}
static inline int b53_prepare_reg_access(struct spi_device *spi, u8 page)
{
int ret = b53_spi_clear_status(spi);
if (ret)
return ret;
return b53_spi_set_page(spi, page);
}
static int b53_spi_prepare_reg_read(struct spi_device *spi, u8 reg)
{
u8 rxbuf;
int retry_count;
int ret;
ret = b53_spi_read_reg(spi, reg, &rxbuf, 1);
if (ret)
return ret;
for (retry_count = 0; retry_count < 10; retry_count++) {
ret = b53_spi_read_reg(spi, B53_SPI_STATUS, &rxbuf, 1);
if (ret)
return ret;
if (rxbuf & B53_SPI_CMD_RACK)
break;
mdelay(1);
}
if (retry_count == 10)
return -EIO;
return 0;
}
static int b53_spi_read(struct b53_device *dev, u8 page, u8 reg, u8 *data,
unsigned len)
{
struct spi_device *spi = dev->priv;
int ret;
ret = b53_prepare_reg_access(spi, page);
if (ret)
return ret;
ret = b53_spi_prepare_reg_read(spi, reg);
if (ret)
return ret;
return b53_spi_read_reg(spi, B53_SPI_DATA, data, len);
}
static int b53_spi_read8(struct b53_device *dev, u8 page, u8 reg, u8 *val)
{
return b53_spi_read(dev, page, reg, val, 1);
}
static int b53_spi_read16(struct b53_device *dev, u8 page, u8 reg, u16 *val)
{
int ret = b53_spi_read(dev, page, reg, (u8 *)val, 2);
if (!ret)
*val = le16_to_cpu(*val);
return ret;
}
static int b53_spi_read32(struct b53_device *dev, u8 page, u8 reg, u32 *val)
{
int ret = b53_spi_read(dev, page, reg, (u8 *)val, 4);
if (!ret)
*val = le32_to_cpu(*val);
return ret;
}
static int b53_spi_read48(struct b53_device *dev, u8 page, u8 reg, u64 *val)
{
int ret;
*val = 0;
ret = b53_spi_read(dev, page, reg, (u8 *)val, 6);
if (!ret)
*val = le64_to_cpu(*val);
return ret;
}
static int b53_spi_read64(struct b53_device *dev, u8 page, u8 reg, u64 *val)
{
int ret = b53_spi_read(dev, page, reg, (u8 *)val, 8);
if (!ret)
*val = le64_to_cpu(*val);
return ret;
}
static int b53_spi_write8(struct b53_device *dev, u8 page, u8 reg, u8 value)
{
struct spi_device *spi = dev->priv;
int ret;
u8 txbuf[3];
ret = b53_prepare_reg_access(spi, page);
if (ret)
return ret;
txbuf[0] = B53_SPI_CMD_NORMAL | B53_SPI_CMD_WRITE;
txbuf[1] = reg;
txbuf[2] = value;
return spi_write(spi, txbuf, sizeof(txbuf));
}
static int b53_spi_write16(struct b53_device *dev, u8 page, u8 reg, u16 value)
{
struct spi_device *spi = dev->priv;
int ret;
u8 txbuf[4];
ret = b53_prepare_reg_access(spi, page);
if (ret)
return ret;
txbuf[0] = B53_SPI_CMD_NORMAL | B53_SPI_CMD_WRITE;
txbuf[1] = reg;
put_unaligned_le16(value, &txbuf[2]);
return spi_write(spi, txbuf, sizeof(txbuf));
}
static int b53_spi_write32(struct b53_device *dev, u8 page, u8 reg, u32 value)
{
struct spi_device *spi = dev->priv;
int ret;
u8 txbuf[6];
ret = b53_prepare_reg_access(spi, page);
if (ret)
return ret;
txbuf[0] = B53_SPI_CMD_NORMAL | B53_SPI_CMD_WRITE;
txbuf[1] = reg;
put_unaligned_le32(value, &txbuf[2]);
return spi_write(spi, txbuf, sizeof(txbuf));
}
static int b53_spi_write48(struct b53_device *dev, u8 page, u8 reg, u64 value)
{
struct spi_device *spi = dev->priv;
int ret;
u8 txbuf[10];
ret = b53_prepare_reg_access(spi, page);
if (ret)
return ret;
txbuf[0] = B53_SPI_CMD_NORMAL | B53_SPI_CMD_WRITE;
txbuf[1] = reg;
put_unaligned_le64(value, &txbuf[2]);
return spi_write(spi, txbuf, sizeof(txbuf) - 2);
}
static int b53_spi_write64(struct b53_device *dev, u8 page, u8 reg, u64 value)
{
struct spi_device *spi = dev->priv;
int ret;
u8 txbuf[10];
ret = b53_prepare_reg_access(spi, page);
if (ret)
return ret;
txbuf[0] = B53_SPI_CMD_NORMAL | B53_SPI_CMD_WRITE;
txbuf[1] = reg;
put_unaligned_le64(value, &txbuf[2]);
return spi_write(spi, txbuf, sizeof(txbuf));
}
static struct b53_io_ops b53_spi_ops = {
.read8 = b53_spi_read8,
.read16 = b53_spi_read16,
.read32 = b53_spi_read32,
.read48 = b53_spi_read48,
.read64 = b53_spi_read64,
.write8 = b53_spi_write8,
.write16 = b53_spi_write16,
.write32 = b53_spi_write32,
.write48 = b53_spi_write48,
.write64 = b53_spi_write64,
};
static int b53_spi_probe(struct spi_device *spi)
{
struct b53_device *dev;
int ret;
dev = b53_swconfig_switch_alloc(&spi->dev, &b53_spi_ops, spi);
if (!dev)
return -ENOMEM;
if (spi->dev.platform_data)
dev->pdata = spi->dev.platform_data;
ret = b53_swconfig_switch_register(dev);
if (ret)
return ret;
spi_set_drvdata(spi, dev);
return 0;
}
static int b53_spi_remove(struct spi_device *spi)
{
struct b53_device *dev = spi_get_drvdata(spi);
if (dev)
b53_switch_remove(dev);
return 0;
}
static const struct of_device_id b53_of_match[] = {
{ .compatible = "brcm,bcm5325" },
{ .compatible = "brcm,bcm53115" },
{ .compatible = "brcm,bcm53125" },
{ .compatible = "brcm,bcm53128" },
{ .compatible = "brcm,bcm5365" },
{ .compatible = "brcm,bcm5395" },
{ .compatible = "brcm,bcm5397" },
{ .compatible = "brcm,bcm5398" },
{ /* sentinel */ },
};
static struct spi_driver b53_spi_driver = {
.driver = {
.name = "b53-switch",
.bus = &spi_bus_type,
.owner = THIS_MODULE,
.of_match_table = b53_of_match,
},
.probe = b53_spi_probe,
.remove = b53_spi_remove,
};
module_spi_driver(b53_spi_driver);
MODULE_AUTHOR("Jonas Gorski <jogo@openwrt.org>");
MODULE_DESCRIPTION("B53 SPI access driver");
MODULE_LICENSE("Dual BSD/GPL");

View file

@ -0,0 +1,385 @@
/*
* B53 register access through Switch Register Access Bridge Registers
*
* Copyright (C) 2013 Hauke Mehrtens <hauke@hauke-m.de>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/platform_data/b53.h>
#include <linux/version.h>
#include "b53_priv.h"
/* command and status register of the SRAB */
#define B53_SRAB_CMDSTAT 0x2c
#define B53_SRAB_CMDSTAT_RST BIT(2)
#define B53_SRAB_CMDSTAT_WRITE BIT(1)
#define B53_SRAB_CMDSTAT_GORDYN BIT(0)
#define B53_SRAB_CMDSTAT_PAGE 24
#define B53_SRAB_CMDSTAT_REG 16
/* high order word of write data to switch registe */
#define B53_SRAB_WD_H 0x30
/* low order word of write data to switch registe */
#define B53_SRAB_WD_L 0x34
/* high order word of read data from switch register */
#define B53_SRAB_RD_H 0x38
/* low order word of read data from switch register */
#define B53_SRAB_RD_L 0x3c
/* command and status register of the SRAB */
#define B53_SRAB_CTRLS 0x40
#define B53_SRAB_CTRLS_RCAREQ BIT(3)
#define B53_SRAB_CTRLS_RCAGNT BIT(4)
#define B53_SRAB_CTRLS_SW_INIT_DONE BIT(6)
/* the register captures interrupt pulses from the switch */
#define B53_SRAB_INTR 0x44
static int b53_srab_request_grant(struct b53_device *dev)
{
u8 __iomem *regs = dev->priv;
u32 ctrls;
int i;
ctrls = readl(regs + B53_SRAB_CTRLS);
ctrls |= B53_SRAB_CTRLS_RCAREQ;
writel(ctrls, regs + B53_SRAB_CTRLS);
for (i = 0; i < 20; i++) {
ctrls = readl(regs + B53_SRAB_CTRLS);
if (ctrls & B53_SRAB_CTRLS_RCAGNT)
break;
usleep_range(10, 100);
}
if (WARN_ON(i == 5))
return -EIO;
return 0;
}
static void b53_srab_release_grant(struct b53_device *dev)
{
u8 __iomem *regs = dev->priv;
u32 ctrls;
ctrls = readl(regs + B53_SRAB_CTRLS);
ctrls &= ~B53_SRAB_CTRLS_RCAREQ;
writel(ctrls, regs + B53_SRAB_CTRLS);
}
static int b53_srab_op(struct b53_device *dev, u8 page, u8 reg, u32 op)
{
int i;
u32 cmdstat;
u8 __iomem *regs = dev->priv;
/* set register address */
cmdstat = (page << B53_SRAB_CMDSTAT_PAGE) |
(reg << B53_SRAB_CMDSTAT_REG) |
B53_SRAB_CMDSTAT_GORDYN |
op;
writel(cmdstat, regs + B53_SRAB_CMDSTAT);
/* check if operation completed */
for (i = 0; i < 5; ++i) {
cmdstat = readl(regs + B53_SRAB_CMDSTAT);
if (!(cmdstat & B53_SRAB_CMDSTAT_GORDYN))
break;
usleep_range(10, 100);
}
if (WARN_ON(i == 5))
return -EIO;
return 0;
}
static int b53_srab_read8(struct b53_device *dev, u8 page, u8 reg, u8 *val)
{
u8 __iomem *regs = dev->priv;
int ret = 0;
ret = b53_srab_request_grant(dev);
if (ret)
goto err;
ret = b53_srab_op(dev, page, reg, 0);
if (ret)
goto err;
*val = readl(regs + B53_SRAB_RD_L) & 0xff;
err:
b53_srab_release_grant(dev);
return ret;
}
static int b53_srab_read16(struct b53_device *dev, u8 page, u8 reg, u16 *val)
{
u8 __iomem *regs = dev->priv;
int ret = 0;
ret = b53_srab_request_grant(dev);
if (ret)
goto err;
ret = b53_srab_op(dev, page, reg, 0);
if (ret)
goto err;
*val = readl(regs + B53_SRAB_RD_L) & 0xffff;
err:
b53_srab_release_grant(dev);
return ret;
}
static int b53_srab_read32(struct b53_device *dev, u8 page, u8 reg, u32 *val)
{
u8 __iomem *regs = dev->priv;
int ret = 0;
ret = b53_srab_request_grant(dev);
if (ret)
goto err;
ret = b53_srab_op(dev, page, reg, 0);
if (ret)
goto err;
*val = readl(regs + B53_SRAB_RD_L);
err:
b53_srab_release_grant(dev);
return ret;
}
static int b53_srab_read48(struct b53_device *dev, u8 page, u8 reg, u64 *val)
{
u8 __iomem *regs = dev->priv;
int ret = 0;
ret = b53_srab_request_grant(dev);
if (ret)
goto err;
ret = b53_srab_op(dev, page, reg, 0);
if (ret)
goto err;
*val = readl(regs + B53_SRAB_RD_L);
*val += ((u64)readl(regs + B53_SRAB_RD_H) & 0xffff) << 32;
err:
b53_srab_release_grant(dev);
return ret;
}
static int b53_srab_read64(struct b53_device *dev, u8 page, u8 reg, u64 *val)
{
u8 __iomem *regs = dev->priv;
int ret = 0;
ret = b53_srab_request_grant(dev);
if (ret)
goto err;
ret = b53_srab_op(dev, page, reg, 0);
if (ret)
goto err;
*val = readl(regs + B53_SRAB_RD_L);
*val += (u64)readl(regs + B53_SRAB_RD_H) << 32;
err:
b53_srab_release_grant(dev);
return ret;
}
static int b53_srab_write8(struct b53_device *dev, u8 page, u8 reg, u8 value)
{
u8 __iomem *regs = dev->priv;
int ret = 0;
ret = b53_srab_request_grant(dev);
if (ret)
goto err;
writel(value, regs + B53_SRAB_WD_L);
ret = b53_srab_op(dev, page, reg, B53_SRAB_CMDSTAT_WRITE);
err:
b53_srab_release_grant(dev);
return ret;
}
static int b53_srab_write16(struct b53_device *dev, u8 page, u8 reg,
u16 value)
{
u8 __iomem *regs = dev->priv;
int ret = 0;
ret = b53_srab_request_grant(dev);
if (ret)
goto err;
writel(value, regs + B53_SRAB_WD_L);
ret = b53_srab_op(dev, page, reg, B53_SRAB_CMDSTAT_WRITE);
err:
b53_srab_release_grant(dev);
return ret;
}
static int b53_srab_write32(struct b53_device *dev, u8 page, u8 reg,
u32 value)
{
u8 __iomem *regs = dev->priv;
int ret = 0;
ret = b53_srab_request_grant(dev);
if (ret)
goto err;
writel(value, regs + B53_SRAB_WD_L);
ret = b53_srab_op(dev, page, reg, B53_SRAB_CMDSTAT_WRITE);
err:
b53_srab_release_grant(dev);
return ret;
}
static int b53_srab_write48(struct b53_device *dev, u8 page, u8 reg,
u64 value)
{
u8 __iomem *regs = dev->priv;
int ret = 0;
ret = b53_srab_request_grant(dev);
if (ret)
goto err;
writel((u32)value, regs + B53_SRAB_WD_L);
writel((u16)(value >> 32), regs + B53_SRAB_WD_H);
ret = b53_srab_op(dev, page, reg, B53_SRAB_CMDSTAT_WRITE);
err:
b53_srab_release_grant(dev);
return ret;
}
static int b53_srab_write64(struct b53_device *dev, u8 page, u8 reg,
u64 value)
{
u8 __iomem *regs = dev->priv;
int ret = 0;
ret = b53_srab_request_grant(dev);
if (ret)
goto err;
writel((u32)value, regs + B53_SRAB_WD_L);
writel((u32)(value >> 32), regs + B53_SRAB_WD_H);
ret = b53_srab_op(dev, page, reg, B53_SRAB_CMDSTAT_WRITE);
err:
b53_srab_release_grant(dev);
return ret;
}
static struct b53_io_ops b53_srab_ops = {
.read8 = b53_srab_read8,
.read16 = b53_srab_read16,
.read32 = b53_srab_read32,
.read48 = b53_srab_read48,
.read64 = b53_srab_read64,
.write8 = b53_srab_write8,
.write16 = b53_srab_write16,
.write32 = b53_srab_write32,
.write48 = b53_srab_write48,
.write64 = b53_srab_write64,
};
static int b53_srab_probe(struct platform_device *pdev)
{
struct b53_platform_data *pdata = pdev->dev.platform_data;
struct b53_device *dev;
if (!pdata)
return -EINVAL;
dev = b53_swconfig_switch_alloc(&pdev->dev, &b53_srab_ops, pdata->regs);
if (!dev)
return -ENOMEM;
if (pdata)
dev->pdata = pdata;
platform_set_drvdata(pdev, dev);
return b53_swconfig_switch_register(dev);
}
#if LINUX_VERSION_CODE < KERNEL_VERSION(6,11,0)
static int b53_srab_remove(struct platform_device *pdev)
#else
static void b53_srab_remove(struct platform_device *pdev)
#endif
{
struct b53_device *dev = platform_get_drvdata(pdev);
if (dev)
b53_switch_remove(dev);
#if LINUX_VERSION_CODE < KERNEL_VERSION(6,11,0)
return 0;
#endif
}
static struct platform_driver b53_srab_driver = {
.probe = b53_srab_probe,
.remove = b53_srab_remove,
.driver = {
.name = "b53-srab-switch",
},
};
module_platform_driver(b53_srab_driver);
MODULE_AUTHOR("Hauke Mehrtens <hauke@hauke-m.de>");
MODULE_DESCRIPTION("B53 Switch Register Access Bridge Registers (SRAB) access driver");
MODULE_LICENSE("Dual BSD/GPL");

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,443 @@
/*
* Lantiq PSB6970 (Tantos) Switch driver
*
* Copyright (c) 2009,2010 Team Embedded.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License v2 as published by the
* Free Software Foundation.
*
* The switch programming done in this driver follows the
* "Ethernet Traffic Separation using VLAN" Application Note as
* published by Lantiq.
*/
#include <linux/module.h>
#include <linux/netdevice.h>
#include <linux/switch.h>
#include <linux/phy.h>
#include <linux/version.h>
#define PSB6970_MAX_VLANS 16
#define PSB6970_NUM_PORTS 7
#define PSB6970_DEFAULT_PORT_CPU 6
#define PSB6970_IS_CPU_PORT(x) ((x) > 4)
#define PHYADDR(_reg) ((_reg >> 5) & 0xff), (_reg & 0x1f)
/* --- Identification --- */
#define PSB6970_CI0 0x0100
#define PSB6970_CI0_MASK 0x000f
#define PSB6970_CI1 0x0101
#define PSB6970_CI1_VAL 0x2599
#define PSB6970_CI1_MASK 0xffff
/* --- VLAN filter table --- */
#define PSB6970_VFxL(i) ((i)*2+0x10) /* VLAN Filter Low */
#define PSB6970_VFxL_VV (1 << 15) /* VLAN_Valid */
#define PSB6970_VFxH(i) ((i)*2+0x11) /* VLAN Filter High */
#define PSB6970_VFxH_TM_SHIFT 7 /* Tagged Member */
/* --- Port registers --- */
#define PSB6970_EC(p) ((p)*0x20+2) /* Extended Control */
#define PSB6970_EC_IFNTE (1 << 1) /* Input Force No Tag Enable */
#define PSB6970_PBVM(p) ((p)*0x20+3) /* Port Base VLAN Map */
#define PSB6970_PBVM_VMCE (1 << 8)
#define PSB6970_PBVM_AOVTP (1 << 9)
#define PSB6970_PBVM_VSD (1 << 10)
#define PSB6970_PBVM_VC (1 << 11) /* VID Check with VID table */
#define PSB6970_PBVM_TBVE (1 << 13) /* Tag-Based VLAN enable */
#define PSB6970_DVID(p) ((p)*0x20+4) /* Default VLAN ID & Priority */
struct psb6970_priv {
struct switch_dev dev;
struct phy_device *phy;
u16 (*read) (struct phy_device* phydev, int reg);
void (*write) (struct phy_device* phydev, int reg, u16 val);
struct mutex reg_mutex;
/* all fields below are cleared on reset */
struct_group(psb6970_priv_volatile,
bool vlan;
u16 vlan_id[PSB6970_MAX_VLANS];
u8 vlan_table[PSB6970_MAX_VLANS];
u8 vlan_tagged;
u16 pvid[PSB6970_NUM_PORTS];
);
};
#define to_psb6970(_dev) container_of(_dev, struct psb6970_priv, dev)
static u16 psb6970_mii_read(struct phy_device *phydev, int reg)
{
struct mii_bus *bus = phydev->mdio.bus;
return bus->read(bus, PHYADDR(reg));
}
static void psb6970_mii_write(struct phy_device *phydev, int reg, u16 val)
{
struct mii_bus *bus = phydev->mdio.bus;
bus->write(bus, PHYADDR(reg), val);
}
static int
psb6970_set_vlan(struct switch_dev *dev, const struct switch_attr *attr,
struct switch_val *val)
{
struct psb6970_priv *priv = to_psb6970(dev);
priv->vlan = !!val->value.i;
return 0;
}
static int
psb6970_get_vlan(struct switch_dev *dev, const struct switch_attr *attr,
struct switch_val *val)
{
struct psb6970_priv *priv = to_psb6970(dev);
val->value.i = priv->vlan;
return 0;
}
static int psb6970_set_pvid(struct switch_dev *dev, int port, int vlan)
{
struct psb6970_priv *priv = to_psb6970(dev);
/* make sure no invalid PVIDs get set */
if (vlan >= dev->vlans)
return -EINVAL;
priv->pvid[port] = vlan;
return 0;
}
static int psb6970_get_pvid(struct switch_dev *dev, int port, int *vlan)
{
struct psb6970_priv *priv = to_psb6970(dev);
*vlan = priv->pvid[port];
return 0;
}
static int
psb6970_set_vid(struct switch_dev *dev, const struct switch_attr *attr,
struct switch_val *val)
{
struct psb6970_priv *priv = to_psb6970(dev);
priv->vlan_id[val->port_vlan] = val->value.i;
return 0;
}
static int
psb6970_get_vid(struct switch_dev *dev, const struct switch_attr *attr,
struct switch_val *val)
{
struct psb6970_priv *priv = to_psb6970(dev);
val->value.i = priv->vlan_id[val->port_vlan];
return 0;
}
static struct switch_attr psb6970_globals[] = {
{
.type = SWITCH_TYPE_INT,
.name = "enable_vlan",
.description = "Enable VLAN mode",
.set = psb6970_set_vlan,
.get = psb6970_get_vlan,
.max = 1},
};
static struct switch_attr psb6970_port[] = {
};
static struct switch_attr psb6970_vlan[] = {
{
.type = SWITCH_TYPE_INT,
.name = "vid",
.description = "VLAN ID (0-4094)",
.set = psb6970_set_vid,
.get = psb6970_get_vid,
.max = 4094,
},
};
static int psb6970_get_ports(struct switch_dev *dev, struct switch_val *val)
{
struct psb6970_priv *priv = to_psb6970(dev);
u8 ports = priv->vlan_table[val->port_vlan];
int i;
val->len = 0;
for (i = 0; i < PSB6970_NUM_PORTS; i++) {
struct switch_port *p;
if (!(ports & (1 << i)))
continue;
p = &val->value.ports[val->len++];
p->id = i;
if (priv->vlan_tagged & (1 << i))
p->flags = (1 << SWITCH_PORT_FLAG_TAGGED);
else
p->flags = 0;
}
return 0;
}
static int psb6970_set_ports(struct switch_dev *dev, struct switch_val *val)
{
struct psb6970_priv *priv = to_psb6970(dev);
u8 *vt = &priv->vlan_table[val->port_vlan];
int i, j;
*vt = 0;
for (i = 0; i < val->len; i++) {
struct switch_port *p = &val->value.ports[i];
if (p->flags & (1 << SWITCH_PORT_FLAG_TAGGED))
priv->vlan_tagged |= (1 << p->id);
else {
priv->vlan_tagged &= ~(1 << p->id);
priv->pvid[p->id] = val->port_vlan;
/* make sure that an untagged port does not
* appear in other vlans */
for (j = 0; j < PSB6970_MAX_VLANS; j++) {
if (j == val->port_vlan)
continue;
priv->vlan_table[j] &= ~(1 << p->id);
}
}
*vt |= 1 << p->id;
}
return 0;
}
static int psb6970_hw_apply(struct switch_dev *dev)
{
struct psb6970_priv *priv = to_psb6970(dev);
int i, j;
mutex_lock(&priv->reg_mutex);
if (priv->vlan) {
/* into the vlan translation unit */
for (j = 0; j < PSB6970_MAX_VLANS; j++) {
u8 vp = priv->vlan_table[j];
if (vp) {
priv->write(priv->phy, PSB6970_VFxL(j),
PSB6970_VFxL_VV | priv->vlan_id[j]);
priv->write(priv->phy, PSB6970_VFxH(j),
((vp & priv->
vlan_tagged) <<
PSB6970_VFxH_TM_SHIFT) | vp);
} else /* clear VLAN Valid flag for unused vlans */
priv->write(priv->phy, PSB6970_VFxL(j), 0);
}
}
/* update the port destination mask registers and tag settings */
for (i = 0; i < PSB6970_NUM_PORTS; i++) {
int dvid = 1, pbvm = 0x7f | PSB6970_PBVM_VSD, ec = 0;
if (priv->vlan) {
ec = PSB6970_EC_IFNTE;
dvid = priv->vlan_id[priv->pvid[i]];
pbvm |= PSB6970_PBVM_TBVE | PSB6970_PBVM_VMCE;
if ((i << 1) & priv->vlan_tagged)
pbvm |= PSB6970_PBVM_AOVTP | PSB6970_PBVM_VC;
}
priv->write(priv->phy, PSB6970_PBVM(i), pbvm);
if (!PSB6970_IS_CPU_PORT(i)) {
priv->write(priv->phy, PSB6970_EC(i), ec);
priv->write(priv->phy, PSB6970_DVID(i), dvid);
}
}
mutex_unlock(&priv->reg_mutex);
return 0;
}
static int psb6970_reset_switch(struct switch_dev *dev)
{
struct psb6970_priv *priv = to_psb6970(dev);
int i;
mutex_lock(&priv->reg_mutex);
memset(&priv->psb6970_priv_volatile, 0,
sizeof(priv->psb6970_priv_volatile));
for (i = 0; i < PSB6970_MAX_VLANS; i++)
priv->vlan_id[i] = i;
mutex_unlock(&priv->reg_mutex);
return psb6970_hw_apply(dev);
}
static const struct switch_dev_ops psb6970_ops = {
.attr_global = {
.attr = psb6970_globals,
.n_attr = ARRAY_SIZE(psb6970_globals),
},
.attr_port = {
.attr = psb6970_port,
.n_attr = ARRAY_SIZE(psb6970_port),
},
.attr_vlan = {
.attr = psb6970_vlan,
.n_attr = ARRAY_SIZE(psb6970_vlan),
},
.get_port_pvid = psb6970_get_pvid,
.set_port_pvid = psb6970_set_pvid,
.get_vlan_ports = psb6970_get_ports,
.set_vlan_ports = psb6970_set_ports,
.apply_config = psb6970_hw_apply,
.reset_switch = psb6970_reset_switch,
};
static int psb6970_config_init(struct phy_device *pdev)
{
struct psb6970_priv *priv;
struct switch_dev *swdev;
int ret;
priv = kzalloc(sizeof(struct psb6970_priv), GFP_KERNEL);
if (priv == NULL)
return -ENOMEM;
priv->phy = pdev;
if (pdev->mdio.addr == 0)
printk(KERN_INFO "%s: psb6970 switch driver attached.\n",
pdev->attached_dev->name);
if (pdev->mdio.addr != 0) {
kfree(priv);
return 0;
}
linkmode_zero(pdev->supported);
linkmode_set_bit(ETHTOOL_LINK_MODE_100baseT_Full_BIT, pdev->supported);
linkmode_copy(pdev->advertising, pdev->supported);
mutex_init(&priv->reg_mutex);
priv->read = psb6970_mii_read;
priv->write = psb6970_mii_write;
pdev->priv = priv;
swdev = &priv->dev;
swdev->cpu_port = PSB6970_DEFAULT_PORT_CPU;
swdev->ops = &psb6970_ops;
swdev->name = "Lantiq PSB6970";
swdev->vlans = PSB6970_MAX_VLANS;
swdev->ports = PSB6970_NUM_PORTS;
if ((ret = register_switch(&priv->dev, pdev->attached_dev)) < 0) {
kfree(priv);
goto done;
}
ret = psb6970_reset_switch(&priv->dev);
if (ret) {
kfree(priv);
goto done;
}
done:
return ret;
}
static int psb6970_read_status(struct phy_device *phydev)
{
phydev->speed = SPEED_100;
phydev->duplex = DUPLEX_FULL;
phydev->link = 1;
phydev->state = PHY_RUNNING;
netif_carrier_on(phydev->attached_dev);
phydev->adjust_link(phydev->attached_dev);
return 0;
}
static int psb6970_config_aneg(struct phy_device *phydev)
{
return 0;
}
static int psb6970_probe(struct phy_device *pdev)
{
return 0;
}
static void psb6970_remove(struct phy_device *pdev)
{
struct psb6970_priv *priv = pdev->priv;
if (!priv)
return;
if (pdev->mdio.addr == 0)
unregister_switch(&priv->dev);
kfree(priv);
}
static int psb6970_fixup(struct phy_device *dev)
{
struct mii_bus *bus = dev->mdio.bus;
u16 reg;
/* look for the switch on the bus */
reg = bus->read(bus, PHYADDR(PSB6970_CI1)) & PSB6970_CI1_MASK;
if (reg != PSB6970_CI1_VAL)
return 0;
dev->phy_id = (reg << 16);
dev->phy_id |= bus->read(bus, PHYADDR(PSB6970_CI0)) & PSB6970_CI0_MASK;
return 0;
}
static struct phy_driver psb6970_driver = {
.name = "Lantiq PSB6970",
.phy_id = PSB6970_CI1_VAL << 16,
.phy_id_mask = 0xffff0000,
.features = PHY_BASIC_FEATURES,
.probe = psb6970_probe,
.remove = psb6970_remove,
.config_init = &psb6970_config_init,
.config_aneg = &psb6970_config_aneg,
.read_status = &psb6970_read_status,
};
int __init psb6970_init(void)
{
phy_register_fixup_for_id(PHY_ANY_ID, psb6970_fixup);
return phy_driver_register(&psb6970_driver, THIS_MODULE);
}
module_init(psb6970_init);
void __exit psb6970_exit(void)
{
phy_driver_unregister(&psb6970_driver);
}
module_exit(psb6970_exit);
MODULE_DESCRIPTION("Lantiq PSB6970 Switch");
MODULE_AUTHOR("Ithamar R. Adema <ithamar.adema@team-embedded.nl>");
MODULE_LICENSE("GPL");

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,171 @@
/*
* Realtek RTL8366 SMI interface driver defines
*
* Copyright (C) 2009-2010 Gabor Juhos <juhosg@openwrt.org>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 as published
* by the Free Software Foundation.
*/
#ifndef _RTL8366_SMI_H
#define _RTL8366_SMI_H
#include <linux/phy.h>
#include <linux/switch.h>
#include <linux/platform_device.h>
#include <linux/reset.h>
struct rtl8366_smi_ops;
struct rtl8366_vlan_ops;
struct mii_bus;
struct dentry;
struct inode;
struct file;
typedef enum rtl8367b_chip_e {
RTL8367B_CHIP_UNKNOWN,
/* Family B */
RTL8367B_CHIP_RTL8367RB,
RTL8367B_CHIP_RTL8367R_VB, /* chip with exception in extif assignment */
/* Family C */
RTL8367B_CHIP_RTL8367RB_VB,
RTL8367B_CHIP_RTL8367S,
/* Family D */
RTL8367B_CHIP_RTL8367S_VB /* chip with exception in extif assignment */
} rtl8367b_chip_t;
struct rtl8366_mib_counter {
unsigned base;
unsigned offset;
unsigned length;
const char *name;
};
struct rtl8366_smi {
struct device *parent;
unsigned int gpio_sda;
unsigned int gpio_sck;
void (*hw_reset)(struct rtl8366_smi *smi, bool active);
unsigned int clk_delay; /* ns */
u8 cmd_read;
u8 cmd_write;
spinlock_t lock;
struct mii_bus *mii_bus;
int mii_irq[PHY_MAX_ADDR];
struct switch_dev sw_dev;
unsigned int cpu_port;
unsigned int num_ports;
unsigned int num_vlan_mc;
unsigned int num_mib_counters;
struct rtl8366_mib_counter *mib_counters;
struct rtl8366_smi_ops *ops;
int vlan_enabled;
int vlan4k_enabled;
char buf[4096];
struct reset_control *reset;
#ifdef CONFIG_RTL8366_SMI_DEBUG_FS
struct dentry *debugfs_root;
u16 dbg_reg;
u8 dbg_vlan_4k_page;
#endif
u32 phy_id;
rtl8367b_chip_t rtl8367b_chip;
struct mii_bus *ext_mbus;
struct rtl8366_vlan_mc *emu_vlanmc;
};
struct rtl8366_vlan_mc {
u16 vid;
u16 untag;
u16 member;
u8 fid;
u8 priority;
};
struct rtl8366_vlan_4k {
u16 vid;
u16 untag;
u16 member;
u8 fid;
};
struct rtl8366_smi_ops {
int (*detect)(struct rtl8366_smi *smi);
int (*reset_chip)(struct rtl8366_smi *smi);
int (*setup)(struct rtl8366_smi *smi);
int (*mii_read)(struct mii_bus *bus, int addr, int reg);
int (*mii_write)(struct mii_bus *bus, int addr, int reg, u16 val);
int (*get_vlan_mc)(struct rtl8366_smi *smi, u32 index,
struct rtl8366_vlan_mc *vlanmc);
int (*set_vlan_mc)(struct rtl8366_smi *smi, u32 index,
const struct rtl8366_vlan_mc *vlanmc);
int (*get_vlan_4k)(struct rtl8366_smi *smi, u32 vid,
struct rtl8366_vlan_4k *vlan4k);
int (*set_vlan_4k)(struct rtl8366_smi *smi,
const struct rtl8366_vlan_4k *vlan4k);
int (*get_mc_index)(struct rtl8366_smi *smi, int port, int *val);
int (*set_mc_index)(struct rtl8366_smi *smi, int port, int index);
int (*get_mib_counter)(struct rtl8366_smi *smi, int counter,
int port, unsigned long long *val);
int (*is_vlan_valid)(struct rtl8366_smi *smi, unsigned vlan);
int (*enable_vlan)(struct rtl8366_smi *smi, int enable);
int (*enable_vlan4k)(struct rtl8366_smi *smi, int enable);
int (*enable_port)(struct rtl8366_smi *smi, int port, int enable);
};
struct rtl8366_smi *rtl8366_smi_alloc(struct device *parent);
int rtl8366_smi_init(struct rtl8366_smi *smi);
void rtl8366_smi_cleanup(struct rtl8366_smi *smi);
int rtl8366_smi_write_reg(struct rtl8366_smi *smi, u32 addr, u32 data);
int rtl8366_smi_write_reg_noack(struct rtl8366_smi *smi, u32 addr, u32 data);
int rtl8366_smi_read_reg(struct rtl8366_smi *smi, u32 addr, u32 *data);
int rtl8366_smi_rmwr(struct rtl8366_smi *smi, u32 addr, u32 mask, u32 data);
#ifdef CONFIG_RTL8366_SMI_DEBUG_FS
int rtl8366_debugfs_open(struct inode *inode, struct file *file);
#endif
static inline struct rtl8366_smi *sw_to_rtl8366_smi(struct switch_dev *sw)
{
return container_of(sw, struct rtl8366_smi, sw_dev);
}
int rtl8366_sw_reset_switch(struct switch_dev *dev);
int rtl8366_sw_get_port_pvid(struct switch_dev *dev, int port, int *val);
int rtl8366_sw_set_port_pvid(struct switch_dev *dev, int port, int val);
int rtl8366_sw_get_port_mib(struct switch_dev *dev,
const struct switch_attr *attr,
struct switch_val *val);
int rtl8366_sw_get_vlan_info(struct switch_dev *dev,
const struct switch_attr *attr,
struct switch_val *val);
int rtl8366_sw_get_vlan_fid(struct switch_dev *dev,
const struct switch_attr *attr,
struct switch_val *val);
int rtl8366_sw_set_vlan_fid(struct switch_dev *dev,
const struct switch_attr *attr,
struct switch_val *val);
int rtl8366_sw_get_vlan_ports(struct switch_dev *dev, struct switch_val *val);
int rtl8366_sw_set_vlan_ports(struct switch_dev *dev, struct switch_val *val);
int rtl8366_sw_get_vlan_enable(struct switch_dev *dev,
const struct switch_attr *attr,
struct switch_val *val);
int rtl8366_sw_set_vlan_enable(struct switch_dev *dev,
const struct switch_attr *attr,
struct switch_val *val);
int rtl8366_sw_get_port_stats(struct switch_dev *dev, int port,
struct switch_port_stats *stats,
int txb_id, int rxb_id);
struct rtl8366_smi* rtl8366_smi_probe(struct platform_device *pdev);
#endif /* _RTL8366_SMI_H */

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,555 @@
/*
* swconfig_led.c: LED trigger support for the switch configuration API
*
* Copyright (C) 2011 Gabor Juhos <juhosg@openwrt.org>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
*/
#ifdef CONFIG_SWCONFIG_LEDS
#include <linux/leds.h>
#include <linux/ctype.h>
#include <linux/device.h>
#include <linux/workqueue.h>
#define SWCONFIG_LED_TIMER_INTERVAL (HZ / 10)
#define SWCONFIG_LED_NUM_PORTS 32
#define SWCONFIG_LED_PORT_SPEED_NA 0x01 /* unknown speed */
#define SWCONFIG_LED_PORT_SPEED_10 0x02 /* 10 Mbps */
#define SWCONFIG_LED_PORT_SPEED_100 0x04 /* 100 Mbps */
#define SWCONFIG_LED_PORT_SPEED_1000 0x08 /* 1000 Mbps */
#define SWCONFIG_LED_PORT_SPEED_ALL (SWCONFIG_LED_PORT_SPEED_NA | \
SWCONFIG_LED_PORT_SPEED_10 | \
SWCONFIG_LED_PORT_SPEED_100 | \
SWCONFIG_LED_PORT_SPEED_1000)
#define SWCONFIG_LED_MODE_LINK 0x01
#define SWCONFIG_LED_MODE_TX 0x02
#define SWCONFIG_LED_MODE_RX 0x04
#define SWCONFIG_LED_MODE_TXRX (SWCONFIG_LED_MODE_TX | \
SWCONFIG_LED_MODE_RX)
#define SWCONFIG_LED_MODE_ALL (SWCONFIG_LED_MODE_LINK | \
SWCONFIG_LED_MODE_TX | \
SWCONFIG_LED_MODE_RX)
struct switch_led_trigger {
struct led_trigger trig;
struct switch_dev *swdev;
struct delayed_work sw_led_work;
u32 port_mask;
u32 port_link;
unsigned long long port_tx_traffic[SWCONFIG_LED_NUM_PORTS];
unsigned long long port_rx_traffic[SWCONFIG_LED_NUM_PORTS];
u8 link_speed[SWCONFIG_LED_NUM_PORTS];
};
struct swconfig_trig_data {
struct led_classdev *led_cdev;
struct switch_dev *swdev;
rwlock_t lock;
u32 port_mask;
bool prev_link;
unsigned long prev_traffic;
enum led_brightness prev_brightness;
u8 mode;
u8 speed_mask;
};
static void
swconfig_trig_set_brightness(struct swconfig_trig_data *trig_data,
enum led_brightness brightness)
{
led_set_brightness(trig_data->led_cdev, brightness);
trig_data->prev_brightness = brightness;
}
static void
swconfig_trig_update_port_mask(struct led_trigger *trigger)
{
struct list_head *entry;
struct switch_led_trigger *sw_trig;
u32 port_mask;
if (!trigger)
return;
sw_trig = (void *) trigger;
port_mask = 0;
spin_lock(&trigger->leddev_list_lock);
list_for_each(entry, &trigger->led_cdevs) {
struct led_classdev *led_cdev;
struct swconfig_trig_data *trig_data;
led_cdev = list_entry(entry, struct led_classdev, trig_list);
trig_data = led_cdev->trigger_data;
if (trig_data) {
read_lock(&trig_data->lock);
port_mask |= trig_data->port_mask;
read_unlock(&trig_data->lock);
}
}
spin_unlock(&trigger->leddev_list_lock);
sw_trig->port_mask = port_mask;
if (port_mask)
schedule_delayed_work(&sw_trig->sw_led_work,
SWCONFIG_LED_TIMER_INTERVAL);
else
cancel_delayed_work_sync(&sw_trig->sw_led_work);
}
static ssize_t
swconfig_trig_port_mask_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t size)
{
struct led_classdev *led_cdev = dev_get_drvdata(dev);
struct swconfig_trig_data *trig_data = led_cdev->trigger_data;
unsigned long port_mask;
int ret;
bool changed;
ret = kstrtoul(buf, 0, &port_mask);
if (ret)
return ret;
write_lock(&trig_data->lock);
changed = (trig_data->port_mask != port_mask);
trig_data->port_mask = port_mask;
write_unlock(&trig_data->lock);
if (changed) {
if (port_mask == 0)
swconfig_trig_set_brightness(trig_data, LED_OFF);
swconfig_trig_update_port_mask(led_cdev->trigger);
}
return size;
}
static ssize_t
swconfig_trig_port_mask_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
struct led_classdev *led_cdev = dev_get_drvdata(dev);
struct swconfig_trig_data *trig_data = led_cdev->trigger_data;
u32 port_mask;
read_lock(&trig_data->lock);
port_mask = trig_data->port_mask;
read_unlock(&trig_data->lock);
sprintf(buf, "%#x\n", port_mask);
return strlen(buf) + 1;
}
static DEVICE_ATTR(port_mask, 0644, swconfig_trig_port_mask_show,
swconfig_trig_port_mask_store);
/* speed_mask file handler - display value */
static ssize_t swconfig_trig_speed_mask_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct led_classdev *led_cdev = dev_get_drvdata(dev);
struct swconfig_trig_data *trig_data = led_cdev->trigger_data;
u8 speed_mask;
read_lock(&trig_data->lock);
speed_mask = trig_data->speed_mask;
read_unlock(&trig_data->lock);
sprintf(buf, "%#x\n", speed_mask);
return strlen(buf) + 1;
}
/* speed_mask file handler - store value */
static ssize_t swconfig_trig_speed_mask_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t size)
{
struct led_classdev *led_cdev = dev_get_drvdata(dev);
struct swconfig_trig_data *trig_data = led_cdev->trigger_data;
u8 speed_mask;
int ret;
ret = kstrtou8(buf, 0, &speed_mask);
if (ret)
return ret;
write_lock(&trig_data->lock);
trig_data->speed_mask = speed_mask & SWCONFIG_LED_PORT_SPEED_ALL;
write_unlock(&trig_data->lock);
return size;
}
/* speed_mask special file */
static DEVICE_ATTR(speed_mask, 0644, swconfig_trig_speed_mask_show,
swconfig_trig_speed_mask_store);
static ssize_t swconfig_trig_mode_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct led_classdev *led_cdev = dev_get_drvdata(dev);
struct swconfig_trig_data *trig_data = led_cdev->trigger_data;
u8 mode;
read_lock(&trig_data->lock);
mode = trig_data->mode;
read_unlock(&trig_data->lock);
if (mode == 0) {
strcpy(buf, "none\n");
} else {
if (mode & SWCONFIG_LED_MODE_LINK)
strcat(buf, "link ");
if (mode & SWCONFIG_LED_MODE_TX)
strcat(buf, "tx ");
if (mode & SWCONFIG_LED_MODE_RX)
strcat(buf, "rx ");
strcat(buf, "\n");
}
return strlen(buf)+1;
}
static ssize_t swconfig_trig_mode_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
struct led_classdev *led_cdev = dev_get_drvdata(dev);
struct swconfig_trig_data *trig_data = led_cdev->trigger_data;
char copybuf[128];
int new_mode = -1;
char *p, *token;
/* take a copy since we don't want to trash the inbound buffer when using strsep */
strncpy(copybuf, buf, sizeof(copybuf));
copybuf[sizeof(copybuf) - 1] = 0;
p = copybuf;
while ((token = strsep(&p, " \t\n")) != NULL) {
if (!*token)
continue;
if (new_mode < 0)
new_mode = 0;
if (!strcmp(token, "none"))
new_mode = 0;
else if (!strcmp(token, "tx"))
new_mode |= SWCONFIG_LED_MODE_TX;
else if (!strcmp(token, "rx"))
new_mode |= SWCONFIG_LED_MODE_RX;
else if (!strcmp(token, "link"))
new_mode |= SWCONFIG_LED_MODE_LINK;
else
return -EINVAL;
}
if (new_mode < 0)
return -EINVAL;
write_lock(&trig_data->lock);
trig_data->mode = (u8)new_mode;
write_unlock(&trig_data->lock);
return size;
}
/* mode special file */
static DEVICE_ATTR(mode, 0644, swconfig_trig_mode_show,
swconfig_trig_mode_store);
static int
swconfig_trig_activate(struct led_classdev *led_cdev)
{
struct switch_led_trigger *sw_trig;
struct swconfig_trig_data *trig_data;
int err;
trig_data = kzalloc(sizeof(struct swconfig_trig_data), GFP_KERNEL);
if (!trig_data)
return -ENOMEM;
sw_trig = (void *) led_cdev->trigger;
rwlock_init(&trig_data->lock);
trig_data->led_cdev = led_cdev;
trig_data->swdev = sw_trig->swdev;
trig_data->speed_mask = SWCONFIG_LED_PORT_SPEED_ALL;
trig_data->mode = SWCONFIG_LED_MODE_ALL;
led_cdev->trigger_data = trig_data;
err = device_create_file(led_cdev->dev, &dev_attr_port_mask);
if (err)
goto err_free;
err = device_create_file(led_cdev->dev, &dev_attr_speed_mask);
if (err)
goto err_dev_free;
err = device_create_file(led_cdev->dev, &dev_attr_mode);
if (err)
goto err_mode_free;
return 0;
err_mode_free:
device_remove_file(led_cdev->dev, &dev_attr_speed_mask);
err_dev_free:
device_remove_file(led_cdev->dev, &dev_attr_port_mask);
err_free:
led_cdev->trigger_data = NULL;
kfree(trig_data);
return err;
}
static void
swconfig_trig_deactivate(struct led_classdev *led_cdev)
{
struct swconfig_trig_data *trig_data;
swconfig_trig_update_port_mask(led_cdev->trigger);
trig_data = (void *) led_cdev->trigger_data;
if (trig_data) {
device_remove_file(led_cdev->dev, &dev_attr_port_mask);
device_remove_file(led_cdev->dev, &dev_attr_speed_mask);
device_remove_file(led_cdev->dev, &dev_attr_mode);
kfree(trig_data);
}
}
/*
* link off -> led off (can't be any other reason to turn it on)
* link on:
* mode link: led on by default only if speed matches, else off
* mode txrx: blink only if speed matches, else off
*/
static void
swconfig_trig_led_event(struct switch_led_trigger *sw_trig,
struct led_classdev *led_cdev)
{
struct swconfig_trig_data *trig_data;
u32 port_mask;
bool link;
u8 speed_mask, mode;
enum led_brightness led_base, led_blink;
trig_data = led_cdev->trigger_data;
if (!trig_data)
return;
read_lock(&trig_data->lock);
port_mask = trig_data->port_mask;
speed_mask = trig_data->speed_mask;
mode = trig_data->mode;
read_unlock(&trig_data->lock);
link = !!(sw_trig->port_link & port_mask);
if (!link) {
if (trig_data->prev_brightness != LED_OFF)
swconfig_trig_set_brightness(trig_data, LED_OFF); /* and stop */
}
else {
unsigned long traffic;
int speedok; /* link speed flag */
int i;
led_base = LED_FULL;
led_blink = LED_OFF;
traffic = 0;
speedok = 0;
for (i = 0; i < SWCONFIG_LED_NUM_PORTS; i++) {
if (port_mask & (1 << i)) {
if (sw_trig->link_speed[i] & speed_mask) {
traffic += ((mode & SWCONFIG_LED_MODE_TX) ?
sw_trig->port_tx_traffic[i] : 0) +
((mode & SWCONFIG_LED_MODE_RX) ?
sw_trig->port_rx_traffic[i] : 0);
speedok = 1;
}
}
}
if (speedok) {
/* At least one port speed matches speed_mask */
if (!(mode & SWCONFIG_LED_MODE_LINK)) {
led_base = LED_OFF;
led_blink = LED_FULL;
}
if (trig_data->prev_brightness != led_base)
swconfig_trig_set_brightness(trig_data,
led_base);
else if (traffic != trig_data->prev_traffic)
swconfig_trig_set_brightness(trig_data,
led_blink);
} else if (trig_data->prev_brightness != LED_OFF)
swconfig_trig_set_brightness(trig_data, LED_OFF);
trig_data->prev_traffic = traffic;
}
trig_data->prev_link = link;
}
static void
swconfig_trig_update_leds(struct switch_led_trigger *sw_trig)
{
struct list_head *entry;
struct led_trigger *trigger;
trigger = &sw_trig->trig;
spin_lock(&trigger->leddev_list_lock);
list_for_each(entry, &trigger->led_cdevs) {
struct led_classdev *led_cdev;
led_cdev = list_entry(entry, struct led_classdev, trig_list);
swconfig_trig_led_event(sw_trig, led_cdev);
}
spin_unlock(&trigger->leddev_list_lock);
}
static void
swconfig_led_work_func(struct work_struct *work)
{
struct switch_led_trigger *sw_trig;
struct switch_dev *swdev;
u32 port_mask;
u32 link;
int i;
sw_trig = container_of(work, struct switch_led_trigger,
sw_led_work.work);
port_mask = sw_trig->port_mask;
swdev = sw_trig->swdev;
link = 0;
for (i = 0; i < SWCONFIG_LED_NUM_PORTS; i++) {
u32 port_bit;
sw_trig->link_speed[i] = 0;
port_bit = BIT(i);
if ((port_mask & port_bit) == 0)
continue;
if (swdev->ops->get_port_link) {
struct switch_port_link port_link;
memset(&port_link, '\0', sizeof(port_link));
swdev->ops->get_port_link(swdev, i, &port_link);
if (port_link.link) {
link |= port_bit;
switch (port_link.speed) {
case SWITCH_PORT_SPEED_UNKNOWN:
sw_trig->link_speed[i] =
SWCONFIG_LED_PORT_SPEED_NA;
break;
case SWITCH_PORT_SPEED_10:
sw_trig->link_speed[i] =
SWCONFIG_LED_PORT_SPEED_10;
break;
case SWITCH_PORT_SPEED_100:
sw_trig->link_speed[i] =
SWCONFIG_LED_PORT_SPEED_100;
break;
case SWITCH_PORT_SPEED_1000:
sw_trig->link_speed[i] =
SWCONFIG_LED_PORT_SPEED_1000;
break;
}
}
}
if (swdev->ops->get_port_stats) {
struct switch_port_stats port_stats;
memset(&port_stats, '\0', sizeof(port_stats));
swdev->ops->get_port_stats(swdev, i, &port_stats);
sw_trig->port_tx_traffic[i] = port_stats.tx_bytes;
sw_trig->port_rx_traffic[i] = port_stats.rx_bytes;
}
}
sw_trig->port_link = link;
swconfig_trig_update_leds(sw_trig);
schedule_delayed_work(&sw_trig->sw_led_work,
SWCONFIG_LED_TIMER_INTERVAL);
}
static int
swconfig_create_led_trigger(struct switch_dev *swdev)
{
struct switch_led_trigger *sw_trig;
int err;
if (!swdev->ops->get_port_link)
return 0;
sw_trig = kzalloc(sizeof(struct switch_led_trigger), GFP_KERNEL);
if (!sw_trig)
return -ENOMEM;
sw_trig->swdev = swdev;
sw_trig->trig.name = swdev->devname;
sw_trig->trig.activate = swconfig_trig_activate;
sw_trig->trig.deactivate = swconfig_trig_deactivate;
INIT_DELAYED_WORK(&sw_trig->sw_led_work, swconfig_led_work_func);
err = led_trigger_register(&sw_trig->trig);
if (err)
goto err_free;
swdev->led_trigger = sw_trig;
return 0;
err_free:
kfree(sw_trig);
return err;
}
static void
swconfig_destroy_led_trigger(struct switch_dev *swdev)
{
struct switch_led_trigger *sw_trig;
sw_trig = swdev->led_trigger;
if (sw_trig) {
cancel_delayed_work_sync(&sw_trig->sw_led_work);
led_trigger_unregister(&sw_trig->trig);
kfree(sw_trig);
}
}
#else /* SWCONFIG_LEDS */
static inline int
swconfig_create_led_trigger(struct switch_dev *swdev) { return 0; }
static inline void
swconfig_destroy_led_trigger(struct switch_dev *swdev) { }
#endif /* CONFIG_SWCONFIG_LEDS */

View file

@ -0,0 +1,31 @@
menuconfig MIKROTIK
bool "Platform support for MikroTik RouterBoard virtual devices"
help
Say Y here to get to see options for the MikroTik RouterBoard platform.
This option alone does not add any kernel code.
if MIKROTIK
config MIKROTIK_RB_SYSFS
tristate "RouterBoot sysfs support"
depends on MTD
select LZO_DECOMPRESS
select CRC32
help
This driver exposes RouterBoot configuration in sysfs.
config NVMEM_LAYOUT_MIKROTIK
tristate "RouterBoot NVMEM layout support"
depends on NVMEM_LAYOUTS
help
This driver exposes MikroTik hard_config via NVMEM layout.
config MIKROTIK_WLAN_DECOMPRESS_LZ77
tristate "Mikrotik factory Wi-Fi caldata LZ77 decompression support"
depends on MIKROTIK_RB_SYSFS
help
Allow Mikrotik LZ77 factory flashed Wi-Fi calibration data to be
decompressed
endif # MIKROTIK

View file

@ -0,0 +1,6 @@
#
# Makefile for MikroTik RouterBoard platform specific drivers
#
obj-$(CONFIG_MIKROTIK_RB_SYSFS) += routerboot.o rb_hardconfig.o rb_softconfig.o
obj-$(CONFIG_NVMEM_LAYOUT_MIKROTIK) += rb_nvmem.o
obj-$(CONFIG_MIKROTIK_WLAN_DECOMPRESS_LZ77) += rb_lz77.o

View file

@ -0,0 +1,844 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Driver for MikroTik RouterBoot hard config.
*
* Copyright (C) 2020 Thibaut VARÈNE <hacks+kernel@slashdirt.org>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 as published
* by the Free Software Foundation.
*
* This driver exposes the data encoded in the "hard_config" flash segment of
* MikroTik RouterBOARDs devices. It presents the data in a sysfs folder
* named "hard_config". The WLAN calibration data is available on demand via
* the 'wlan_data' sysfs file in that folder.
*
* This driver permanently allocates a chunk of RAM as large as the hard_config
* MTD partition, although it is technically possible to operate entirely from
* the MTD device without using a local buffer (except when requesting WLAN
* calibration data), at the cost of a performance penalty.
*
* Note: PAGE_SIZE is assumed to be >= 4K, hence the device attribute show
* routines need not check for output overflow.
*
* Some constant defines extracted from routerboot.{c,h} by Gabor Juhos
* <juhosg@openwrt.org>
*/
#include <linux/types.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/errno.h>
#include <linux/kobject.h>
#include <linux/bitops.h>
#include <linux/string.h>
#include <linux/mtd/mtd.h>
#include <linux/sysfs.h>
#include <linux/lzo.h>
#include "rb_hardconfig.h"
#include "routerboot.h"
#include "rb_lz77.h"
#define RB_HARDCONFIG_VER "0.08"
#define RB_HC_PR_PFX "[rb_hardconfig] "
/* Bit definitions for hardware options */
#define RB_HW_OPT_NO_UART BIT(0)
#define RB_HW_OPT_HAS_VOLTAGE BIT(1)
#define RB_HW_OPT_HAS_USB BIT(2)
#define RB_HW_OPT_HAS_ATTINY BIT(3)
#define RB_HW_OPT_PULSE_DUTY_CYCLE BIT(9)
#define RB_HW_OPT_NO_NAND BIT(14)
#define RB_HW_OPT_HAS_LCD BIT(15)
#define RB_HW_OPT_HAS_POE_OUT BIT(16)
#define RB_HW_OPT_HAS_uSD BIT(17)
#define RB_HW_OPT_HAS_SIM BIT(18)
#define RB_HW_OPT_HAS_SFP BIT(20)
#define RB_HW_OPT_HAS_WIFI BIT(21)
#define RB_HW_OPT_HAS_TS_FOR_ADC BIT(22)
#define RB_HW_OPT_HAS_PLC BIT(29)
/*
* Tag ID values for ERD data.
* Mikrotik used to pack all calibration data under a single tag id 0x1, but
* recently switched to a new scheme where each radio calibration gets a
* separate tag. The new scheme has tag id bit 15 always set and seems to be
* mutually exclusive with the old scheme.
*/
#define RB_WLAN_ERD_ID_SOLO 0x0001
#define RB_WLAN_ERD_ID_MULTI_8001 0x8001
#define RB_WLAN_ERD_ID_MULTI_8201 0x8201
static struct kobject *hc_kobj;
static u8 *hc_buf; // ro buffer after init(): no locking required
static size_t hc_buflen;
/*
* For LZOR style WLAN data unpacking.
* This binary blob is prepended to the data encoded on some devices as
* RB_ID_WLAN_DATA, the result is then first decompressed with LZO, and then
* finally RLE-decoded.
* This binary blob has been extracted from RouterOS by
* https://forum.openwrt.org/u/ius
*/
static const u8 hc_lzor_prefix[] = {
0x00, 0x05, 0x4c, 0x4c, 0x44, 0x00, 0x34, 0xfe,
0xfe, 0x34, 0x11, 0x3c, 0x1e, 0x3c, 0x2e, 0x3c,
0x4c, 0x34, 0x00, 0x52, 0x62, 0x92, 0xa2, 0xb2,
0xc3, 0x2a, 0x14, 0x00, 0x00, 0x05, 0xfe, 0x6a,
0x3c, 0x16, 0x32, 0x16, 0x11, 0x1e, 0x12, 0x46,
0x32, 0x46, 0x11, 0x4e, 0x12, 0x36, 0x32, 0x36,
0x11, 0x3e, 0x12, 0x5a, 0x9a, 0x64, 0x00, 0x04,
0xfe, 0x10, 0x3c, 0x00, 0x01, 0x00, 0x00, 0x28,
0x0c, 0x00, 0x0f, 0xfe, 0x14, 0x00, 0x24, 0x24,
0x23, 0x24, 0x24, 0x23, 0x25, 0x22, 0x21, 0x21,
0x23, 0x22, 0x21, 0x22, 0x21, 0x2d, 0x38, 0x00,
0x0c, 0x25, 0x25, 0x24, 0x25, 0x25, 0x24, 0x23,
0x22, 0x21, 0x20, 0x23, 0x21, 0x21, 0x22, 0x21,
0x2d, 0x38, 0x00, 0x28, 0xb0, 0x00, 0x00, 0x22,
0x00, 0x00, 0xc0, 0xfe, 0x03, 0x00, 0xc0, 0x00,
0x62, 0xff, 0x62, 0xff, 0xfe, 0x06, 0x00, 0xbb,
0xff, 0xba, 0xff, 0xfe, 0x08, 0x00, 0x9e, 0xff,
0xfe, 0x0a, 0x00, 0x53, 0xff, 0xfe, 0x02, 0x00,
0x20, 0xff, 0xb1, 0xfe, 0xfe, 0xb2, 0xfe, 0xfe,
0xed, 0xfe, 0xfe, 0xfe, 0x04, 0x00, 0x3a, 0xff,
0x3a, 0xff, 0xde, 0xfd, 0x5f, 0x04, 0x33, 0xff,
0x4c, 0x74, 0x03, 0x05, 0x05, 0xff, 0x6d, 0xfe,
0xfe, 0x6d, 0xfe, 0xfe, 0xaf, 0x08, 0x63, 0xff,
0x64, 0x6f, 0x08, 0xac, 0xff, 0xbf, 0x6d, 0x08,
0x7a, 0x6d, 0x08, 0x96, 0x74, 0x04, 0x00, 0x08,
0x79, 0xff, 0xda, 0xfe, 0xfe, 0xdb, 0xfe, 0xfe,
0x56, 0xff, 0xfe, 0x04, 0x00, 0x5e, 0xff, 0x5e,
0xff, 0x6c, 0xfe, 0xfe, 0xfe, 0x06, 0x00, 0x41,
0xff, 0x7f, 0x74, 0x03, 0x00, 0x11, 0x44, 0xff,
0xa9, 0xfe, 0xfe, 0xa9, 0xfe, 0xfe, 0xa5, 0x8f,
0x01, 0x00, 0x08, 0x01, 0x01, 0x02, 0x04, 0x08,
0x02, 0x04, 0x08, 0x08, 0x01, 0x01, 0xfe, 0x22,
0x00, 0x4c, 0x60, 0x64, 0x8c, 0x90, 0xd0, 0xd4,
0xd8, 0x5c, 0x10, 0x09, 0xd8, 0xff, 0xb0, 0xff,
0x00, 0x00, 0xba, 0xff, 0x14, 0x00, 0xba, 0xff,
0x64, 0x00, 0x00, 0x08, 0xfe, 0x06, 0x00, 0x74,
0xff, 0x42, 0xff, 0xce, 0xff, 0x60, 0xff, 0x0a,
0x00, 0xb4, 0x00, 0xa0, 0x00, 0xa0, 0xfe, 0x07,
0x00, 0x0a, 0x00, 0xb0, 0xff, 0x96, 0x4d, 0x00,
0x56, 0x57, 0x18, 0xa6, 0xff, 0x92, 0x70, 0x11,
0x00, 0x12, 0x90, 0x90, 0x76, 0x5a, 0x54, 0x54,
0x4c, 0x46, 0x38, 0x00, 0x10, 0x10, 0x08, 0xfe,
0x05, 0x00, 0x38, 0x29, 0x25, 0x23, 0x22, 0x22,
0x1f, 0x00, 0x00, 0x00, 0xf6, 0xe1, 0xdd, 0xf8,
0xfe, 0x00, 0xfe, 0x15, 0x00, 0x00, 0xd0, 0x02,
0x74, 0x02, 0x08, 0xf8, 0xe5, 0xde, 0x02, 0x04,
0x04, 0xfd, 0x00, 0x00, 0x00, 0x07, 0x50, 0x2d,
0x01, 0x90, 0x90, 0x76, 0x60, 0xb0, 0x07, 0x07,
0x0c, 0x0c, 0x04, 0xfe, 0x05, 0x00, 0x66, 0x66,
0x5a, 0x56, 0xbc, 0x01, 0x06, 0xfc, 0xfc, 0xf1,
0xfe, 0x07, 0x00, 0x24, 0x95, 0x70, 0x64, 0x18,
0x06, 0x2c, 0xff, 0xb5, 0xfe, 0xfe, 0xb5, 0xfe,
0xfe, 0xe2, 0x8c, 0x24, 0x02, 0x2f, 0xff, 0x2f,
0xff, 0xb4, 0x78, 0x02, 0x05, 0x73, 0xff, 0xed,
0xfe, 0xfe, 0x4f, 0xff, 0x36, 0x74, 0x1e, 0x09,
0x4f, 0xff, 0x50, 0xff, 0xfe, 0x16, 0x00, 0x70,
0xac, 0x70, 0x8e, 0xac, 0x40, 0x0e, 0x01, 0x70,
0x7f, 0x8e, 0xac, 0x6c, 0x00, 0x0b, 0xfe, 0x02,
0x00, 0xfe, 0x0a, 0x2c, 0x2a, 0x2a, 0x28, 0x26,
0x1e, 0x1e, 0xfe, 0x02, 0x20, 0x65, 0x20, 0x00,
0x00, 0x05, 0x12, 0x00, 0x11, 0x1e, 0x11, 0x11,
0x41, 0x1e, 0x41, 0x11, 0x31, 0x1e, 0x31, 0x11,
0x70, 0x75, 0x7a, 0x7f, 0x84, 0x89, 0x8e, 0x93,
0x98, 0x30, 0x20, 0x00, 0x02, 0x00, 0xfe, 0x06,
0x3c, 0xbc, 0x32, 0x0c, 0x00, 0x00, 0x2a, 0x12,
0x1e, 0x12, 0x2e, 0x12, 0xcc, 0x12, 0x11, 0x1a,
0x1e, 0x1a, 0x2e, 0x1a, 0x4c, 0x10, 0x1e, 0x10,
0x11, 0x18, 0x1e, 0x42, 0x1e, 0x42, 0x2e, 0x42,
0xcc, 0x42, 0x11, 0x4a, 0x1e, 0x4a, 0x2e, 0x4a,
0x4c, 0x40, 0x1e, 0x40, 0x11, 0x48, 0x1e, 0x32,
0x1e, 0x32, 0x2e, 0x32, 0xcc, 0x32, 0x11, 0x3a,
0x1e, 0x3a, 0x2e, 0x3a, 0x4c, 0x30, 0x1e, 0x30,
0x11, 0x38, 0x1e, 0x27, 0x9a, 0x01, 0x9d, 0xa2,
0x2f, 0x28, 0x00, 0x00, 0x46, 0xde, 0xc4, 0xbf,
0xa6, 0x9d, 0x81, 0x7b, 0x5c, 0x61, 0x40, 0xc7,
0xc0, 0xae, 0xa9, 0x8c, 0x83, 0x6a, 0x62, 0x50,
0x3e, 0xce, 0xc2, 0xae, 0xa3, 0x8c, 0x7b, 0x6a,
0x5a, 0x50, 0x35, 0xd7, 0xc2, 0xb7, 0xa4, 0x95,
0x7e, 0x72, 0x5a, 0x59, 0x37, 0xfe, 0x02, 0xf8,
0x8c, 0x95, 0x90, 0x8f, 0x00, 0xd7, 0xc0, 0xb7,
0xa2, 0x95, 0x7b, 0x72, 0x56, 0x59, 0x32, 0xc7,
0xc3, 0xae, 0xad, 0x8c, 0x85, 0x6a, 0x63, 0x50,
0x3e, 0xce, 0xc3, 0xae, 0xa4, 0x8c, 0x7c, 0x6a,
0x59, 0x50, 0x34, 0xd7, 0xc2, 0xb7, 0xa5, 0x95,
0x7e, 0x72, 0x59, 0x59, 0x36, 0xfc, 0x05, 0x00,
0x02, 0xce, 0xc5, 0xae, 0xa5, 0x95, 0x83, 0x72,
0x5c, 0x59, 0x36, 0xbf, 0xc6, 0xa5, 0xab, 0x8c,
0x8c, 0x6a, 0x67, 0x50, 0x41, 0x64, 0x07, 0x00,
0x02, 0x95, 0x8c, 0x72, 0x65, 0x59, 0x3f, 0xce,
0xc7, 0xae, 0xa8, 0x95, 0x86, 0x72, 0x5f, 0x59,
0x39, 0xfe, 0x02, 0xf8, 0x8b, 0x7c, 0x0b, 0x09,
0xb7, 0xc2, 0x9d, 0xa4, 0x83, 0x85, 0x6a, 0x6b,
0x50, 0x44, 0xb7, 0xc1, 0x64, 0x01, 0x00, 0x06,
0x61, 0x5d, 0x48, 0x3d, 0xae, 0xc4, 0x9d, 0xad,
0x7b, 0x85, 0x61, 0x66, 0x48, 0x46, 0xae, 0xc3,
0x95, 0xa3, 0x72, 0x7c, 0x59, 0x56, 0x38, 0x31,
0x7c, 0x0b, 0x00, 0x0c, 0x96, 0x91, 0x8f, 0x00,
0xb7, 0xc0, 0xa5, 0xab, 0x8c, 0x8a, 0x6a, 0x64,
0x50, 0x3c, 0xb7, 0xc0, 0x9d, 0xa0, 0x83, 0x80,
0x6a, 0x64, 0x50, 0x3d, 0xb7, 0xc5, 0x9d, 0xa5,
0x83, 0x87, 0x6c, 0x08, 0x07, 0xae, 0xc0, 0x9d,
0xa8, 0x83, 0x88, 0x6a, 0x6d, 0x50, 0x46, 0xfc,
0x05, 0x00, 0x16, 0xbf, 0xc0, 0xa5, 0xa2, 0x8c,
0x7f, 0x6a, 0x57, 0x50, 0x2f, 0xb7, 0xc7, 0xa5,
0xb1, 0x8c, 0x8e, 0x72, 0x6d, 0x59, 0x45, 0xbf,
0xc6, 0xa5, 0xa8, 0x8c, 0x87, 0x6a, 0x5f, 0x50,
0x37, 0xbf, 0xc2, 0xa5, 0xa4, 0x8c, 0x83, 0x6a,
0x5c, 0x50, 0x34, 0xbc, 0x05, 0x00, 0x0e, 0x90,
0x00, 0xc7, 0xc2, 0xae, 0xaa, 0x95, 0x82, 0x7b,
0x60, 0x61, 0x3f, 0xb7, 0xc6, 0xa5, 0xb1, 0x8c,
0x8d, 0x72, 0x6b, 0x61, 0x51, 0xbf, 0xc4, 0xa5,
0xa5, 0x8c, 0x82, 0x72, 0x61, 0x59, 0x39, 0x6c,
0x26, 0x03, 0x95, 0x82, 0x7b, 0x61, 0x61, 0x40,
0xfc, 0x05, 0x00, 0x00, 0x7e, 0xd7, 0xc3, 0xb7,
0xa8, 0x9d, 0x80, 0x83, 0x5d, 0x6a, 0x3f, 0xbf,
0xc7, 0xa5, 0xa8, 0x8c, 0x84, 0x72, 0x60, 0x61,
0x46, 0xbf, 0xc2, 0xae, 0xb0, 0x9d, 0x92, 0x83,
0x6f, 0x6a, 0x50, 0xd7, 0xc3, 0xb7, 0xa7, 0x9d,
0x80, 0x83, 0x5e, 0x6a, 0x40, 0xfe, 0x02, 0xf8,
0x8d, 0x96, 0x90, 0x90, 0xfe, 0x05, 0x00, 0x8a,
0xc4, 0x63, 0xb8, 0x3c, 0xa6, 0x29, 0x97, 0x16,
0x81, 0x84, 0xb7, 0x5b, 0xa9, 0x33, 0x94, 0x1e,
0x83, 0x11, 0x70, 0xb8, 0xc2, 0x70, 0xb1, 0x4d,
0xa3, 0x2a, 0x8d, 0x1b, 0x7b, 0xa8, 0xbc, 0x68,
0xab, 0x47, 0x9d, 0x27, 0x87, 0x18, 0x75, 0xae,
0xc6, 0x7d, 0xbb, 0x4d, 0xaa, 0x1c, 0x84, 0x11,
0x72, 0xa3, 0xbb, 0x6e, 0xad, 0x3c, 0x97, 0x24,
0x85, 0x16, 0x71, 0x80, 0xb2, 0x57, 0xa4, 0x30,
0x8e, 0x1c, 0x7c, 0x10, 0x68, 0xbb, 0xbd, 0x75,
0xac, 0x4f, 0x9e, 0x2b, 0x87, 0x1a, 0x76, 0x96,
0xc5, 0x5e, 0xb5, 0x3e, 0xa5, 0x1f, 0x8c, 0x12,
0x7a, 0xc1, 0xc6, 0x42, 0x9f, 0x27, 0x8c, 0x16,
0x77, 0x0f, 0x67, 0x9d, 0xbc, 0x68, 0xad, 0x36,
0x95, 0x20, 0x83, 0x11, 0x6d, 0x9b, 0xb8, 0x67,
0xa8, 0x34, 0x90, 0x1f, 0x7c, 0x10, 0x67, 0x9e,
0xc9, 0x6a, 0xbb, 0x37, 0xa4, 0x20, 0x90, 0x11,
0x7b, 0xc6, 0xc8, 0x47, 0xa4, 0x2a, 0x90, 0x18,
0x7b, 0x10, 0x6c, 0xae, 0xc4, 0x5d, 0xad, 0x37,
0x9a, 0x1f, 0x85, 0x13, 0x75, 0x70, 0xad, 0x42,
0x99, 0x25, 0x84, 0x17, 0x74, 0x0b, 0x56, 0x87,
0xc8, 0x57, 0xb8, 0x2b, 0x9e, 0x19, 0x8a, 0x0d,
0x74, 0xa7, 0xc8, 0x6e, 0xb9, 0x36, 0xa0, 0x1f,
0x8b, 0x11, 0x75, 0x94, 0xbe, 0x4b, 0xa5, 0x2a,
0x92, 0x18, 0x7c, 0x0f, 0x6b, 0xaf, 0xc0, 0x58,
0xa8, 0x34, 0x94, 0x1d, 0x7d, 0x12, 0x6d, 0x82,
0xc0, 0x52, 0xb0, 0x25, 0x94, 0x14, 0x7f, 0x0c,
0x68, 0x84, 0xbf, 0x3e, 0xa4, 0x22, 0x8e, 0x10,
0x76, 0x0b, 0x65, 0x88, 0xb6, 0x42, 0x9b, 0x26,
0x87, 0x14, 0x70, 0x0c, 0x5f, 0xc5, 0xc2, 0x3e,
0x97, 0x23, 0x83, 0x13, 0x6c, 0x0c, 0x5c, 0xb1,
0xc9, 0x76, 0xbc, 0x4a, 0xaa, 0x20, 0x8d, 0x12,
0x78, 0x93, 0xbf, 0x46, 0xa3, 0x26, 0x8d, 0x14,
0x74, 0x0c, 0x62, 0xc8, 0xc4, 0x3b, 0x97, 0x21,
0x82, 0x11, 0x6a, 0x0a, 0x59, 0xa3, 0xb9, 0x68,
0xa9, 0x30, 0x8d, 0x1a, 0x78, 0x0f, 0x61, 0xa0,
0xc9, 0x73, 0xbe, 0x50, 0xb1, 0x30, 0x9f, 0x14,
0x80, 0x83, 0xb7, 0x3c, 0x9a, 0x20, 0x84, 0x0e,
0x6a, 0x0a, 0x57, 0xac, 0xc2, 0x68, 0xb0, 0x2e,
0x92, 0x19, 0x7c, 0x0d, 0x63, 0x93, 0xbe, 0x62,
0xb0, 0x3c, 0x9e, 0x1a, 0x80, 0x0e, 0x6b, 0xbb,
0x02, 0xa0, 0x02, 0xa0, 0x02, 0x6f, 0x00, 0x75,
0x00, 0x75, 0x00, 0x00, 0x00, 0xad, 0x02, 0xb3,
0x02, 0x6f, 0x00, 0x87, 0x00, 0x85, 0xfe, 0x03,
0x00, 0xc2, 0x02, 0x82, 0x4d, 0x92, 0x6e, 0x4d,
0xb1, 0xa8, 0x84, 0x01, 0x00, 0x07, 0x7e, 0x00,
0xa8, 0x02, 0xa4, 0x02, 0xa4, 0x02, 0xa2, 0x00,
0xa6, 0x00, 0xa6, 0x00, 0x00, 0x00, 0xb4, 0x02,
0xb4, 0x02, 0x92, 0x00, 0x96, 0x00, 0x96, 0x46,
0x04, 0xb0, 0x02, 0x64, 0x02, 0x0a, 0x8c, 0x00,
0x90, 0x02, 0x98, 0x02, 0x98, 0x02, 0x0e, 0x01,
0x11, 0x01, 0x11, 0x50, 0xc3, 0x08, 0x88, 0x02,
0x88, 0x02, 0x19, 0x01, 0x02, 0x01, 0x02, 0x01,
0xf3, 0x2d, 0x00, 0x00
};
/* Array of known hw_options bits with human-friendly parsing */
static struct hc_hwopt {
const u32 bit;
const char *str;
} const hc_hwopts[] = {
{
.bit = RB_HW_OPT_NO_UART,
.str = "no UART\t\t",
}, {
.bit = RB_HW_OPT_HAS_VOLTAGE,
.str = "has Vreg\t",
}, {
.bit = RB_HW_OPT_HAS_USB,
.str = "has usb\t\t",
}, {
.bit = RB_HW_OPT_HAS_ATTINY,
.str = "has ATtiny\t",
}, {
.bit = RB_HW_OPT_NO_NAND,
.str = "no NAND\t\t",
}, {
.bit = RB_HW_OPT_HAS_LCD,
.str = "has LCD\t\t",
}, {
.bit = RB_HW_OPT_HAS_POE_OUT,
.str = "has POE out\t",
}, {
.bit = RB_HW_OPT_HAS_uSD,
.str = "has MicroSD\t",
}, {
.bit = RB_HW_OPT_HAS_SIM,
.str = "has SIM\t\t",
}, {
.bit = RB_HW_OPT_HAS_SFP,
.str = "has SFP\t\t",
}, {
.bit = RB_HW_OPT_HAS_WIFI,
.str = "has WiFi\t",
}, {
.bit = RB_HW_OPT_HAS_TS_FOR_ADC,
.str = "has TS ADC\t",
}, {
.bit = RB_HW_OPT_HAS_PLC,
.str = "has PLC\t\t",
},
};
/*
* The MAC is stored network-endian on all devices, in 2 32-bit segments:
* <XX:XX:XX:XX> <XX:XX:00:00>. Kernel print has us covered.
*/
static ssize_t hc_tag_show_mac(const u8 *pld, u16 pld_len, char *buf)
{
if (8 != pld_len)
return -EINVAL;
return sprintf(buf, "%pM\n", pld);
}
/*
* Print HW options in a human readable way:
* The raw number and in decoded form
*/
static ssize_t hc_tag_show_hwoptions(const u8 *pld, u16 pld_len, char *buf)
{
char *out = buf;
u32 data; // cpu-endian
int i;
if (sizeof(data) != pld_len)
return -EINVAL;
data = *(u32 *)pld;
out += sprintf(out, "raw\t\t: 0x%08x\n\n", data);
for (i = 0; i < ARRAY_SIZE(hc_hwopts); i++)
out += sprintf(out, "%s: %s\n", hc_hwopts[i].str,
(data & hc_hwopts[i].bit) ? "true" : "false");
return out - buf;
}
static ssize_t hc_wlan_data_bin_read(struct file *filp, struct kobject *kobj,
struct bin_attribute *attr, char *buf,
loff_t off, size_t count);
static struct hc_wlan_attr {
const u16 erd_tag_id;
struct bin_attribute battr;
u16 pld_ofs;
u16 pld_len;
} hc_wd_multi_battrs[] = {
{
.erd_tag_id = RB_WLAN_ERD_ID_MULTI_8001,
.battr = __BIN_ATTR(data_0, S_IRUSR, hc_wlan_data_bin_read, NULL, 0),
}, {
.erd_tag_id = RB_WLAN_ERD_ID_MULTI_8201,
.battr = __BIN_ATTR(data_2, S_IRUSR, hc_wlan_data_bin_read, NULL, 0),
}
};
static struct hc_wlan_attr hc_wd_solo_battr = {
.erd_tag_id = RB_WLAN_ERD_ID_SOLO,
.battr = __BIN_ATTR(wlan_data, S_IRUSR, hc_wlan_data_bin_read, NULL, 0),
};
static ssize_t hc_attr_show(struct kobject *kobj, struct kobj_attribute *attr,
char *buf);
/* Array of known tags to publish in sysfs */
static struct hc_attr {
const u16 tag_id;
ssize_t (* const tshow)(const u8 *pld, u16 pld_len, char *buf);
struct kobj_attribute kattr;
u16 pld_ofs;
u16 pld_len;
} hc_attrs[] = {
{
.tag_id = RB_ID_FLASH_INFO,
.tshow = routerboot_tag_show_u32s,
.kattr = __ATTR(flash_info, S_IRUSR, hc_attr_show, NULL),
}, {
.tag_id = RB_ID_MAC_ADDRESS_PACK,
.tshow = hc_tag_show_mac,
.kattr = __ATTR(mac_base, S_IRUSR, hc_attr_show, NULL),
}, {
.tag_id = RB_ID_BOARD_PRODUCT_CODE,
.tshow = routerboot_tag_show_string,
.kattr = __ATTR(board_product_code, S_IRUSR, hc_attr_show, NULL),
}, {
.tag_id = RB_ID_BIOS_VERSION,
.tshow = routerboot_tag_show_string,
.kattr = __ATTR(booter_version, S_IRUSR, hc_attr_show, NULL),
}, {
.tag_id = RB_ID_SERIAL_NUMBER,
.tshow = routerboot_tag_show_string,
.kattr = __ATTR(board_serial, S_IRUSR, hc_attr_show, NULL),
}, {
.tag_id = RB_ID_MEMORY_SIZE,
.tshow = routerboot_tag_show_u32s,
.kattr = __ATTR(mem_size, S_IRUSR, hc_attr_show, NULL),
}, {
.tag_id = RB_ID_MAC_ADDRESS_COUNT,
.tshow = routerboot_tag_show_u32s,
.kattr = __ATTR(mac_count, S_IRUSR, hc_attr_show, NULL),
}, {
.tag_id = RB_ID_HW_OPTIONS,
.tshow = hc_tag_show_hwoptions,
.kattr = __ATTR(hw_options, S_IRUSR, hc_attr_show, NULL),
}, {
.tag_id = RB_ID_WLAN_DATA,
.tshow = NULL,
}, {
.tag_id = RB_ID_BOARD_IDENTIFIER,
.tshow = routerboot_tag_show_string,
.kattr = __ATTR(board_identifier, S_IRUSR, hc_attr_show, NULL),
}, {
.tag_id = RB_ID_PRODUCT_NAME,
.tshow = routerboot_tag_show_string,
.kattr = __ATTR(product_name, S_IRUSR, hc_attr_show, NULL),
}, {
.tag_id = RB_ID_DEFCONF,
.tshow = routerboot_tag_show_string,
.kattr = __ATTR(defconf, S_IRUSR, hc_attr_show, NULL),
}, {
.tag_id = RB_ID_BOARD_REVISION,
.tshow = routerboot_tag_show_string,
.kattr = __ATTR(board_revision, S_IRUSR, hc_attr_show, NULL),
}
};
/*
* If the RB_ID_WLAN_DATA payload starts with RB_MAGIC_ERD, then past
* that magic number the payload itself contains a routerboot tag node
* locating the LZO-compressed calibration data. So far this scheme is only
* known to use a single tag at id 0x1.
*/
static int hc_wlan_data_unpack_erd(const u16 tag_id, const u8 *inbuf, size_t inlen,
void *outbuf, size_t *outlen)
{
u16 lzo_ofs, lzo_len;
int ret;
/* Find embedded tag */
ret = routerboot_tag_find(inbuf, inlen, tag_id, &lzo_ofs, &lzo_len);
if (ret) {
pr_debug(RB_HC_PR_PFX "no ERD data for id 0x%04x\n", tag_id);
goto fail;
}
if (lzo_len > inlen) {
pr_debug(RB_HC_PR_PFX "Invalid ERD data length\n");
ret = -EINVAL;
goto fail;
}
ret = lzo1x_decompress_safe(inbuf+lzo_ofs, lzo_len, outbuf, outlen);
if (ret)
pr_debug(RB_HC_PR_PFX "LZO decompression error (%d)\n", ret);
fail:
return ret;
}
/*
* If the RB_ID_WLAN_DATA payload starts with RB_MAGIC_LZOR, then past
* that magic number is a payload that must be appended to the hc_lzor_prefix,
* the resulting blob is LZO-compressed.
* If payload starts with RB_MAGIC_LZ77, a separate (bit level LZ77)
* decompression function needs to be used. In the decompressed result,
* the RB_MAGIC_ERD magic number (aligned) must be located. Following that
* magic, there is one or more routerboot tag node(s) locating the RLE-encoded
* calibration data payload.
*/
static int hc_wlan_data_unpack_lzor_lz77(const u16 tag_id, const u8 *inbuf, size_t inlen,
void *outbuf, size_t *outlen, u32 magic)
{
u16 rle_ofs, rle_len;
const u32 *needle;
u8 *tempbuf;
size_t templen, lzo_len;
int ret;
const char lzor[] = "LZOR";
const char lz77[] = "LZ77";
const char *lz_type;
/* Temporary buffer same size as the outbuf */
templen = *outlen;
tempbuf = kmalloc(templen, GFP_KERNEL);
if (!tempbuf)
return -ENOMEM;
lzo_len = inlen;
if (magic == RB_MAGIC_LZOR)
lzo_len += sizeof(hc_lzor_prefix);
if (lzo_len > *outlen)
return -EFBIG;
switch (magic) {
case RB_MAGIC_LZOR:
lz_type = lzor;
/* Concatenate into the outbuf */
memcpy(outbuf, hc_lzor_prefix, sizeof(hc_lzor_prefix));
memcpy(outbuf + sizeof(hc_lzor_prefix), inbuf, inlen);
/* LZO-decompress lzo_len bytes of outbuf into the tempbuf */
ret = lzo1x_decompress_safe(outbuf, lzo_len, tempbuf, &templen);
if (ret) {
if (LZO_E_INPUT_NOT_CONSUMED == ret) {
/*
* The tag length is always aligned thus the LZO payload may be padded,
* which can trigger a spurious error which we ignore here.
*/
pr_debug(RB_HC_PR_PFX "LZOR: LZO EOF before buffer end - this may be harmless\n");
} else {
pr_debug(RB_HC_PR_PFX "LZOR: LZO decompression error (%d)\n", ret);
goto fail;
}
}
break;
case RB_MAGIC_LZ77:
lz_type = lz77;
/* LZO-decompress lzo_len bytes of inbuf into the tempbuf */
ret = rb_lz77_decompress(inbuf, inlen, tempbuf, &templen);
if (ret) {
pr_err(RB_HC_PR_PFX "LZ77: LZ77 decompress error %d\n", ret);
goto fail;
}
pr_debug(RB_HC_PR_PFX "LZ77: decompressed from %zu to %zu\n",
inlen, templen);
break;
default:
return -EINVAL;
break;
}
/*
* Post decompression we have a blob (possibly byproduct of the lzo
* dictionary). We need to find RB_MAGIC_ERD. The magic number seems to
* be 32bit-aligned in the decompression output.
*/
needle = (const u32 *)tempbuf;
while (RB_MAGIC_ERD != *needle++) {
if ((u8 *)needle >= tempbuf+templen) {
pr_warn(RB_HC_PR_PFX "%s: ERD magic not found. Decompressed first word: 0x%08x\n", lz_type, *(u32 *)tempbuf);
ret = -ENODATA;
goto fail;
}
};
templen -= (u8 *)needle - tempbuf;
/* Past magic. Look for tag node */
ret = routerboot_tag_find((u8 *)needle, templen, tag_id, &rle_ofs, &rle_len);
if (ret) {
pr_debug(RB_HC_PR_PFX "%s: no RLE data for id 0x%04x\n", lz_type, tag_id);
goto fail;
}
if (rle_len > templen) {
pr_debug(RB_HC_PR_PFX "%s: Invalid RLE data length\n", lz_type);
ret = -EINVAL;
goto fail;
}
/* RLE-decode tempbuf from needle back into the outbuf */
ret = routerboot_rle_decode((u8 *)needle+rle_ofs, rle_len, outbuf, outlen);
if (ret)
pr_debug(RB_HC_PR_PFX "%s: RLE decoding error (%d)\n", lz_type, ret);
fail:
kfree(tempbuf);
return ret;
}
static int hc_wlan_data_unpack(const u16 tag_id, const size_t tofs, size_t tlen,
void *outbuf, size_t *outlen)
{
const u8 *lbuf;
u32 magic;
int ret;
/* Caller ensure tlen > 0. tofs is aligned */
if ((tofs + tlen) > hc_buflen)
return -EIO;
lbuf = hc_buf + tofs;
magic = *(u32 *)lbuf;
ret = -ENODATA;
switch (magic) {
case RB_MAGIC_LZ77:
/* no known instances of lz77 without 8001/8201 data, skip SOLO */
if (tag_id == RB_WLAN_ERD_ID_SOLO) {
pr_debug(RB_HC_PR_PFX "skipped LZ77 decompress in search for SOLO tag\n");
break;
}
fallthrough;
case RB_MAGIC_LZOR:
/* Skip magic */
lbuf += sizeof(magic);
tlen -= sizeof(magic);
ret = hc_wlan_data_unpack_lzor_lz77(tag_id, lbuf, tlen, outbuf, outlen, magic);
break;
case RB_MAGIC_ERD:
/* Skip magic */
lbuf += sizeof(magic);
tlen -= sizeof(magic);
ret = hc_wlan_data_unpack_erd(tag_id, lbuf, tlen, outbuf, outlen);
break;
default:
/*
* If the RB_ID_WLAN_DATA payload doesn't start with a
* magic number, the payload itself is the raw RLE-encoded
* calibration data. Only RB_WLAN_ERD_ID_SOLO makes sense here.
*/
if (RB_WLAN_ERD_ID_SOLO == tag_id) {
ret = routerboot_rle_decode(lbuf, tlen, outbuf, outlen);
if (ret)
pr_debug(RB_HC_PR_PFX "RLE decoding error (%d)\n", ret);
}
break;
}
return ret;
}
static ssize_t hc_attr_show(struct kobject *kobj, struct kobj_attribute *attr,
char *buf)
{
const struct hc_attr *hc_attr;
const u8 *pld;
u16 pld_len;
hc_attr = container_of(attr, typeof(*hc_attr), kattr);
if (!hc_attr->pld_len)
return -ENOENT;
pld = hc_buf + hc_attr->pld_ofs;
pld_len = hc_attr->pld_len;
return hc_attr->tshow(pld, pld_len, buf);
}
/*
* This function will allocate and free memory every time it is called. This
* is not the fastest way to do this, but since the data is rarely read (mainly
* at boot time to load wlan caldata), this makes it possible to save memory for
* the system.
*/
static ssize_t hc_wlan_data_bin_read(struct file *filp, struct kobject *kobj,
struct bin_attribute *attr, char *buf,
loff_t off, size_t count)
{
struct hc_wlan_attr *hc_wattr;
size_t outlen;
void *outbuf;
int ret;
hc_wattr = container_of(attr, typeof(*hc_wattr), battr);
if (!hc_wattr->pld_len)
return -ENOENT;
outlen = RB_ART_SIZE;
/* Don't bother unpacking if the source is already too large */
if (hc_wattr->pld_len > outlen)
return -EFBIG;
outbuf = kmalloc(outlen, GFP_KERNEL);
if (!outbuf)
return -ENOMEM;
ret = hc_wlan_data_unpack(hc_wattr->erd_tag_id, hc_wattr->pld_ofs, hc_wattr->pld_len, outbuf, &outlen);
if (ret) {
kfree(outbuf);
return ret;
}
if (off >= outlen) {
kfree(outbuf);
return 0;
}
if (off + count > outlen)
count = outlen - off;
memcpy(buf, outbuf + off, count);
kfree(outbuf);
return count;
}
int rb_hardconfig_init(struct kobject *rb_kobj, struct mtd_info *mtd)
{
struct kobject *hc_wlan_kobj;
size_t bytes_read, buflen, outlen;
const u8 *buf;
void *outbuf;
int i, j, ret;
u32 magic;
hc_buf = NULL;
hc_kobj = NULL;
hc_wlan_kobj = NULL;
ret = __get_mtd_device(mtd);
if (ret)
return -ENODEV;
hc_buflen = mtd->size;
hc_buf = kmalloc(hc_buflen, GFP_KERNEL);
if (!hc_buf) {
__put_mtd_device(mtd);
return -ENOMEM;
}
ret = mtd_read(mtd, 0, hc_buflen, &bytes_read, hc_buf);
__put_mtd_device(mtd);
if (ret)
goto fail;
if (bytes_read != hc_buflen) {
ret = -EIO;
goto fail;
}
/* Check we have what we expect */
magic = *(const u32 *)hc_buf;
if (RB_MAGIC_HARD != magic) {
ret = -EINVAL;
goto fail;
}
/* Skip magic */
buf = hc_buf + sizeof(magic);
buflen = hc_buflen - sizeof(magic);
/* Populate sysfs */
ret = -ENOMEM;
hc_kobj = kobject_create_and_add(RB_MTD_HARD_CONFIG, rb_kobj);
if (!hc_kobj)
goto fail;
/* Locate and publish all known tags */
for (i = 0; i < ARRAY_SIZE(hc_attrs); i++) {
ret = routerboot_tag_find(buf, buflen, hc_attrs[i].tag_id,
&hc_attrs[i].pld_ofs, &hc_attrs[i].pld_len);
if (ret) {
hc_attrs[i].pld_ofs = hc_attrs[i].pld_len = 0;
continue;
}
/* Account for skipped magic */
hc_attrs[i].pld_ofs += sizeof(magic);
/*
* Special case RB_ID_WLAN_DATA to prep and create the binary attribute.
* We first check if the data is "old style" within a single tag (or no tag at all):
* If it is we publish this single blob as a binary attribute child of hc_kobj to
* preserve backward compatibility.
* If it isn't and instead uses multiple ERD tags, we create a subfolder and
* publish the known ones there.
*/
if ((RB_ID_WLAN_DATA == hc_attrs[i].tag_id) && hc_attrs[i].pld_len) {
outlen = RB_ART_SIZE;
outbuf = kmalloc(outlen, GFP_KERNEL);
if (!outbuf) {
pr_warn(RB_HC_PR_PFX "Out of memory parsing WLAN tag\n");
continue;
}
/* Test ID_SOLO first, if found: done */
ret = hc_wlan_data_unpack(RB_WLAN_ERD_ID_SOLO, hc_attrs[i].pld_ofs, hc_attrs[i].pld_len, outbuf, &outlen);
if (!ret) {
hc_wd_solo_battr.pld_ofs = hc_attrs[i].pld_ofs;
hc_wd_solo_battr.pld_len = hc_attrs[i].pld_len;
ret = sysfs_create_bin_file(hc_kobj, &hc_wd_solo_battr.battr);
if (ret)
pr_warn(RB_HC_PR_PFX "Could not create %s sysfs entry (%d)\n",
hc_wd_solo_battr.battr.attr.name, ret);
}
/* Otherwise, create "wlan_data" subtree and publish known data */
else {
hc_wlan_kobj = kobject_create_and_add("wlan_data", hc_kobj);
if (!hc_wlan_kobj) {
kfree(outbuf);
pr_warn(RB_HC_PR_PFX "Could not create wlan_data sysfs folder\n");
continue;
}
for (j = 0; j < ARRAY_SIZE(hc_wd_multi_battrs); j++) {
outlen = RB_ART_SIZE;
ret = hc_wlan_data_unpack(hc_wd_multi_battrs[j].erd_tag_id,
hc_attrs[i].pld_ofs, hc_attrs[i].pld_len, outbuf, &outlen);
if (ret) {
hc_wd_multi_battrs[j].pld_ofs = hc_wd_multi_battrs[j].pld_len = 0;
continue;
}
hc_wd_multi_battrs[j].pld_ofs = hc_attrs[i].pld_ofs;
hc_wd_multi_battrs[j].pld_len = hc_attrs[i].pld_len;
ret = sysfs_create_bin_file(hc_wlan_kobj, &hc_wd_multi_battrs[j].battr);
if (ret)
pr_warn(RB_HC_PR_PFX "Could not create wlan_data/%s sysfs entry (%d)\n",
hc_wd_multi_battrs[j].battr.attr.name, ret);
}
}
kfree(outbuf);
}
/* All other tags are published via standard attributes */
else {
ret = sysfs_create_file(hc_kobj, &hc_attrs[i].kattr.attr);
if (ret)
pr_warn(RB_HC_PR_PFX "Could not create %s sysfs entry (%d)\n",
hc_attrs[i].kattr.attr.name, ret);
}
}
pr_info("MikroTik RouterBOARD hardware configuration sysfs driver v" RB_HARDCONFIG_VER "\n");
return 0;
fail:
kfree(hc_buf);
hc_buf = NULL;
return ret;
}
void rb_hardconfig_exit(void)
{
kobject_put(hc_kobj);
hc_kobj = NULL;
kfree(hc_buf);
hc_buf = NULL;
}

View file

@ -0,0 +1,32 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Common definitions for MikroTik RouterBoot hard config data.
*
* Copyright (C) 2020 Thibaut VARÈNE <hacks+kernel@slashdirt.org>
*
* Some constant defines extracted from routerboot.{c,h} by Gabor Juhos
* <juhosg@openwrt.org>
*/
#ifndef _ROUTERBOOT_HARD_CONFIG_H_
#define _ROUTERBOOT_HARD_CONFIG_H_
/* ID values for hardware settings */
#define RB_ID_FLASH_INFO 0x03
#define RB_ID_MAC_ADDRESS_PACK 0x04
#define RB_ID_BOARD_PRODUCT_CODE 0x05
#define RB_ID_BIOS_VERSION 0x06
#define RB_ID_SDRAM_TIMINGS 0x08
#define RB_ID_DEVICE_TIMINGS 0x09
#define RB_ID_SOFTWARE_ID 0x0A
#define RB_ID_SERIAL_NUMBER 0x0B
#define RB_ID_MEMORY_SIZE 0x0D
#define RB_ID_MAC_ADDRESS_COUNT 0x0E
#define RB_ID_HW_OPTIONS 0x15
#define RB_ID_WLAN_DATA 0x16
#define RB_ID_BOARD_IDENTIFIER 0x17
#define RB_ID_PRODUCT_NAME 0x21
#define RB_ID_DEFCONF 0x26
#define RB_ID_BOARD_REVISION 0x27
#endif /* _ROUTERBOOT_HARD_CONFIG_H_ */

View file

@ -0,0 +1,446 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2023 John Thomson
*/
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/errno.h>
#include <linux/minmax.h>
#include "rb_lz77.h"
#define MIKRO_LZ77 "[rb lz77] "
/*
* The maximum number of bits used in a counter.
* For the look behind window, long instruction match offsets
* up to 6449 have been seen in provided compressed caldata blobs
* (that would need 21 counter bits: 4 to 12 + 11 to 0).
* conservative value here: 27 provides offset up to 0x8000 bytes
* uses a u8 in this code
*/
#define MIKRO_LZ77_MAX_COUNT_BIT_LEN 27
enum rb_lz77_instruction {
INSTR_ERROR = -1,
INSTR_LITERAL_BYTE = 0,
/* a (non aligned) byte follows this instruction,
* which is directly copied into output
*/
INSTR_PREVIOUS_OFFSET = 1,
/* this group is a match, with a bytes length defined by
* following counter bits, starting at bitshift 0,
* less the built-in count of 1
* using the previous offset as source
*/
INSTR_LONG = 2
/* this group has two counters,
* the first counter starts at bitshift 4,
* if this counter == 0, this is a non-matching group
* the second counter (bytes length) starts at bitshift 4,
* less the built-in count of 11+1.
* The final match group has this count 0,
* and following bits which pad to byte-alignment.
*
* if this counter > 0, this is a matching group
* this first count is the match offset (in bytes)
* the second count is the match length (in bytes),
* less the built-in count of 2
* these groups can source bytes that are part of this group
*/
};
struct rb_lz77_instr_opcodes {
/* group instruction */
enum rb_lz77_instruction instruction;
/* if >0, a match group,
* which starts at byte output_position - 1*offset
*/
size_t offset;
/* how long the match group is,
* or how long the (following counter) non-match group is
*/
size_t length;
/* how many bits were used for this instruction + op code(s) */
size_t bits_used;
/* input char */
u8 *in;
/* offset where this instruction started */
size_t in_pos;
};
/**
* rb_lz77_get_bit
*
* @in: compressed data ptr
* @in_offset_bit: bit offset to extract
*
* convert the bit offset to byte offset,
* shift to modulo of bits per bytes, so that wanted bit is lsb
* and to extract only that bit.
* Caller is responsible for ensuring that in_offset_bit/8
* does not exceed input length
*/
static inline u8 rb_lz77_get_bit(const u8 *in, const size_t in_offset_bit)
{
return ((in[in_offset_bit / BITS_PER_BYTE] >>
(in_offset_bit % BITS_PER_BYTE)) &
1);
}
/**
* rb_lz77_get_byte
*
* @in: compressed data
* @in_offset_bit: bit offset to extract byte
*/
static inline u8 rb_lz77_get_byte(const u8 *in, const size_t in_offset_bit)
{
u8 buf = 0;
int i;
/* built a reversed byte from (likely) unaligned bits */
for (i = 0; i <= 7; ++i)
buf += rb_lz77_get_bit(in, in_offset_bit + i) << (7 - i);
return buf;
}
/**
* rb_lz77_decode_count - decode bits at given offset as a count
*
* @in: compressed data
* @in_len: length of compressed data
* @in_offset_bit: bit offset where count starts
* @shift: left shift operand value of first count bit
* @count: initial count
* @bits_used: how many bits were consumed by this count
* @max_bits: maximum bit count for this counter
*
* Returns the decoded count
*/
static int rb_lz77_decode_count(const u8 *in, const size_t in_len,
const size_t in_offset_bit, u8 shift,
size_t count, u8 *bits_used, const u8 max_bits)
{
size_t pos = in_offset_bit;
const size_t max_pos = min(pos + max_bits, in_len * BITS_PER_BYTE);
bool up = true;
*bits_used = 0;
pr_debug(MIKRO_LZ77
"decode_count inbit: %zu, start shift:%u, initial count:%zu\n",
in_offset_bit, shift, count);
while (true) {
/* check the input offset bit does not overflow the minimum of
* a reasonable length for this encoded count, and
* the end of the input */
if (unlikely(pos >= max_pos)) {
pr_err(MIKRO_LZ77
"max bit index reached before count completed\n");
return -EFBIG;
}
/* if the bit value at offset is set */
if (rb_lz77_get_bit(in, pos))
count += (1 << shift);
/* shift increases until we find an unsed bit */
else if (up)
up = false;
if (up)
++shift;
else {
if (!shift) {
*bits_used = pos - in_offset_bit + 1;
return count;
}
--shift;
}
++pos;
}
return -EINVAL;
}
/**
* rb_lz77_decode_instruction
*
* @in: compressed data
* @in_offset_bit: bit offset where instruction starts
* @bits_used: how many bits were consumed by this count
*
* Returns the decoded instruction
*/
static enum rb_lz77_instruction
rb_lz77_decode_instruction(const u8 *in, size_t in_offset_bit, u8 *bits_used)
{
if (rb_lz77_get_bit(in, in_offset_bit)) {
*bits_used = 2;
if (rb_lz77_get_bit(in, ++in_offset_bit))
return INSTR_LONG;
else
return INSTR_PREVIOUS_OFFSET;
} else {
*bits_used = 1;
return INSTR_LITERAL_BYTE;
}
return INSTR_ERROR;
}
/**
* rb_lz77_decode_instruction_operators
*
* @in: compressed data
* @in_len: length of compressed data
* @in_offset_bit: bit offset where instruction starts
* @previous_offset: last used match offset
* @opcode: struct to hold instruction & operators
*
* Returns error code
*/
static int rb_lz77_decode_instruction_operators(
const u8 *in, const size_t in_len, const size_t in_offset_bit,
const size_t previous_offset, struct rb_lz77_instr_opcodes *opcode)
{
enum rb_lz77_instruction instruction;
u8 bit_count = 0;
u8 bits_used = 0;
int offset = 0;
int length = 0;
instruction = rb_lz77_decode_instruction(in, in_offset_bit, &bit_count);
/* skip bits used by instruction */
bits_used += bit_count;
switch (instruction) {
case INSTR_LITERAL_BYTE:
/* non-matching char */
offset = 0;
length = 1;
break;
case INSTR_PREVIOUS_OFFSET:
/* matching group uses previous offset */
offset = previous_offset;
length = rb_lz77_decode_count(in, in_len,
in_offset_bit + bits_used, 0, 1,
&bit_count,
MIKRO_LZ77_MAX_COUNT_BIT_LEN);
if (unlikely(length < 0))
return length;
/* skip bits used by count */
bits_used += bit_count;
break;
case INSTR_LONG:
offset = rb_lz77_decode_count(in, in_len,
in_offset_bit + bits_used, 4, 0,
&bit_count,
MIKRO_LZ77_MAX_COUNT_BIT_LEN);
if (unlikely(offset < 0))
return offset;
/* skip bits used by offset count */
bits_used += bit_count;
if (offset == 0) {
/* non-matching long group */
length = rb_lz77_decode_count(
in, in_len, in_offset_bit + bits_used, 4, 12,
&bit_count, MIKRO_LZ77_MAX_COUNT_BIT_LEN);
if (unlikely(length < 0))
return length;
/* skip bits used by length count */
bits_used += bit_count;
} else {
/* matching group */
length = rb_lz77_decode_count(
in, in_len, in_offset_bit + bits_used, 0, 2,
&bit_count, MIKRO_LZ77_MAX_COUNT_BIT_LEN);
if (unlikely(length < 0))
return length;
/* skip bits used by length count */
bits_used += bit_count;
}
break;
case INSTR_ERROR:
return -EINVAL;
}
opcode->instruction = instruction;
opcode->offset = offset;
opcode->length = length;
opcode->bits_used = bits_used;
opcode->in = (u8 *)in;
opcode->in_pos = in_offset_bit;
return 0;
}
/**
* rb_lz77_decompress
*
* @in: compressed data ptr
* @in_len: length of compressed data
* @out: buffer ptr to decompress into
* @out_len: length of decompressed buffer in input,
* length of decompressed data in success
*
* Returns 0 on success, or negative error
*/
int rb_lz77_decompress(const u8 *in, const size_t in_len, u8 *out,
size_t *out_len)
{
u8 *output_ptr;
size_t input_bit = 0;
const u8 *output_end = out + *out_len;
struct rb_lz77_instr_opcodes *opcode;
size_t match_offset = 0;
int rc = 0;
size_t match_length, partial_count, i;
output_ptr = out;
if (unlikely((in_len * BITS_PER_BYTE) > SIZE_MAX)) {
pr_err(MIKRO_LZ77 "input longer than expected\n");
return -EFBIG;
}
opcode = kmalloc(sizeof(*opcode), GFP_KERNEL);
if (!opcode)
return -ENOMEM;
while (true) {
if (unlikely(output_ptr > output_end)) {
pr_err(MIKRO_LZ77 "output overrun\n");
rc = -EOVERFLOW;
goto free_lz77_struct;
}
if (unlikely(input_bit > in_len * BITS_PER_BYTE)) {
pr_err(MIKRO_LZ77 "input overrun\n");
rc = -ENODATA;
goto free_lz77_struct;
}
rc = rb_lz77_decode_instruction_operators(in, in_len, input_bit,
match_offset, opcode);
if (unlikely(rc < 0)) {
pr_err(MIKRO_LZ77
"instruction operands decode error\n");
goto free_lz77_struct;
}
pr_debug(MIKRO_LZ77 "inbit:0x%zx->outbyte:0x%zx", input_bit,
output_ptr - out);
input_bit += opcode->bits_used;
switch (opcode->instruction) {
case INSTR_LITERAL_BYTE:
pr_debug(" short");
fallthrough;
case INSTR_LONG:
if (opcode->offset == 0) {
/* this is a non-matching group */
pr_debug(" non-match, len: 0x%zx\n",
opcode->length);
/* test end marker */
if (opcode->length == 0xc &&
((input_bit +
opcode->length * BITS_PER_BYTE) >
in_len)) {
*out_len = output_ptr - out;
pr_debug(
MIKRO_LZ77
"lz77 decompressed from %zu to %zu\n",
in_len, *out_len);
rc = 0;
goto free_lz77_struct;
}
for (i = opcode->length; i > 0; --i) {
*output_ptr =
rb_lz77_get_byte(in, input_bit);
++output_ptr;
input_bit += BITS_PER_BYTE;
}
/* do no fallthrough if a non-match group */
break;
}
match_offset = opcode->offset;
fallthrough;
case INSTR_PREVIOUS_OFFSET:
match_length = opcode->length;
partial_count = 0;
pr_debug(" match, offset: 0x%zx, len: 0x%zx",
opcode->offset, match_length);
if (unlikely(opcode->offset == 0)) {
pr_err(MIKRO_LZ77
"match group missing opcode->offset\n");
rc = -EBADMSG;
goto free_lz77_struct;
}
/* overflow */
if (unlikely((output_ptr + match_length) >
output_end)) {
pr_err(MIKRO_LZ77
"match group output overflow\n");
rc = -ENOBUFS;
goto free_lz77_struct;
}
/* underflow */
if (unlikely((output_ptr - opcode->offset) < out)) {
pr_err(MIKRO_LZ77
"match group offset underflow\n");
rc = -ESPIPE;
goto free_lz77_struct;
}
/* there are cases where the match (length) includes
* data that is a part of the same match
*/
while (opcode->offset < match_length) {
++partial_count;
memcpy(output_ptr, output_ptr - opcode->offset,
opcode->offset);
output_ptr += opcode->offset;
match_length -= opcode->offset;
}
memcpy(output_ptr, output_ptr - opcode->offset,
match_length);
output_ptr += match_length;
if (partial_count)
pr_debug(" (%zu partial memcpy)",
partial_count);
pr_debug("\n");
break;
case INSTR_ERROR:
rc = -EINVAL;
goto free_lz77_struct;
}
}
pr_err(MIKRO_LZ77 "decode loop broken\n");
rc = -EINVAL;
free_lz77_struct:
kfree(opcode);
return rc;
}
EXPORT_SYMBOL_GPL(rb_lz77_decompress);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Mikrotik Wi-Fi caldata LZ77 decompressor");
MODULE_AUTHOR("John Thomson");

View file

@ -0,0 +1,35 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2024 John Thomson
*/
#ifndef __MIKROTIK_WLAN_LZ77_H__
#define __MIKROTIK_WLAN_LZ77_H__
#include <linux/errno.h>
#ifdef CONFIG_MIKROTIK_WLAN_DECOMPRESS_LZ77
/**
* rb_lz77_decompress
*
* @in: compressed data ptr
* @in_len: length of compressed data
* @out: buffer ptr to decompress into
* @out_len: length of decompressed buffer in input,
* length of decompressed data in success
*
* Returns 0 on success, or negative error
*/
int rb_lz77_decompress(const u8 *in, const size_t in_len, u8 *out,
size_t *out_len);
#else /* CONFIG_MIKROTIK_WLAN_DECOMPRESS_LZ77 */
static inline int rb_lz77_decompress(const u8 *in, const size_t in_len, u8 *out,
size_t *out_len)
{
return -EOPNOTSUPP;
}
#endif /* CONFIG_MIKROTIK_WLAN_DECOMPRESS_LZ77 */
#endif /* __MIKROTIK_WLAN_LZ77_H__ */

View file

@ -0,0 +1,230 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* NVMEM layout driver for MikroTik Routerboard hard config cells
*
* Copyright (C) 2024 Robert Marko <robimarko@gmail.com>
* Based on the sysfs hard config driver by Thibaut VARÈNE <hacks+kernel@slashdirt.org>
* Comments documenting the format carried over from routerboot.c
*/
#include <linux/bitfield.h>
#include <linux/etherdevice.h>
#include <linux/mod_devicetable.h>
#include <linux/nvmem-consumer.h>
#include <linux/nvmem-provider.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include "rb_hardconfig.h"
#include "routerboot.h"
#define TLV_TAG_MASK GENMASK(15, 0)
#define TLV_LEN_MASK GENMASK(31, 16)
static const char *rb_tlv_cell_name(u16 tag)
{
switch (tag) {
case RB_ID_FLASH_INFO:
return "flash-info";
case RB_ID_MAC_ADDRESS_PACK:
return "base-mac-address";
case RB_ID_BOARD_PRODUCT_CODE:
return "board-product-code";
case RB_ID_BIOS_VERSION:
return "booter-version";
case RB_ID_SERIAL_NUMBER:
return "board-serial";
case RB_ID_MEMORY_SIZE:
return "mem-size";
case RB_ID_MAC_ADDRESS_COUNT:
return "mac-count";
case RB_ID_HW_OPTIONS:
return "hw-options";
case RB_ID_WLAN_DATA:
return "wlan-data";
case RB_ID_BOARD_IDENTIFIER:
return "board-identifier";
case RB_ID_PRODUCT_NAME:
return "product-name";
case RB_ID_DEFCONF:
return "defconf";
case RB_ID_BOARD_REVISION:
return "board-revision";
default:
break;
}
return NULL;
}
static int rb_tlv_mac_read_cb(void *priv, const char *id, int index,
unsigned int offset, void *buf,
size_t bytes)
{
if (index < 0)
return -EINVAL;
if (!is_valid_ether_addr(buf))
return -EINVAL;
eth_addr_add(buf, index);
return 0;
}
static nvmem_cell_post_process_t rb_tlv_read_cb(u16 tag)
{
switch (tag) {
case RB_ID_MAC_ADDRESS_PACK:
return &rb_tlv_mac_read_cb;
default:
break;
}
return NULL;
}
static int rb_add_cells(struct device *dev, struct nvmem_device *nvmem,
const size_t data_len, u8 *data)
{
u32 node, offset = sizeof(RB_MAGIC_HARD);
struct nvmem_cell_info cell = {};
struct device_node *layout;
u16 tlv_tag, tlv_len;
int ret;
layout = of_nvmem_layout_get_container(nvmem);
if (!layout)
return -ENOENT;
/*
* Routerboot tag nodes are u32 values:
* - The low nibble is the tag identification number,
* - The high nibble is the tag payload length (node excluded) in bytes.
* Tag nodes are CPU-endian.
* Tag nodes are 32bit-aligned.
*
* The payload immediately follows the tag node.
* Payload offset will always be aligned. while length may not end on 32bit
* boundary (the only known case is when parsing ERD data).
* The payload is CPU-endian when applicable.
* Tag nodes are not ordered (by ID) on flash.
*/
while ((offset + sizeof(node)) <= data_len) {
node = *((const u32 *) (data + offset));
/* Tag list ends with null node */
if (!node)
break;
tlv_tag = FIELD_GET(TLV_TAG_MASK, node);
tlv_len = FIELD_GET(TLV_LEN_MASK, node);
offset += sizeof(node);
if (offset + tlv_len > data_len) {
dev_err(dev, "Out of bounds field (0x%x bytes at 0x%x)\n",
tlv_len, offset);
break;
}
cell.name = rb_tlv_cell_name(tlv_tag);
if (!cell.name)
goto skip;
cell.offset = offset;
/*
* MikroTik stores MAC-s with length of 8 bytes,
* but kernel expects it to be ETH_ALEN (6 bytes),
* so we need to make sure that is the case.
*/
if (tlv_tag == RB_ID_MAC_ADDRESS_PACK)
cell.bytes = ETH_ALEN;
else
cell.bytes = tlv_len;
cell.np = of_get_child_by_name(layout, cell.name);
cell.read_post_process = rb_tlv_read_cb(tlv_tag);
ret = nvmem_add_one_cell(nvmem, &cell);
if (ret) {
of_node_put(layout);
return ret;
}
/*
* The only known situation where len may not end on 32bit
* boundary is within ERD data. Since we're only extracting
* one tag (the first and only one) from that data, we should
* never need to forcefully ALIGN(). Do it anyway, this is not a
* performance path.
*/
skip:
offset += ALIGN(tlv_len, sizeof(offset));
}
of_node_put(layout);
return 0;
}
static int rb_parse_table(struct nvmem_layout *layout)
{
struct nvmem_device *nvmem = layout->nvmem;
struct device *dev = &layout->dev;
size_t mtd_size;
u8 *data;
u32 hdr;
int ret;
ret = nvmem_device_read(nvmem, 0, sizeof(hdr), &hdr);
if (ret < 0)
return ret;
if (hdr != RB_MAGIC_HARD) {
dev_err(dev, "Invalid header\n");
return -EINVAL;
}
mtd_size = nvmem_dev_size(nvmem);
data = devm_kmalloc(dev, mtd_size, GFP_KERNEL);
if (!data)
return -ENOMEM;
ret = nvmem_device_read(nvmem, 0, mtd_size, data);
if (ret != mtd_size)
return ret;
return rb_add_cells(dev, nvmem, mtd_size, data);
}
static int rb_nvmem_probe(struct nvmem_layout *layout)
{
layout->add_cells = rb_parse_table;
return nvmem_layout_register(layout);
}
static void rb_nvmem_remove(struct nvmem_layout *layout)
{
nvmem_layout_unregister(layout);
}
static const struct of_device_id rb_nvmem_of_match_table[] = {
{ .compatible = "mikrotik,routerboot-nvmem", },
{},
};
MODULE_DEVICE_TABLE(of, rb_nvmem_of_match_table);
static struct nvmem_layout_driver rb_nvmem_layout = {
.probe = rb_nvmem_probe,
.remove = rb_nvmem_remove,
.driver = {
.owner = THIS_MODULE,
.name = "rb_nvmem",
.of_match_table = rb_nvmem_of_match_table,
},
};
module_nvmem_layout_driver(rb_nvmem_layout);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Robert Marko <robimarko@gmail.com>");
MODULE_DESCRIPTION("NVMEM layout driver for MikroTik Routerboard hard config cells");

View file

@ -0,0 +1,795 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Driver for MikroTik RouterBoot soft config.
*
* Copyright (C) 2020 Thibaut VARÈNE <hacks+kernel@slashdirt.org>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 as published
* by the Free Software Foundation.
*
* This driver exposes the data encoded in the "soft_config" flash segment of
* MikroTik RouterBOARDs devices. It presents the data in a sysfs folder
* named "soft_config". The data is presented in a user/machine-friendly way
* with just as much parsing as can be generalized across mikrotik platforms
* (as inferred from reverse-engineering).
*
* The known soft_config tags are presented in the "soft_config" sysfs folder,
* with the addition of one specific file named "commit", which is only
* available if the driver supports writes to the mtd device: no modifications
* made to any of the other attributes are actually written back to flash media
* until a true value is input into this file (e.g. [Yy1]). This is to avoid
* unnecessary flash wear, and to permit to revert all changes by issuing a
* false value ([Nn0]). Reading the content of this file shows the current
* status of the driver: if the data in sysfs matches the content of the
* soft_config partition, the file will read "clean". Otherwise, it will read
* "dirty".
*
* The writeable sysfs files presented by this driver will accept only inputs
* which are in a valid range for the given tag. As a design choice, the driver
* will not assess whether the inputs are identical to the existing data.
*
* Note: PAGE_SIZE is assumed to be >= 4K, hence the device attribute show
* routines need not check for output overflow.
*
* Some constant defines extracted from rbcfg.h by Gabor Juhos
* <juhosg@openwrt.org>
*/
#include <linux/types.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/errno.h>
#include <linux/kobject.h>
#include <linux/string.h>
#include <linux/mtd/mtd.h>
#include <linux/sysfs.h>
#include <linux/version.h>
#include <linux/capability.h>
#include <linux/spinlock.h>
#include <linux/crc32.h>
#ifdef CONFIG_ATH79
#include <asm/mach-ath79/ath79.h>
#endif
#include "routerboot.h"
#define RB_SOFTCONFIG_VER "0.05"
#define RB_SC_PR_PFX "[rb_softconfig] "
#define RB_SC_HAS_WRITE_SUPPORT true
#define RB_SC_WMODE S_IWUSR
#define RB_SC_RMODE S_IRUSR
/* ID values for software settings */
#define RB_SCID_UART_SPEED 0x01 // u32*1
#define RB_SCID_BOOT_DELAY 0x02 // u32*1
#define RB_SCID_BOOT_DEVICE 0x03 // u32*1
#define RB_SCID_BOOT_KEY 0x04 // u32*1
#define RB_SCID_CPU_MODE 0x05 // u32*1
#define RB_SCID_BIOS_VERSION 0x06 // str
#define RB_SCID_BOOT_PROTOCOL 0x09 // u32*1
#define RB_SCID_CPU_FREQ_IDX 0x0C // u32*1
#define RB_SCID_BOOTER 0x0D // u32*1
#define RB_SCID_SILENT_BOOT 0x0F // u32*1
/*
* protected_routerboot seems to use tag 0x1F. It only works in combination with
* RouterOS, resulting in a wiped board otherwise, so it's not implemented here.
* The tag values are as follows:
* - off: 0x0
* - on: the lower halfword encodes the max value in s for the reset feature,
* the higher halfword encodes the min value in s for the reset feature.
* Default value when on: 0x00140258: 0x14 = 20s / 0x258= 600s
* See details here: https://wiki.mikrotik.com/wiki/Manual:RouterBOARD_settings#Protected_bootloader
*/
/* Tag values */
#define RB_UART_SPEED_115200 0
#define RB_UART_SPEED_57600 1
#define RB_UART_SPEED_38400 2
#define RB_UART_SPEED_19200 3
#define RB_UART_SPEED_9600 4
#define RB_UART_SPEED_4800 5
#define RB_UART_SPEED_2400 6
#define RB_UART_SPEED_1200 7
#define RB_UART_SPEED_OFF 8
/* valid boot delay: 1 - 9s in 1s increment */
#define RB_BOOT_DELAY_MIN 1
#define RB_BOOT_DELAY_MAX 9
#define RB_BOOT_DEVICE_ETHER 0 // "boot over Ethernet"
#define RB_BOOT_DEVICE_NANDETH 1 // "boot from NAND, if fail then Ethernet"
#define RB_BOOT_DEVICE_CFCARD 2 // (not available in rbcfg)
#define RB_BOOT_DEVICE_ETHONCE 3 // "boot Ethernet once, then NAND"
#define RB_BOOT_DEVICE_NANDONLY 5 // "boot from NAND only"
#define RB_BOOT_DEVICE_FLASHCFG 7 // "boot in flash configuration mode"
#define RB_BOOT_DEVICE_FLSHONCE 8 // "boot in flash configuration mode once, then NAND"
/*
* ATH79 9xxx CPU frequency indices.
* It is unknown if they apply to all ATH79 RBs, and some do not seem to feature
* the upper levels (QCA955x), while F is presumably AR9344-only.
*/
#define RB_CPU_FREQ_IDX_ATH79_9X_A (0 << 3)
#define RB_CPU_FREQ_IDX_ATH79_9X_B (1 << 3) // 0x8
#define RB_CPU_FREQ_IDX_ATH79_9X_C (2 << 3) // 0x10 - factory freq for many devices
#define RB_CPU_FREQ_IDX_ATH79_9X_D (3 << 3) // 0x18
#define RB_CPU_FREQ_IDX_ATH79_9X_E (4 << 3) // 0x20
#define RB_CPU_FREQ_IDX_ATH79_9X_F (5 << 3) // 0x28
#define RB_CPU_FREQ_IDX_ATH79_9X_MIN 0 // all devices support lowest setting
#define RB_CPU_FREQ_IDX_ATH79_9X_AR9334_MAX 5 // stops at F
#define RB_CPU_FREQ_IDX_ATH79_9X_QCA953X_MAX 4 // stops at E
#define RB_CPU_FREQ_IDX_ATH79_9X_QCA9556_MAX 2 // stops at C
#define RB_CPU_FREQ_IDX_ATH79_9X_QCA9558_MAX 3 // stops at D
/* ATH79 7xxx CPU frequency indices. */
#define RB_CPU_FREQ_IDX_ATH79_7X_A ((0 * 9) << 4)
#define RB_CPU_FREQ_IDX_ATH79_7X_B ((1 * 9) << 4)
#define RB_CPU_FREQ_IDX_ATH79_7X_C ((2 * 9) << 4)
#define RB_CPU_FREQ_IDX_ATH79_7X_D ((3 * 9) << 4)
#define RB_CPU_FREQ_IDX_ATH79_7X_E ((4 * 9) << 4)
#define RB_CPU_FREQ_IDX_ATH79_7X_F ((5 * 9) << 4)
#define RB_CPU_FREQ_IDX_ATH79_7X_G ((6 * 9) << 4)
#define RB_CPU_FREQ_IDX_ATH79_7X_H ((7 * 9) << 4)
#define RB_CPU_FREQ_IDX_ATH79_7X_MIN 0 // all devices support lowest setting
#define RB_CPU_FREQ_IDX_ATH79_7X_AR724X_MAX 3 // stops at D
#define RB_CPU_FREQ_IDX_ATH79_7X_AR7161_MAX 7 // stops at H - check if applies to all AR71xx devices
#define RB_SC_CRC32_OFFSET 4 // located right after magic
static struct kobject *sc_kobj;
static u8 *sc_buf;
static size_t sc_buflen;
static rwlock_t sc_bufrwl; // rw lock to sc_buf
/* MUST be used with lock held */
#define RB_SC_CLRCRC() *(u32 *)(sc_buf + RB_SC_CRC32_OFFSET) = 0
#define RB_SC_GETCRC() *(u32 *)(sc_buf + RB_SC_CRC32_OFFSET)
#define RB_SC_SETCRC(_crc) *(u32 *)(sc_buf + RB_SC_CRC32_OFFSET) = (_crc)
struct sc_u32tvs {
const u32 val;
const char *str;
};
#define RB_SC_TVS(_val, _str) { \
.val = (_val), \
.str = (_str), \
}
static ssize_t sc_tag_show_u32tvs(const u8 *pld, u16 pld_len, char *buf,
const struct sc_u32tvs tvs[], const int tvselmts)
{
const char *fmt;
char *out = buf;
u32 data; // cpu-endian
int i;
// fallback to raw hex output if we can't handle the input
if (tvselmts < 0)
return routerboot_tag_show_u32s(pld, pld_len, buf);
if (sizeof(data) != pld_len)
return -EINVAL;
read_lock(&sc_bufrwl);
data = *(u32 *)pld; // pld aliases sc_buf
read_unlock(&sc_bufrwl);
for (i = 0; i < tvselmts; i++) {
fmt = (tvs[i].val == data) ? "[%s] " : "%s ";
out += sprintf(out, fmt, tvs[i].str);
}
out += sprintf(out, "\n");
return out - buf;
}
static ssize_t sc_tag_store_u32tvs(const u8 *pld, u16 pld_len, const char *buf, size_t count,
const struct sc_u32tvs tvs[], const int tvselmts)
{
int i;
if (tvselmts < 0)
return tvselmts;
if (sizeof(u32) != pld_len)
return -EINVAL;
for (i = 0; i < tvselmts; i++) {
if (sysfs_streq(buf, tvs[i].str)) {
write_lock(&sc_bufrwl);
*(u32 *)pld = tvs[i].val; // pld aliases sc_buf
RB_SC_CLRCRC();
write_unlock(&sc_bufrwl);
return count;
}
}
return -EINVAL;
}
struct sc_boolts {
const char *strfalse;
const char *strtrue;
};
static ssize_t sc_tag_show_boolts(const u8 *pld, u16 pld_len, char *buf,
const struct sc_boolts *bts)
{
const char *fmt;
char *out = buf;
u32 data; // cpu-endian
if (sizeof(data) != pld_len)
return -EINVAL;
read_lock(&sc_bufrwl);
data = *(u32 *)pld; // pld aliases sc_buf
read_unlock(&sc_bufrwl);
fmt = (data) ? "%s [%s]\n" : "[%s] %s\n";
out += sprintf(out, fmt, bts->strfalse, bts->strtrue);
return out - buf;
}
static ssize_t sc_tag_store_boolts(const u8 *pld, u16 pld_len, const char *buf, size_t count,
const struct sc_boolts *bts)
{
u32 data; // cpu-endian
if (sizeof(data) != pld_len)
return -EINVAL;
if (sysfs_streq(buf, bts->strfalse))
data = 0;
else if (sysfs_streq(buf, bts->strtrue))
data = 1;
else
return -EINVAL;
write_lock(&sc_bufrwl);
*(u32 *)pld = data; // pld aliases sc_buf
RB_SC_CLRCRC();
write_unlock(&sc_bufrwl);
return count;
}
static struct sc_u32tvs const sc_uartspeeds[] = {
RB_SC_TVS(RB_UART_SPEED_OFF, "off"),
RB_SC_TVS(RB_UART_SPEED_1200, "1200"),
RB_SC_TVS(RB_UART_SPEED_2400, "2400"),
RB_SC_TVS(RB_UART_SPEED_4800, "4800"),
RB_SC_TVS(RB_UART_SPEED_9600, "9600"),
RB_SC_TVS(RB_UART_SPEED_19200, "19200"),
RB_SC_TVS(RB_UART_SPEED_38400, "38400"),
RB_SC_TVS(RB_UART_SPEED_57600, "57600"),
RB_SC_TVS(RB_UART_SPEED_115200, "115200"),
};
/*
* While the defines are carried over from rbcfg, use strings that more clearly
* show the actual setting purpose (especially since the NAND* settings apply
* to both nand- and nor-based devices). "cfcard" was disabled in rbcfg: disable
* it here too.
*/
static struct sc_u32tvs const sc_bootdevices[] = {
RB_SC_TVS(RB_BOOT_DEVICE_ETHER, "eth"),
RB_SC_TVS(RB_BOOT_DEVICE_NANDETH, "flasheth"),
//RB_SC_TVS(RB_BOOT_DEVICE_CFCARD, "cfcard"),
RB_SC_TVS(RB_BOOT_DEVICE_ETHONCE, "ethonce"),
RB_SC_TVS(RB_BOOT_DEVICE_NANDONLY, "flash"),
RB_SC_TVS(RB_BOOT_DEVICE_FLASHCFG, "cfg"),
RB_SC_TVS(RB_BOOT_DEVICE_FLSHONCE, "cfgonce"),
};
static struct sc_boolts const sc_bootkey = {
.strfalse = "any",
.strtrue = "del",
};
static struct sc_boolts const sc_cpumode = {
.strfalse = "powersave",
.strtrue = "regular",
};
static struct sc_boolts const sc_bootproto = {
.strfalse = "bootp",
.strtrue = "dhcp",
};
static struct sc_boolts const sc_booter = {
.strfalse = "regular",
.strtrue = "backup",
};
static struct sc_boolts const sc_silent_boot = {
.strfalse = "off",
.strtrue = "on",
};
#define SC_TAG_SHOW_STORE_U32TVS_FUNCS(_name) \
static ssize_t sc_tag_show_##_name(const u8 *pld, u16 pld_len, char *buf) \
{ \
return sc_tag_show_u32tvs(pld, pld_len, buf, sc_##_name, ARRAY_SIZE(sc_##_name)); \
} \
static ssize_t sc_tag_store_##_name(const u8 *pld, u16 pld_len, const char *buf, size_t count) \
{ \
return sc_tag_store_u32tvs(pld, pld_len, buf, count, sc_##_name, ARRAY_SIZE(sc_##_name)); \
}
#define SC_TAG_SHOW_STORE_BOOLTS_FUNCS(_name) \
static ssize_t sc_tag_show_##_name(const u8 *pld, u16 pld_len, char *buf) \
{ \
return sc_tag_show_boolts(pld, pld_len, buf, &sc_##_name); \
} \
static ssize_t sc_tag_store_##_name(const u8 *pld, u16 pld_len, const char *buf, size_t count) \
{ \
return sc_tag_store_boolts(pld, pld_len, buf, count, &sc_##_name); \
}
SC_TAG_SHOW_STORE_U32TVS_FUNCS(uartspeeds)
SC_TAG_SHOW_STORE_U32TVS_FUNCS(bootdevices)
SC_TAG_SHOW_STORE_BOOLTS_FUNCS(bootkey)
SC_TAG_SHOW_STORE_BOOLTS_FUNCS(cpumode)
SC_TAG_SHOW_STORE_BOOLTS_FUNCS(bootproto)
SC_TAG_SHOW_STORE_BOOLTS_FUNCS(booter)
SC_TAG_SHOW_STORE_BOOLTS_FUNCS(silent_boot)
static ssize_t sc_tag_show_bootdelays(const u8 *pld, u16 pld_len, char *buf)
{
const char *fmt;
char *out = buf;
u32 data; // cpu-endian
int i;
if (sizeof(data) != pld_len)
return -EINVAL;
read_lock(&sc_bufrwl);
data = *(u32 *)pld; // pld aliases sc_buf
read_unlock(&sc_bufrwl);
for (i = RB_BOOT_DELAY_MIN; i <= RB_BOOT_DELAY_MAX; i++) {
fmt = (i == data) ? "[%d] " : "%d ";
out += sprintf(out, fmt, i);
}
out += sprintf(out, "\n");
return out - buf;
}
static ssize_t sc_tag_store_bootdelays(const u8 *pld, u16 pld_len, const char *buf, size_t count)
{
u32 data; // cpu-endian
int ret;
if (sizeof(data) != pld_len)
return -EINVAL;
ret = kstrtou32(buf, 10, &data);
if (ret)
return ret;
if ((data < RB_BOOT_DELAY_MIN) || (RB_BOOT_DELAY_MAX < data))
return -EINVAL;
write_lock(&sc_bufrwl);
*(u32 *)pld = data; // pld aliases sc_buf
RB_SC_CLRCRC();
write_unlock(&sc_bufrwl);
return count;
}
/* Support CPU frequency accessors only when the tag format has been asserted */
#if defined(CONFIG_ATH79)
/* Use the same letter-based nomenclature as RouterBOOT */
static struct sc_u32tvs const sc_cpufreq_indexes_ath79_9x[] = {
RB_SC_TVS(RB_CPU_FREQ_IDX_ATH79_9X_A, "a"),
RB_SC_TVS(RB_CPU_FREQ_IDX_ATH79_9X_B, "b"),
RB_SC_TVS(RB_CPU_FREQ_IDX_ATH79_9X_C, "c"),
RB_SC_TVS(RB_CPU_FREQ_IDX_ATH79_9X_D, "d"),
RB_SC_TVS(RB_CPU_FREQ_IDX_ATH79_9X_E, "e"),
RB_SC_TVS(RB_CPU_FREQ_IDX_ATH79_9X_F, "f"),
};
static struct sc_u32tvs const sc_cpufreq_indexes_ath79_7x[] = {
RB_SC_TVS(RB_CPU_FREQ_IDX_ATH79_7X_A, "a"),
RB_SC_TVS(RB_CPU_FREQ_IDX_ATH79_7X_B, "b"),
RB_SC_TVS(RB_CPU_FREQ_IDX_ATH79_7X_C, "c"),
RB_SC_TVS(RB_CPU_FREQ_IDX_ATH79_7X_D, "d"),
RB_SC_TVS(RB_CPU_FREQ_IDX_ATH79_7X_E, "e"),
RB_SC_TVS(RB_CPU_FREQ_IDX_ATH79_7X_F, "f"),
RB_SC_TVS(RB_CPU_FREQ_IDX_ATH79_7X_G, "g"),
RB_SC_TVS(RB_CPU_FREQ_IDX_ATH79_7X_H, "h"),
};
static int sc_tag_cpufreq_ath79_arraysize(void)
{
int idx_max;
if (ATH79_SOC_AR7161 == ath79_soc)
idx_max = RB_CPU_FREQ_IDX_ATH79_7X_AR7161_MAX+1;
else if (soc_is_ar724x())
idx_max = RB_CPU_FREQ_IDX_ATH79_7X_AR724X_MAX+1;
else if (soc_is_ar9344())
idx_max = RB_CPU_FREQ_IDX_ATH79_9X_AR9334_MAX+1;
else if (soc_is_qca953x())
idx_max = RB_CPU_FREQ_IDX_ATH79_9X_QCA953X_MAX+1;
else if (soc_is_qca9556())
idx_max = RB_CPU_FREQ_IDX_ATH79_9X_QCA9556_MAX+1;
else if (soc_is_qca9558())
idx_max = RB_CPU_FREQ_IDX_ATH79_9X_QCA9558_MAX+1;
else
idx_max = -EOPNOTSUPP;
return idx_max;
}
static ssize_t sc_tag_show_cpufreq_indexes(const u8 *pld, u16 pld_len, char *buf)
{
const struct sc_u32tvs *tvs;
if (soc_is_ar71xx() || soc_is_ar724x())
tvs = sc_cpufreq_indexes_ath79_7x;
else
tvs = sc_cpufreq_indexes_ath79_9x;
return sc_tag_show_u32tvs(pld, pld_len, buf, tvs, sc_tag_cpufreq_ath79_arraysize());
}
static ssize_t sc_tag_store_cpufreq_indexes(const u8 *pld, u16 pld_len, const char *buf, size_t count)
{
const struct sc_u32tvs *tvs;
if (soc_is_ar71xx() || soc_is_ar724x())
tvs = sc_cpufreq_indexes_ath79_7x;
else
tvs = sc_cpufreq_indexes_ath79_9x;
return sc_tag_store_u32tvs(pld, pld_len, buf, count, tvs, sc_tag_cpufreq_ath79_arraysize());
}
#else
/* By default we only show the raw value to help with reverse-engineering */
#define sc_tag_show_cpufreq_indexes routerboot_tag_show_u32s
#define sc_tag_store_cpufreq_indexes NULL
#endif
static ssize_t sc_attr_show(struct kobject *kobj, struct kobj_attribute *attr,
char *buf);
static ssize_t sc_attr_store(struct kobject *kobj, struct kobj_attribute *attr,
const char *buf, size_t count);
/* Array of known tags to publish in sysfs */
static struct sc_attr {
const u16 tag_id;
/* sysfs tag show attribute. Must lock sc_buf when dereferencing pld */
ssize_t (* const tshow)(const u8 *pld, u16 pld_len, char *buf);
/* sysfs tag store attribute. Must lock sc_buf when dereferencing pld */
ssize_t (* const tstore)(const u8 *pld, u16 pld_len, const char *buf, size_t count);
struct kobj_attribute kattr;
u16 pld_ofs;
u16 pld_len;
} sc_attrs[] = {
{
.tag_id = RB_SCID_UART_SPEED,
.tshow = sc_tag_show_uartspeeds,
.tstore = sc_tag_store_uartspeeds,
.kattr = __ATTR(uart_speed, RB_SC_RMODE|RB_SC_WMODE, sc_attr_show, sc_attr_store),
}, {
.tag_id = RB_SCID_BOOT_DELAY,
.tshow = sc_tag_show_bootdelays,
.tstore = sc_tag_store_bootdelays,
.kattr = __ATTR(boot_delay, RB_SC_RMODE|RB_SC_WMODE, sc_attr_show, sc_attr_store),
}, {
.tag_id = RB_SCID_BOOT_DEVICE,
.tshow = sc_tag_show_bootdevices,
.tstore = sc_tag_store_bootdevices,
.kattr = __ATTR(boot_device, RB_SC_RMODE|RB_SC_WMODE, sc_attr_show, sc_attr_store),
}, {
.tag_id = RB_SCID_BOOT_KEY,
.tshow = sc_tag_show_bootkey,
.tstore = sc_tag_store_bootkey,
.kattr = __ATTR(boot_key, RB_SC_RMODE|RB_SC_WMODE, sc_attr_show, sc_attr_store),
}, {
.tag_id = RB_SCID_CPU_MODE,
.tshow = sc_tag_show_cpumode,
.tstore = sc_tag_store_cpumode,
.kattr = __ATTR(cpu_mode, RB_SC_RMODE|RB_SC_WMODE, sc_attr_show, sc_attr_store),
}, {
.tag_id = RB_SCID_BIOS_VERSION,
.tshow = routerboot_tag_show_string,
.tstore = NULL,
.kattr = __ATTR(bios_version, RB_SC_RMODE, sc_attr_show, NULL),
}, {
.tag_id = RB_SCID_BOOT_PROTOCOL,
.tshow = sc_tag_show_bootproto,
.tstore = sc_tag_store_bootproto,
.kattr = __ATTR(boot_proto, RB_SC_RMODE|RB_SC_WMODE, sc_attr_show, sc_attr_store),
}, {
.tag_id = RB_SCID_CPU_FREQ_IDX,
.tshow = sc_tag_show_cpufreq_indexes,
.tstore = sc_tag_store_cpufreq_indexes,
.kattr = __ATTR(cpufreq_index, RB_SC_RMODE|RB_SC_WMODE, sc_attr_show, sc_attr_store),
}, {
.tag_id = RB_SCID_BOOTER,
.tshow = sc_tag_show_booter,
.tstore = sc_tag_store_booter,
.kattr = __ATTR(booter, RB_SC_RMODE|RB_SC_WMODE, sc_attr_show, sc_attr_store),
}, {
.tag_id = RB_SCID_SILENT_BOOT,
.tshow = sc_tag_show_silent_boot,
.tstore = sc_tag_store_silent_boot,
.kattr = __ATTR(silent_boot, RB_SC_RMODE|RB_SC_WMODE, sc_attr_show, sc_attr_store),
},
};
static ssize_t sc_attr_show(struct kobject *kobj, struct kobj_attribute *attr,
char *buf)
{
const struct sc_attr *sc_attr;
const u8 *pld;
u16 pld_len;
sc_attr = container_of(attr, typeof(*sc_attr), kattr);
if (!sc_attr->pld_len)
return -ENOENT;
pld = sc_buf + sc_attr->pld_ofs; // pld aliases sc_buf -> lock!
pld_len = sc_attr->pld_len;
return sc_attr->tshow(pld, pld_len, buf);
}
static ssize_t sc_attr_store(struct kobject *kobj, struct kobj_attribute *attr,
const char *buf, size_t count)
{
const struct sc_attr *sc_attr;
const u8 *pld;
u16 pld_len;
if (!RB_SC_HAS_WRITE_SUPPORT)
return -EOPNOTSUPP;
if (!capable(CAP_SYS_ADMIN))
return -EACCES;
sc_attr = container_of(attr, typeof(*sc_attr), kattr);
if (!sc_attr->tstore)
return -EOPNOTSUPP;
if (!sc_attr->pld_len)
return -ENOENT;
pld = sc_buf + sc_attr->pld_ofs; // pld aliases sc_buf -> lock!
pld_len = sc_attr->pld_len;
return sc_attr->tstore(pld, pld_len, buf, count);
}
/*
* Shows the current buffer status:
* "clean": the buffer is in sync with the mtd data
* "dirty": the buffer is out of sync with the mtd data
*/
static ssize_t sc_commit_show(struct kobject *kobj, struct kobj_attribute *attr,
char *buf)
{
const char *str;
char *out = buf;
u32 crc;
read_lock(&sc_bufrwl);
crc = RB_SC_GETCRC();
read_unlock(&sc_bufrwl);
str = (crc) ? "clean" : "dirty";
out += sprintf(out, "%s\n", str);
return out - buf;
}
/*
* Performs buffer flushing:
* This routine expects an input compatible with kstrtobool().
* - a "false" input discards the current changes and reads data back from mtd.
* - a "true" input commits the current changes to mtd.
* If there is no pending changes, this routine is a no-op.
* Handling failures is left as an exercise to userspace.
*/
static ssize_t sc_commit_store(struct kobject *kobj, struct kobj_attribute *attr,
const char *buf, size_t count)
{
struct mtd_info *mtd;
struct erase_info ei;
size_t bytes_rw, ret = count;
bool flush;
u32 crc;
if (!RB_SC_HAS_WRITE_SUPPORT)
return -EOPNOTSUPP;
read_lock(&sc_bufrwl);
crc = RB_SC_GETCRC();
read_unlock(&sc_bufrwl);
if (crc)
return count; // NO-OP
ret = kstrtobool(buf, &flush);
if (ret)
return ret;
mtd = get_mtd_device_nm(RB_MTD_SOFT_CONFIG); // TODO allow override
if (IS_ERR(mtd))
return -ENODEV;
write_lock(&sc_bufrwl);
if (!flush) // reread
ret = mtd_read(mtd, 0, mtd->size, &bytes_rw, sc_buf);
else { // crc32 + commit
/*
* CRC32 is computed on the entire buffer, excluding the CRC
* value itself. CRC is already null when we reach this point,
* so we can compute the CRC32 on the buffer as is.
* The expected CRC32 is Ethernet FCS style, meaning the seed is
* ~0 and the final result is also bitflipped.
*/
crc = ~crc32(~0, sc_buf, sc_buflen);
RB_SC_SETCRC(crc);
/*
* The soft_config partition is assumed to be entirely contained
* in a single eraseblock.
*/
ei.addr = 0;
ei.len = mtd->size;
ret = mtd_erase(mtd, &ei);
if (!ret)
ret = mtd_write(mtd, 0, mtd->size, &bytes_rw, sc_buf);
/*
* Handling mtd_write() failure here is a tricky situation. The
* proposed approach is to let userspace deal with retrying,
* with the caveat that it must try to flush the buffer again as
* rereading the mtd contents could potentially read garbage.
* The rationale is: even if we keep a shadow buffer of the
* original content, there is no guarantee that we will ever be
* able to write it anyway.
* Regardless, it appears that RouterBOOT will ignore an invalid
* soft_config (including a completely wiped segment) and will
* write back factory defaults when it happens.
*/
}
write_unlock(&sc_bufrwl);
put_mtd_device(mtd);
if (ret)
goto mtdfail;
if (bytes_rw != sc_buflen) {
ret = -EIO;
goto mtdfail;
}
return count;
mtdfail:
RB_SC_CLRCRC(); // mark buffer content as dirty/invalid
return ret;
}
static struct kobj_attribute sc_kattrcommit = __ATTR(commit, RB_SC_RMODE|RB_SC_WMODE, sc_commit_show, sc_commit_store);
int rb_softconfig_init(struct kobject *rb_kobj, struct mtd_info *mtd)
{
size_t bytes_read, buflen;
const u8 *buf;
int i, ret;
u32 magic;
sc_buf = NULL;
sc_kobj = NULL;
ret = __get_mtd_device(mtd);
if (ret)
return -ENODEV;
sc_buflen = mtd->size;
sc_buf = kmalloc(sc_buflen, GFP_KERNEL);
if (!sc_buf) {
__put_mtd_device(mtd);
return -ENOMEM;
}
ret = mtd_read(mtd, 0, sc_buflen, &bytes_read, sc_buf);
__put_mtd_device(mtd);
if (ret)
goto fail;
if (bytes_read != sc_buflen) {
ret = -EIO;
goto fail;
}
/* Check we have what we expect */
magic = *(const u32 *)sc_buf;
if (RB_MAGIC_SOFT != magic) {
ret = -EINVAL;
goto fail;
}
/* Skip magic and 32bit CRC located immediately after */
buf = sc_buf + (sizeof(magic) + sizeof(u32));
buflen = sc_buflen - (sizeof(magic) + sizeof(u32));
/* Populate sysfs */
ret = -ENOMEM;
sc_kobj = kobject_create_and_add(RB_MTD_SOFT_CONFIG, rb_kobj);
if (!sc_kobj)
goto fail;
rwlock_init(&sc_bufrwl);
/* Locate and publish all known tags */
for (i = 0; i < ARRAY_SIZE(sc_attrs); i++) {
ret = routerboot_tag_find(buf, buflen, sc_attrs[i].tag_id,
&sc_attrs[i].pld_ofs, &sc_attrs[i].pld_len);
if (ret) {
sc_attrs[i].pld_ofs = sc_attrs[i].pld_len = 0;
continue;
}
/* Account for skipped magic and crc32 */
sc_attrs[i].pld_ofs += sizeof(magic) + sizeof(u32);
ret = sysfs_create_file(sc_kobj, &sc_attrs[i].kattr.attr);
if (ret)
pr_warn(RB_SC_PR_PFX "Could not create %s sysfs entry (%d)\n",
sc_attrs[i].kattr.attr.name, ret);
}
/* Finally add the 'commit' attribute */
if (RB_SC_HAS_WRITE_SUPPORT) {
ret = sysfs_create_file(sc_kobj, &sc_kattrcommit.attr);
if (ret) {
pr_err(RB_SC_PR_PFX "Could not create %s sysfs entry (%d), aborting!\n",
sc_kattrcommit.attr.name, ret);
goto sysfsfail; // required attribute
}
}
pr_info("MikroTik RouterBOARD software configuration sysfs driver v" RB_SOFTCONFIG_VER "\n");
return 0;
sysfsfail:
kobject_put(sc_kobj);
sc_kobj = NULL;
fail:
kfree(sc_buf);
sc_buf = NULL;
return ret;
}
void rb_softconfig_exit(void)
{
kobject_put(sc_kobj);
sc_kobj = NULL;
kfree(sc_buf);
sc_buf = NULL;
}

View file

@ -0,0 +1,251 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Driver for MikroTik RouterBoot flash data. Common routines.
*
* Copyright (C) 2020 Thibaut VARÈNE <hacks+kernel@slashdirt.org>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 as published
* by the Free Software Foundation.
*/
#include <linux/types.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/sysfs.h>
#include <linux/mtd/mtd.h>
#include "routerboot.h"
static struct kobject *rb_kobj;
/**
* routerboot_tag_find() - Locate a given tag in routerboot config data.
* @bufhead: the buffer to look into. Must start with a tag node.
* @buflen: size of bufhead
* @tag_id: the tag identifier to look for
* @pld_ofs: will be updated with tag payload offset in bufhead, if tag found
* @pld_len: will be updated with tag payload size, if tag found
*
* This incarnation of tag_find() does only that: it finds a specific routerboot
* tag node in the input buffer. Routerboot tag nodes are u32 values:
* - The low nibble is the tag identification number,
* - The high nibble is the tag payload length (node excluded) in bytes.
* The payload immediately follows the tag node. Tag nodes are 32bit-aligned.
* The returned pld_ofs will always be aligned. pld_len may not end on 32bit
* boundary (the only known case is when parsing ERD data).
* The nodes are cpu-endian on the flash media. The payload is cpu-endian when
* applicable. Tag nodes are not ordered (by ID) on flash.
*
* Return: 0 on success (tag found) or errno
*/
int routerboot_tag_find(const u8 *bufhead, const size_t buflen, const u16 tag_id,
u16 *pld_ofs, u16 *pld_len)
{
const u32 *datum, *bufend;
u32 node;
u16 id, len;
int ret;
if (!bufhead || !tag_id)
return -EINVAL;
ret = -ENOENT;
datum = (const u32 *)bufhead;
bufend = (const u32 *)(bufhead + buflen);
while (datum < bufend) {
node = *datum++;
/* Tag list ends with null node */
if (!node)
break;
id = node & 0xFFFF;
len = node >> 16;
if (tag_id == id) {
if (datum >= bufend)
break;
if (pld_ofs)
*pld_ofs = (u16)((u8 *)datum - bufhead);
if (pld_len)
*pld_len = len;
ret = 0;
break;
}
/*
* The only known situation where len may not end on 32bit
* boundary is within ERD data. Since we're only extracting
* one tag (the first and only one) from that data, we should
* never need to forcefully ALIGN(). Do it anyway, this is not a
* performance path.
*/
len = ALIGN(len, sizeof(*datum));
datum += len / sizeof(*datum);
}
return ret;
}
/**
* routerboot_rle_decode() - Simple RLE (MikroTik variant) decoding routine.
* @in: input buffer to decode
* @inlen: size of in
* @out: output buffer to write decoded data to
* @outlen: pointer to out size when function is called, will be updated with
* size of decoded output on return
*
* MikroTik's variant of RLE operates as follows, considering a signed run byte:
* - positive run => classic RLE
* - negative run => the next -<run> bytes must be copied verbatim
* The API is matched to the lzo1x routines for convenience.
*
* NB: The output buffer cannot overlap with the input buffer.
*
* Return: 0 on success or errno
*/
int routerboot_rle_decode(const u8 *in, size_t inlen, u8 *out, size_t *outlen)
{
int ret, run, nbytes; // use native types for speed
u8 byte;
if (!in || (inlen < 2) || !out)
return -EINVAL;
ret = -ENOSPC;
nbytes = 0;
while (inlen >= 2) {
run = *in++;
inlen--;
/* Verbatim copies */
if (run & 0x80) {
/* Invert run byte sign */
run = ~run & 0xFF;
run++;
if (run > inlen)
goto fail;
inlen -= run;
nbytes += run;
if (nbytes > *outlen)
goto fail;
/* Basic memcpy */
while (run-- > 0)
*out++ = *in++;
}
/* Stream of half-words RLE: <run><byte>. run == 0 is ignored */
else {
byte = *in++;
inlen--;
nbytes += run;
if (nbytes > *outlen)
goto fail;
while (run-- > 0)
*out++ = byte;
}
}
ret = 0;
fail:
*outlen = nbytes;
return ret;
}
static void routerboot_mtd_notifier_add(struct mtd_info *mtd)
{
/* Currently routerboot is only known to live on NOR flash */
if (mtd->type != MTD_NORFLASH)
return;
/*
* We ignore the following return values and always register.
* These init() routines are designed so that their failed state is
* always manageable by the corresponding exit() calls.
* Notifier is called with MTD mutex held: use __get/__put variants.
* TODO: allow partition names override
*/
if (!strcmp(mtd->name, RB_MTD_HARD_CONFIG))
rb_hardconfig_init(rb_kobj, mtd);
else if (!strcmp(mtd->name, RB_MTD_SOFT_CONFIG))
rb_softconfig_init(rb_kobj, mtd);
}
static void routerboot_mtd_notifier_remove(struct mtd_info *mtd)
{
if (mtd->type != MTD_NORFLASH)
return;
if (!strcmp(mtd->name, RB_MTD_HARD_CONFIG))
rb_hardconfig_exit();
else if (!strcmp(mtd->name, RB_MTD_SOFT_CONFIG))
rb_softconfig_exit();
}
/* Note: using a notifier prevents qualifying init()/exit() functions with __init/__exit */
static struct mtd_notifier routerboot_mtd_notifier = {
.add = routerboot_mtd_notifier_add,
.remove = routerboot_mtd_notifier_remove,
};
static int __init routerboot_init(void)
{
rb_kobj = kobject_create_and_add("mikrotik", firmware_kobj);
if (!rb_kobj)
return -ENOMEM;
register_mtd_user(&routerboot_mtd_notifier);
return 0;
}
static void __exit routerboot_exit(void)
{
unregister_mtd_user(&routerboot_mtd_notifier);
/* Exit routines are idempotent */
rb_softconfig_exit();
rb_hardconfig_exit();
kobject_put(rb_kobj); // recursive afaict
}
/* Common routines */
ssize_t routerboot_tag_show_string(const u8 *pld, u16 pld_len, char *buf)
{
return scnprintf(buf, pld_len+1, "%s\n", pld);
}
ssize_t routerboot_tag_show_u32s(const u8 *pld, u16 pld_len, char *buf)
{
char *out = buf;
u32 *data; // cpu-endian
/* Caller ensures pld_len > 0 */
if (pld_len % sizeof(*data))
return -EINVAL;
data = (u32 *)pld;
do {
out += sprintf(out, "0x%08x\n", *data);
data++;
} while ((pld_len -= sizeof(*data)));
return out - buf;
}
module_init(routerboot_init);
module_exit(routerboot_exit);
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("MikroTik RouterBoot sysfs support");
MODULE_AUTHOR("Thibaut VARENE");

View file

@ -0,0 +1,38 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Common definitions for MikroTik RouterBoot data.
*
* Copyright (C) 2020 Thibaut VARÈNE <hacks+kernel@slashdirt.org>
*/
#ifndef _ROUTERBOOT_H_
#define _ROUTERBOOT_H_
#include <linux/types.h>
// these magic values are stored in cpu-endianness on flash
#define RB_MAGIC_HARD (('H') | ('a' << 8) | ('r' << 16) | ('d' << 24))
#define RB_MAGIC_SOFT (('S') | ('o' << 8) | ('f' << 16) | ('t' << 24))
#define RB_MAGIC_LZOR (('L') | ('Z' << 8) | ('O' << 16) | ('R' << 24))
#define RB_MAGIC_LZ77 (('L' << 24) | ('Z' << 16) | ('7' << 8) | ('7'))
#define RB_MAGIC_ERD (('E' << 16) | ('R' << 8) | ('D'))
#define RB_ART_SIZE 0x10000
#define RB_MTD_HARD_CONFIG "hard_config"
#define RB_MTD_SOFT_CONFIG "soft_config"
int routerboot_tag_find(const u8 *bufhead, const size_t buflen, const u16 tag_id, u16 *pld_ofs, u16 *pld_len);
int routerboot_rle_decode(const u8 *in, size_t inlen, u8 *out, size_t *outlen);
int rb_hardconfig_init(struct kobject *rb_kobj, struct mtd_info *mtd);
void rb_hardconfig_exit(void);
int rb_softconfig_init(struct kobject *rb_kobj, struct mtd_info *mtd);
void rb_softconfig_exit(void);
ssize_t routerboot_tag_show_string(const u8 *pld, u16 pld_len, char *buf);
ssize_t routerboot_tag_show_u32s(const u8 *pld, u16 pld_len, char *buf);
#endif /* _ROUTERBOOT_H_ */

View file

@ -0,0 +1,745 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* SSB Fallback SPROM Driver
*
* Copyright (C) 2020 Álvaro Fernández Rojas <noltari@gmail.com>
* Copyright (C) 2014 Jonas Gorski <jonas.gorski@gmail.com>
* Copyright (C) 2008 Maxime Bizon <mbizon@freebox.fr>
* Copyright (C) 2008 Florian Fainelli <f.fainelli@gmail.com>
*/
#include <linux/etherdevice.h>
#include <linux/firmware.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/mtd/mtd.h>
#include <linux/of_net.h>
#include <linux/of_platform.h>
#include <linux/ssb/ssb.h>
#include "fallback-sprom.h"
#define SSB_FBS_MAX_SIZE 440
/* Get the word-offset for a SSB_SPROM_XXX define. */
#define SPOFF(offset) ((offset) / sizeof(u16))
/* Helper to extract some _offset, which is one of the SSB_SPROM_XXX defines. */
#define SPEX16(_outvar, _offset, _mask, _shift) \
out->_outvar = ((in[SPOFF(_offset)] & (_mask)) >> (_shift))
#define SPEX32(_outvar, _offset, _mask, _shift) \
out->_outvar = ((((u32)in[SPOFF((_offset)+2)] << 16 | \
in[SPOFF(_offset)]) & (_mask)) >> (_shift))
#define SPEX(_outvar, _offset, _mask, _shift) \
SPEX16(_outvar, _offset, _mask, _shift)
#define SPEX_ARRAY8(_field, _offset, _mask, _shift) \
do { \
SPEX(_field[0], _offset + 0, _mask, _shift); \
SPEX(_field[1], _offset + 2, _mask, _shift); \
SPEX(_field[2], _offset + 4, _mask, _shift); \
SPEX(_field[3], _offset + 6, _mask, _shift); \
SPEX(_field[4], _offset + 8, _mask, _shift); \
SPEX(_field[5], _offset + 10, _mask, _shift); \
SPEX(_field[6], _offset + 12, _mask, _shift); \
SPEX(_field[7], _offset + 14, _mask, _shift); \
} while (0)
struct ssb_fbs {
struct device *dev;
struct list_head list;
struct ssb_sprom sprom;
u32 pci_bus;
u32 pci_dev;
bool devid_override;
};
static DEFINE_SPINLOCK(ssb_fbs_lock);
static struct list_head ssb_fbs_list = LIST_HEAD_INIT(ssb_fbs_list);
int ssb_get_fallback_sprom(struct ssb_bus *bus, struct ssb_sprom *out)
{
struct ssb_fbs *pos;
u32 pci_bus, pci_dev;
if (bus->bustype != SSB_BUSTYPE_PCI)
return -ENOENT;
pci_bus = bus->host_pci->bus->number;
pci_dev = PCI_SLOT(bus->host_pci->devfn);
list_for_each_entry(pos, &ssb_fbs_list, list) {
if (pos->pci_bus != pci_bus ||
pos->pci_dev != pci_dev)
continue;
if (pos->devid_override)
bus->host_pci->device = pos->sprom.dev_id;
memcpy(out, &pos->sprom, sizeof(struct ssb_sprom));
dev_info(pos->dev, "requested by [%x:%x]",
pos->pci_bus, pos->pci_dev);
return 0;
}
pr_err("unable to fill SPROM for [%x:%x]\n", pci_bus, pci_dev);
return -EINVAL;
}
static s8 sprom_extract_antgain(u8 sprom_revision, const u16 *in, u16 offset,
u16 mask, u16 shift)
{
u16 v;
u8 gain;
v = in[SPOFF(offset)];
gain = (v & mask) >> shift;
if (gain == 0xFF)
gain = 2; /* If unset use 2dBm */
if (sprom_revision == 1) {
/* Convert to Q5.2 */
gain <<= 2;
} else {
/* Q5.2 Fractional part is stored in 0xC0 */
gain = ((gain & 0xC0) >> 6) | ((gain & 0x3F) << 2);
}
return (s8)gain;
}
static void sprom_extract_r23(struct ssb_sprom *out, const u16 *in)
{
SPEX(boardflags_hi, SSB_SPROM2_BFLHI, 0xFFFF, 0);
SPEX(opo, SSB_SPROM2_OPO, SSB_SPROM2_OPO_VALUE, 0);
SPEX(pa1lob0, SSB_SPROM2_PA1LOB0, 0xFFFF, 0);
SPEX(pa1lob1, SSB_SPROM2_PA1LOB1, 0xFFFF, 0);
SPEX(pa1lob2, SSB_SPROM2_PA1LOB2, 0xFFFF, 0);
SPEX(pa1hib0, SSB_SPROM2_PA1HIB0, 0xFFFF, 0);
SPEX(pa1hib1, SSB_SPROM2_PA1HIB1, 0xFFFF, 0);
SPEX(pa1hib2, SSB_SPROM2_PA1HIB2, 0xFFFF, 0);
SPEX(maxpwr_ah, SSB_SPROM2_MAXP_A, SSB_SPROM2_MAXP_A_HI, 0);
SPEX(maxpwr_al, SSB_SPROM2_MAXP_A, SSB_SPROM2_MAXP_A_LO,
SSB_SPROM2_MAXP_A_LO_SHIFT);
}
static void sprom_extract_r123(struct ssb_sprom *out, const u16 *in)
{
SPEX(et0phyaddr, SSB_SPROM1_ETHPHY, SSB_SPROM1_ETHPHY_ET0A, 0);
SPEX(et1phyaddr, SSB_SPROM1_ETHPHY, SSB_SPROM1_ETHPHY_ET1A,
SSB_SPROM1_ETHPHY_ET1A_SHIFT);
SPEX(et0mdcport, SSB_SPROM1_ETHPHY, SSB_SPROM1_ETHPHY_ET0M, 14);
SPEX(et1mdcport, SSB_SPROM1_ETHPHY, SSB_SPROM1_ETHPHY_ET1M, 15);
SPEX(board_rev, SSB_SPROM1_BINF, SSB_SPROM1_BINF_BREV, 0);
SPEX(board_type, SSB_SPROM1_SPID, 0xFFFF, 0);
if (out->revision == 1)
SPEX(country_code, SSB_SPROM1_BINF, SSB_SPROM1_BINF_CCODE,
SSB_SPROM1_BINF_CCODE_SHIFT);
SPEX(ant_available_a, SSB_SPROM1_BINF, SSB_SPROM1_BINF_ANTA,
SSB_SPROM1_BINF_ANTA_SHIFT);
SPEX(ant_available_bg, SSB_SPROM1_BINF, SSB_SPROM1_BINF_ANTBG,
SSB_SPROM1_BINF_ANTBG_SHIFT);
SPEX(pa0b0, SSB_SPROM1_PA0B0, 0xFFFF, 0);
SPEX(pa0b1, SSB_SPROM1_PA0B1, 0xFFFF, 0);
SPEX(pa0b2, SSB_SPROM1_PA0B2, 0xFFFF, 0);
SPEX(pa1b0, SSB_SPROM1_PA1B0, 0xFFFF, 0);
SPEX(pa1b1, SSB_SPROM1_PA1B1, 0xFFFF, 0);
SPEX(pa1b2, SSB_SPROM1_PA1B2, 0xFFFF, 0);
SPEX(gpio0, SSB_SPROM1_GPIOA, SSB_SPROM1_GPIOA_P0, 0);
SPEX(gpio1, SSB_SPROM1_GPIOA, SSB_SPROM1_GPIOA_P1,
SSB_SPROM1_GPIOA_P1_SHIFT);
SPEX(gpio2, SSB_SPROM1_GPIOB, SSB_SPROM1_GPIOB_P2, 0);
SPEX(gpio3, SSB_SPROM1_GPIOB, SSB_SPROM1_GPIOB_P3,
SSB_SPROM1_GPIOB_P3_SHIFT);
SPEX(maxpwr_a, SSB_SPROM1_MAXPWR, SSB_SPROM1_MAXPWR_A,
SSB_SPROM1_MAXPWR_A_SHIFT);
SPEX(maxpwr_bg, SSB_SPROM1_MAXPWR, SSB_SPROM1_MAXPWR_BG, 0);
SPEX(itssi_a, SSB_SPROM1_ITSSI, SSB_SPROM1_ITSSI_A,
SSB_SPROM1_ITSSI_A_SHIFT);
SPEX(itssi_bg, SSB_SPROM1_ITSSI, SSB_SPROM1_ITSSI_BG, 0);
SPEX(boardflags_lo, SSB_SPROM1_BFLLO, 0xFFFF, 0);
SPEX(alpha2[0], SSB_SPROM1_CCODE, 0xff00, 8);
SPEX(alpha2[1], SSB_SPROM1_CCODE, 0x00ff, 0);
/* Extract the antenna gain values. */
out->antenna_gain.a0 = sprom_extract_antgain(out->revision, in,
SSB_SPROM1_AGAIN,
SSB_SPROM1_AGAIN_BG,
SSB_SPROM1_AGAIN_BG_SHIFT);
out->antenna_gain.a1 = sprom_extract_antgain(out->revision, in,
SSB_SPROM1_AGAIN,
SSB_SPROM1_AGAIN_A,
SSB_SPROM1_AGAIN_A_SHIFT);
if (out->revision >= 2)
sprom_extract_r23(out, in);
}
/* Revs 4 5 and 8 have partially shared layout */
static void sprom_extract_r458(struct ssb_sprom *out, const u16 *in)
{
SPEX(txpid2g[0], SSB_SPROM4_TXPID2G01,
SSB_SPROM4_TXPID2G0, SSB_SPROM4_TXPID2G0_SHIFT);
SPEX(txpid2g[1], SSB_SPROM4_TXPID2G01,
SSB_SPROM4_TXPID2G1, SSB_SPROM4_TXPID2G1_SHIFT);
SPEX(txpid2g[2], SSB_SPROM4_TXPID2G23,
SSB_SPROM4_TXPID2G2, SSB_SPROM4_TXPID2G2_SHIFT);
SPEX(txpid2g[3], SSB_SPROM4_TXPID2G23,
SSB_SPROM4_TXPID2G3, SSB_SPROM4_TXPID2G3_SHIFT);
SPEX(txpid5gl[0], SSB_SPROM4_TXPID5GL01,
SSB_SPROM4_TXPID5GL0, SSB_SPROM4_TXPID5GL0_SHIFT);
SPEX(txpid5gl[1], SSB_SPROM4_TXPID5GL01,
SSB_SPROM4_TXPID5GL1, SSB_SPROM4_TXPID5GL1_SHIFT);
SPEX(txpid5gl[2], SSB_SPROM4_TXPID5GL23,
SSB_SPROM4_TXPID5GL2, SSB_SPROM4_TXPID5GL2_SHIFT);
SPEX(txpid5gl[3], SSB_SPROM4_TXPID5GL23,
SSB_SPROM4_TXPID5GL3, SSB_SPROM4_TXPID5GL3_SHIFT);
SPEX(txpid5g[0], SSB_SPROM4_TXPID5G01,
SSB_SPROM4_TXPID5G0, SSB_SPROM4_TXPID5G0_SHIFT);
SPEX(txpid5g[1], SSB_SPROM4_TXPID5G01,
SSB_SPROM4_TXPID5G1, SSB_SPROM4_TXPID5G1_SHIFT);
SPEX(txpid5g[2], SSB_SPROM4_TXPID5G23,
SSB_SPROM4_TXPID5G2, SSB_SPROM4_TXPID5G2_SHIFT);
SPEX(txpid5g[3], SSB_SPROM4_TXPID5G23,
SSB_SPROM4_TXPID5G3, SSB_SPROM4_TXPID5G3_SHIFT);
SPEX(txpid5gh[0], SSB_SPROM4_TXPID5GH01,
SSB_SPROM4_TXPID5GH0, SSB_SPROM4_TXPID5GH0_SHIFT);
SPEX(txpid5gh[1], SSB_SPROM4_TXPID5GH01,
SSB_SPROM4_TXPID5GH1, SSB_SPROM4_TXPID5GH1_SHIFT);
SPEX(txpid5gh[2], SSB_SPROM4_TXPID5GH23,
SSB_SPROM4_TXPID5GH2, SSB_SPROM4_TXPID5GH2_SHIFT);
SPEX(txpid5gh[3], SSB_SPROM4_TXPID5GH23,
SSB_SPROM4_TXPID5GH3, SSB_SPROM4_TXPID5GH3_SHIFT);
}
static void sprom_extract_r45(struct ssb_sprom *out, const u16 *in)
{
static const u16 pwr_info_offset[] = {
SSB_SPROM4_PWR_INFO_CORE0, SSB_SPROM4_PWR_INFO_CORE1,
SSB_SPROM4_PWR_INFO_CORE2, SSB_SPROM4_PWR_INFO_CORE3
};
int i;
BUILD_BUG_ON(ARRAY_SIZE(pwr_info_offset) !=
ARRAY_SIZE(out->core_pwr_info));
SPEX(et0phyaddr, SSB_SPROM4_ETHPHY, SSB_SPROM4_ETHPHY_ET0A, 0);
SPEX(et1phyaddr, SSB_SPROM4_ETHPHY, SSB_SPROM4_ETHPHY_ET1A,
SSB_SPROM4_ETHPHY_ET1A_SHIFT);
SPEX(board_rev, SSB_SPROM4_BOARDREV, 0xFFFF, 0);
SPEX(board_type, SSB_SPROM1_SPID, 0xFFFF, 0);
if (out->revision == 4) {
SPEX(alpha2[0], SSB_SPROM4_CCODE, 0xff00, 8);
SPEX(alpha2[1], SSB_SPROM4_CCODE, 0x00ff, 0);
SPEX(boardflags_lo, SSB_SPROM4_BFLLO, 0xFFFF, 0);
SPEX(boardflags_hi, SSB_SPROM4_BFLHI, 0xFFFF, 0);
SPEX(boardflags2_lo, SSB_SPROM4_BFL2LO, 0xFFFF, 0);
SPEX(boardflags2_hi, SSB_SPROM4_BFL2HI, 0xFFFF, 0);
} else {
SPEX(alpha2[0], SSB_SPROM5_CCODE, 0xff00, 8);
SPEX(alpha2[1], SSB_SPROM5_CCODE, 0x00ff, 0);
SPEX(boardflags_lo, SSB_SPROM5_BFLLO, 0xFFFF, 0);
SPEX(boardflags_hi, SSB_SPROM5_BFLHI, 0xFFFF, 0);
SPEX(boardflags2_lo, SSB_SPROM5_BFL2LO, 0xFFFF, 0);
SPEX(boardflags2_hi, SSB_SPROM5_BFL2HI, 0xFFFF, 0);
}
SPEX(ant_available_a, SSB_SPROM4_ANTAVAIL, SSB_SPROM4_ANTAVAIL_A,
SSB_SPROM4_ANTAVAIL_A_SHIFT);
SPEX(ant_available_bg, SSB_SPROM4_ANTAVAIL, SSB_SPROM4_ANTAVAIL_BG,
SSB_SPROM4_ANTAVAIL_BG_SHIFT);
SPEX(maxpwr_bg, SSB_SPROM4_MAXP_BG, SSB_SPROM4_MAXP_BG_MASK, 0);
SPEX(itssi_bg, SSB_SPROM4_MAXP_BG, SSB_SPROM4_ITSSI_BG,
SSB_SPROM4_ITSSI_BG_SHIFT);
SPEX(maxpwr_a, SSB_SPROM4_MAXP_A, SSB_SPROM4_MAXP_A_MASK, 0);
SPEX(itssi_a, SSB_SPROM4_MAXP_A, SSB_SPROM4_ITSSI_A,
SSB_SPROM4_ITSSI_A_SHIFT);
if (out->revision == 4) {
SPEX(gpio0, SSB_SPROM4_GPIOA, SSB_SPROM4_GPIOA_P0, 0);
SPEX(gpio1, SSB_SPROM4_GPIOA, SSB_SPROM4_GPIOA_P1,
SSB_SPROM4_GPIOA_P1_SHIFT);
SPEX(gpio2, SSB_SPROM4_GPIOB, SSB_SPROM4_GPIOB_P2, 0);
SPEX(gpio3, SSB_SPROM4_GPIOB, SSB_SPROM4_GPIOB_P3,
SSB_SPROM4_GPIOB_P3_SHIFT);
} else {
SPEX(gpio0, SSB_SPROM5_GPIOA, SSB_SPROM5_GPIOA_P0, 0);
SPEX(gpio1, SSB_SPROM5_GPIOA, SSB_SPROM5_GPIOA_P1,
SSB_SPROM5_GPIOA_P1_SHIFT);
SPEX(gpio2, SSB_SPROM5_GPIOB, SSB_SPROM5_GPIOB_P2, 0);
SPEX(gpio3, SSB_SPROM5_GPIOB, SSB_SPROM5_GPIOB_P3,
SSB_SPROM5_GPIOB_P3_SHIFT);
}
/* Extract the antenna gain values. */
out->antenna_gain.a0 = sprom_extract_antgain(out->revision, in,
SSB_SPROM4_AGAIN01,
SSB_SPROM4_AGAIN0,
SSB_SPROM4_AGAIN0_SHIFT);
out->antenna_gain.a1 = sprom_extract_antgain(out->revision, in,
SSB_SPROM4_AGAIN01,
SSB_SPROM4_AGAIN1,
SSB_SPROM4_AGAIN1_SHIFT);
out->antenna_gain.a2 = sprom_extract_antgain(out->revision, in,
SSB_SPROM4_AGAIN23,
SSB_SPROM4_AGAIN2,
SSB_SPROM4_AGAIN2_SHIFT);
out->antenna_gain.a3 = sprom_extract_antgain(out->revision, in,
SSB_SPROM4_AGAIN23,
SSB_SPROM4_AGAIN3,
SSB_SPROM4_AGAIN3_SHIFT);
/* Extract cores power info info */
for (i = 0; i < ARRAY_SIZE(pwr_info_offset); i++) {
u16 o = pwr_info_offset[i];
SPEX(core_pwr_info[i].itssi_2g, o + SSB_SPROM4_2G_MAXP_ITSSI,
SSB_SPROM4_2G_ITSSI, SSB_SPROM4_2G_ITSSI_SHIFT);
SPEX(core_pwr_info[i].maxpwr_2g, o + SSB_SPROM4_2G_MAXP_ITSSI,
SSB_SPROM4_2G_MAXP, 0);
SPEX(core_pwr_info[i].pa_2g[0], o + SSB_SPROM4_2G_PA_0, ~0, 0);
SPEX(core_pwr_info[i].pa_2g[1], o + SSB_SPROM4_2G_PA_1, ~0, 0);
SPEX(core_pwr_info[i].pa_2g[2], o + SSB_SPROM4_2G_PA_2, ~0, 0);
SPEX(core_pwr_info[i].pa_2g[3], o + SSB_SPROM4_2G_PA_3, ~0, 0);
SPEX(core_pwr_info[i].itssi_5g, o + SSB_SPROM4_5G_MAXP_ITSSI,
SSB_SPROM4_5G_ITSSI, SSB_SPROM4_5G_ITSSI_SHIFT);
SPEX(core_pwr_info[i].maxpwr_5g, o + SSB_SPROM4_5G_MAXP_ITSSI,
SSB_SPROM4_5G_MAXP, 0);
SPEX(core_pwr_info[i].maxpwr_5gh, o + SSB_SPROM4_5GHL_MAXP,
SSB_SPROM4_5GH_MAXP, 0);
SPEX(core_pwr_info[i].maxpwr_5gl, o + SSB_SPROM4_5GHL_MAXP,
SSB_SPROM4_5GL_MAXP, SSB_SPROM4_5GL_MAXP_SHIFT);
SPEX(core_pwr_info[i].pa_5gl[0], o + SSB_SPROM4_5GL_PA_0, ~0, 0);
SPEX(core_pwr_info[i].pa_5gl[1], o + SSB_SPROM4_5GL_PA_1, ~0, 0);
SPEX(core_pwr_info[i].pa_5gl[2], o + SSB_SPROM4_5GL_PA_2, ~0, 0);
SPEX(core_pwr_info[i].pa_5gl[3], o + SSB_SPROM4_5GL_PA_3, ~0, 0);
SPEX(core_pwr_info[i].pa_5g[0], o + SSB_SPROM4_5G_PA_0, ~0, 0);
SPEX(core_pwr_info[i].pa_5g[1], o + SSB_SPROM4_5G_PA_1, ~0, 0);
SPEX(core_pwr_info[i].pa_5g[2], o + SSB_SPROM4_5G_PA_2, ~0, 0);
SPEX(core_pwr_info[i].pa_5g[3], o + SSB_SPROM4_5G_PA_3, ~0, 0);
SPEX(core_pwr_info[i].pa_5gh[0], o + SSB_SPROM4_5GH_PA_0, ~0, 0);
SPEX(core_pwr_info[i].pa_5gh[1], o + SSB_SPROM4_5GH_PA_1, ~0, 0);
SPEX(core_pwr_info[i].pa_5gh[2], o + SSB_SPROM4_5GH_PA_2, ~0, 0);
SPEX(core_pwr_info[i].pa_5gh[3], o + SSB_SPROM4_5GH_PA_3, ~0, 0);
}
sprom_extract_r458(out, in);
/* TODO - get remaining rev 4 stuff needed */
}
static void sprom_extract_r8(struct ssb_sprom *out, const u16 *in)
{
int i;
u16 o;
static const u16 pwr_info_offset[] = {
SSB_SROM8_PWR_INFO_CORE0, SSB_SROM8_PWR_INFO_CORE1,
SSB_SROM8_PWR_INFO_CORE2, SSB_SROM8_PWR_INFO_CORE3
};
BUILD_BUG_ON(ARRAY_SIZE(pwr_info_offset) !=
ARRAY_SIZE(out->core_pwr_info));
SPEX(board_rev, SSB_SPROM8_BOARDREV, 0xFFFF, 0);
SPEX(board_type, SSB_SPROM1_SPID, 0xFFFF, 0);
SPEX(alpha2[0], SSB_SPROM8_CCODE, 0xff00, 8);
SPEX(alpha2[1], SSB_SPROM8_CCODE, 0x00ff, 0);
SPEX(boardflags_lo, SSB_SPROM8_BFLLO, 0xFFFF, 0);
SPEX(boardflags_hi, SSB_SPROM8_BFLHI, 0xFFFF, 0);
SPEX(boardflags2_lo, SSB_SPROM8_BFL2LO, 0xFFFF, 0);
SPEX(boardflags2_hi, SSB_SPROM8_BFL2HI, 0xFFFF, 0);
SPEX(ant_available_a, SSB_SPROM8_ANTAVAIL, SSB_SPROM8_ANTAVAIL_A,
SSB_SPROM8_ANTAVAIL_A_SHIFT);
SPEX(ant_available_bg, SSB_SPROM8_ANTAVAIL, SSB_SPROM8_ANTAVAIL_BG,
SSB_SPROM8_ANTAVAIL_BG_SHIFT);
SPEX(maxpwr_bg, SSB_SPROM8_MAXP_BG, SSB_SPROM8_MAXP_BG_MASK, 0);
SPEX(itssi_bg, SSB_SPROM8_MAXP_BG, SSB_SPROM8_ITSSI_BG,
SSB_SPROM8_ITSSI_BG_SHIFT);
SPEX(maxpwr_a, SSB_SPROM8_MAXP_A, SSB_SPROM8_MAXP_A_MASK, 0);
SPEX(itssi_a, SSB_SPROM8_MAXP_A, SSB_SPROM8_ITSSI_A,
SSB_SPROM8_ITSSI_A_SHIFT);
SPEX(maxpwr_ah, SSB_SPROM8_MAXP_AHL, SSB_SPROM8_MAXP_AH_MASK, 0);
SPEX(maxpwr_al, SSB_SPROM8_MAXP_AHL, SSB_SPROM8_MAXP_AL_MASK,
SSB_SPROM8_MAXP_AL_SHIFT);
SPEX(gpio0, SSB_SPROM8_GPIOA, SSB_SPROM8_GPIOA_P0, 0);
SPEX(gpio1, SSB_SPROM8_GPIOA, SSB_SPROM8_GPIOA_P1,
SSB_SPROM8_GPIOA_P1_SHIFT);
SPEX(gpio2, SSB_SPROM8_GPIOB, SSB_SPROM8_GPIOB_P2, 0);
SPEX(gpio3, SSB_SPROM8_GPIOB, SSB_SPROM8_GPIOB_P3,
SSB_SPROM8_GPIOB_P3_SHIFT);
SPEX(tri2g, SSB_SPROM8_TRI25G, SSB_SPROM8_TRI2G, 0);
SPEX(tri5g, SSB_SPROM8_TRI25G, SSB_SPROM8_TRI5G,
SSB_SPROM8_TRI5G_SHIFT);
SPEX(tri5gl, SSB_SPROM8_TRI5GHL, SSB_SPROM8_TRI5GL, 0);
SPEX(tri5gh, SSB_SPROM8_TRI5GHL, SSB_SPROM8_TRI5GH,
SSB_SPROM8_TRI5GH_SHIFT);
SPEX(rxpo2g, SSB_SPROM8_RXPO, SSB_SPROM8_RXPO2G, 0);
SPEX(rxpo5g, SSB_SPROM8_RXPO, SSB_SPROM8_RXPO5G,
SSB_SPROM8_RXPO5G_SHIFT);
SPEX(rssismf2g, SSB_SPROM8_RSSIPARM2G, SSB_SPROM8_RSSISMF2G, 0);
SPEX(rssismc2g, SSB_SPROM8_RSSIPARM2G, SSB_SPROM8_RSSISMC2G,
SSB_SPROM8_RSSISMC2G_SHIFT);
SPEX(rssisav2g, SSB_SPROM8_RSSIPARM2G, SSB_SPROM8_RSSISAV2G,
SSB_SPROM8_RSSISAV2G_SHIFT);
SPEX(bxa2g, SSB_SPROM8_RSSIPARM2G, SSB_SPROM8_BXA2G,
SSB_SPROM8_BXA2G_SHIFT);
SPEX(rssismf5g, SSB_SPROM8_RSSIPARM5G, SSB_SPROM8_RSSISMF5G, 0);
SPEX(rssismc5g, SSB_SPROM8_RSSIPARM5G, SSB_SPROM8_RSSISMC5G,
SSB_SPROM8_RSSISMC5G_SHIFT);
SPEX(rssisav5g, SSB_SPROM8_RSSIPARM5G, SSB_SPROM8_RSSISAV5G,
SSB_SPROM8_RSSISAV5G_SHIFT);
SPEX(bxa5g, SSB_SPROM8_RSSIPARM5G, SSB_SPROM8_BXA5G,
SSB_SPROM8_BXA5G_SHIFT);
SPEX(pa0b0, SSB_SPROM8_PA0B0, 0xFFFF, 0);
SPEX(pa0b1, SSB_SPROM8_PA0B1, 0xFFFF, 0);
SPEX(pa0b2, SSB_SPROM8_PA0B2, 0xFFFF, 0);
SPEX(pa1b0, SSB_SPROM8_PA1B0, 0xFFFF, 0);
SPEX(pa1b1, SSB_SPROM8_PA1B1, 0xFFFF, 0);
SPEX(pa1b2, SSB_SPROM8_PA1B2, 0xFFFF, 0);
SPEX(pa1lob0, SSB_SPROM8_PA1LOB0, 0xFFFF, 0);
SPEX(pa1lob1, SSB_SPROM8_PA1LOB1, 0xFFFF, 0);
SPEX(pa1lob2, SSB_SPROM8_PA1LOB2, 0xFFFF, 0);
SPEX(pa1hib0, SSB_SPROM8_PA1HIB0, 0xFFFF, 0);
SPEX(pa1hib1, SSB_SPROM8_PA1HIB1, 0xFFFF, 0);
SPEX(pa1hib2, SSB_SPROM8_PA1HIB2, 0xFFFF, 0);
SPEX(cck2gpo, SSB_SPROM8_CCK2GPO, 0xFFFF, 0);
SPEX32(ofdm2gpo, SSB_SPROM8_OFDM2GPO, 0xFFFFFFFF, 0);
SPEX32(ofdm5glpo, SSB_SPROM8_OFDM5GLPO, 0xFFFFFFFF, 0);
SPEX32(ofdm5gpo, SSB_SPROM8_OFDM5GPO, 0xFFFFFFFF, 0);
SPEX32(ofdm5ghpo, SSB_SPROM8_OFDM5GHPO, 0xFFFFFFFF, 0);
/* Extract the antenna gain values. */
out->antenna_gain.a0 = sprom_extract_antgain(out->revision, in,
SSB_SPROM8_AGAIN01,
SSB_SPROM8_AGAIN0,
SSB_SPROM8_AGAIN0_SHIFT);
out->antenna_gain.a1 = sprom_extract_antgain(out->revision, in,
SSB_SPROM8_AGAIN01,
SSB_SPROM8_AGAIN1,
SSB_SPROM8_AGAIN1_SHIFT);
out->antenna_gain.a2 = sprom_extract_antgain(out->revision, in,
SSB_SPROM8_AGAIN23,
SSB_SPROM8_AGAIN2,
SSB_SPROM8_AGAIN2_SHIFT);
out->antenna_gain.a3 = sprom_extract_antgain(out->revision, in,
SSB_SPROM8_AGAIN23,
SSB_SPROM8_AGAIN3,
SSB_SPROM8_AGAIN3_SHIFT);
/* Extract cores power info info */
for (i = 0; i < ARRAY_SIZE(pwr_info_offset); i++) {
o = pwr_info_offset[i];
SPEX(core_pwr_info[i].itssi_2g, o + SSB_SROM8_2G_MAXP_ITSSI,
SSB_SPROM8_2G_ITSSI, SSB_SPROM8_2G_ITSSI_SHIFT);
SPEX(core_pwr_info[i].maxpwr_2g, o + SSB_SROM8_2G_MAXP_ITSSI,
SSB_SPROM8_2G_MAXP, 0);
SPEX(core_pwr_info[i].pa_2g[0], o + SSB_SROM8_2G_PA_0, ~0, 0);
SPEX(core_pwr_info[i].pa_2g[1], o + SSB_SROM8_2G_PA_1, ~0, 0);
SPEX(core_pwr_info[i].pa_2g[2], o + SSB_SROM8_2G_PA_2, ~0, 0);
SPEX(core_pwr_info[i].itssi_5g, o + SSB_SROM8_5G_MAXP_ITSSI,
SSB_SPROM8_5G_ITSSI, SSB_SPROM8_5G_ITSSI_SHIFT);
SPEX(core_pwr_info[i].maxpwr_5g, o + SSB_SROM8_5G_MAXP_ITSSI,
SSB_SPROM8_5G_MAXP, 0);
SPEX(core_pwr_info[i].maxpwr_5gh, o + SSB_SPROM8_5GHL_MAXP,
SSB_SPROM8_5GH_MAXP, 0);
SPEX(core_pwr_info[i].maxpwr_5gl, o + SSB_SPROM8_5GHL_MAXP,
SSB_SPROM8_5GL_MAXP, SSB_SPROM8_5GL_MAXP_SHIFT);
SPEX(core_pwr_info[i].pa_5gl[0], o + SSB_SROM8_5GL_PA_0, ~0, 0);
SPEX(core_pwr_info[i].pa_5gl[1], o + SSB_SROM8_5GL_PA_1, ~0, 0);
SPEX(core_pwr_info[i].pa_5gl[2], o + SSB_SROM8_5GL_PA_2, ~0, 0);
SPEX(core_pwr_info[i].pa_5g[0], o + SSB_SROM8_5G_PA_0, ~0, 0);
SPEX(core_pwr_info[i].pa_5g[1], o + SSB_SROM8_5G_PA_1, ~0, 0);
SPEX(core_pwr_info[i].pa_5g[2], o + SSB_SROM8_5G_PA_2, ~0, 0);
SPEX(core_pwr_info[i].pa_5gh[0], o + SSB_SROM8_5GH_PA_0, ~0, 0);
SPEX(core_pwr_info[i].pa_5gh[1], o + SSB_SROM8_5GH_PA_1, ~0, 0);
SPEX(core_pwr_info[i].pa_5gh[2], o + SSB_SROM8_5GH_PA_2, ~0, 0);
}
/* Extract FEM info */
SPEX(fem.ghz2.tssipos, SSB_SPROM8_FEM2G,
SSB_SROM8_FEM_TSSIPOS, SSB_SROM8_FEM_TSSIPOS_SHIFT);
SPEX(fem.ghz2.extpa_gain, SSB_SPROM8_FEM2G,
SSB_SROM8_FEM_EXTPA_GAIN, SSB_SROM8_FEM_EXTPA_GAIN_SHIFT);
SPEX(fem.ghz2.pdet_range, SSB_SPROM8_FEM2G,
SSB_SROM8_FEM_PDET_RANGE, SSB_SROM8_FEM_PDET_RANGE_SHIFT);
SPEX(fem.ghz2.tr_iso, SSB_SPROM8_FEM2G,
SSB_SROM8_FEM_TR_ISO, SSB_SROM8_FEM_TR_ISO_SHIFT);
SPEX(fem.ghz2.antswlut, SSB_SPROM8_FEM2G,
SSB_SROM8_FEM_ANTSWLUT, SSB_SROM8_FEM_ANTSWLUT_SHIFT);
SPEX(fem.ghz5.tssipos, SSB_SPROM8_FEM5G,
SSB_SROM8_FEM_TSSIPOS, SSB_SROM8_FEM_TSSIPOS_SHIFT);
SPEX(fem.ghz5.extpa_gain, SSB_SPROM8_FEM5G,
SSB_SROM8_FEM_EXTPA_GAIN, SSB_SROM8_FEM_EXTPA_GAIN_SHIFT);
SPEX(fem.ghz5.pdet_range, SSB_SPROM8_FEM5G,
SSB_SROM8_FEM_PDET_RANGE, SSB_SROM8_FEM_PDET_RANGE_SHIFT);
SPEX(fem.ghz5.tr_iso, SSB_SPROM8_FEM5G,
SSB_SROM8_FEM_TR_ISO, SSB_SROM8_FEM_TR_ISO_SHIFT);
SPEX(fem.ghz5.antswlut, SSB_SPROM8_FEM5G,
SSB_SROM8_FEM_ANTSWLUT, SSB_SROM8_FEM_ANTSWLUT_SHIFT);
SPEX(leddc_on_time, SSB_SPROM8_LEDDC, SSB_SPROM8_LEDDC_ON,
SSB_SPROM8_LEDDC_ON_SHIFT);
SPEX(leddc_off_time, SSB_SPROM8_LEDDC, SSB_SPROM8_LEDDC_OFF,
SSB_SPROM8_LEDDC_OFF_SHIFT);
SPEX(txchain, SSB_SPROM8_TXRXC, SSB_SPROM8_TXRXC_TXCHAIN,
SSB_SPROM8_TXRXC_TXCHAIN_SHIFT);
SPEX(rxchain, SSB_SPROM8_TXRXC, SSB_SPROM8_TXRXC_RXCHAIN,
SSB_SPROM8_TXRXC_RXCHAIN_SHIFT);
SPEX(antswitch, SSB_SPROM8_TXRXC, SSB_SPROM8_TXRXC_SWITCH,
SSB_SPROM8_TXRXC_SWITCH_SHIFT);
SPEX(opo, SSB_SPROM8_OFDM2GPO, 0x00ff, 0);
SPEX_ARRAY8(mcs2gpo, SSB_SPROM8_2G_MCSPO, ~0, 0);
SPEX_ARRAY8(mcs5gpo, SSB_SPROM8_5G_MCSPO, ~0, 0);
SPEX_ARRAY8(mcs5glpo, SSB_SPROM8_5GL_MCSPO, ~0, 0);
SPEX_ARRAY8(mcs5ghpo, SSB_SPROM8_5GH_MCSPO, ~0, 0);
SPEX(rawtempsense, SSB_SPROM8_RAWTS, SSB_SPROM8_RAWTS_RAWTEMP,
SSB_SPROM8_RAWTS_RAWTEMP_SHIFT);
SPEX(measpower, SSB_SPROM8_RAWTS, SSB_SPROM8_RAWTS_MEASPOWER,
SSB_SPROM8_RAWTS_MEASPOWER_SHIFT);
SPEX(tempsense_slope, SSB_SPROM8_OPT_CORRX,
SSB_SPROM8_OPT_CORRX_TEMP_SLOPE,
SSB_SPROM8_OPT_CORRX_TEMP_SLOPE_SHIFT);
SPEX(tempcorrx, SSB_SPROM8_OPT_CORRX, SSB_SPROM8_OPT_CORRX_TEMPCORRX,
SSB_SPROM8_OPT_CORRX_TEMPCORRX_SHIFT);
SPEX(tempsense_option, SSB_SPROM8_OPT_CORRX,
SSB_SPROM8_OPT_CORRX_TEMP_OPTION,
SSB_SPROM8_OPT_CORRX_TEMP_OPTION_SHIFT);
SPEX(freqoffset_corr, SSB_SPROM8_HWIQ_IQSWP,
SSB_SPROM8_HWIQ_IQSWP_FREQ_CORR,
SSB_SPROM8_HWIQ_IQSWP_FREQ_CORR_SHIFT);
SPEX(iqcal_swp_dis, SSB_SPROM8_HWIQ_IQSWP,
SSB_SPROM8_HWIQ_IQSWP_IQCAL_SWP,
SSB_SPROM8_HWIQ_IQSWP_IQCAL_SWP_SHIFT);
SPEX(hw_iqcal_en, SSB_SPROM8_HWIQ_IQSWP, SSB_SPROM8_HWIQ_IQSWP_HW_IQCAL,
SSB_SPROM8_HWIQ_IQSWP_HW_IQCAL_SHIFT);
SPEX(bw40po, SSB_SPROM8_BW40PO, ~0, 0);
SPEX(cddpo, SSB_SPROM8_CDDPO, ~0, 0);
SPEX(stbcpo, SSB_SPROM8_STBCPO, ~0, 0);
SPEX(bwduppo, SSB_SPROM8_BWDUPPO, ~0, 0);
SPEX(tempthresh, SSB_SPROM8_THERMAL, SSB_SPROM8_THERMAL_TRESH,
SSB_SPROM8_THERMAL_TRESH_SHIFT);
SPEX(tempoffset, SSB_SPROM8_THERMAL, SSB_SPROM8_THERMAL_OFFSET,
SSB_SPROM8_THERMAL_OFFSET_SHIFT);
SPEX(phycal_tempdelta, SSB_SPROM8_TEMPDELTA,
SSB_SPROM8_TEMPDELTA_PHYCAL,
SSB_SPROM8_TEMPDELTA_PHYCAL_SHIFT);
SPEX(temps_period, SSB_SPROM8_TEMPDELTA, SSB_SPROM8_TEMPDELTA_PERIOD,
SSB_SPROM8_TEMPDELTA_PERIOD_SHIFT);
SPEX(temps_hysteresis, SSB_SPROM8_TEMPDELTA,
SSB_SPROM8_TEMPDELTA_HYSTERESIS,
SSB_SPROM8_TEMPDELTA_HYSTERESIS_SHIFT);
sprom_extract_r458(out, in);
/* TODO - get remaining rev 8 stuff needed */
}
static int sprom_extract(struct ssb_fbs *priv, const u16 *in, u16 size)
{
struct ssb_sprom *out = &priv->sprom;
memset(out, 0, sizeof(*out));
out->revision = in[size - 1] & 0x00FF;
switch (out->revision) {
case 1:
case 2:
case 3:
sprom_extract_r123(out, in);
break;
case 4:
case 5:
sprom_extract_r45(out, in);
break;
case 8:
sprom_extract_r8(out, in);
break;
default:
dev_warn(priv->dev,
"Unsupported SPROM revision %d detected."
" Will extract v1\n",
out->revision);
out->revision = 1;
sprom_extract_r123(out, in);
}
if (out->boardflags_lo == 0xFFFF)
out->boardflags_lo = 0; /* per specs */
if (out->boardflags_hi == 0xFFFF)
out->boardflags_hi = 0; /* per specs */
return 0;
}
static void ssb_fbs_fixup(struct ssb_fbs *priv, u16 *sprom)
{
struct device_node *node = priv->dev->of_node;
u32 fixups, off, val;
int i = 0;
if (!of_get_property(node, "brcm,sprom-fixups", &fixups))
return;
fixups /= sizeof(u32);
dev_info(priv->dev, "patching SPROM with %u fixups...\n", fixups >> 1);
while (i < fixups) {
if (of_property_read_u32_index(node, "brcm,sprom-fixups",
i++, &off)) {
dev_err(priv->dev, "error reading fixup[%u] offset\n",
i - 1);
return;
}
if (of_property_read_u32_index(node, "brcm,sprom-fixups",
i++, &val)) {
dev_err(priv->dev, "error reading fixup[%u] value\n",
i - 1);
return;
}
dev_dbg(priv->dev, "fixup[%d]=0x%04x\n", off, val);
sprom[off] = val;
}
}
static bool sprom_override_devid(struct ssb_fbs *priv, struct ssb_sprom *out,
const u16 *in)
{
SPEX(dev_id, SSB_SPROM1_PID, 0xFFFF, 0);
return !!out->dev_id;
}
static int ssb_fbs_set(struct ssb_fbs *priv, struct device_node *node)
{
struct ssb_sprom *sprom = &priv->sprom;
const struct firmware *fw;
const char *sprom_name;
int err;
if (of_property_read_string(node, "brcm,sprom", &sprom_name))
sprom_name = NULL;
if (sprom_name) {
err = request_firmware_direct(&fw, sprom_name, priv->dev);
if (err)
dev_err(priv->dev, "%s load error\n", sprom_name);
} else {
err = -ENOENT;
}
if (err) {
sprom->revision = 0x02;
sprom->board_rev = 0x0017;
sprom->country_code = 0x00;
sprom->ant_available_bg = 0x03;
sprom->pa0b0 = 0x15ae;
sprom->pa0b1 = 0xfa85;
sprom->pa0b2 = 0xfe8d;
sprom->pa1b0 = 0xffff;
sprom->pa1b1 = 0xffff;
sprom->pa1b2 = 0xffff;
sprom->gpio0 = 0xff;
sprom->gpio1 = 0xff;
sprom->gpio2 = 0xff;
sprom->gpio3 = 0xff;
sprom->maxpwr_bg = 0x4c;
sprom->itssi_bg = 0x00;
sprom->boardflags_lo = 0x2848;
sprom->boardflags_hi = 0x0000;
priv->devid_override = false;
dev_warn(priv->dev, "using basic SPROM\n");
} else {
size_t size = min(fw->size, (size_t) SSB_FBS_MAX_SIZE);
u16 tmp_sprom[SSB_FBS_MAX_SIZE >> 1];
u32 i, j;
for (i = 0, j = 0; i < size; i += 2, j++)
tmp_sprom[j] = (fw->data[i] << 8) | fw->data[i + 1];
release_firmware(fw);
ssb_fbs_fixup(priv, tmp_sprom);
sprom_extract(priv, tmp_sprom, size >> 1);
priv->devid_override = sprom_override_devid(priv, sprom,
tmp_sprom);
}
return 0;
}
static int ssb_fbs_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *node = dev->of_node;
struct ssb_fbs *priv;
unsigned long flags;
u8 mac[ETH_ALEN];
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->dev = dev;
ssb_fbs_set(priv, node);
of_property_read_u32(node, "pci-bus", &priv->pci_bus);
of_property_read_u32(node, "pci-dev", &priv->pci_dev);
of_get_mac_address(node, mac);
if (is_valid_ether_addr(mac)) {
dev_info(dev, "mtd mac %pM\n", mac);
} else {
eth_random_addr(mac);
dev_info(dev, "random mac %pM\n", mac);
}
memcpy(priv->sprom.il0mac, mac, ETH_ALEN);
memcpy(priv->sprom.et0mac, mac, ETH_ALEN);
memcpy(priv->sprom.et1mac, mac, ETH_ALEN);
memcpy(priv->sprom.et2mac, mac, ETH_ALEN);
spin_lock_irqsave(&ssb_fbs_lock, flags);
list_add(&priv->list, &ssb_fbs_list);
spin_unlock_irqrestore(&ssb_fbs_lock, flags);
dev_info(dev, "registered SPROM for [%x:%x]\n",
priv->pci_bus, priv->pci_dev);
return 0;
}
static const struct of_device_id ssb_fbs_of_match[] = {
{ .compatible = "brcm,ssb-sprom", },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, ssb_fbs_of_match);
static struct platform_driver ssb_fbs_driver = {
.probe = ssb_fbs_probe,
.driver = {
.name = "ssb-sprom",
.of_match_table = ssb_fbs_of_match,
},
};
int __init ssb_fbs_register(void)
{
return platform_driver_register(&ssb_fbs_driver);
}

View file

@ -0,0 +1,7 @@
#ifndef _FALLBACK_SPROM_H
#define _FALLBACK_SPROM_H
int __init ssb_fbs_register(void);
int ssb_get_fallback_sprom(struct ssb_bus *dev, struct ssb_sprom *out);
#endif /* _FALLBACK_SPROM_H */

View file

@ -0,0 +1,198 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* *** IMPORTANT ***
* This file is not only included from C-code but also from devicetree source
* files. As such this file MUST only contain comments and defines.
*
* Based on image.h from U-Boot which is
* (C) Copyright 2008 Semihalf
* (C) Copyright 2000-2005 Wolfgang Denk, DENX Software Engineering, wd@denx.de.
*/
#ifndef __UIMAGE_H__
#define __UIMAGE_H__
/*
* Operating System Codes
*
* The following are exposed to uImage header.
* New IDs *MUST* be appended at the end of the list and *NEVER*
* inserted for backward compatibility.
*/
#define IH_OS_INVALID 0 /* Invalid OS */
#define IH_OS_OPENBSD 1 /* OpenBSD */
#define IH_OS_NETBSD 2 /* NetBSD */
#define IH_OS_FREEBSD 3 /* FreeBSD */
#define IH_OS_4_4BSD 4 /* 4.4BSD */
#define IH_OS_LINUX 5 /* Linux */
#define IH_OS_SVR4 6 /* SVR4 */
#define IH_OS_ESIX 7 /* Esix */
#define IH_OS_SOLARIS 8 /* Solaris */
#define IH_OS_IRIX 9 /* Irix */
#define IH_OS_SCO 10 /* SCO */
#define IH_OS_DELL 11 /* Dell */
#define IH_OS_NCR 12 /* NCR */
#define IH_OS_LYNXOS 13 /* LynxOS */
#define IH_OS_VXWORKS 14 /* VxWorks */
#define IH_OS_PSOS 15 /* pSOS */
#define IH_OS_QNX 16 /* QNX */
#define IH_OS_U_BOOT 17 /* Firmware */
#define IH_OS_RTEMS 18 /* RTEMS */
#define IH_OS_ARTOS 19 /* ARTOS */
#define IH_OS_UNITY 20 /* Unity OS */
#define IH_OS_INTEGRITY 21 /* INTEGRITY */
#define IH_OS_OSE 22 /* OSE */
#define IH_OS_PLAN9 23 /* Plan 9 */
#define IH_OS_OPENRTOS 24 /* OpenRTOS */
#define IH_OS_ARM_TRUSTED_FIRMWARE 25 /* ARM Trusted Firmware */
#define IH_OS_TEE 26 /* Trusted Execution Environment */
#define IH_OS_OPENSBI 27 /* RISC-V OpenSBI */
#define IH_OS_EFI 28 /* EFI Firmware (e.g. GRUB2) */
/*
* CPU Architecture Codes (supported by Linux)
*
* The following are exposed to uImage header.
* New IDs *MUST* be appended at the end of the list and *NEVER*
* inserted for backward compatibility.
*/
#define IH_ARCH_INVALID 0 /* Invalid CPU */
#define IH_ARCH_ALPHA 1 /* Alpha */
#define IH_ARCH_ARM 2 /* ARM */
#define IH_ARCH_I386 3 /* Intel x86 */
#define IH_ARCH_IA64 4 /* IA64 */
#define IH_ARCH_MIPS 5 /* MIPS */
#define IH_ARCH_MIPS64 6 /* MIPS 64 Bit */
#define IH_ARCH_PPC 7 /* PowerPC */
#define IH_ARCH_S390 8 /* IBM S390 */
#define IH_ARCH_SH 9 /* SuperH */
#define IH_ARCH_SPARC 10 /* Sparc */
#define IH_ARCH_SPARC64 11 /* Sparc 64 Bit */
#define IH_ARCH_M68K 12 /* M68K */
#define IH_ARCH_NIOS 13 /* Nios-32 */
#define IH_ARCH_MICROBLAZE 14 /* MicroBlaze */
#define IH_ARCH_NIOS2 15 /* Nios-II */
#define IH_ARCH_BLACKFIN 16 /* Blackfin */
#define IH_ARCH_AVR32 17 /* AVR32 */
#define IH_ARCH_ST200 18 /* STMicroelectronics ST200 */
#define IH_ARCH_SANDBOX 19 /* Sandbox architecture (test only) */
#define IH_ARCH_NDS32 20 /* ANDES Technology - NDS32 */
#define IH_ARCH_OPENRISC 21 /* OpenRISC 1000 */
#define IH_ARCH_ARM64 22 /* ARM64 */
#define IH_ARCH_ARC 23 /* Synopsys DesignWare ARC */
#define IH_ARCH_X86_64 24 /* AMD x86_64, Intel and Via */
#define IH_ARCH_XTENSA 25 /* Xtensa */
#define IH_ARCH_RISCV 26 /* RISC-V */
/*
* Image Types
*
* "Standalone Programs" are directly runnable in the environment
* provided by U-Boot; it is expected that (if they behave
* well) you can continue to work in U-Boot after return from
* the Standalone Program.
* "OS Kernel Images" are usually images of some Embedded OS which
* will take over control completely. Usually these programs
* will install their own set of exception handlers, device
* drivers, set up the MMU, etc. - this means, that you cannot
* expect to re-enter U-Boot except by resetting the CPU.
* "RAMDisk Images" are more or less just data blocks, and their
* parameters (address, size) are passed to an OS kernel that is
* being started.
* "Multi-File Images" contain several images, typically an OS
* (Linux) kernel image and one or more data images like
* RAMDisks. This construct is useful for instance when you want
* to boot over the network using BOOTP etc., where the boot
* server provides just a single image file, but you want to get
* for instance an OS kernel and a RAMDisk image.
*
* "Multi-File Images" start with a list of image sizes, each
* image size (in bytes) specified by an "uint32_t" in network
* byte order. This list is terminated by an "(uint32_t)0".
* Immediately after the terminating 0 follow the images, one by
* one, all aligned on "uint32_t" boundaries (size rounded up to
* a multiple of 4 bytes - except for the last file).
*
* "Firmware Images" are binary images containing firmware (like
* U-Boot or FPGA images) which usually will be programmed to
* flash memory.
*
* "Script files" are command sequences that will be executed by
* U-Boot's command interpreter; this feature is especially
* useful when you configure U-Boot to use a real shell (hush)
* as command interpreter (=> Shell Scripts).
*
* The following are exposed to uImage header.
* New IDs *MUST* be appended at the end of the list and *NEVER*
* inserted for backward compatibility.
*/
#define IH_TYPE_INVALID 0 /* Invalid Image */
#define IH_TYPE_STANDALONE 1 /* Standalone Program */
#define IH_TYPE_KERNEL 2 /* OS Kernel Image */
#define IH_TYPE_RAMDISK 3 /* RAMDisk Image */
#define IH_TYPE_MULTI 4 /* Multi-File Image */
#define IH_TYPE_FIRMWARE 5 /* Firmware Image */
#define IH_TYPE_SCRIPT 6 /* Script file */
#define IH_TYPE_FILESYSTEM 7 /* Filesystem Image (any type) */
#define IH_TYPE_FLATDT 8 /* Binary Flat Device Tree Blob */
#define IH_TYPE_KWBIMAGE 9 /* Kirkwood Boot Image */
#define IH_TYPE_IMXIMAGE 10 /* Freescale IMXBoot Image */
#define IH_TYPE_UBLIMAGE 11 /* Davinci UBL Image */
#define IH_TYPE_OMAPIMAGE 12 /* TI OMAP Config Header Image */
#define IH_TYPE_AISIMAGE 13 /* TI Davinci AIS Image */
/* OS Kernel Image, can run from any load address */
#define IH_TYPE_KERNEL_NOLOAD 14
#define IH_TYPE_PBLIMAGE 15 /* Freescale PBL Boot Image */
#define IH_TYPE_MXSIMAGE 16 /* Freescale MXSBoot Image */
#define IH_TYPE_GPIMAGE 17 /* TI Keystone GPHeader Image */
#define IH_TYPE_ATMELIMAGE 18 /* ATMEL ROM bootable Image */
#define IH_TYPE_SOCFPGAIMAGE 19 /* Altera SOCFPGA CV/AV Preloader */
#define IH_TYPE_X86_SETUP 20 /* x86 setup.bin Image */
#define IH_TYPE_LPC32XXIMAGE 21 /* x86 setup.bin Image */
#define IH_TYPE_LOADABLE 22 /* A list of typeless images */
#define IH_TYPE_RKIMAGE 23 /* Rockchip Boot Image */
#define IH_TYPE_RKSD 24 /* Rockchip SD card */
#define IH_TYPE_RKSPI 25 /* Rockchip SPI image */
#define IH_TYPE_ZYNQIMAGE 26 /* Xilinx Zynq Boot Image */
#define IH_TYPE_ZYNQMPIMAGE 27 /* Xilinx ZynqMP Boot Image */
#define IH_TYPE_ZYNQMPBIF 28 /* Xilinx ZynqMP Boot Image (bif) */
#define IH_TYPE_FPGA 29 /* FPGA Image */
#define IH_TYPE_VYBRIDIMAGE 30 /* VYBRID .vyb Image */
#define IH_TYPE_TEE 31 /* Trusted Execution Environment OS Image */
#define IH_TYPE_FIRMWARE_IVT 32 /* Firmware Image with HABv4 IVT */
#define IH_TYPE_PMMC 33 /* TI Power Management Micro-Controller Firmware */
#define IH_TYPE_STM32IMAGE 34 /* STMicroelectronics STM32 Image */
#define IH_TYPE_SOCFPGAIMAGE_V1 35 /* Altera SOCFPGA A10 Preloader */
#define IH_TYPE_MTKIMAGE 36 /* MediaTek BootROM loadable Image */
#define IH_TYPE_IMX8MIMAGE 37 /* Freescale IMX8MBoot Image */
#define IH_TYPE_IMX8IMAGE 38 /* Freescale IMX8Boot Image */
#define IH_TYPE_COPRO 39 /* Coprocessor Image for remoteproc*/
/*
* Compression Types
*
* The following are exposed to uImage header.
* New IDs *MUST* be appended at the end of the list and *NEVER*
* inserted for backward compatibility.
*/
#define IH_COMP_NONE 0 /* No Compression Used */
#define IH_COMP_GZIP 1 /* gzip Compression Used */
#define IH_COMP_BZIP2 2 /* bzip2 Compression Used */
#define IH_COMP_LZMA 3 /* lzma Compression Used */
#define IH_COMP_LZO 4 /* lzo Compression Used */
#define IH_COMP_LZ4 5 /* lz4 Compression Used */
#define LZ4F_MAGIC 0x184D2204 /* LZ4 Magic Number */
#define IH_MAGIC 0x27051956 /* Image Magic Number */
#define IH_NMLEN 32 /* Image Name Length */
/*
* Magic values specific to "openwrt,uimage" partitions
*/
#define IH_MAGIC_OKLI 0x4f4b4c49 /* 'OKLI' */
#define FW_EDIMAX_OFFSET 20 /* Edimax Firmware Offset */
#define FW_MAGIC_EDIMAX 0x43535953 /* Edimax Firmware Magic Number */
#endif /* __UIMAGE_H__ */

View file

@ -0,0 +1,133 @@
/*
* AR8216 switch driver platform data
*
* Copyright (C) 2012 Gabor Juhos <juhosg@openwrt.org>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#ifndef AR8216_PLATFORM_H
#define AR8216_PLATFORM_H
enum ar8327_pad_mode {
AR8327_PAD_NC = 0,
AR8327_PAD_MAC2MAC_MII,
AR8327_PAD_MAC2MAC_GMII,
AR8327_PAD_MAC_SGMII,
AR8327_PAD_MAC2PHY_MII,
AR8327_PAD_MAC2PHY_GMII,
AR8327_PAD_MAC_RGMII,
AR8327_PAD_PHY_GMII,
AR8327_PAD_PHY_RGMII,
AR8327_PAD_PHY_MII,
};
enum ar8327_clk_delay_sel {
AR8327_CLK_DELAY_SEL0 = 0,
AR8327_CLK_DELAY_SEL1,
AR8327_CLK_DELAY_SEL2,
AR8327_CLK_DELAY_SEL3,
};
struct ar8327_pad_cfg {
enum ar8327_pad_mode mode;
bool rxclk_sel;
bool txclk_sel;
bool pipe_rxclk_sel;
bool txclk_delay_en;
bool rxclk_delay_en;
bool sgmii_delay_en;
enum ar8327_clk_delay_sel txclk_delay_sel;
enum ar8327_clk_delay_sel rxclk_delay_sel;
bool mac06_exchange_dis;
};
enum ar8327_port_speed {
AR8327_PORT_SPEED_10 = 0,
AR8327_PORT_SPEED_100,
AR8327_PORT_SPEED_1000,
};
struct ar8327_port_cfg {
int force_link:1;
enum ar8327_port_speed speed;
int txpause:1;
int rxpause:1;
int duplex:1;
};
struct ar8327_sgmii_cfg {
u32 sgmii_ctrl;
bool serdes_aen;
};
struct ar8327_led_cfg {
u32 led_ctrl0;
u32 led_ctrl1;
u32 led_ctrl2;
u32 led_ctrl3;
bool open_drain;
};
enum ar8327_led_num {
AR8327_LED_PHY0_0 = 0,
AR8327_LED_PHY0_1,
AR8327_LED_PHY0_2,
AR8327_LED_PHY1_0,
AR8327_LED_PHY1_1,
AR8327_LED_PHY1_2,
AR8327_LED_PHY2_0,
AR8327_LED_PHY2_1,
AR8327_LED_PHY2_2,
AR8327_LED_PHY3_0,
AR8327_LED_PHY3_1,
AR8327_LED_PHY3_2,
AR8327_LED_PHY4_0,
AR8327_LED_PHY4_1,
AR8327_LED_PHY4_2,
};
enum ar8327_led_mode {
AR8327_LED_MODE_HW = 0,
AR8327_LED_MODE_SW,
};
struct ar8327_led_info {
const char *name;
const char *default_trigger;
bool active_low;
enum ar8327_led_num led_num;
enum ar8327_led_mode mode;
};
#define AR8327_LED_INFO(_led, _mode, _name) { \
.name = (_name), \
.led_num = AR8327_LED_ ## _led, \
.mode = AR8327_LED_MODE_ ## _mode \
}
struct ar8327_platform_data {
struct ar8327_pad_cfg *pad0_cfg;
struct ar8327_pad_cfg *pad5_cfg;
struct ar8327_pad_cfg *pad6_cfg;
struct ar8327_sgmii_cfg *sgmii_cfg;
struct ar8327_port_cfg port0_cfg;
struct ar8327_port_cfg port6_cfg;
struct ar8327_led_cfg *led_cfg;
int (*get_port_link)(unsigned port);
unsigned num_leds;
const struct ar8327_led_info *leds;
};
#endif /* AR8216_PLATFORM_H */

View file

@ -0,0 +1,30 @@
/*
* Copyright (c) 2008 Atheros Communications Inc.
* Copyright (c) 2009 Gabor Juhos <juhosg@openwrt.org>
* Copyright (c) 2009 Imre Kaloz <kaloz@openwrt.org>
* Copyright (c) 2010 Daniel Golle <daniel.golle@gmail.com>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#ifndef _LINUX_ATH5K_PLATFORM_H
#define _LINUX_ATH5K_PLATFORM_H
#define ATH5K_PLAT_EEP_MAX_WORDS 2048
struct ath5k_platform_data {
u16 *eeprom_data;
u8 *macaddr;
};
#endif /* _LINUX_ATH5K_PLATFORM_H */

View file

@ -0,0 +1,58 @@
/*
* Copyright (c) 2008 Atheros Communications Inc.
* Copyright (c) 2009 Gabor Juhos <juhosg@openwrt.org>
* Copyright (c) 2009 Imre Kaloz <kaloz@openwrt.org>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#ifndef _LINUX_ATH9K_PLATFORM_H
#define _LINUX_ATH9K_PLATFORM_H
#define ATH9K_PLAT_EEP_MAX_WORDS 2048
struct ath9k_platform_data {
const char *eeprom_name;
u16 eeprom_data[ATH9K_PLAT_EEP_MAX_WORDS];
u8 *macaddr;
int led_pin;
u32 gpio_mask;
u32 gpio_val;
u32 bt_active_pin;
u32 bt_priority_pin;
u32 wlan_active_pin;
bool endian_check;
bool is_clk_25mhz;
bool tx_gain_buffalo;
bool disable_2ghz;
bool disable_5ghz;
bool led_active_high;
int (*get_mac_revision)(void);
int (*external_reset)(void);
bool use_eeprom;
int num_leds;
const struct gpio_led *leds;
unsigned num_btns;
const struct gpio_keys_button *btns;
unsigned btn_poll_interval;
};
#endif /* _LINUX_ATH9K_PLATFORM_H */

View file

@ -0,0 +1,18 @@
#ifndef __MTK_BMT_H
#define __MTK_BMT_H
#ifdef CONFIG_MTD_NAND_MTK_BMT
int mtk_bmt_attach(struct mtd_info *mtd);
void mtk_bmt_detach(struct mtd_info *mtd);
#else
static inline int mtk_bmt_attach(struct mtd_info *mtd)
{
return 0;
}
static inline void mtk_bmt_detach(struct mtd_info *mtd)
{
}
#endif
#endif

View file

@ -0,0 +1,121 @@
/*
* Compex's MyLoader specific definitions
*
* Copyright (C) 2006-2008 Gabor Juhos <juhosg@openwrt.org>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 as published
* by the Free Software Foundation.
*
*/
#ifndef _MYLOADER_H_
#define _MYLOADER_H_
/* Myloader specific magic numbers */
#define MYLO_MAGIC_SYS_PARAMS 0x20021107
#define MYLO_MAGIC_PARTITIONS 0x20021103
#define MYLO_MAGIC_BOARD_PARAMS 0x20021103
/* Vendor ID's (seems to be same as the PCI vendor ID's) */
#define VENID_COMPEX 0x11F6
/* Devices based on the ADM5120 */
#define DEVID_COMPEX_NP27G 0x0078
#define DEVID_COMPEX_NP28G 0x044C
#define DEVID_COMPEX_NP28GHS 0x044E
#define DEVID_COMPEX_WP54Gv1C 0x0514
#define DEVID_COMPEX_WP54G 0x0515
#define DEVID_COMPEX_WP54AG 0x0546
#define DEVID_COMPEX_WPP54AG 0x0550
#define DEVID_COMPEX_WPP54G 0x0555
/* Devices based on the Atheros AR2317 */
#define DEVID_COMPEX_NP25G 0x05E6
#define DEVID_COMPEX_WPE53G 0x05DC
/* Devices based on the Atheros AR71xx */
#define DEVID_COMPEX_WP543 0x0640
#define DEVID_COMPEX_WPE72 0x0672
/* Devices based on the IXP422 */
#define DEVID_COMPEX_WP18 0x047E
#define DEVID_COMPEX_NP18A 0x0489
/* Other devices */
#define DEVID_COMPEX_NP26G8M 0x03E8
#define DEVID_COMPEX_NP26G16M 0x03E9
struct mylo_partition {
uint16_t flags; /* partition flags */
uint16_t type; /* type of the partition */
uint32_t addr; /* relative address of the partition from the
flash start */
uint32_t size; /* size of the partition in bytes */
uint32_t param; /* if this is the active partition, the
MyLoader load code to this address */
};
#define PARTITION_FLAG_ACTIVE 0x8000 /* this is the active partition,
* MyLoader loads firmware from here */
#define PARTITION_FLAG_ISRAM 0x2000 /* FIXME: this is a RAM partition? */
#define PARTIIION_FLAG_RAMLOAD 0x1000 /* FIXME: load this partition into the RAM? */
#define PARTITION_FLAG_PRELOAD 0x0800 /* the partition data preloaded to RAM
* before decompression */
#define PARTITION_FLAG_LZMA 0x0100 /* partition data compressed by LZMA */
#define PARTITION_FLAG_HAVEHDR 0x0002 /* the partition data have a header */
#define PARTITION_TYPE_FREE 0
#define PARTITION_TYPE_USED 1
#define MYLO_MAX_PARTITIONS 8 /* maximum number of partitions in the
partition table */
struct mylo_partition_table {
uint32_t magic; /* must be MYLO_MAGIC_PARTITIONS */
uint32_t res0; /* unknown/unused */
uint32_t res1; /* unknown/unused */
uint32_t res2; /* unknown/unused */
struct mylo_partition partitions[MYLO_MAX_PARTITIONS];
};
struct mylo_partition_header {
uint32_t len; /* length of the partition data */
uint32_t crc; /* CRC value of the partition data */
};
struct mylo_system_params {
uint32_t magic; /* must be MYLO_MAGIC_SYS_PARAMS */
uint32_t res0;
uint32_t res1;
uint32_t mylo_ver;
uint16_t vid; /* Vendor ID */
uint16_t did; /* Device ID */
uint16_t svid; /* Sub Vendor ID */
uint16_t sdid; /* Sub Device ID */
uint32_t rev; /* device revision */
uint32_t fwhi;
uint32_t fwlo;
uint32_t tftp_addr;
uint32_t prog_start;
uint32_t flash_size; /* size of boot FLASH in bytes */
uint32_t dram_size; /* size of onboard RAM in bytes */
};
struct mylo_eth_addr {
uint8_t mac[6];
uint8_t csum[2];
};
#define MYLO_ETHADDR_COUNT 8 /* maximum number of ethernet address
in the board parameters */
struct mylo_board_params {
uint32_t magic; /* must be MYLO_MAGIC_BOARD_PARAMS */
uint32_t res0;
uint32_t res1;
uint32_t res2;
struct mylo_eth_addr addr[MYLO_ETHADDR_COUNT];
};
#endif /* _MYLOADER_H_*/

View file

@ -0,0 +1,29 @@
/*
* ADM6996 GPIO platform data
*
* Copyright (C) 2013 Hauke Mehrtens <hauke@hauke-m.de>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License v2 as published by the
* Free Software Foundation
*/
#ifndef __PLATFORM_ADM6996_GPIO_H
#define __PLATFORM_ADM6996_GPIO_H
#include <linux/kernel.h>
enum adm6996_model {
ADM6996FC = 1,
ADM6996M = 2,
ADM6996L = 3,
};
struct adm6996_gpio_platform_data {
u8 eecs;
u8 eesk;
u8 eedi;
enum adm6996_model model;
};
#endif

View file

@ -0,0 +1,106 @@
/*
* Mikrotik's RouterBOOT definitions
*
* Copyright (C) 2007-2008 Gabor Juhos <juhosg@openwrt.org>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 as published
* by the Free Software Foundation.
*
*/
#ifndef _ROUTERBOOT_H
#define _ROUTERBOOT_H
#define RB_MAC_SIZE 6
/*
* Magic numbers
*/
#define RB_MAGIC_HARD 0x64726148 /* "Hard" */
#define RB_MAGIC_SOFT 0x74666F53 /* "Soft" */
#define RB_MAGIC_DAWN 0x6E776144 /* "Dawn" */
#define RB_ID_TERMINATOR 0
/*
* ID values for Hardware settings
*/
#define RB_ID_HARD_01 1
#define RB_ID_HARD_02 2
#define RB_ID_FLASH_INFO 3
#define RB_ID_MAC_ADDRESS_PACK 4
#define RB_ID_BOARD_NAME 5
#define RB_ID_BIOS_VERSION 6
#define RB_ID_HARD_07 7
#define RB_ID_SDRAM_TIMINGS 8
#define RB_ID_DEVICE_TIMINGS 9
#define RB_ID_SOFTWARE_ID 10
#define RB_ID_SERIAL_NUMBER 11
#define RB_ID_HARD_12 12
#define RB_ID_MEMORY_SIZE 13
#define RB_ID_MAC_ADDRESS_COUNT 14
#define RB_ID_HW_OPTIONS 21
#define RB_ID_WLAN_DATA 22
/*
* ID values for Software settings
*/
#define RB_ID_UART_SPEED 1
#define RB_ID_BOOT_DELAY 2
#define RB_ID_BOOT_DEVICE 3
#define RB_ID_BOOT_KEY 4
#define RB_ID_CPU_MODE 5
#define RB_ID_FW_VERSION 6
#define RB_ID_SOFT_07 7
#define RB_ID_SOFT_08 8
#define RB_ID_BOOT_PROTOCOL 9
#define RB_ID_SOFT_10 10
#define RB_ID_SOFT_11 11
/*
* UART_SPEED values
*/
#define RB_UART_SPEED_115200 0
#define RB_UART_SPEED_57600 1
#define RB_UART_SPEED_38400 2
#define RB_UART_SPEED_19200 3
#define RB_UART_SPEED_9600 4
#define RB_UART_SPEED_4800 5
#define RB_UART_SPEED_2400 6
#define RB_UART_SPEED_1200 7
/*
* BOOT_DELAY values
*/
#define RB_BOOT_DELAY_0SEC 0
#define RB_BOOT_DELAY_1SEC 1
#define RB_BOOT_DELAY_2SEC 2
/*
* BOOT_DEVICE values
*/
#define RB_BOOT_DEVICE_ETHER 0
#define RB_BOOT_DEVICE_NANDETH 1
#define RB_BOOT_DEVICE_ETHONCE 2
#define RB_BOOT_DEVICE_NANDONLY 3
/*
* BOOT_KEY values
*/
#define RB_BOOT_KEY_ANY 0
#define RB_BOOT_KEY_DEL 1
/*
* CPU_MODE values
*/
#define RB_CPU_MODE_POWERSAVE 0
#define RB_CPU_MODE_REGULAR 1
/*
* BOOT_PROTOCOL values
*/
#define RB_BOOT_PROTOCOL_BOOTP 0
#define RB_BOOT_PROTOCOL_DHCP 1
#endif /* _ROUTERBOOT_H */

View file

@ -0,0 +1,23 @@
/*
* Platform data definition for the rt2x00 driver
*
* Copyright (C) 2011 Gabor Juhos <juhosg@openwrt.org>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 as published
* by the Free Software Foundation.
*
*/
#ifndef _RT2X00_PLATFORM_H
#define _RT2X00_PLATFORM_H
struct rt2x00_platform_data {
char *eeprom_file_name;
const u8 *mac_address;
int disable_2ghz;
int disable_5ghz;
};
#endif /* _RT2X00_PLATFORM_H */

View file

@ -0,0 +1,42 @@
/*
* Platform data definition for the Realtek RTL8366RB/S ethernet switch driver
*
* Copyright (C) 2009-2010 Gabor Juhos <juhosg@openwrt.org>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 as published
* by the Free Software Foundation.
*/
#ifndef _RTL8366_H
#define _RTL8366_H
#define RTL8366_DRIVER_NAME "rtl8366"
#define RTL8366S_DRIVER_NAME "rtl8366s"
#define RTL8366RB_DRIVER_NAME "rtl8366rb"
struct rtl8366_smi;
enum rtl8366_type {
RTL8366_TYPE_UNKNOWN,
RTL8366_TYPE_S,
RTL8366_TYPE_RB,
};
struct rtl8366_initval {
unsigned reg;
u16 val;
};
struct rtl8366_platform_data {
unsigned gpio_sda;
unsigned gpio_sck;
void (*hw_reset)(struct rtl8366_smi *smi, bool active);
unsigned num_initvals;
struct rtl8366_initval *initvals;
};
enum rtl8366_type rtl8366_smi_detect(struct rtl8366_platform_data *pdata);
#endif /* _RTL8366_H */

Some files were not shown because too many files have changed in this diff Show more