From 3b50d9a174575473cc5ed8eba3570e02ea3d0733 Mon Sep 17 00:00:00 2001 From: Lukas Bulwahn Date: Tue, 14 Mar 2023 05:18:48 +0100 Subject: MAINTAINERS: adjust file entries after wifi driver movement Commit f79cbc77abde ("wifi: move mac80211_hwsim and virt_wifi to virtual directory") and commit 298e50ad8eb8 ("wifi: move raycs, wl3501 and rndis_wlan to legacy directory") move remaining wireless drivers into subdirectories, but does not adjust the entries in MAINTAINERS. Hence, ./scripts/get_maintainer.pl --self-test=patterns complains about broken references. Repair these file references in those wireless driver sections. Signed-off-by: Lukas Bulwahn Signed-off-by: Kalle Valo Link: https://lore.kernel.org/r/20230314041848.5120-1-lukas.bulwahn@gmail.com --- MAINTAINERS | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 8d5bc223f305..0dab503c88ac 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -12275,7 +12275,7 @@ T: git git://git.kernel.org/pub/scm/linux/kernel/git/wireless/wireless.git T: git git://git.kernel.org/pub/scm/linux/kernel/git/wireless/wireless-next.git F: Documentation/networking/mac80211-injection.rst F: Documentation/networking/mac80211_hwsim/mac80211_hwsim.rst -F: drivers/net/wireless/mac80211_hwsim.[ch] +F: drivers/net/wireless/virtual/mac80211_hwsim.[ch] F: include/net/mac80211.h F: net/mac80211/ @@ -17542,7 +17542,7 @@ F: include/ras/ras_event.h RAYLINK/WEBGEAR 802.11 WIRELESS LAN DRIVER L: linux-wireless@vger.kernel.org S: Orphan -F: drivers/net/wireless/ray* +F: drivers/net/wireless/legacy/ray* RC-CORE / LIRC FRAMEWORK M: Sean Young @@ -21797,7 +21797,7 @@ USB WIRELESS RNDIS DRIVER (rndis_wlan) M: Jussi Kivilinna L: linux-wireless@vger.kernel.org S: Maintained -F: drivers/net/wireless/rndis_wlan.c +F: drivers/net/wireless/legacy/rndis_wlan.c USB XHCI DRIVER M: Mathias Nyman @@ -22551,7 +22551,7 @@ F: drivers/input/misc/wistron_btns.c WL3501 WIRELESS PCMCIA CARD DRIVER L: linux-wireless@vger.kernel.org S: Odd fixes -F: drivers/net/wireless/wl3501* +F: drivers/net/wireless/legacy/wl3501* WOLFSON MICROELECTRONICS DRIVERS L: patches@opensource.cirrus.com -- cgit v1.2.3 From 56aecc0a655d997342ca85a3dd1f59715f2b3349 Mon Sep 17 00:00:00 2001 From: Lukas Bulwahn Date: Mon, 20 Mar 2023 08:32:01 +0100 Subject: MAINTAINERS: remove file entry in NFC SUBSYSTEM after platform_data movement Commit 053fdaa841bd ("nfc: mrvl: Move platform_data struct into driver") moves the nfcmrvl.h header file from include/linux/platform_data to the driver's directory, but misses to adjust MAINTAINERS. Hence, ./scripts/get_maintainer.pl --self-test=patterns complains about a broken reference. Just remove the file entry in NFC SUBSYSTEM, as the new location of the code is already covered by another pattern in that section. Fixes: 053fdaa841bd ("nfc: mrvl: Move platform_data struct into driver") Signed-off-by: Lukas Bulwahn Reviewed-by: Krzysztof Kozlowski Signed-off-by: David S. Miller --- MAINTAINERS | 1 - 1 file changed, 1 deletion(-) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 9faef5784c03..30ca644d704f 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -14662,7 +14662,6 @@ S: Maintained B: mailto:linux-nfc@lists.01.org F: Documentation/devicetree/bindings/net/nfc/ F: drivers/nfc/ -F: include/linux/platform_data/nfcmrvl.h F: include/net/nfc/ F: include/uapi/linux/nfc.h F: net/nfc/ -- cgit v1.2.3 From 4765a9722e09765866e131ec31f7b9cf4c1f4854 Mon Sep 17 00:00:00 2001 From: Daniel Golle Date: Sun, 19 Mar 2023 12:57:50 +0000 Subject: net: pcs: add driver for MediaTek SGMII PCS The SGMII core found in several MediaTek SoCs is identical to what can also be found in MediaTek's MT7531 Ethernet switch IC. As this has not always been clear, both drivers developed different implementations to deal with the PCS. Recently Alexander Couzens pointed out this fact which lead to the development of this shared driver. Add a dedicated driver, mostly by copying the code now found in the Ethernet driver. The now redundant code will be removed by a follow-up commit. Suggested-by: Alexander Couzens Suggested-by: Russell King (Oracle) Signed-off-by: Daniel Golle Tested-by: Frank Wunderlich Reviewed-by: Russell King (Oracle) Signed-off-by: Jakub Kicinski --- MAINTAINERS | 8 + drivers/net/pcs/Kconfig | 7 + drivers/net/pcs/Makefile | 1 + drivers/net/pcs/pcs-mtk-lynxi.c | 305 ++++++++++++++++++++++++++++++++++++++ include/linux/pcs/pcs-mtk-lynxi.h | 13 ++ 5 files changed, 334 insertions(+) create mode 100644 drivers/net/pcs/pcs-mtk-lynxi.c create mode 100644 include/linux/pcs/pcs-mtk-lynxi.h (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 30ca644d704f..54a2a8122a97 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -13042,6 +13042,14 @@ L: netdev@vger.kernel.org S: Maintained F: drivers/net/ethernet/mediatek/ +MEDIATEK ETHERNET PCS DRIVER +M: Alexander Couzens +M: Daniel Golle +L: netdev@vger.kernel.org +S: Maintained +F: drivers/net/pcs/pcs-mtk-lynxi.c +F: include/linux/pcs/pcs-mtk-lynxi.h + MEDIATEK I2C CONTROLLER DRIVER M: Qii Wang L: linux-i2c@vger.kernel.org diff --git a/drivers/net/pcs/Kconfig b/drivers/net/pcs/Kconfig index 6e7e6c346a3e..7c34fb7cbf7b 100644 --- a/drivers/net/pcs/Kconfig +++ b/drivers/net/pcs/Kconfig @@ -18,6 +18,13 @@ config PCS_LYNX This module provides helpers to phylink for managing the Lynx PCS which is part of the Layerscape and QorIQ Ethernet SERDES. +config PCS_MTK_LYNXI + tristate + select REGMAP + help + This module provides helpers to phylink for managing the LynxI PCS + which is part of MediaTek's SoC and Ethernet switch ICs. + config PCS_RZN1_MIIC tristate "Renesas RZ/N1 MII converter" depends on OF && (ARCH_RZN1 || COMPILE_TEST) diff --git a/drivers/net/pcs/Makefile b/drivers/net/pcs/Makefile index 4c780d8f2e98..9b9afd6b1c22 100644 --- a/drivers/net/pcs/Makefile +++ b/drivers/net/pcs/Makefile @@ -5,5 +5,6 @@ pcs_xpcs-$(CONFIG_PCS_XPCS) := pcs-xpcs.o pcs-xpcs-nxp.o obj-$(CONFIG_PCS_XPCS) += pcs_xpcs.o obj-$(CONFIG_PCS_LYNX) += pcs-lynx.o +obj-$(CONFIG_PCS_MTK_LYNXI) += pcs-mtk-lynxi.o obj-$(CONFIG_PCS_RZN1_MIIC) += pcs-rzn1-miic.o obj-$(CONFIG_PCS_ALTERA_TSE) += pcs-altera-tse.o diff --git a/drivers/net/pcs/pcs-mtk-lynxi.c b/drivers/net/pcs/pcs-mtk-lynxi.c new file mode 100644 index 000000000000..888452325edc --- /dev/null +++ b/drivers/net/pcs/pcs-mtk-lynxi.c @@ -0,0 +1,305 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2018-2019 MediaTek Inc. +/* A library for MediaTek SGMII circuit + * + * Author: Sean Wang + * Author: Alexander Couzens + * Author: Daniel Golle + * + */ + +#include +#include +#include +#include +#include + +/* SGMII subsystem config registers */ +/* BMCR (low 16) BMSR (high 16) */ +#define SGMSYS_PCS_CONTROL_1 0x0 +#define SGMII_BMCR GENMASK(15, 0) +#define SGMII_BMSR GENMASK(31, 16) + +#define SGMSYS_PCS_DEVICE_ID 0x4 +#define SGMII_LYNXI_DEV_ID 0x4d544950 + +#define SGMSYS_PCS_ADVERTISE 0x8 +#define SGMII_ADVERTISE GENMASK(15, 0) +#define SGMII_LPA GENMASK(31, 16) + +#define SGMSYS_PCS_SCRATCH 0x14 +#define SGMII_DEV_VERSION GENMASK(31, 16) + +/* Register to programmable link timer, the unit in 2 * 8ns */ +#define SGMSYS_PCS_LINK_TIMER 0x18 +#define SGMII_LINK_TIMER_MASK GENMASK(19, 0) +#define SGMII_LINK_TIMER_VAL(ns) FIELD_PREP(SGMII_LINK_TIMER_MASK, \ + ((ns) / 2 / 8)) + +/* Register to control remote fault */ +#define SGMSYS_SGMII_MODE 0x20 +#define SGMII_IF_MODE_SGMII BIT(0) +#define SGMII_SPEED_DUPLEX_AN BIT(1) +#define SGMII_SPEED_MASK GENMASK(3, 2) +#define SGMII_SPEED_10 FIELD_PREP(SGMII_SPEED_MASK, 0) +#define SGMII_SPEED_100 FIELD_PREP(SGMII_SPEED_MASK, 1) +#define SGMII_SPEED_1000 FIELD_PREP(SGMII_SPEED_MASK, 2) +#define SGMII_DUPLEX_HALF BIT(4) +#define SGMII_REMOTE_FAULT_DIS BIT(8) + +/* Register to reset SGMII design */ +#define SGMSYS_RESERVED_0 0x34 +#define SGMII_SW_RESET BIT(0) + +/* Register to set SGMII speed, ANA RG_ Control Signals III */ +#define SGMII_PHY_SPEED_MASK GENMASK(3, 2) +#define SGMII_PHY_SPEED_1_25G FIELD_PREP(SGMII_PHY_SPEED_MASK, 0) +#define SGMII_PHY_SPEED_3_125G FIELD_PREP(SGMII_PHY_SPEED_MASK, 1) + +/* Register to power up QPHY */ +#define SGMSYS_QPHY_PWR_STATE_CTRL 0xe8 +#define SGMII_PHYA_PWD BIT(4) + +/* Register to QPHY wrapper control */ +#define SGMSYS_QPHY_WRAP_CTRL 0xec +#define SGMII_PN_SWAP_MASK GENMASK(1, 0) +#define SGMII_PN_SWAP_TX_RX (BIT(0) | BIT(1)) + +/* struct mtk_pcs_lynxi - This structure holds each sgmii regmap andassociated + * data + * @regmap: The register map pointing at the range used to setup + * SGMII modes + * @dev: Pointer to device owning the PCS + * @ana_rgc3: The offset of register ANA_RGC3 relative to regmap + * @interface: Currently configured interface mode + * @pcs: Phylink PCS structure + * @flags: Flags indicating hardware properties + */ +struct mtk_pcs_lynxi { + struct regmap *regmap; + u32 ana_rgc3; + phy_interface_t interface; + struct phylink_pcs pcs; + u32 flags; +}; + +static struct mtk_pcs_lynxi *pcs_to_mtk_pcs_lynxi(struct phylink_pcs *pcs) +{ + return container_of(pcs, struct mtk_pcs_lynxi, pcs); +} + +static void mtk_pcs_lynxi_get_state(struct phylink_pcs *pcs, + struct phylink_link_state *state) +{ + struct mtk_pcs_lynxi *mpcs = pcs_to_mtk_pcs_lynxi(pcs); + unsigned int bm, adv; + + /* Read the BMSR and LPA */ + regmap_read(mpcs->regmap, SGMSYS_PCS_CONTROL_1, &bm); + regmap_read(mpcs->regmap, SGMSYS_PCS_ADVERTISE, &adv); + + phylink_mii_c22_pcs_decode_state(state, FIELD_GET(SGMII_BMSR, bm), + FIELD_GET(SGMII_LPA, adv)); +} + +static int mtk_pcs_lynxi_config(struct phylink_pcs *pcs, unsigned int mode, + phy_interface_t interface, + const unsigned long *advertising, + bool permit_pause_to_mac) +{ + struct mtk_pcs_lynxi *mpcs = pcs_to_mtk_pcs_lynxi(pcs); + bool mode_changed = false, changed, use_an; + unsigned int rgc3, sgm_mode, bmcr; + int advertise, link_timer; + + advertise = phylink_mii_c22_pcs_encode_advertisement(interface, + advertising); + if (advertise < 0) + return advertise; + + /* Clearing IF_MODE_BIT0 switches the PCS to BASE-X mode, and + * we assume that fixes it's speed at bitrate = line rate (in + * other words, 1000Mbps or 2500Mbps). + */ + if (interface == PHY_INTERFACE_MODE_SGMII) { + sgm_mode = SGMII_IF_MODE_SGMII; + if (phylink_autoneg_inband(mode)) { + sgm_mode |= SGMII_REMOTE_FAULT_DIS | + SGMII_SPEED_DUPLEX_AN; + use_an = true; + } else { + use_an = false; + } + } else if (phylink_autoneg_inband(mode)) { + /* 1000base-X or 2500base-X autoneg */ + sgm_mode = SGMII_REMOTE_FAULT_DIS; + use_an = linkmode_test_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, + advertising); + } else { + /* 1000base-X or 2500base-X without autoneg */ + sgm_mode = 0; + use_an = false; + } + + if (use_an) + bmcr = BMCR_ANENABLE; + else + bmcr = 0; + + if (mpcs->interface != interface) { + link_timer = phylink_get_link_timer_ns(interface); + if (link_timer < 0) + return link_timer; + + /* PHYA power down */ + regmap_set_bits(mpcs->regmap, SGMSYS_QPHY_PWR_STATE_CTRL, + SGMII_PHYA_PWD); + + /* Reset SGMII PCS state */ + regmap_set_bits(mpcs->regmap, SGMSYS_RESERVED_0, + SGMII_SW_RESET); + + if (mpcs->flags & MTK_SGMII_FLAG_PN_SWAP) + regmap_update_bits(mpcs->regmap, SGMSYS_QPHY_WRAP_CTRL, + SGMII_PN_SWAP_MASK, + SGMII_PN_SWAP_TX_RX); + + if (interface == PHY_INTERFACE_MODE_2500BASEX) + rgc3 = SGMII_PHY_SPEED_3_125G; + else + rgc3 = SGMII_PHY_SPEED_1_25G; + + /* Configure the underlying interface speed */ + regmap_update_bits(mpcs->regmap, mpcs->ana_rgc3, + SGMII_PHY_SPEED_MASK, rgc3); + + /* Setup the link timer */ + regmap_write(mpcs->regmap, SGMSYS_PCS_LINK_TIMER, + SGMII_LINK_TIMER_VAL(link_timer)); + + mpcs->interface = interface; + mode_changed = true; + } + + /* Update the advertisement, noting whether it has changed */ + regmap_update_bits_check(mpcs->regmap, SGMSYS_PCS_ADVERTISE, + SGMII_ADVERTISE, advertise, &changed); + + /* Update the sgmsys mode register */ + regmap_update_bits(mpcs->regmap, SGMSYS_SGMII_MODE, + SGMII_REMOTE_FAULT_DIS | SGMII_SPEED_DUPLEX_AN | + SGMII_IF_MODE_SGMII, sgm_mode); + + /* Update the BMCR */ + regmap_update_bits(mpcs->regmap, SGMSYS_PCS_CONTROL_1, + BMCR_ANENABLE, bmcr); + + /* Release PHYA power down state + * Only removing bit SGMII_PHYA_PWD isn't enough. + * There are cases when the SGMII_PHYA_PWD register contains 0x9 which + * prevents SGMII from working. The SGMII still shows link but no traffic + * can flow. Writing 0x0 to the PHYA_PWD register fix the issue. 0x0 was + * taken from a good working state of the SGMII interface. + * Unknown how much the QPHY needs but it is racy without a sleep. + * Tested on mt7622 & mt7986. + */ + usleep_range(50, 100); + regmap_write(mpcs->regmap, SGMSYS_QPHY_PWR_STATE_CTRL, 0); + + return changed || mode_changed; +} + +static void mtk_pcs_lynxi_restart_an(struct phylink_pcs *pcs) +{ + struct mtk_pcs_lynxi *mpcs = pcs_to_mtk_pcs_lynxi(pcs); + + regmap_set_bits(mpcs->regmap, SGMSYS_PCS_CONTROL_1, BMCR_ANRESTART); +} + +static void mtk_pcs_lynxi_link_up(struct phylink_pcs *pcs, unsigned int mode, + phy_interface_t interface, int speed, + int duplex) +{ + struct mtk_pcs_lynxi *mpcs = pcs_to_mtk_pcs_lynxi(pcs); + unsigned int sgm_mode; + + if (!phylink_autoneg_inband(mode)) { + /* Force the speed and duplex setting */ + if (speed == SPEED_10) + sgm_mode = SGMII_SPEED_10; + else if (speed == SPEED_100) + sgm_mode = SGMII_SPEED_100; + else + sgm_mode = SGMII_SPEED_1000; + + if (duplex != DUPLEX_FULL) + sgm_mode |= SGMII_DUPLEX_HALF; + + regmap_update_bits(mpcs->regmap, SGMSYS_SGMII_MODE, + SGMII_DUPLEX_HALF | SGMII_SPEED_MASK, + sgm_mode); + } +} + +static const struct phylink_pcs_ops mtk_pcs_lynxi_ops = { + .pcs_get_state = mtk_pcs_lynxi_get_state, + .pcs_config = mtk_pcs_lynxi_config, + .pcs_an_restart = mtk_pcs_lynxi_restart_an, + .pcs_link_up = mtk_pcs_lynxi_link_up, +}; + +struct phylink_pcs *mtk_pcs_lynxi_create(struct device *dev, + struct regmap *regmap, u32 ana_rgc3, + u32 flags) +{ + struct mtk_pcs_lynxi *mpcs; + u32 id, ver; + int ret; + + ret = regmap_read(regmap, SGMSYS_PCS_DEVICE_ID, &id); + if (ret < 0) + return NULL; + + if (id != SGMII_LYNXI_DEV_ID) { + dev_err(dev, "unknown PCS device id %08x\n", id); + return NULL; + } + + ret = regmap_read(regmap, SGMSYS_PCS_SCRATCH, &ver); + if (ret < 0) + return NULL; + + ver = FIELD_GET(SGMII_DEV_VERSION, ver); + if (ver != 0x1) { + dev_err(dev, "unknown PCS device version %04x\n", ver); + return NULL; + } + + dev_dbg(dev, "MediaTek LynxI SGMII PCS (id 0x%08x, ver 0x%04x)\n", id, + ver); + + mpcs = kzalloc(sizeof(*mpcs), GFP_KERNEL); + if (!mpcs) + return NULL; + + mpcs->ana_rgc3 = ana_rgc3; + mpcs->regmap = regmap; + mpcs->flags = flags; + mpcs->pcs.ops = &mtk_pcs_lynxi_ops; + mpcs->pcs.poll = true; + mpcs->interface = PHY_INTERFACE_MODE_NA; + + return &mpcs->pcs; +} +EXPORT_SYMBOL(mtk_pcs_lynxi_create); + +void mtk_pcs_lynxi_destroy(struct phylink_pcs *pcs) +{ + if (!pcs) + return; + + kfree(pcs_to_mtk_pcs_lynxi(pcs)); +} +EXPORT_SYMBOL(mtk_pcs_lynxi_destroy); + +MODULE_LICENSE("GPL"); diff --git a/include/linux/pcs/pcs-mtk-lynxi.h b/include/linux/pcs/pcs-mtk-lynxi.h new file mode 100644 index 000000000000..be3b4ab32f4a --- /dev/null +++ b/include/linux/pcs/pcs-mtk-lynxi.h @@ -0,0 +1,13 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __LINUX_PCS_MTK_LYNXI_H +#define __LINUX_PCS_MTK_LYNXI_H + +#include +#include + +#define MTK_SGMII_FLAG_PN_SWAP BIT(0) +struct phylink_pcs *mtk_pcs_lynxi_create(struct device *dev, + struct regmap *regmap, + u32 ana_rgc3, u32 flags); +void mtk_pcs_lynxi_destroy(struct phylink_pcs *pcs); +#endif -- cgit v1.2.3 From f00647d8127be4d3f37f7e07dace24c04689ec63 Mon Sep 17 00:00:00 2001 From: Dario Binacchi Date: Tue, 28 Mar 2023 09:33:28 +0200 Subject: can: bxcan: add support for ST bxCAN controller Add support for the basic extended CAN controller (bxCAN) found in many low- to middle-end STM32 SoCs. It supports the Basic Extended CAN protocol versions 2.0A and B with a maximum bit rate of 1 Mbit/s. The controller supports two channels (CAN1 as primary and CAN2 as secondary) and the driver can enable either or both of the channels. They share some of the required logic (e. g. clocks and filters), and that means you cannot use the secondary CAN without enabling some hardware resources managed by the primary CAN. Each channel has 3 transmit mailboxes, 2 receive FIFOs with 3 stages and 28 scalable filter banks. It also manages 4 dedicated interrupt vectors: - transmit interrupt - FIFO 0 receive interrupt - FIFO 1 receive interrupt - status change error interrupt Driver uses all 3 available mailboxes for transmission and FIFO 0 for reception. Rx filter rules are configured to the minimum. They accept all messages and assign filter 0 to CAN1 and filter 14 to CAN2 in identifier mask mode with 32 bits width. It enables and uses transmit, receive buffers for FIFO 0 and error and status change interrupts. Signed-off-by: Dario Binacchi Reviewed-by: Vincent Mailhol Link: https://lore.kernel.org/all/20230328073328.3949796-6-dario.binacchi@amarulasolutions.com Signed-off-by: Marc Kleine-Budde --- MAINTAINERS | 7 + drivers/net/can/Kconfig | 12 + drivers/net/can/Makefile | 1 + drivers/net/can/bxcan.c | 1098 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 1118 insertions(+) create mode 100644 drivers/net/can/bxcan.c (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index fbbda4671e73..6a784baa57e8 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -4431,6 +4431,13 @@ S: Maintained F: drivers/scsi/BusLogic.* F: drivers/scsi/FlashPoint.* +BXCAN CAN NETWORK DRIVER +M: Dario Binacchi +L: linux-can@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/net/can/st,stm32-bxcan.yaml +F: drivers/net/can/bxcan.c + C-MEDIA CMI8788 DRIVER M: Clemens Ladisch L: alsa-devel@alsa-project.org (moderated for non-subscribers) diff --git a/drivers/net/can/Kconfig b/drivers/net/can/Kconfig index cd34e8dc9394..3ceccafd701b 100644 --- a/drivers/net/can/Kconfig +++ b/drivers/net/can/Kconfig @@ -93,6 +93,18 @@ config CAN_AT91 This is a driver for the SoC CAN controller in Atmel's AT91SAM9263 and AT91SAM9X5 processors. +config CAN_BXCAN + tristate "STM32 Basic Extended CAN (bxCAN) devices" + depends on OF || ARCH_STM32 || COMPILE_TEST + depends on HAS_IOMEM + select CAN_RX_OFFLOAD + help + Say yes here to build support for the STMicroelectronics STM32 basic + extended CAN Controller (bxCAN). + + This driver can also be built as a module. If so, the module + will be called bxcan. + config CAN_CAN327 tristate "Serial / USB serial ELM327 based OBD-II Interfaces (can327)" depends on TTY diff --git a/drivers/net/can/Makefile b/drivers/net/can/Makefile index 52b0f6e10668..ff8f76295d13 100644 --- a/drivers/net/can/Makefile +++ b/drivers/net/can/Makefile @@ -14,6 +14,7 @@ obj-y += usb/ obj-y += softing/ obj-$(CONFIG_CAN_AT91) += at91_can.o +obj-$(CONFIG_CAN_BXCAN) += bxcan.o obj-$(CONFIG_CAN_CAN327) += can327.o obj-$(CONFIG_CAN_CC770) += cc770/ obj-$(CONFIG_CAN_C_CAN) += c_can/ diff --git a/drivers/net/can/bxcan.c b/drivers/net/can/bxcan.c new file mode 100644 index 000000000000..e26ccd41e3cb --- /dev/null +++ b/drivers/net/can/bxcan.c @@ -0,0 +1,1098 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// bxcan.c - STM32 Basic Extended CAN controller driver +// +// Copyright (c) 2022 Dario Binacchi +// +// NOTE: The ST documentation uses the terms master/slave instead of +// primary/secondary. + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define BXCAN_NAPI_WEIGHT 3 +#define BXCAN_TIMEOUT_US 10000 + +#define BXCAN_RX_MB_NUM 2 +#define BXCAN_TX_MB_NUM 3 + +/* Primary control register (MCR) bits */ +#define BXCAN_MCR_RESET BIT(15) +#define BXCAN_MCR_TTCM BIT(7) +#define BXCAN_MCR_ABOM BIT(6) +#define BXCAN_MCR_AWUM BIT(5) +#define BXCAN_MCR_NART BIT(4) +#define BXCAN_MCR_RFLM BIT(3) +#define BXCAN_MCR_TXFP BIT(2) +#define BXCAN_MCR_SLEEP BIT(1) +#define BXCAN_MCR_INRQ BIT(0) + +/* Primary status register (MSR) bits */ +#define BXCAN_MSR_ERRI BIT(2) +#define BXCAN_MSR_SLAK BIT(1) +#define BXCAN_MSR_INAK BIT(0) + +/* Transmit status register (TSR) bits */ +#define BXCAN_TSR_RQCP2 BIT(16) +#define BXCAN_TSR_RQCP1 BIT(8) +#define BXCAN_TSR_RQCP0 BIT(0) + +/* Receive FIFO 0 register (RF0R) bits */ +#define BXCAN_RF0R_RFOM0 BIT(5) +#define BXCAN_RF0R_FMP0_MASK GENMASK(1, 0) + +/* Interrupt enable register (IER) bits */ +#define BXCAN_IER_SLKIE BIT(17) +#define BXCAN_IER_WKUIE BIT(16) +#define BXCAN_IER_ERRIE BIT(15) +#define BXCAN_IER_LECIE BIT(11) +#define BXCAN_IER_BOFIE BIT(10) +#define BXCAN_IER_EPVIE BIT(9) +#define BXCAN_IER_EWGIE BIT(8) +#define BXCAN_IER_FOVIE1 BIT(6) +#define BXCAN_IER_FFIE1 BIT(5) +#define BXCAN_IER_FMPIE1 BIT(4) +#define BXCAN_IER_FOVIE0 BIT(3) +#define BXCAN_IER_FFIE0 BIT(2) +#define BXCAN_IER_FMPIE0 BIT(1) +#define BXCAN_IER_TMEIE BIT(0) + +/* Error status register (ESR) bits */ +#define BXCAN_ESR_REC_MASK GENMASK(31, 24) +#define BXCAN_ESR_TEC_MASK GENMASK(23, 16) +#define BXCAN_ESR_LEC_MASK GENMASK(6, 4) +#define BXCAN_ESR_BOFF BIT(2) +#define BXCAN_ESR_EPVF BIT(1) +#define BXCAN_ESR_EWGF BIT(0) + +/* Bit timing register (BTR) bits */ +#define BXCAN_BTR_SILM BIT(31) +#define BXCAN_BTR_LBKM BIT(30) +#define BXCAN_BTR_SJW_MASK GENMASK(25, 24) +#define BXCAN_BTR_TS2_MASK GENMASK(22, 20) +#define BXCAN_BTR_TS1_MASK GENMASK(19, 16) +#define BXCAN_BTR_BRP_MASK GENMASK(9, 0) + +/* TX mailbox identifier register (TIxR, x = 0..2) bits */ +#define BXCAN_TIxR_STID_MASK GENMASK(31, 21) +#define BXCAN_TIxR_EXID_MASK GENMASK(31, 3) +#define BXCAN_TIxR_IDE BIT(2) +#define BXCAN_TIxR_RTR BIT(1) +#define BXCAN_TIxR_TXRQ BIT(0) + +/* TX mailbox data length and time stamp register (TDTxR, x = 0..2 bits */ +#define BXCAN_TDTxR_DLC_MASK GENMASK(3, 0) + +/* RX FIFO mailbox identifier register (RIxR, x = 0..1 */ +#define BXCAN_RIxR_STID_MASK GENMASK(31, 21) +#define BXCAN_RIxR_EXID_MASK GENMASK(31, 3) +#define BXCAN_RIxR_IDE BIT(2) +#define BXCAN_RIxR_RTR BIT(1) + +/* RX FIFO mailbox data length and timestamp register (RDTxR, x = 0..1) bits */ +#define BXCAN_RDTxR_TIME_MASK GENMASK(31, 16) +#define BXCAN_RDTxR_DLC_MASK GENMASK(3, 0) + +#define BXCAN_FMR_REG 0x00 +#define BXCAN_FM1R_REG 0x04 +#define BXCAN_FS1R_REG 0x0c +#define BXCAN_FFA1R_REG 0x14 +#define BXCAN_FA1R_REG 0x1c +#define BXCAN_FiR1_REG(b) (0x40 + (b) * 8) +#define BXCAN_FiR2_REG(b) (0x44 + (b) * 8) + +#define BXCAN_FILTER_ID(primary) (primary ? 0 : 14) + +/* Filter primary register (FMR) bits */ +#define BXCAN_FMR_CANSB_MASK GENMASK(13, 8) +#define BXCAN_FMR_FINIT BIT(0) + +enum bxcan_lec_code { + BXCAN_LEC_NO_ERROR = 0, + BXCAN_LEC_STUFF_ERROR, + BXCAN_LEC_FORM_ERROR, + BXCAN_LEC_ACK_ERROR, + BXCAN_LEC_BIT1_ERROR, + BXCAN_LEC_BIT0_ERROR, + BXCAN_LEC_CRC_ERROR, + BXCAN_LEC_UNUSED +}; + +/* Structure of the message buffer */ +struct bxcan_mb { + u32 id; /* can identifier */ + u32 dlc; /* data length control and timestamp */ + u32 data[2]; /* data */ +}; + +/* Structure of the hardware registers */ +struct bxcan_regs { + u32 mcr; /* 0x00 - primary control */ + u32 msr; /* 0x04 - primary status */ + u32 tsr; /* 0x08 - transmit status */ + u32 rf0r; /* 0x0c - FIFO 0 */ + u32 rf1r; /* 0x10 - FIFO 1 */ + u32 ier; /* 0x14 - interrupt enable */ + u32 esr; /* 0x18 - error status */ + u32 btr; /* 0x1c - bit timing*/ + u32 reserved0[88]; /* 0x20 */ + struct bxcan_mb tx_mb[BXCAN_TX_MB_NUM]; /* 0x180 - tx mailbox */ + struct bxcan_mb rx_mb[BXCAN_RX_MB_NUM]; /* 0x1b0 - rx mailbox */ +}; + +struct bxcan_priv { + struct can_priv can; + struct can_rx_offload offload; + struct device *dev; + struct net_device *ndev; + + struct bxcan_regs __iomem *regs; + struct regmap *gcan; + int tx_irq; + int sce_irq; + bool primary; + struct clk *clk; + spinlock_t rmw_lock; /* lock for read-modify-write operations */ + unsigned int tx_head; + unsigned int tx_tail; + u32 timestamp; +}; + +static const struct can_bittiming_const bxcan_bittiming_const = { + .name = KBUILD_MODNAME, + .tseg1_min = 1, + .tseg1_max = 16, + .tseg2_min = 1, + .tseg2_max = 8, + .sjw_max = 4, + .brp_min = 1, + .brp_max = 1024, + .brp_inc = 1, +}; + +static inline void bxcan_rmw(struct bxcan_priv *priv, void __iomem *addr, + u32 clear, u32 set) +{ + unsigned long flags; + u32 old, val; + + spin_lock_irqsave(&priv->rmw_lock, flags); + old = readl(addr); + val = (old & ~clear) | set; + if (val != old) + writel(val, addr); + + spin_unlock_irqrestore(&priv->rmw_lock, flags); +} + +static void bxcan_disable_filters(struct bxcan_priv *priv, bool primary) +{ + unsigned int fid = BXCAN_FILTER_ID(primary); + u32 fmask = BIT(fid); + + regmap_update_bits(priv->gcan, BXCAN_FA1R_REG, fmask, 0); +} + +static void bxcan_enable_filters(struct bxcan_priv *priv, bool primary) +{ + unsigned int fid = BXCAN_FILTER_ID(primary); + u32 fmask = BIT(fid); + + /* Filter settings: + * + * Accept all messages. + * Assign filter 0 to CAN1 and filter 14 to CAN2 in identifier + * mask mode with 32 bits width. + */ + + /* Enter filter initialization mode and assing filters to CAN + * controllers. + */ + regmap_update_bits(priv->gcan, BXCAN_FMR_REG, + BXCAN_FMR_CANSB_MASK | BXCAN_FMR_FINIT, + FIELD_PREP(BXCAN_FMR_CANSB_MASK, 14) | + BXCAN_FMR_FINIT); + + /* Deactivate filter */ + regmap_update_bits(priv->gcan, BXCAN_FA1R_REG, fmask, 0); + + /* Two 32-bit registers in identifier mask mode */ + regmap_update_bits(priv->gcan, BXCAN_FM1R_REG, fmask, 0); + + /* Single 32-bit scale configuration */ + regmap_update_bits(priv->gcan, BXCAN_FS1R_REG, fmask, fmask); + + /* Assign filter to FIFO 0 */ + regmap_update_bits(priv->gcan, BXCAN_FFA1R_REG, fmask, 0); + + /* Accept all messages */ + regmap_write(priv->gcan, BXCAN_FiR1_REG(fid), 0); + regmap_write(priv->gcan, BXCAN_FiR2_REG(fid), 0); + + /* Activate filter */ + regmap_update_bits(priv->gcan, BXCAN_FA1R_REG, fmask, fmask); + + /* Exit filter initialization mode */ + regmap_update_bits(priv->gcan, BXCAN_FMR_REG, BXCAN_FMR_FINIT, 0); +} + +static inline u8 bxcan_get_tx_head(const struct bxcan_priv *priv) +{ + return priv->tx_head % BXCAN_TX_MB_NUM; +} + +static inline u8 bxcan_get_tx_tail(const struct bxcan_priv *priv) +{ + return priv->tx_tail % BXCAN_TX_MB_NUM; +} + +static inline u8 bxcan_get_tx_free(const struct bxcan_priv *priv) +{ + return BXCAN_TX_MB_NUM - (priv->tx_head - priv->tx_tail); +} + +static bool bxcan_tx_busy(const struct bxcan_priv *priv) +{ + if (bxcan_get_tx_free(priv) > 0) + return false; + + netif_stop_queue(priv->ndev); + + /* Memory barrier before checking tx_free (head and tail) */ + smp_mb(); + + if (bxcan_get_tx_free(priv) == 0) { + netdev_dbg(priv->ndev, + "Stopping tx-queue (tx_head=0x%08x, tx_tail=0x%08x, len=%d).\n", + priv->tx_head, priv->tx_tail, + priv->tx_head - priv->tx_tail); + + return true; + } + + netif_start_queue(priv->ndev); + + return false; +} + +static int bxcan_chip_softreset(struct bxcan_priv *priv) +{ + struct bxcan_regs __iomem *regs = priv->regs; + u32 value; + + bxcan_rmw(priv, ®s->mcr, 0, BXCAN_MCR_RESET); + return readx_poll_timeout(readl, ®s->msr, value, + value & BXCAN_MSR_SLAK, BXCAN_TIMEOUT_US, + USEC_PER_SEC); +} + +static int bxcan_enter_init_mode(struct bxcan_priv *priv) +{ + struct bxcan_regs __iomem *regs = priv->regs; + u32 value; + + bxcan_rmw(priv, ®s->mcr, 0, BXCAN_MCR_INRQ); + return readx_poll_timeout(readl, ®s->msr, value, + value & BXCAN_MSR_INAK, BXCAN_TIMEOUT_US, + USEC_PER_SEC); +} + +static int bxcan_leave_init_mode(struct bxcan_priv *priv) +{ + struct bxcan_regs __iomem *regs = priv->regs; + u32 value; + + bxcan_rmw(priv, ®s->mcr, BXCAN_MCR_INRQ, 0); + return readx_poll_timeout(readl, ®s->msr, value, + !(value & BXCAN_MSR_INAK), BXCAN_TIMEOUT_US, + USEC_PER_SEC); +} + +static int bxcan_enter_sleep_mode(struct bxcan_priv *priv) +{ + struct bxcan_regs __iomem *regs = priv->regs; + u32 value; + + bxcan_rmw(priv, ®s->mcr, 0, BXCAN_MCR_SLEEP); + return readx_poll_timeout(readl, ®s->msr, value, + value & BXCAN_MSR_SLAK, BXCAN_TIMEOUT_US, + USEC_PER_SEC); +} + +static int bxcan_leave_sleep_mode(struct bxcan_priv *priv) +{ + struct bxcan_regs __iomem *regs = priv->regs; + u32 value; + + bxcan_rmw(priv, ®s->mcr, BXCAN_MCR_SLEEP, 0); + return readx_poll_timeout(readl, ®s->msr, value, + !(value & BXCAN_MSR_SLAK), BXCAN_TIMEOUT_US, + USEC_PER_SEC); +} + +static inline +struct bxcan_priv *rx_offload_to_priv(struct can_rx_offload *offload) +{ + return container_of(offload, struct bxcan_priv, offload); +} + +static struct sk_buff *bxcan_mailbox_read(struct can_rx_offload *offload, + unsigned int mbxno, u32 *timestamp, + bool drop) +{ + struct bxcan_priv *priv = rx_offload_to_priv(offload); + struct bxcan_regs __iomem *regs = priv->regs; + struct bxcan_mb __iomem *mb_regs = ®s->rx_mb[0]; + struct sk_buff *skb = NULL; + struct can_frame *cf; + u32 rf0r, id, dlc; + + rf0r = readl(®s->rf0r); + if (unlikely(drop)) { + skb = ERR_PTR(-ENOBUFS); + goto mark_as_read; + } + + if (!(rf0r & BXCAN_RF0R_FMP0_MASK)) + goto mark_as_read; + + skb = alloc_can_skb(offload->dev, &cf); + if (unlikely(!skb)) { + skb = ERR_PTR(-ENOMEM); + goto mark_as_read; + } + + id = readl(&mb_regs->id); + if (id & BXCAN_RIxR_IDE) + cf->can_id = FIELD_GET(BXCAN_RIxR_EXID_MASK, id) | CAN_EFF_FLAG; + else + cf->can_id = FIELD_GET(BXCAN_RIxR_STID_MASK, id) & CAN_SFF_MASK; + + dlc = readl(&mb_regs->dlc); + priv->timestamp = FIELD_GET(BXCAN_RDTxR_TIME_MASK, dlc); + cf->len = can_cc_dlc2len(FIELD_GET(BXCAN_RDTxR_DLC_MASK, dlc)); + + if (id & BXCAN_RIxR_RTR) { + cf->can_id |= CAN_RTR_FLAG; + } else { + int i, j; + + for (i = 0, j = 0; i < cf->len; i += 4, j++) + *(u32 *)(cf->data + i) = readl(&mb_regs->data[j]); + } + + mark_as_read: + rf0r |= BXCAN_RF0R_RFOM0; + writel(rf0r, ®s->rf0r); + return skb; +} + +static irqreturn_t bxcan_rx_isr(int irq, void *dev_id) +{ + struct net_device *ndev = dev_id; + struct bxcan_priv *priv = netdev_priv(ndev); + struct bxcan_regs __iomem *regs = priv->regs; + u32 rf0r; + + rf0r = readl(®s->rf0r); + if (!(rf0r & BXCAN_RF0R_FMP0_MASK)) + return IRQ_NONE; + + can_rx_offload_irq_offload_fifo(&priv->offload); + can_rx_offload_irq_finish(&priv->offload); + + return IRQ_HANDLED; +} + +static irqreturn_t bxcan_tx_isr(int irq, void *dev_id) +{ + struct net_device *ndev = dev_id; + struct bxcan_priv *priv = netdev_priv(ndev); + struct bxcan_regs __iomem *regs = priv->regs; + struct net_device_stats *stats = &ndev->stats; + u32 tsr, rqcp_bit; + int idx; + + tsr = readl(®s->tsr); + if (!(tsr & (BXCAN_TSR_RQCP0 | BXCAN_TSR_RQCP1 | BXCAN_TSR_RQCP2))) + return IRQ_NONE; + + while (priv->tx_head - priv->tx_tail > 0) { + idx = bxcan_get_tx_tail(priv); + rqcp_bit = BXCAN_TSR_RQCP0 << (idx << 3); + if (!(tsr & rqcp_bit)) + break; + + stats->tx_packets++; + stats->tx_bytes += can_get_echo_skb(ndev, idx, NULL); + priv->tx_tail++; + } + + writel(tsr, ®s->tsr); + + if (bxcan_get_tx_free(priv)) { + /* Make sure that anybody stopping the queue after + * this sees the new tx_ring->tail. + */ + smp_mb(); + netif_wake_queue(ndev); + } + + return IRQ_HANDLED; +} + +static void bxcan_handle_state_change(struct net_device *ndev, u32 esr) +{ + struct bxcan_priv *priv = netdev_priv(ndev); + enum can_state new_state = priv->can.state; + struct can_berr_counter bec; + enum can_state rx_state, tx_state; + struct sk_buff *skb; + struct can_frame *cf; + + /* Early exit if no error flag is set */ + if (!(esr & (BXCAN_ESR_EWGF | BXCAN_ESR_EPVF | BXCAN_ESR_BOFF))) + return; + + bec.txerr = FIELD_GET(BXCAN_ESR_TEC_MASK, esr); + bec.rxerr = FIELD_GET(BXCAN_ESR_REC_MASK, esr); + + if (esr & BXCAN_ESR_BOFF) + new_state = CAN_STATE_BUS_OFF; + else if (esr & BXCAN_ESR_EPVF) + new_state = CAN_STATE_ERROR_PASSIVE; + else if (esr & BXCAN_ESR_EWGF) + new_state = CAN_STATE_ERROR_WARNING; + + /* state hasn't changed */ + if (unlikely(new_state == priv->can.state)) + return; + + skb = alloc_can_err_skb(ndev, &cf); + + tx_state = bec.txerr >= bec.rxerr ? new_state : 0; + rx_state = bec.txerr <= bec.rxerr ? new_state : 0; + can_change_state(ndev, cf, tx_state, rx_state); + + if (new_state == CAN_STATE_BUS_OFF) { + can_bus_off(ndev); + } else if (skb) { + cf->can_id |= CAN_ERR_CNT; + cf->data[6] = bec.txerr; + cf->data[7] = bec.rxerr; + } + + if (skb) { + int err; + + err = can_rx_offload_queue_timestamp(&priv->offload, skb, + priv->timestamp); + if (err) + ndev->stats.rx_fifo_errors++; + } +} + +static void bxcan_handle_bus_err(struct net_device *ndev, u32 esr) +{ + struct bxcan_priv *priv = netdev_priv(ndev); + enum bxcan_lec_code lec_code; + struct can_frame *cf; + struct sk_buff *skb; + + lec_code = FIELD_GET(BXCAN_ESR_LEC_MASK, esr); + + /* Early exit if no lec update or no error. + * No lec update means that no CAN bus event has been detected + * since CPU wrote BXCAN_LEC_UNUSED value to status reg. + */ + if (lec_code == BXCAN_LEC_UNUSED || lec_code == BXCAN_LEC_NO_ERROR) + return; + + /* Common for all type of bus errors */ + priv->can.can_stats.bus_error++; + + /* Propagate the error condition to the CAN stack */ + skb = alloc_can_err_skb(ndev, &cf); + if (skb) + cf->can_id |= CAN_ERR_PROT | CAN_ERR_BUSERROR; + + switch (lec_code) { + case BXCAN_LEC_STUFF_ERROR: + netdev_dbg(ndev, "Stuff error\n"); + ndev->stats.rx_errors++; + if (skb) + cf->data[2] |= CAN_ERR_PROT_STUFF; + break; + + case BXCAN_LEC_FORM_ERROR: + netdev_dbg(ndev, "Form error\n"); + ndev->stats.rx_errors++; + if (skb) + cf->data[2] |= CAN_ERR_PROT_FORM; + break; + + case BXCAN_LEC_ACK_ERROR: + netdev_dbg(ndev, "Ack error\n"); + ndev->stats.tx_errors++; + if (skb) { + cf->can_id |= CAN_ERR_ACK; + cf->data[3] = CAN_ERR_PROT_LOC_ACK; + } + break; + + case BXCAN_LEC_BIT1_ERROR: + netdev_dbg(ndev, "Bit error (recessive)\n"); + ndev->stats.tx_errors++; + if (skb) + cf->data[2] |= CAN_ERR_PROT_BIT1; + break; + + case BXCAN_LEC_BIT0_ERROR: + netdev_dbg(ndev, "Bit error (dominant)\n"); + ndev->stats.tx_errors++; + if (skb) + cf->data[2] |= CAN_ERR_PROT_BIT0; + break; + + case BXCAN_LEC_CRC_ERROR: + netdev_dbg(ndev, "CRC error\n"); + ndev->stats.rx_errors++; + if (skb) { + cf->data[2] |= CAN_ERR_PROT_BIT; + cf->data[3] = CAN_ERR_PROT_LOC_CRC_SEQ; + } + break; + + default: + break; + } + + if (skb) { + int err; + + err = can_rx_offload_queue_timestamp(&priv->offload, skb, + priv->timestamp); + if (err) + ndev->stats.rx_fifo_errors++; + } +} + +static irqreturn_t bxcan_state_change_isr(int irq, void *dev_id) +{ + struct net_device *ndev = dev_id; + struct bxcan_priv *priv = netdev_priv(ndev); + struct bxcan_regs __iomem *regs = priv->regs; + u32 msr, esr; + + msr = readl(®s->msr); + if (!(msr & BXCAN_MSR_ERRI)) + return IRQ_NONE; + + esr = readl(®s->esr); + bxcan_handle_state_change(ndev, esr); + + if (priv->can.ctrlmode & CAN_CTRLMODE_BERR_REPORTING) + bxcan_handle_bus_err(ndev, esr); + + msr |= BXCAN_MSR_ERRI; + writel(msr, ®s->msr); + can_rx_offload_irq_finish(&priv->offload); + + return IRQ_HANDLED; +} + +static int bxcan_chip_start(struct net_device *ndev) +{ + struct bxcan_priv *priv = netdev_priv(ndev); + struct bxcan_regs __iomem *regs = priv->regs; + struct can_bittiming *bt = &priv->can.bittiming; + u32 clr, set; + int err; + + err = bxcan_chip_softreset(priv); + if (err) { + netdev_err(ndev, "failed to reset chip, error %pe\n", + ERR_PTR(err)); + return err; + } + + err = bxcan_leave_sleep_mode(priv); + if (err) { + netdev_err(ndev, "failed to leave sleep mode, error %pe\n", + ERR_PTR(err)); + goto failed_leave_sleep; + } + + err = bxcan_enter_init_mode(priv); + if (err) { + netdev_err(ndev, "failed to enter init mode, error %pe\n", + ERR_PTR(err)); + goto failed_enter_init; + } + + /* MCR + * + * select request order priority + * enable time triggered mode + * bus-off state left on sw request + * sleep mode left on sw request + * retransmit automatically on error + * do not lock RX FIFO on overrun + */ + bxcan_rmw(priv, ®s->mcr, + BXCAN_MCR_ABOM | BXCAN_MCR_AWUM | BXCAN_MCR_NART | + BXCAN_MCR_RFLM, BXCAN_MCR_TTCM | BXCAN_MCR_TXFP); + + /* Bit timing register settings */ + set = FIELD_PREP(BXCAN_BTR_BRP_MASK, bt->brp - 1) | + FIELD_PREP(BXCAN_BTR_TS1_MASK, bt->phase_seg1 + + bt->prop_seg - 1) | + FIELD_PREP(BXCAN_BTR_TS2_MASK, bt->phase_seg2 - 1) | + FIELD_PREP(BXCAN_BTR_SJW_MASK, bt->sjw - 1); + + /* loopback + silent mode put the controller in test mode, + * useful for hot self-test + */ + if (priv->can.ctrlmode & CAN_CTRLMODE_LOOPBACK) + set |= BXCAN_BTR_LBKM; + + if (priv->can.ctrlmode & CAN_CTRLMODE_LISTENONLY) + set |= BXCAN_BTR_SILM; + + bxcan_rmw(priv, ®s->btr, BXCAN_BTR_SILM | BXCAN_BTR_LBKM | + BXCAN_BTR_BRP_MASK | BXCAN_BTR_TS1_MASK | BXCAN_BTR_TS2_MASK | + BXCAN_BTR_SJW_MASK, set); + + bxcan_enable_filters(priv, priv->primary); + + /* Clear all internal status */ + priv->tx_head = 0; + priv->tx_tail = 0; + + err = bxcan_leave_init_mode(priv); + if (err) { + netdev_err(ndev, "failed to leave init mode, error %pe\n", + ERR_PTR(err)); + goto failed_leave_init; + } + + /* Set a `lec` value so that we can check for updates later */ + bxcan_rmw(priv, ®s->esr, BXCAN_ESR_LEC_MASK, + FIELD_PREP(BXCAN_ESR_LEC_MASK, BXCAN_LEC_UNUSED)); + + /* IER + * + * Enable interrupt for: + * bus-off + * passive error + * warning error + * last error code + * RX FIFO pending message + * TX mailbox empty + */ + clr = BXCAN_IER_WKUIE | BXCAN_IER_SLKIE | BXCAN_IER_FOVIE1 | + BXCAN_IER_FFIE1 | BXCAN_IER_FMPIE1 | BXCAN_IER_FOVIE0 | + BXCAN_IER_FFIE0; + set = BXCAN_IER_ERRIE | BXCAN_IER_BOFIE | BXCAN_IER_EPVIE | + BXCAN_IER_EWGIE | BXCAN_IER_FMPIE0 | BXCAN_IER_TMEIE; + + if (priv->can.ctrlmode & CAN_CTRLMODE_BERR_REPORTING) + set |= BXCAN_IER_LECIE; + else + clr |= BXCAN_IER_LECIE; + + bxcan_rmw(priv, ®s->ier, clr, set); + + priv->can.state = CAN_STATE_ERROR_ACTIVE; + return 0; + +failed_leave_init: +failed_enter_init: +failed_leave_sleep: + bxcan_chip_softreset(priv); + return err; +} + +static int bxcan_open(struct net_device *ndev) +{ + struct bxcan_priv *priv = netdev_priv(ndev); + int err; + + err = clk_prepare_enable(priv->clk); + if (err) { + netdev_err(ndev, "failed to enable clock, error %pe\n", + ERR_PTR(err)); + return err; + } + + err = open_candev(ndev); + if (err) { + netdev_err(ndev, "open_candev() failed, error %pe\n", + ERR_PTR(err)); + goto out_disable_clock; + } + + can_rx_offload_enable(&priv->offload); + err = request_irq(ndev->irq, bxcan_rx_isr, IRQF_SHARED, ndev->name, + ndev); + if (err) { + netdev_err(ndev, "failed to register rx irq(%d), error %pe\n", + ndev->irq, ERR_PTR(err)); + goto out_close_candev; + } + + err = request_irq(priv->tx_irq, bxcan_tx_isr, IRQF_SHARED, ndev->name, + ndev); + if (err) { + netdev_err(ndev, "failed to register tx irq(%d), error %pe\n", + priv->tx_irq, ERR_PTR(err)); + goto out_free_rx_irq; + } + + err = request_irq(priv->sce_irq, bxcan_state_change_isr, IRQF_SHARED, + ndev->name, ndev); + if (err) { + netdev_err(ndev, "failed to register sce irq(%d), error %pe\n", + priv->sce_irq, ERR_PTR(err)); + goto out_free_tx_irq; + } + + err = bxcan_chip_start(ndev); + if (err) + goto out_free_sce_irq; + + netif_start_queue(ndev); + return 0; + +out_free_sce_irq: + free_irq(priv->sce_irq, ndev); +out_free_tx_irq: + free_irq(priv->tx_irq, ndev); +out_free_rx_irq: + free_irq(ndev->irq, ndev); +out_close_candev: + can_rx_offload_disable(&priv->offload); + close_candev(ndev); +out_disable_clock: + clk_disable_unprepare(priv->clk); + return err; +} + +static void bxcan_chip_stop(struct net_device *ndev) +{ + struct bxcan_priv *priv = netdev_priv(ndev); + struct bxcan_regs __iomem *regs = priv->regs; + + /* disable all interrupts */ + bxcan_rmw(priv, ®s->ier, BXCAN_IER_SLKIE | BXCAN_IER_WKUIE | + BXCAN_IER_ERRIE | BXCAN_IER_LECIE | BXCAN_IER_BOFIE | + BXCAN_IER_EPVIE | BXCAN_IER_EWGIE | BXCAN_IER_FOVIE1 | + BXCAN_IER_FFIE1 | BXCAN_IER_FMPIE1 | BXCAN_IER_FOVIE0 | + BXCAN_IER_FFIE0 | BXCAN_IER_FMPIE0 | BXCAN_IER_TMEIE, 0); + bxcan_disable_filters(priv, priv->primary); + bxcan_enter_sleep_mode(priv); + priv->can.state = CAN_STATE_STOPPED; +} + +static int bxcan_stop(struct net_device *ndev) +{ + struct bxcan_priv *priv = netdev_priv(ndev); + + netif_stop_queue(ndev); + bxcan_chip_stop(ndev); + free_irq(ndev->irq, ndev); + free_irq(priv->tx_irq, ndev); + free_irq(priv->sce_irq, ndev); + can_rx_offload_disable(&priv->offload); + close_candev(ndev); + clk_disable_unprepare(priv->clk); + return 0; +} + +static netdev_tx_t bxcan_start_xmit(struct sk_buff *skb, + struct net_device *ndev) +{ + struct bxcan_priv *priv = netdev_priv(ndev); + struct can_frame *cf = (struct can_frame *)skb->data; + struct bxcan_regs __iomem *regs = priv->regs; + struct bxcan_mb __iomem *mb_regs; + unsigned int idx; + u32 id; + int i, j; + + if (can_dropped_invalid_skb(ndev, skb)) + return NETDEV_TX_OK; + + if (bxcan_tx_busy(priv)) + return NETDEV_TX_BUSY; + + idx = bxcan_get_tx_head(priv); + priv->tx_head++; + if (bxcan_get_tx_free(priv) == 0) + netif_stop_queue(ndev); + + mb_regs = ®s->tx_mb[idx]; + if (cf->can_id & CAN_EFF_FLAG) + id = FIELD_PREP(BXCAN_TIxR_EXID_MASK, cf->can_id) | + BXCAN_TIxR_IDE; + else + id = FIELD_PREP(BXCAN_TIxR_STID_MASK, cf->can_id); + + if (cf->can_id & CAN_RTR_FLAG) { /* Remote transmission request */ + id |= BXCAN_TIxR_RTR; + } else { + for (i = 0, j = 0; i < cf->len; i += 4, j++) + writel(*(u32 *)(cf->data + i), &mb_regs->data[j]); + } + + writel(FIELD_PREP(BXCAN_TDTxR_DLC_MASK, cf->len), &mb_regs->dlc); + + can_put_echo_skb(skb, ndev, idx, 0); + + /* Start transmission */ + writel(id | BXCAN_TIxR_TXRQ, &mb_regs->id); + + return NETDEV_TX_OK; +} + +static const struct net_device_ops bxcan_netdev_ops = { + .ndo_open = bxcan_open, + .ndo_stop = bxcan_stop, + .ndo_start_xmit = bxcan_start_xmit, + .ndo_change_mtu = can_change_mtu, +}; + +static const struct ethtool_ops bxcan_ethtool_ops = { + .get_ts_info = ethtool_op_get_ts_info, +}; + +static int bxcan_do_set_mode(struct net_device *ndev, enum can_mode mode) +{ + int err; + + switch (mode) { + case CAN_MODE_START: + err = bxcan_chip_start(ndev); + if (err) + return err; + + netif_wake_queue(ndev); + break; + + default: + return -EOPNOTSUPP; + } + + return 0; +} + +static int bxcan_get_berr_counter(const struct net_device *ndev, + struct can_berr_counter *bec) +{ + struct bxcan_priv *priv = netdev_priv(ndev); + struct bxcan_regs __iomem *regs = priv->regs; + u32 esr; + int err; + + err = clk_prepare_enable(priv->clk); + if (err) + return err; + + esr = readl(®s->esr); + bec->txerr = FIELD_GET(BXCAN_ESR_TEC_MASK, esr); + bec->rxerr = FIELD_GET(BXCAN_ESR_REC_MASK, esr); + clk_disable_unprepare(priv->clk); + return 0; +} + +static int bxcan_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct device *dev = &pdev->dev; + struct net_device *ndev; + struct bxcan_priv *priv; + struct clk *clk = NULL; + void __iomem *regs; + struct regmap *gcan; + bool primary; + int err, rx_irq, tx_irq, sce_irq; + + regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(regs)) { + dev_err(dev, "failed to get base address\n"); + return PTR_ERR(regs); + } + + gcan = syscon_regmap_lookup_by_phandle(np, "st,gcan"); + if (IS_ERR(gcan)) { + dev_err(dev, "failed to get shared memory base address\n"); + return PTR_ERR(gcan); + } + + primary = of_property_read_bool(np, "st,can-primary"); + clk = devm_clk_get(dev, NULL); + if (IS_ERR(clk)) { + dev_err(dev, "failed to get clock\n"); + return PTR_ERR(clk); + } + + rx_irq = platform_get_irq_byname(pdev, "rx0"); + if (rx_irq < 0) { + dev_err(dev, "failed to get rx0 irq\n"); + return rx_irq; + } + + tx_irq = platform_get_irq_byname(pdev, "tx"); + if (tx_irq < 0) { + dev_err(dev, "failed to get tx irq\n"); + return tx_irq; + } + + sce_irq = platform_get_irq_byname(pdev, "sce"); + if (sce_irq < 0) { + dev_err(dev, "failed to get sce irq\n"); + return sce_irq; + } + + ndev = alloc_candev(sizeof(struct bxcan_priv), BXCAN_TX_MB_NUM); + if (!ndev) { + dev_err(dev, "alloc_candev() failed\n"); + return -ENOMEM; + } + + priv = netdev_priv(ndev); + platform_set_drvdata(pdev, ndev); + SET_NETDEV_DEV(ndev, dev); + ndev->netdev_ops = &bxcan_netdev_ops; + ndev->ethtool_ops = &bxcan_ethtool_ops; + ndev->irq = rx_irq; + ndev->flags |= IFF_ECHO; + + priv->dev = dev; + priv->ndev = ndev; + priv->regs = regs; + priv->gcan = gcan; + priv->clk = clk; + priv->tx_irq = tx_irq; + priv->sce_irq = sce_irq; + priv->primary = primary; + priv->can.clock.freq = clk_get_rate(clk); + spin_lock_init(&priv->rmw_lock); + priv->tx_head = 0; + priv->tx_tail = 0; + priv->can.bittiming_const = &bxcan_bittiming_const; + priv->can.do_set_mode = bxcan_do_set_mode; + priv->can.do_get_berr_counter = bxcan_get_berr_counter; + priv->can.ctrlmode_supported = CAN_CTRLMODE_LOOPBACK | + CAN_CTRLMODE_LISTENONLY | CAN_CTRLMODE_BERR_REPORTING; + + priv->offload.mailbox_read = bxcan_mailbox_read; + err = can_rx_offload_add_fifo(ndev, &priv->offload, BXCAN_NAPI_WEIGHT); + if (err) { + dev_err(dev, "failed to add FIFO rx_offload\n"); + goto out_free_candev; + } + + err = register_candev(ndev); + if (err) { + dev_err(dev, "failed to register netdev\n"); + goto out_can_rx_offload_del; + } + + dev_info(dev, "clk: %d Hz, IRQs: %d, %d, %d\n", priv->can.clock.freq, + tx_irq, rx_irq, sce_irq); + return 0; + +out_can_rx_offload_del: + can_rx_offload_del(&priv->offload); +out_free_candev: + free_candev(ndev); + return err; +} + +static int bxcan_remove(struct platform_device *pdev) +{ + struct net_device *ndev = platform_get_drvdata(pdev); + struct bxcan_priv *priv = netdev_priv(ndev); + + unregister_candev(ndev); + clk_disable_unprepare(priv->clk); + can_rx_offload_del(&priv->offload); + free_candev(ndev); + return 0; +} + +static int __maybe_unused bxcan_suspend(struct device *dev) +{ + struct net_device *ndev = dev_get_drvdata(dev); + struct bxcan_priv *priv = netdev_priv(ndev); + + if (!netif_running(ndev)) + return 0; + + netif_stop_queue(ndev); + netif_device_detach(ndev); + + bxcan_enter_sleep_mode(priv); + priv->can.state = CAN_STATE_SLEEPING; + clk_disable_unprepare(priv->clk); + return 0; +} + +static int __maybe_unused bxcan_resume(struct device *dev) +{ + struct net_device *ndev = dev_get_drvdata(dev); + struct bxcan_priv *priv = netdev_priv(ndev); + + if (!netif_running(ndev)) + return 0; + + clk_prepare_enable(priv->clk); + bxcan_leave_sleep_mode(priv); + priv->can.state = CAN_STATE_ERROR_ACTIVE; + + netif_device_attach(ndev); + netif_start_queue(ndev); + return 0; +} + +static SIMPLE_DEV_PM_OPS(bxcan_pm_ops, bxcan_suspend, bxcan_resume); + +static const struct of_device_id bxcan_of_match[] = { + {.compatible = "st,stm32f4-bxcan"}, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, bxcan_of_match); + +static struct platform_driver bxcan_driver = { + .driver = { + .name = KBUILD_MODNAME, + .pm = &bxcan_pm_ops, + .of_match_table = bxcan_of_match, + }, + .probe = bxcan_probe, + .remove = bxcan_remove, +}; + +module_platform_driver(bxcan_driver); + +MODULE_AUTHOR("Dario Binacchi "); +MODULE_DESCRIPTION("STMicroelectronics Basic Extended CAN controller driver"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3 From 615927f1a487bd9066425df48d06cec3bb3d8dbb Mon Sep 17 00:00:00 2001 From: Tianfei Zhang Date: Tue, 28 Mar 2023 10:24:55 -0400 Subject: ptp: add ToD device driver for Intel FPGA cards MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adding a DFL (Device Feature List) device driver of ToD device for Intel FPGA cards. The Intel FPGA Time of Day(ToD) IP within the FPGA DFL bus is exposed as PTP Hardware clock(PHC) device to the Linux PTP stack to synchronize the system clock to its ToD information using phc2sys utility of the Linux PTP stack. The DFL is a hardware List within FPGA, which defines a linked list of feature headers within the device MMIO space to provide an extensible way of adding subdevice features. Signed-off-by: Raghavendra Khadatare Signed-off-by: Tianfei Zhang Acked-by: Richard Cochran Reviewed-by: Ilpo Järvinen Link: https://lore.kernel.org/r/20230328142455.481146-1-tianfei.zhang@intel.com Signed-off-by: Jakub Kicinski --- MAINTAINERS | 7 + drivers/ptp/Kconfig | 14 ++ drivers/ptp/Makefile | 1 + drivers/ptp/ptp_dfl_tod.c | 332 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 354 insertions(+) create mode 100644 drivers/ptp/ptp_dfl_tod.c (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index fbbda4671e73..91201c2b8190 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -15630,6 +15630,13 @@ L: netdev@vger.kernel.org S: Maintained F: drivers/ptp/ptp_ocp.c +INTEL PTP DFL ToD DRIVER +M: Tianfei Zhang +L: linux-fpga@vger.kernel.org +L: netdev@vger.kernel.org +S: Maintained +F: drivers/ptp/ptp_dfl_tod.c + OPENCORES I2C BUS DRIVER M: Peter Korsgaard M: Andrew Lunn diff --git a/drivers/ptp/Kconfig b/drivers/ptp/Kconfig index fe4971b65c64..b00201d81313 100644 --- a/drivers/ptp/Kconfig +++ b/drivers/ptp/Kconfig @@ -186,4 +186,18 @@ config PTP_1588_CLOCK_OCP More information is available at http://www.timingcard.com/ +config PTP_DFL_TOD + tristate "FPGA DFL ToD Driver" + depends on FPGA_DFL + depends on PTP_1588_CLOCK + help + The DFL (Device Feature List) device driver for the Intel ToD + (Time-of-Day) device in FPGA card. The ToD IP within the FPGA + is exposed as PTP Hardware Clock (PHC) device to the Linux PTP + stack to synchronize the system clock to its ToD information + using phc2sys utility of the Linux PTP stack. + + To compile this driver as a module, choose M here: the module + will be called ptp_dfl_tod. + endmenu diff --git a/drivers/ptp/Makefile b/drivers/ptp/Makefile index 28a6fe342d3e..553f18bf3c83 100644 --- a/drivers/ptp/Makefile +++ b/drivers/ptp/Makefile @@ -18,3 +18,4 @@ obj-$(CONFIG_PTP_1588_CLOCK_IDTCM) += ptp_clockmatrix.o obj-$(CONFIG_PTP_1588_CLOCK_IDT82P33) += ptp_idt82p33.o obj-$(CONFIG_PTP_1588_CLOCK_VMW) += ptp_vmw.o obj-$(CONFIG_PTP_1588_CLOCK_OCP) += ptp_ocp.o +obj-$(CONFIG_PTP_DFL_TOD) += ptp_dfl_tod.o diff --git a/drivers/ptp/ptp_dfl_tod.c b/drivers/ptp/ptp_dfl_tod.c new file mode 100644 index 000000000000..f699d541b360 --- /dev/null +++ b/drivers/ptp/ptp_dfl_tod.c @@ -0,0 +1,332 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * DFL device driver for Time-of-Day (ToD) private feature + * + * Copyright (C) 2023 Intel Corporation + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define FME_FEATURE_ID_TOD 0x22 + +/* ToD clock register space. */ +#define TOD_CLK_FREQ 0x038 + +/* + * The read sequence of ToD timestamp registers: TOD_NANOSEC, TOD_SECONDSL and + * TOD_SECONDSH, because there is a hardware snapshot whenever the TOD_NANOSEC + * register is read. + * + * The ToD IP requires writing registers in the reverse order to the read sequence. + * The timestamp is corrected when the TOD_NANOSEC register is written, so the + * sequence of write TOD registers: TOD_SECONDSH, TOD_SECONDSL and TOD_NANOSEC. + */ +#define TOD_SECONDSH 0x100 +#define TOD_SECONDSL 0x104 +#define TOD_NANOSEC 0x108 +#define TOD_PERIOD 0x110 +#define TOD_ADJUST_PERIOD 0x114 +#define TOD_ADJUST_COUNT 0x118 +#define TOD_DRIFT_ADJUST 0x11c +#define TOD_DRIFT_ADJUST_RATE 0x120 +#define PERIOD_FRAC_OFFSET 16 +#define SECONDS_MSB GENMASK_ULL(47, 32) +#define SECONDS_LSB GENMASK_ULL(31, 0) +#define TOD_SECONDSH_SEC_MSB GENMASK_ULL(15, 0) + +#define CAL_SECONDS(m, l) ((FIELD_GET(TOD_SECONDSH_SEC_MSB, (m)) << 32) | (l)) + +#define TOD_PERIOD_MASK GENMASK_ULL(19, 0) +#define TOD_PERIOD_MAX FIELD_MAX(TOD_PERIOD_MASK) +#define TOD_PERIOD_MIN 0 +#define TOD_DRIFT_ADJUST_MASK GENMASK_ULL(15, 0) +#define TOD_DRIFT_ADJUST_FNS_MAX FIELD_MAX(TOD_DRIFT_ADJUST_MASK) +#define TOD_DRIFT_ADJUST_RATE_MAX TOD_DRIFT_ADJUST_FNS_MAX +#define TOD_ADJUST_COUNT_MASK GENMASK_ULL(19, 0) +#define TOD_ADJUST_COUNT_MAX FIELD_MAX(TOD_ADJUST_COUNT_MASK) +#define TOD_ADJUST_INTERVAL_US 10 +#define TOD_ADJUST_MS \ + (((TOD_PERIOD_MAX >> 16) + 1) * (TOD_ADJUST_COUNT_MAX + 1)) +#define TOD_ADJUST_MS_MAX (TOD_ADJUST_MS / MICRO) +#define TOD_ADJUST_MAX_US (TOD_ADJUST_MS_MAX * USEC_PER_MSEC) +#define TOD_MAX_ADJ (500 * MEGA) + +struct dfl_tod { + struct ptp_clock_info ptp_clock_ops; + struct device *dev; + struct ptp_clock *ptp_clock; + + /* ToD Clock address space */ + void __iomem *tod_ctrl; + + /* ToD clock registers protection */ + spinlock_t tod_lock; +}; + +/* + * A fine ToD HW clock offset adjustment. To perform the fine offset adjustment, the + * adjust_period and adjust_count argument are used to update the TOD_ADJUST_PERIOD + * and TOD_ADJUST_COUNT register for in hardware. The dt->tod_lock spinlock must be + * held when calling this function. + */ +static int fine_adjust_tod_clock(struct dfl_tod *dt, u32 adjust_period, + u32 adjust_count) +{ + void __iomem *base = dt->tod_ctrl; + u32 val; + + writel(adjust_period, base + TOD_ADJUST_PERIOD); + writel(adjust_count, base + TOD_ADJUST_COUNT); + + /* Wait for present offset adjustment update to complete */ + return readl_poll_timeout_atomic(base + TOD_ADJUST_COUNT, val, !val, TOD_ADJUST_INTERVAL_US, + TOD_ADJUST_MAX_US); +} + +/* + * A coarse ToD HW clock offset adjustment. The coarse time adjustment performs by + * adding or subtracting the delta value from the current ToD HW clock time. + */ +static int coarse_adjust_tod_clock(struct dfl_tod *dt, s64 delta) +{ + u32 seconds_msb, seconds_lsb, nanosec; + void __iomem *base = dt->tod_ctrl; + u64 seconds, now; + + if (delta == 0) + return 0; + + nanosec = readl(base + TOD_NANOSEC); + seconds_lsb = readl(base + TOD_SECONDSL); + seconds_msb = readl(base + TOD_SECONDSH); + + /* Calculate new time */ + seconds = CAL_SECONDS(seconds_msb, seconds_lsb); + now = seconds * NSEC_PER_SEC + nanosec + delta; + + seconds = div_u64_rem(now, NSEC_PER_SEC, &nanosec); + seconds_msb = FIELD_GET(SECONDS_MSB, seconds); + seconds_lsb = FIELD_GET(SECONDS_LSB, seconds); + + writel(seconds_msb, base + TOD_SECONDSH); + writel(seconds_lsb, base + TOD_SECONDSL); + writel(nanosec, base + TOD_NANOSEC); + + return 0; +} + +static int dfl_tod_adjust_fine(struct ptp_clock_info *ptp, long scaled_ppm) +{ + struct dfl_tod *dt = container_of(ptp, struct dfl_tod, ptp_clock_ops); + u32 tod_period, tod_rem, tod_drift_adjust_fns, tod_drift_adjust_rate; + void __iomem *base = dt->tod_ctrl; + unsigned long flags, rate; + u64 ppb; + + /* Get the clock rate from clock frequency register offset */ + rate = readl(base + TOD_CLK_FREQ); + + /* add GIGA as nominal ppb */ + ppb = scaled_ppm_to_ppb(scaled_ppm) + GIGA; + + tod_period = div_u64_rem(ppb << PERIOD_FRAC_OFFSET, rate, &tod_rem); + if (tod_period > TOD_PERIOD_MAX) + return -ERANGE; + + /* + * The drift of ToD adjusted periodically by adding a drift_adjust_fns + * correction value every drift_adjust_rate count of clock cycles. + */ + tod_drift_adjust_fns = tod_rem / gcd(tod_rem, rate); + tod_drift_adjust_rate = rate / gcd(tod_rem, rate); + + while ((tod_drift_adjust_fns > TOD_DRIFT_ADJUST_FNS_MAX) || + (tod_drift_adjust_rate > TOD_DRIFT_ADJUST_RATE_MAX)) { + tod_drift_adjust_fns >>= 1; + tod_drift_adjust_rate >>= 1; + } + + if (tod_drift_adjust_fns == 0) + tod_drift_adjust_rate = 0; + + spin_lock_irqsave(&dt->tod_lock, flags); + writel(tod_period, base + TOD_PERIOD); + writel(0, base + TOD_ADJUST_PERIOD); + writel(0, base + TOD_ADJUST_COUNT); + writel(tod_drift_adjust_fns, base + TOD_DRIFT_ADJUST); + writel(tod_drift_adjust_rate, base + TOD_DRIFT_ADJUST_RATE); + spin_unlock_irqrestore(&dt->tod_lock, flags); + + return 0; +} + +static int dfl_tod_adjust_time(struct ptp_clock_info *ptp, s64 delta) +{ + struct dfl_tod *dt = container_of(ptp, struct dfl_tod, ptp_clock_ops); + u32 period, diff, rem, rem_period, adj_period; + void __iomem *base = dt->tod_ctrl; + unsigned long flags; + bool neg_adj; + u64 count; + int ret; + + neg_adj = delta < 0; + if (neg_adj) + delta = -delta; + + spin_lock_irqsave(&dt->tod_lock, flags); + + /* + * Get the maximum possible value of the Period register offset + * adjustment in nanoseconds scale. This depends on the current + * Period register setting and the maximum and minimum possible + * values of the Period register. + */ + period = readl(base + TOD_PERIOD); + + if (neg_adj) { + diff = (period - TOD_PERIOD_MIN) >> PERIOD_FRAC_OFFSET; + adj_period = period - (diff << PERIOD_FRAC_OFFSET); + count = div_u64_rem(delta, diff, &rem); + rem_period = period - (rem << PERIOD_FRAC_OFFSET); + } else { + diff = (TOD_PERIOD_MAX - period) >> PERIOD_FRAC_OFFSET; + adj_period = period + (diff << PERIOD_FRAC_OFFSET); + count = div_u64_rem(delta, diff, &rem); + rem_period = period + (rem << PERIOD_FRAC_OFFSET); + } + + ret = 0; + + if (count > TOD_ADJUST_COUNT_MAX) { + ret = coarse_adjust_tod_clock(dt, delta); + } else { + /* Adjust the period by count cycles to adjust the time */ + if (count) + ret = fine_adjust_tod_clock(dt, adj_period, count); + + /* If there is a remainder, adjust the period for an additional cycle */ + if (rem) + ret = fine_adjust_tod_clock(dt, rem_period, 1); + } + + spin_unlock_irqrestore(&dt->tod_lock, flags); + + return ret; +} + +static int dfl_tod_get_timex(struct ptp_clock_info *ptp, struct timespec64 *ts, + struct ptp_system_timestamp *sts) +{ + struct dfl_tod *dt = container_of(ptp, struct dfl_tod, ptp_clock_ops); + u32 seconds_msb, seconds_lsb, nanosec; + void __iomem *base = dt->tod_ctrl; + unsigned long flags; + u64 seconds; + + spin_lock_irqsave(&dt->tod_lock, flags); + ptp_read_system_prets(sts); + nanosec = readl(base + TOD_NANOSEC); + seconds_lsb = readl(base + TOD_SECONDSL); + seconds_msb = readl(base + TOD_SECONDSH); + ptp_read_system_postts(sts); + spin_unlock_irqrestore(&dt->tod_lock, flags); + + seconds = CAL_SECONDS(seconds_msb, seconds_lsb); + + ts->tv_nsec = nanosec; + ts->tv_sec = seconds; + + return 0; +} + +static int dfl_tod_set_time(struct ptp_clock_info *ptp, + const struct timespec64 *ts) +{ + struct dfl_tod *dt = container_of(ptp, struct dfl_tod, ptp_clock_ops); + u32 seconds_msb = FIELD_GET(SECONDS_MSB, ts->tv_sec); + u32 seconds_lsb = FIELD_GET(SECONDS_LSB, ts->tv_sec); + u32 nanosec = FIELD_GET(SECONDS_LSB, ts->tv_nsec); + void __iomem *base = dt->tod_ctrl; + unsigned long flags; + + spin_lock_irqsave(&dt->tod_lock, flags); + writel(seconds_msb, base + TOD_SECONDSH); + writel(seconds_lsb, base + TOD_SECONDSL); + writel(nanosec, base + TOD_NANOSEC); + spin_unlock_irqrestore(&dt->tod_lock, flags); + + return 0; +} + +static struct ptp_clock_info dfl_tod_clock_ops = { + .owner = THIS_MODULE, + .name = "dfl_tod", + .max_adj = TOD_MAX_ADJ, + .adjfine = dfl_tod_adjust_fine, + .adjtime = dfl_tod_adjust_time, + .gettimex64 = dfl_tod_get_timex, + .settime64 = dfl_tod_set_time, +}; + +static int dfl_tod_probe(struct dfl_device *ddev) +{ + struct device *dev = &ddev->dev; + struct dfl_tod *dt; + + dt = devm_kzalloc(dev, sizeof(*dt), GFP_KERNEL); + if (!dt) + return -ENOMEM; + + dt->tod_ctrl = devm_ioremap_resource(dev, &ddev->mmio_res); + if (IS_ERR(dt->tod_ctrl)) + return PTR_ERR(dt->tod_ctrl); + + dt->dev = dev; + spin_lock_init(&dt->tod_lock); + dev_set_drvdata(dev, dt); + + dt->ptp_clock_ops = dfl_tod_clock_ops; + + dt->ptp_clock = ptp_clock_register(&dt->ptp_clock_ops, dev); + if (IS_ERR(dt->ptp_clock)) + return dev_err_probe(dt->dev, PTR_ERR(dt->ptp_clock), + "Unable to register PTP clock\n"); + + return 0; +} + +static void dfl_tod_remove(struct dfl_device *ddev) +{ + struct dfl_tod *dt = dev_get_drvdata(&ddev->dev); + + ptp_clock_unregister(dt->ptp_clock); +} + +static const struct dfl_device_id dfl_tod_ids[] = { + { FME_ID, FME_FEATURE_ID_TOD }, + { } +}; +MODULE_DEVICE_TABLE(dfl, dfl_tod_ids); + +static struct dfl_driver dfl_tod_driver = { + .drv = { + .name = "dfl-tod", + }, + .id_table = dfl_tod_ids, + .probe = dfl_tod_probe, + .remove = dfl_tod_remove, +}; +module_dfl_driver(dfl_tod_driver); + +MODULE_DESCRIPTION("FPGA DFL ToD driver"); +MODULE_AUTHOR("Intel Corporation"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3 From cb675afcddbbeb2bfa6596e3bc236bc026cd425f Mon Sep 17 00:00:00 2001 From: Daniel Golle Date: Mon, 3 Apr 2023 02:19:13 +0100 Subject: net: dsa: mt7530: introduce separate MDIO driver Split MT7530 switch driver into a common part and a part specific for MDIO connected switches and multi-chip modules. Move MDIO-specific functions to newly introduced mt7530-mdio.c while keeping the common parts in mt7530.c. Introduce new Kconfig symbol CONFIG_NET_DSA_MT7530_MDIO which is implied by CONFIG_NET_DSA_MT7530. Signed-off-by: Daniel Golle Reviewed-by: Andrew Lunn Signed-off-by: David S. Miller --- MAINTAINERS | 1 + drivers/net/dsa/Kconfig | 15 ++- drivers/net/dsa/Makefile | 1 + drivers/net/dsa/mt7530-mdio.c | 271 ++++++++++++++++++++++++++++++++++++++++++ drivers/net/dsa/mt7530.c | 264 ++-------------------------------------- drivers/net/dsa/mt7530.h | 6 + 6 files changed, 300 insertions(+), 258 deletions(-) create mode 100644 drivers/net/dsa/mt7530-mdio.c (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index dffcc253563b..4970a6c6a0a8 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -13177,6 +13177,7 @@ M: Landen Chao M: DENG Qingfang L: netdev@vger.kernel.org S: Maintained +F: drivers/net/dsa/mt7530-mdio.c F: drivers/net/dsa/mt7530.* F: net/dsa/tag_mtk.c diff --git a/drivers/net/dsa/Kconfig b/drivers/net/dsa/Kconfig index 6b45fa8b6907..941d020fe4b6 100644 --- a/drivers/net/dsa/Kconfig +++ b/drivers/net/dsa/Kconfig @@ -38,11 +38,22 @@ config NET_DSA_MT7530 tristate "MediaTek MT7530 and MT7531 Ethernet switch support" select NET_DSA_TAG_MTK select MEDIATEK_GE_PHY - select PCS_MTK_LYNXI + imply NET_DSA_MT7530_MDIO help This enables support for the MediaTek MT7530 and MT7531 Ethernet switch chips. Multi-chip module MT7530 in MT7621AT, MT7621DAT, - MT7621ST and MT7623AI SoCs is supported. + MT7621ST and MT7623AI SoCs, and built-in switch in MT7988 SoC are + supported as well. + +config NET_DSA_MT7530_MDIO + tristate "MediaTek MT7530 MDIO interface driver" + depends on NET_DSA_MT7530 + select PCS_MTK_LYNXI + help + This enables support for the MediaTek MT7530 and MT7531 switch + chips which are connected via MDIO, as well as multi-chip + module MT7530 which can be found in the MT7621AT, MT7621DAT, + MT7621ST and MT7623AI SoCs. config NET_DSA_MV88E6060 tristate "Marvell 88E6060 ethernet switch chip support" diff --git a/drivers/net/dsa/Makefile b/drivers/net/dsa/Makefile index 16eb879e0cb4..c5da75cb0a6b 100644 --- a/drivers/net/dsa/Makefile +++ b/drivers/net/dsa/Makefile @@ -7,6 +7,7 @@ obj-$(CONFIG_FIXED_PHY) += dsa_loop_bdinfo.o endif obj-$(CONFIG_NET_DSA_LANTIQ_GSWIP) += lantiq_gswip.o obj-$(CONFIG_NET_DSA_MT7530) += mt7530.o +obj-$(CONFIG_NET_DSA_MT7530_MDIO) += mt7530-mdio.o obj-$(CONFIG_NET_DSA_MV88E6060) += mv88e6060.o obj-$(CONFIG_NET_DSA_RZN1_A5PSW) += rzn1_a5psw.o obj-$(CONFIG_NET_DSA_SMSC_LAN9303) += lan9303-core.o diff --git a/drivers/net/dsa/mt7530-mdio.c b/drivers/net/dsa/mt7530-mdio.c new file mode 100644 index 000000000000..34a547b88e49 --- /dev/null +++ b/drivers/net/dsa/mt7530-mdio.c @@ -0,0 +1,271 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mt7530.h" + +static int +mt7530_regmap_write(void *context, unsigned int reg, unsigned int val) +{ + struct mii_bus *bus = context; + u16 page, r, lo, hi; + int ret; + + page = (reg >> 6) & 0x3ff; + r = (reg >> 2) & 0xf; + lo = val & 0xffff; + hi = val >> 16; + + /* MT7530 uses 31 as the pseudo port */ + ret = bus->write(bus, 0x1f, 0x1f, page); + if (ret < 0) + return ret; + + ret = bus->write(bus, 0x1f, r, lo); + if (ret < 0) + return ret; + + ret = bus->write(bus, 0x1f, 0x10, hi); + return ret; +} + +static int +mt7530_regmap_read(void *context, unsigned int reg, unsigned int *val) +{ + struct mii_bus *bus = context; + u16 page, r, lo, hi; + int ret; + + page = (reg >> 6) & 0x3ff; + r = (reg >> 2) & 0xf; + + /* MT7530 uses 31 as the pseudo port */ + ret = bus->write(bus, 0x1f, 0x1f, page); + if (ret < 0) + return ret; + + lo = bus->read(bus, 0x1f, r); + hi = bus->read(bus, 0x1f, 0x10); + + *val = (hi << 16) | (lo & 0xffff); + + return 0; +} + +static void +mt7530_mdio_regmap_lock(void *mdio_lock) +{ + mutex_lock_nested(mdio_lock, MDIO_MUTEX_NESTED); +} + +static void +mt7530_mdio_regmap_unlock(void *mdio_lock) +{ + mutex_unlock(mdio_lock); +} + +static const struct regmap_bus mt7530_regmap_bus = { + .reg_write = mt7530_regmap_write, + .reg_read = mt7530_regmap_read, +}; + +static int +mt7531_create_sgmii(struct mt7530_priv *priv) +{ + struct regmap_config *mt7531_pcs_config[2]; + struct phylink_pcs *pcs; + struct regmap *regmap; + int i, ret = 0; + + for (i = 0; i < 2; i++) { + mt7531_pcs_config[i] = devm_kzalloc(priv->dev, + sizeof(struct regmap_config), + GFP_KERNEL); + if (!mt7531_pcs_config[i]) { + ret = -ENOMEM; + break; + } + + mt7531_pcs_config[i]->name = i ? "port6" : "port5"; + mt7531_pcs_config[i]->reg_bits = 16; + mt7531_pcs_config[i]->val_bits = 32; + mt7531_pcs_config[i]->reg_stride = 4; + mt7531_pcs_config[i]->reg_base = MT7531_SGMII_REG_BASE(5 + i); + mt7531_pcs_config[i]->max_register = 0x17c; + mt7531_pcs_config[i]->lock = mt7530_mdio_regmap_lock; + mt7531_pcs_config[i]->unlock = mt7530_mdio_regmap_unlock; + mt7531_pcs_config[i]->lock_arg = &priv->bus->mdio_lock; + + regmap = devm_regmap_init(priv->dev, + &mt7530_regmap_bus, priv->bus, + mt7531_pcs_config[i]); + if (IS_ERR(regmap)) { + ret = PTR_ERR(regmap); + break; + } + pcs = mtk_pcs_lynxi_create(priv->dev, regmap, + MT7531_PHYA_CTRL_SIGNAL3, 0); + if (!pcs) { + ret = -ENXIO; + break; + } + priv->ports[5 + i].sgmii_pcs = pcs; + } + + if (ret && i) + mtk_pcs_lynxi_destroy(priv->ports[5].sgmii_pcs); + + return ret; +} + +static const struct of_device_id mt7530_of_match[] = { + { .compatible = "mediatek,mt7621", .data = &mt753x_table[ID_MT7621], }, + { .compatible = "mediatek,mt7530", .data = &mt753x_table[ID_MT7530], }, + { .compatible = "mediatek,mt7531", .data = &mt753x_table[ID_MT7531], }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, mt7530_of_match); + +static int +mt7530_probe(struct mdio_device *mdiodev) +{ + static struct regmap_config *regmap_config; + struct mt7530_priv *priv; + struct device_node *dn; + int ret; + + dn = mdiodev->dev.of_node; + + priv = devm_kzalloc(&mdiodev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->bus = mdiodev->bus; + priv->dev = &mdiodev->dev; + + ret = mt7530_probe_common(priv); + if (ret) + return ret; + + /* Use medatek,mcm property to distinguish hardware type that would + * cause a little bit differences on power-on sequence. + * Not MCM that indicates switch works as the remote standalone + * integrated circuit so the GPIO pin would be used to complete + * the reset, otherwise memory-mapped register accessing used + * through syscon provides in the case of MCM. + */ + priv->mcm = of_property_read_bool(dn, "mediatek,mcm"); + if (priv->mcm) { + dev_info(&mdiodev->dev, "MT7530 adapts as multi-chip module\n"); + + priv->rstc = devm_reset_control_get(&mdiodev->dev, "mcm"); + if (IS_ERR(priv->rstc)) { + dev_err(&mdiodev->dev, "Couldn't get our reset line\n"); + return PTR_ERR(priv->rstc); + } + } else { + priv->reset = devm_gpiod_get_optional(&mdiodev->dev, "reset", + GPIOD_OUT_LOW); + if (IS_ERR(priv->reset)) { + dev_err(&mdiodev->dev, "Couldn't get our reset line\n"); + return PTR_ERR(priv->reset); + } + } + + if (priv->id == ID_MT7530) { + priv->core_pwr = devm_regulator_get(&mdiodev->dev, "core"); + if (IS_ERR(priv->core_pwr)) + return PTR_ERR(priv->core_pwr); + + priv->io_pwr = devm_regulator_get(&mdiodev->dev, "io"); + if (IS_ERR(priv->io_pwr)) + return PTR_ERR(priv->io_pwr); + } + + regmap_config = devm_kzalloc(&mdiodev->dev, sizeof(*regmap_config), + GFP_KERNEL); + if (!regmap_config) + return -ENOMEM; + + regmap_config->reg_bits = 16; + regmap_config->val_bits = 32; + regmap_config->reg_stride = 4; + regmap_config->max_register = MT7530_CREV; + regmap_config->disable_locking = true; + priv->regmap = devm_regmap_init(priv->dev, &mt7530_regmap_bus, + priv->bus, regmap_config); + if (IS_ERR(priv->regmap)) + return PTR_ERR(priv->regmap); + + if (priv->id == ID_MT7531) { + ret = mt7531_create_sgmii(priv); + if (ret) + return ret; + } + + return dsa_register_switch(priv->ds); +} + +static void +mt7530_remove(struct mdio_device *mdiodev) +{ + struct mt7530_priv *priv = dev_get_drvdata(&mdiodev->dev); + int ret = 0, i; + + if (!priv) + return; + + ret = regulator_disable(priv->core_pwr); + if (ret < 0) + dev_err(priv->dev, + "Failed to disable core power: %d\n", ret); + + ret = regulator_disable(priv->io_pwr); + if (ret < 0) + dev_err(priv->dev, "Failed to disable io pwr: %d\n", + ret); + + mt7530_remove_common(priv); + + for (i = 0; i < 2; ++i) + mtk_pcs_lynxi_destroy(priv->ports[5 + i].sgmii_pcs); +} + +static void mt7530_shutdown(struct mdio_device *mdiodev) +{ + struct mt7530_priv *priv = dev_get_drvdata(&mdiodev->dev); + + if (!priv) + return; + + dsa_switch_shutdown(priv->ds); + + dev_set_drvdata(&mdiodev->dev, NULL); +} + +static struct mdio_driver mt7530_mdio_driver = { + .probe = mt7530_probe, + .remove = mt7530_remove, + .shutdown = mt7530_shutdown, + .mdiodrv.driver = { + .name = "mt7530-mdio", + .of_match_table = mt7530_of_match, + }, +}; + +mdio_module_driver(mt7530_mdio_driver); + +MODULE_AUTHOR("Sean Wang "); +MODULE_DESCRIPTION("Driver for Mediatek MT7530 Switch (MDIO)"); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/dsa/mt7530.c b/drivers/net/dsa/mt7530.c index 47e3c413ccf1..ac485ee83ed0 100644 --- a/drivers/net/dsa/mt7530.c +++ b/drivers/net/dsa/mt7530.c @@ -14,7 +14,6 @@ #include #include #include -#include #include #include #include @@ -191,31 +190,6 @@ core_clear(struct mt7530_priv *priv, u32 reg, u32 val) core_rmw(priv, reg, val, 0); } -static int -mt7530_regmap_write(void *context, unsigned int reg, unsigned int val) -{ - struct mii_bus *bus = context; - u16 page, r, lo, hi; - int ret; - - page = (reg >> 6) & 0x3ff; - r = (reg >> 2) & 0xf; - lo = val & 0xffff; - hi = val >> 16; - - /* MT7530 uses 31 as the pseudo port */ - ret = bus->write(bus, 0x1f, 0x1f, page); - if (ret < 0) - return ret; - - ret = bus->write(bus, 0x1f, r, lo); - if (ret < 0) - return ret; - - ret = bus->write(bus, 0x1f, 0x10, hi); - return ret; -} - static int mt7530_mii_write(struct mt7530_priv *priv, u32 reg, u32 val) { @@ -230,29 +204,6 @@ mt7530_mii_write(struct mt7530_priv *priv, u32 reg, u32 val) return ret; } -static int -mt7530_regmap_read(void *context, unsigned int reg, unsigned int *val) -{ - struct mii_bus *bus = context; - u16 page, r, lo, hi; - int ret; - - page = (reg >> 6) & 0x3ff; - r = (reg >> 2) & 0xf; - - /* MT7530 uses 31 as the pseudo port */ - ret = bus->write(bus, 0x1f, 0x1f, page); - if (ret < 0) - return ret; - - lo = bus->read(bus, 0x1f, r); - hi = bus->read(bus, 0x1f, 0x10); - - *val = (hi << 16) | (lo & 0xffff); - - return 0; -} - static u32 mt7530_mii_read(struct mt7530_priv *priv, u32 reg) { @@ -2945,72 +2896,6 @@ static const struct phylink_pcs_ops mt7530_pcs_ops = { .pcs_an_restart = mt7530_pcs_an_restart, }; -static void -mt7530_mdio_regmap_lock(void *mdio_lock) -{ - mutex_lock_nested(mdio_lock, MDIO_MUTEX_NESTED); -} - -static void -mt7530_mdio_regmap_unlock(void *mdio_lock) -{ - mutex_unlock(mdio_lock); -} - -static const struct regmap_bus mt7530_regmap_bus = { - .reg_write = mt7530_regmap_write, - .reg_read = mt7530_regmap_read, -}; - -static int -mt7531_create_sgmii(struct mt7530_priv *priv) -{ - struct regmap_config *mt7531_pcs_config[2]; - struct phylink_pcs *pcs; - struct regmap *regmap; - int i, ret = 0; - - for (i = 0; i < 2; i++) { - mt7531_pcs_config[i] = devm_kzalloc(priv->dev, - sizeof(struct regmap_config), - GFP_KERNEL); - if (!mt7531_pcs_config[i]) { - ret = -ENOMEM; - break; - } - - mt7531_pcs_config[i]->name = i ? "port6" : "port5"; - mt7531_pcs_config[i]->reg_bits = 16; - mt7531_pcs_config[i]->val_bits = 32; - mt7531_pcs_config[i]->reg_stride = 4; - mt7531_pcs_config[i]->reg_base = MT7531_SGMII_REG_BASE(5 + i); - mt7531_pcs_config[i]->max_register = 0x17c; - mt7531_pcs_config[i]->lock = mt7530_mdio_regmap_lock; - mt7531_pcs_config[i]->unlock = mt7530_mdio_regmap_unlock; - mt7531_pcs_config[i]->lock_arg = &priv->bus->mdio_lock; - - regmap = devm_regmap_init(priv->dev, - &mt7530_regmap_bus, priv->bus, - mt7531_pcs_config[i]); - if (IS_ERR(regmap)) { - ret = PTR_ERR(regmap); - break; - } - pcs = mtk_pcs_lynxi_create(priv->dev, regmap, - MT7531_PHYA_CTRL_SIGNAL3, 0); - if (!pcs) { - ret = -ENXIO; - break; - } - priv->ports[5 + i].sgmii_pcs = pcs; - } - - if (ret && i) - mtk_pcs_lynxi_destroy(priv->ports[5].sgmii_pcs); - - return ret; -} - static int mt753x_setup(struct dsa_switch *ds) { @@ -3069,7 +2954,7 @@ static int mt753x_set_mac_eee(struct dsa_switch *ds, int port, return 0; } -static const struct dsa_switch_ops mt7530_switch_ops = { +const struct dsa_switch_ops mt7530_switch_ops = { .get_tag_protocol = mtk_get_tag_protocol, .setup = mt753x_setup, .get_strings = mt7530_get_strings, @@ -3103,8 +2988,9 @@ static const struct dsa_switch_ops mt7530_switch_ops = { .get_mac_eee = mt753x_get_mac_eee, .set_mac_eee = mt753x_set_mac_eee, }; +EXPORT_SYMBOL_GPL(mt7530_switch_ops); -static const struct mt753x_info mt753x_table[] = { +const struct mt753x_info mt753x_table[] = { [ID_MT7621] = { .id = ID_MT7621, .pcs_ops = &mt7530_pcs_ops, @@ -3143,16 +3029,9 @@ static const struct mt753x_info mt753x_table[] = { .mac_port_config = mt7531_mac_config, }, }; +EXPORT_SYMBOL_GPL(mt753x_table); -static const struct of_device_id mt7530_of_match[] = { - { .compatible = "mediatek,mt7621", .data = &mt753x_table[ID_MT7621], }, - { .compatible = "mediatek,mt7530", .data = &mt753x_table[ID_MT7530], }, - { .compatible = "mediatek,mt7531", .data = &mt753x_table[ID_MT7531], }, - { /* sentinel */ }, -}; -MODULE_DEVICE_TABLE(of, mt7530_of_match); - -static int +int mt7530_probe_common(struct mt7530_priv *priv) { struct device *dev = priv->dev; @@ -3189,88 +3068,9 @@ mt7530_probe_common(struct mt7530_priv *priv) return 0; } +EXPORT_SYMBOL_GPL(mt7530_probe_common); -static int -mt7530_probe(struct mdio_device *mdiodev) -{ - static struct regmap_config *regmap_config; - struct mt7530_priv *priv; - struct device_node *dn; - int ret; - - dn = mdiodev->dev.of_node; - - priv = devm_kzalloc(&mdiodev->dev, sizeof(*priv), GFP_KERNEL); - if (!priv) - return -ENOMEM; - - priv->bus = mdiodev->bus; - priv->dev = &mdiodev->dev; - - ret = mt7530_probe_common(priv); - if (ret) - return ret; - - /* Use medatek,mcm property to distinguish hardware type that would - * cause a little bit differences on power-on sequence. - * Not MCM that indicates switch works as the remote standalone - * integrated circuit so the GPIO pin would be used to complete - * the reset, otherwise memory-mapped register accessing used - * through syscon provides in the case of MCM. - */ - priv->mcm = of_property_read_bool(dn, "mediatek,mcm"); - if (priv->mcm) { - dev_info(&mdiodev->dev, "MT7530 adapts as multi-chip module\n"); - - priv->rstc = devm_reset_control_get(&mdiodev->dev, "mcm"); - if (IS_ERR(priv->rstc)) { - dev_err(&mdiodev->dev, "Couldn't get our reset line\n"); - return PTR_ERR(priv->rstc); - } - } else { - priv->reset = devm_gpiod_get_optional(&mdiodev->dev, "reset", - GPIOD_OUT_LOW); - if (IS_ERR(priv->reset)) { - dev_err(&mdiodev->dev, "Couldn't get our reset line\n"); - return PTR_ERR(priv->reset); - } - } - - if (priv->id == ID_MT7530) { - priv->core_pwr = devm_regulator_get(&mdiodev->dev, "core"); - if (IS_ERR(priv->core_pwr)) - return PTR_ERR(priv->core_pwr); - - priv->io_pwr = devm_regulator_get(&mdiodev->dev, "io"); - if (IS_ERR(priv->io_pwr)) - return PTR_ERR(priv->io_pwr); - } - - regmap_config = devm_kzalloc(&mdiodev->dev, sizeof(*regmap_config), - GFP_KERNEL); - if (!regmap_config) - return -ENOMEM; - - regmap_config->reg_bits = 16; - regmap_config->val_bits = 32; - regmap_config->reg_stride = 4; - regmap_config->max_register = MT7530_CREV; - regmap_config->disable_locking = true; - priv->regmap = devm_regmap_init(priv->dev, &mt7530_regmap_bus, - priv->bus, regmap_config); - if (IS_ERR(priv->regmap)) - return PTR_ERR(priv->regmap); - - if (priv->id == ID_MT7531) { - ret = mt7531_create_sgmii(priv); - if (ret) - return ret; - } - - return dsa_register_switch(priv->ds); -} - -static void +void mt7530_remove_common(struct mt7530_priv *priv) { if (priv->irq) @@ -3280,55 +3080,7 @@ mt7530_remove_common(struct mt7530_priv *priv) mutex_destroy(&priv->reg_mutex); } - -static void -mt7530_remove(struct mdio_device *mdiodev) -{ - struct mt7530_priv *priv = dev_get_drvdata(&mdiodev->dev); - int ret = 0, i; - - if (!priv) - return; - - ret = regulator_disable(priv->core_pwr); - if (ret < 0) - dev_err(priv->dev, - "Failed to disable core power: %d\n", ret); - - ret = regulator_disable(priv->io_pwr); - if (ret < 0) - dev_err(priv->dev, "Failed to disable io pwr: %d\n", - ret); - - mt7530_remove_common(priv); - - for (i = 0; i < 2; ++i) - mtk_pcs_lynxi_destroy(priv->ports[5 + i].sgmii_pcs); -} - -static void mt7530_shutdown(struct mdio_device *mdiodev) -{ - struct mt7530_priv *priv = dev_get_drvdata(&mdiodev->dev); - - if (!priv) - return; - - dsa_switch_shutdown(priv->ds); - - dev_set_drvdata(&mdiodev->dev, NULL); -} - -static struct mdio_driver mt7530_mdio_driver = { - .probe = mt7530_probe, - .remove = mt7530_remove, - .shutdown = mt7530_shutdown, - .mdiodrv.driver = { - .name = "mt7530", - .of_match_table = mt7530_of_match, - }, -}; - -mdio_module_driver(mt7530_mdio_driver); +EXPORT_SYMBOL_GPL(mt7530_remove_common); MODULE_AUTHOR("Sean Wang "); MODULE_DESCRIPTION("Driver for Mediatek MT7530 Switch"); diff --git a/drivers/net/dsa/mt7530.h b/drivers/net/dsa/mt7530.h index 2a611173a7d0..ce02aa592a7a 100644 --- a/drivers/net/dsa/mt7530.h +++ b/drivers/net/dsa/mt7530.h @@ -814,4 +814,10 @@ static inline void INIT_MT7530_DUMMY_POLL(struct mt7530_dummy_poll *p, p->reg = reg; } +int mt7530_probe_common(struct mt7530_priv *priv); +void mt7530_remove_common(struct mt7530_priv *priv); + +extern const struct dsa_switch_ops mt7530_switch_ops; +extern const struct mt753x_info mt753x_table[]; + #endif /* __MT7530_H */ -- cgit v1.2.3 From 110c18bfed41421edd677935dd33be5e6507ba92 Mon Sep 17 00:00:00 2001 From: Daniel Golle Date: Mon, 3 Apr 2023 02:19:40 +0100 Subject: net: dsa: mt7530: introduce driver for MT7988 built-in switch Add driver for the built-in Gigabit Ethernet switch which can be found in the MediaTek MT7988 SoC. The switch shares most of its design with MT7530 and MT7531, but has it's registers mapped into the SoCs register space rather than being connected externally or internally via MDIO. Introduce a new platform driver to support that. Signed-off-by: Daniel Golle Reviewed-by: Andrew Lunn Signed-off-by: David S. Miller --- MAINTAINERS | 2 + drivers/net/dsa/Kconfig | 12 ++++ drivers/net/dsa/Makefile | 1 + drivers/net/dsa/mt7530-mmio.c | 101 +++++++++++++++++++++++++++++++ drivers/net/dsa/mt7530.c | 137 ++++++++++++++++++++++++++++++++++++++++-- drivers/net/dsa/mt7530.h | 12 ++-- 6 files changed, 255 insertions(+), 10 deletions(-) create mode 100644 drivers/net/dsa/mt7530-mmio.c (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 4970a6c6a0a8..7812f0e251ad 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -13175,9 +13175,11 @@ MEDIATEK SWITCH DRIVER M: Sean Wang M: Landen Chao M: DENG Qingfang +M: Daniel Golle L: netdev@vger.kernel.org S: Maintained F: drivers/net/dsa/mt7530-mdio.c +F: drivers/net/dsa/mt7530-mmio.c F: drivers/net/dsa/mt7530.* F: net/dsa/tag_mtk.c diff --git a/drivers/net/dsa/Kconfig b/drivers/net/dsa/Kconfig index 941d020fe4b6..3ed5391bb18d 100644 --- a/drivers/net/dsa/Kconfig +++ b/drivers/net/dsa/Kconfig @@ -39,6 +39,7 @@ config NET_DSA_MT7530 select NET_DSA_TAG_MTK select MEDIATEK_GE_PHY imply NET_DSA_MT7530_MDIO + imply NET_DSA_MT7530_MMIO help This enables support for the MediaTek MT7530 and MT7531 Ethernet switch chips. Multi-chip module MT7530 in MT7621AT, MT7621DAT, @@ -55,6 +56,17 @@ config NET_DSA_MT7530_MDIO module MT7530 which can be found in the MT7621AT, MT7621DAT, MT7621ST and MT7623AI SoCs. +config NET_DSA_MT7530_MMIO + tristate "MediaTek MT7530 MMIO interface driver" + depends on NET_DSA_MT7530 + depends on HAS_IOMEM + help + This enables support for the built-in Ethernet switch found + in the MediaTek MT7988 SoC. + The switch is a similar design as MT7531, but the switch registers + are directly mapped into the SoCs register space rather than being + accessible via MDIO. + config NET_DSA_MV88E6060 tristate "Marvell 88E6060 ethernet switch chip support" select NET_DSA_TAG_TRAILER diff --git a/drivers/net/dsa/Makefile b/drivers/net/dsa/Makefile index c5da75cb0a6b..cb9a97340e58 100644 --- a/drivers/net/dsa/Makefile +++ b/drivers/net/dsa/Makefile @@ -8,6 +8,7 @@ endif obj-$(CONFIG_NET_DSA_LANTIQ_GSWIP) += lantiq_gswip.o obj-$(CONFIG_NET_DSA_MT7530) += mt7530.o obj-$(CONFIG_NET_DSA_MT7530_MDIO) += mt7530-mdio.o +obj-$(CONFIG_NET_DSA_MT7530_MMIO) += mt7530-mmio.o obj-$(CONFIG_NET_DSA_MV88E6060) += mv88e6060.o obj-$(CONFIG_NET_DSA_RZN1_A5PSW) += rzn1_a5psw.o obj-$(CONFIG_NET_DSA_SMSC_LAN9303) += lan9303-core.o diff --git a/drivers/net/dsa/mt7530-mmio.c b/drivers/net/dsa/mt7530-mmio.c new file mode 100644 index 000000000000..1a3d4b692f34 --- /dev/null +++ b/drivers/net/dsa/mt7530-mmio.c @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include +#include +#include +#include +#include +#include + +#include "mt7530.h" + +static const struct of_device_id mt7988_of_match[] = { + { .compatible = "mediatek,mt7988-switch", .data = &mt753x_table[ID_MT7988], }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, mt7988_of_match); + +static int +mt7988_probe(struct platform_device *pdev) +{ + static struct regmap_config *sw_regmap_config; + struct mt7530_priv *priv; + void __iomem *base_addr; + int ret; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->bus = NULL; + priv->dev = &pdev->dev; + + ret = mt7530_probe_common(priv); + if (ret) + return ret; + + priv->rstc = devm_reset_control_get(&pdev->dev, NULL); + if (IS_ERR(priv->rstc)) { + dev_err(&pdev->dev, "Couldn't get our reset line\n"); + return PTR_ERR(priv->rstc); + } + + base_addr = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(base_addr)) { + dev_err(&pdev->dev, "cannot request I/O memory space\n"); + return -ENXIO; + } + + sw_regmap_config = devm_kzalloc(&pdev->dev, sizeof(*sw_regmap_config), GFP_KERNEL); + if (!sw_regmap_config) + return -ENOMEM; + + sw_regmap_config->name = "switch"; + sw_regmap_config->reg_bits = 16; + sw_regmap_config->val_bits = 32; + sw_regmap_config->reg_stride = 4; + sw_regmap_config->max_register = MT7530_CREV; + priv->regmap = devm_regmap_init_mmio(&pdev->dev, base_addr, sw_regmap_config); + if (IS_ERR(priv->regmap)) + return PTR_ERR(priv->regmap); + + return dsa_register_switch(priv->ds); +} + +static int +mt7988_remove(struct platform_device *pdev) +{ + struct mt7530_priv *priv = platform_get_drvdata(pdev); + + if (priv) + mt7530_remove_common(priv); + + return 0; +} + +static void mt7988_shutdown(struct platform_device *pdev) +{ + struct mt7530_priv *priv = platform_get_drvdata(pdev); + + if (!priv) + return; + + dsa_switch_shutdown(priv->ds); + + dev_set_drvdata(&pdev->dev, NULL); +} + +static struct platform_driver mt7988_platform_driver = { + .probe = mt7988_probe, + .remove = mt7988_remove, + .shutdown = mt7988_shutdown, + .driver = { + .name = "mt7530-mmio", + .of_match_table = mt7988_of_match, + }, +}; +module_platform_driver(mt7988_platform_driver); + +MODULE_AUTHOR("Daniel Golle "); +MODULE_DESCRIPTION("Driver for Mediatek MT7530 Switch (MMIO)"); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/dsa/mt7530.c b/drivers/net/dsa/mt7530.c index 394fada7ddf1..e4bb5037d352 100644 --- a/drivers/net/dsa/mt7530.c +++ b/drivers/net/dsa/mt7530.c @@ -1981,6 +1981,47 @@ static const struct irq_domain_ops mt7530_irq_domain_ops = { .xlate = irq_domain_xlate_onecell, }; +static void +mt7988_irq_mask(struct irq_data *d) +{ + struct mt7530_priv *priv = irq_data_get_irq_chip_data(d); + + priv->irq_enable &= ~BIT(d->hwirq); + mt7530_mii_write(priv, MT7530_SYS_INT_EN, priv->irq_enable); +} + +static void +mt7988_irq_unmask(struct irq_data *d) +{ + struct mt7530_priv *priv = irq_data_get_irq_chip_data(d); + + priv->irq_enable |= BIT(d->hwirq); + mt7530_mii_write(priv, MT7530_SYS_INT_EN, priv->irq_enable); +} + +static struct irq_chip mt7988_irq_chip = { + .name = KBUILD_MODNAME, + .irq_mask = mt7988_irq_mask, + .irq_unmask = mt7988_irq_unmask, +}; + +static int +mt7988_irq_map(struct irq_domain *domain, unsigned int irq, + irq_hw_number_t hwirq) +{ + irq_set_chip_data(irq, domain->host_data); + irq_set_chip_and_handler(irq, &mt7988_irq_chip, handle_simple_irq); + irq_set_nested_thread(irq, true); + irq_set_noprobe(irq); + + return 0; +} + +static const struct irq_domain_ops mt7988_irq_domain_ops = { + .map = mt7988_irq_map, + .xlate = irq_domain_xlate_onecell, +}; + static void mt7530_setup_mdio_irq(struct mt7530_priv *priv) { @@ -2015,8 +2056,15 @@ mt7530_setup_irq(struct mt7530_priv *priv) return priv->irq ? : -EINVAL; } - priv->irq_domain = irq_domain_add_linear(np, MT7530_NUM_PHYS, - &mt7530_irq_domain_ops, priv); + if (priv->id == ID_MT7988) + priv->irq_domain = irq_domain_add_linear(np, MT7530_NUM_PHYS, + &mt7988_irq_domain_ops, + priv); + else + priv->irq_domain = irq_domain_add_linear(np, MT7530_NUM_PHYS, + &mt7530_irq_domain_ops, + priv); + if (!priv->irq_domain) { dev_err(dev, "failed to create IRQ domain\n"); return -ENOMEM; @@ -2511,6 +2559,25 @@ static void mt7531_mac_port_get_caps(struct dsa_switch *ds, int port, } } +static void mt7988_mac_port_get_caps(struct dsa_switch *ds, int port, + struct phylink_config *config) +{ + phy_interface_zero(config->supported_interfaces); + + switch (port) { + case 0 ... 4: /* Internal phy */ + __set_bit(PHY_INTERFACE_MODE_INTERNAL, + config->supported_interfaces); + break; + + case 6: + __set_bit(PHY_INTERFACE_MODE_INTERNAL, + config->supported_interfaces); + config->mac_capabilities = MAC_ASYM_PAUSE | MAC_SYM_PAUSE | + MAC_10000FD; + } +} + static int mt753x_pad_setup(struct dsa_switch *ds, const struct phylink_link_state *state) { @@ -2586,6 +2653,17 @@ static bool mt753x_is_mac_port(u32 port) return (port == 5 || port == 6); } +static int +mt7988_mac_config(struct dsa_switch *ds, int port, unsigned int mode, + phy_interface_t interface) +{ + if (dsa_is_cpu_port(ds, port) && + interface == PHY_INTERFACE_MODE_INTERNAL) + return 0; + + return -EINVAL; +} + static int mt7531_mac_config(struct dsa_switch *ds, int port, unsigned int mode, phy_interface_t interface) @@ -2656,7 +2734,8 @@ mt753x_phylink_mac_config(struct dsa_switch *ds, int port, unsigned int mode, switch (port) { case 0 ... 4: /* Internal phy */ - if (state->interface != PHY_INTERFACE_MODE_GMII) + if (state->interface != PHY_INTERFACE_MODE_GMII && + state->interface != PHY_INTERFACE_MODE_INTERNAL) goto unsupported; break; case 5: /* 2nd cpu port with phy of port 0 or 4 / external phy */ @@ -2734,7 +2813,8 @@ static void mt753x_phylink_mac_link_up(struct dsa_switch *ds, int port, /* MT753x MAC works in 1G full duplex mode for all up-clocked * variants. */ - if (interface == PHY_INTERFACE_MODE_TRGMII || + if (interface == PHY_INTERFACE_MODE_INTERNAL || + interface == PHY_INTERFACE_MODE_TRGMII || (phy_interface_mode_is_8023z(interface))) { speed = SPEED_1000; duplex = DUPLEX_FULL; @@ -2814,6 +2894,21 @@ mt7531_cpu_port_config(struct dsa_switch *ds, int port) return 0; } +static int +mt7988_cpu_port_config(struct dsa_switch *ds, int port) +{ + struct mt7530_priv *priv = ds->priv; + + mt7530_write(priv, MT7530_PMCR_P(port), + PMCR_CPU_PORT_SETTING(priv->id)); + + mt753x_phylink_mac_link_up(ds, port, MLO_AN_FIXED, + PHY_INTERFACE_MODE_INTERNAL, NULL, + SPEED_10000, DUPLEX_FULL, true, true); + + return 0; +} + static void mt753x_phylink_get_caps(struct dsa_switch *ds, int port, struct phylink_config *config) { @@ -2956,6 +3051,27 @@ static int mt753x_set_mac_eee(struct dsa_switch *ds, int port, return 0; } +static int mt7988_pad_setup(struct dsa_switch *ds, phy_interface_t interface) +{ + return 0; +} + +static int mt7988_setup(struct dsa_switch *ds) +{ + struct mt7530_priv *priv = ds->priv; + + /* Reset the switch */ + reset_control_assert(priv->rstc); + usleep_range(20, 50); + reset_control_deassert(priv->rstc); + usleep_range(20, 50); + + /* Reset the switch PHYs */ + mt7530_write(priv, MT7530_SYS_CTRL, SYS_CTRL_PHY_RST); + + return mt7531_setup_common(ds); +} + const struct dsa_switch_ops mt7530_switch_ops = { .get_tag_protocol = mtk_get_tag_protocol, .setup = mt753x_setup, @@ -3030,6 +3146,19 @@ const struct mt753x_info mt753x_table[] = { .mac_port_get_caps = mt7531_mac_port_get_caps, .mac_port_config = mt7531_mac_config, }, + [ID_MT7988] = { + .id = ID_MT7988, + .pcs_ops = &mt7530_pcs_ops, + .sw_setup = mt7988_setup, + .phy_read_c22 = mt7531_ind_c22_phy_read, + .phy_write_c22 = mt7531_ind_c22_phy_write, + .phy_read_c45 = mt7531_ind_c45_phy_read, + .phy_write_c45 = mt7531_ind_c45_phy_write, + .pad_setup = mt7988_pad_setup, + .cpu_port_config = mt7988_cpu_port_config, + .mac_port_get_caps = mt7988_mac_port_get_caps, + .mac_port_config = mt7988_mac_config, + }, }; EXPORT_SYMBOL_GPL(mt753x_table); diff --git a/drivers/net/dsa/mt7530.h b/drivers/net/dsa/mt7530.h index ce02aa592a7a..01db5c9724fa 100644 --- a/drivers/net/dsa/mt7530.h +++ b/drivers/net/dsa/mt7530.h @@ -18,6 +18,7 @@ enum mt753x_id { ID_MT7530 = 0, ID_MT7621 = 1, ID_MT7531 = 2, + ID_MT7988 = 3, }; #define NUM_TRGMII_CTRL 5 @@ -54,11 +55,11 @@ enum mt753x_id { #define MT7531_MIRROR_PORT_SET(x) (((x) & MIRROR_MASK) << 16) #define MT7531_CPU_PMAP_MASK GENMASK(7, 0) -#define MT753X_MIRROR_REG(id) (((id) == ID_MT7531) ? \ +#define MT753X_MIRROR_REG(id) ((((id) == ID_MT7531) || ((id) == ID_MT7988)) ? \ MT7531_CFC : MT7530_MFC) -#define MT753X_MIRROR_EN(id) (((id) == ID_MT7531) ? \ +#define MT753X_MIRROR_EN(id) ((((id) == ID_MT7531) || ((id) == ID_MT7988)) ? \ MT7531_MIRROR_EN : MIRROR_EN) -#define MT753X_MIRROR_MASK(id) (((id) == ID_MT7531) ? \ +#define MT753X_MIRROR_MASK(id) ((((id) == ID_MT7531) || ((id) == ID_MT7988)) ? \ MT7531_MIRROR_MASK : MIRROR_MASK) /* Registers for BPDU and PAE frame control*/ @@ -295,9 +296,8 @@ enum mt7530_vlan_port_acc_frm { MT7531_FORCE_DPX | \ MT7531_FORCE_RX_FC | \ MT7531_FORCE_TX_FC) -#define PMCR_FORCE_MODE_ID(id) (((id) == ID_MT7531) ? \ - MT7531_FORCE_MODE : \ - PMCR_FORCE_MODE) +#define PMCR_FORCE_MODE_ID(id) ((((id) == ID_MT7531) || ((id) == ID_MT7988)) ? \ + MT7531_FORCE_MODE : PMCR_FORCE_MODE) #define PMCR_LINK_SETTINGS_MASK (PMCR_TX_EN | PMCR_FORCE_SPEED_1000 | \ PMCR_RX_EN | PMCR_FORCE_SPEED_100 | \ PMCR_TX_FC_EN | PMCR_RX_FC_EN | \ -- cgit v1.2.3 From 02e98ce3db149919d6aa35f873e5d74c1ac36640 Mon Sep 17 00:00:00 2001 From: Bhupesh Sharma Date: Tue, 11 Apr 2023 15:04:00 -0500 Subject: dt-bindings: net: qcom,ethqos: Convert bindings to yaml Convert Qualcomm ETHQOS Ethernet devicetree binding to YAML. In doing so add a new property for iommus since newer platforms support using one, and without such make dtbs_check fails on them. While at it, also update the MAINTAINERS file to point to the yaml version of the bindings. Signed-off-by: Bhupesh Sharma [halaney: Remove duplicated properties, add MAINTAINERS and iommus] Reviewed-by: Krzysztof Kozlowski Signed-off-by: Andrew Halaney Tested-by: Brian Masney Signed-off-by: Paolo Abeni --- .../devicetree/bindings/net/qcom,ethqos.txt | 66 ------------- .../devicetree/bindings/net/qcom,ethqos.yaml | 110 +++++++++++++++++++++ MAINTAINERS | 2 +- 3 files changed, 111 insertions(+), 67 deletions(-) delete mode 100644 Documentation/devicetree/bindings/net/qcom,ethqos.txt create mode 100644 Documentation/devicetree/bindings/net/qcom,ethqos.yaml (limited to 'MAINTAINERS') diff --git a/Documentation/devicetree/bindings/net/qcom,ethqos.txt b/Documentation/devicetree/bindings/net/qcom,ethqos.txt deleted file mode 100644 index 1f5746849a71..000000000000 --- a/Documentation/devicetree/bindings/net/qcom,ethqos.txt +++ /dev/null @@ -1,66 +0,0 @@ -Qualcomm Ethernet ETHQOS device - -This documents dwmmac based ethernet device which supports Gigabit -ethernet for version v2.3.0 onwards. - -This device has following properties: - -Required properties: - -- compatible: Should be one of: - "qcom,qcs404-ethqos" - "qcom,sm8150-ethqos" - -- reg: Address and length of the register set for the device - -- reg-names: Should contain register names "stmmaceth", "rgmii" - -- clocks: Should contain phandle to clocks - -- clock-names: Should contain clock names "stmmaceth", "pclk", - "ptp_ref", "rgmii" - -- interrupts: Should contain phandle to interrupts - -- interrupt-names: Should contain interrupt names "macirq", "eth_lpi" - -Rest of the properties are defined in stmmac.txt file in same directory - - -Example: - -ethernet: ethernet@7a80000 { - compatible = "qcom,qcs404-ethqos"; - reg = <0x07a80000 0x10000>, - <0x07a96000 0x100>; - reg-names = "stmmaceth", "rgmii"; - clock-names = "stmmaceth", "pclk", "ptp_ref", "rgmii"; - clocks = <&gcc GCC_ETH_AXI_CLK>, - <&gcc GCC_ETH_SLAVE_AHB_CLK>, - <&gcc GCC_ETH_PTP_CLK>, - <&gcc GCC_ETH_RGMII_CLK>; - interrupts = , - ; - interrupt-names = "macirq", "eth_lpi"; - snps,reset-gpio = <&tlmm 60 GPIO_ACTIVE_LOW>; - snps,reset-active-low; - - snps,txpbl = <8>; - snps,rxpbl = <2>; - snps,aal; - snps,tso; - - phy-handle = <&phy1>; - phy-mode = "rgmii"; - - mdio { - #address-cells = <0x1>; - #size-cells = <0x0>; - compatible = "snps,dwmac-mdio"; - phy1: phy@4 { - device_type = "ethernet-phy"; - reg = <0x4>; - }; - }; - -}; diff --git a/Documentation/devicetree/bindings/net/qcom,ethqos.yaml b/Documentation/devicetree/bindings/net/qcom,ethqos.yaml new file mode 100644 index 000000000000..88234a2010b1 --- /dev/null +++ b/Documentation/devicetree/bindings/net/qcom,ethqos.yaml @@ -0,0 +1,110 @@ +# SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/net/qcom,ethqos.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Qualcomm Ethernet ETHQOS device + +maintainers: + - Bhupesh Sharma + +description: + dwmmac based Qualcomm ethernet devices which support Gigabit + ethernet (version v2.3.0 and onwards). + +allOf: + - $ref: snps,dwmac.yaml# + +properties: + compatible: + enum: + - qcom,qcs404-ethqos + - qcom,sm8150-ethqos + + reg: + maxItems: 2 + + reg-names: + items: + - const: stmmaceth + - const: rgmii + + interrupts: + items: + - description: Combined signal for various interrupt events + - description: The interrupt that occurs when Rx exits the LPI state + + interrupt-names: + items: + - const: macirq + - const: eth_lpi + + clocks: + maxItems: 4 + + clock-names: + items: + - const: stmmaceth + - const: pclk + - const: ptp_ref + - const: rgmii + + iommus: + maxItems: 1 + +required: + - compatible + - clocks + - clock-names + - reg-names + +unevaluatedProperties: false + +examples: + - | + #include + #include + #include + + ethernet: ethernet@7a80000 { + compatible = "qcom,qcs404-ethqos"; + reg = <0x07a80000 0x10000>, + <0x07a96000 0x100>; + reg-names = "stmmaceth", "rgmii"; + clock-names = "stmmaceth", "pclk", "ptp_ref", "rgmii"; + clocks = <&gcc GCC_ETH_AXI_CLK>, + <&gcc GCC_ETH_SLAVE_AHB_CLK>, + <&gcc GCC_ETH_PTP_CLK>, + <&gcc GCC_ETH_RGMII_CLK>; + interrupts = , + ; + interrupt-names = "macirq", "eth_lpi"; + + rx-fifo-depth = <4096>; + tx-fifo-depth = <4096>; + + snps,tso; + snps,reset-gpio = <&tlmm 60 GPIO_ACTIVE_LOW>; + snps,reset-active-low; + snps,reset-delays-us = <0 10000 10000>; + + pinctrl-names = "default"; + pinctrl-0 = <ðernet_defaults>; + + phy-handle = <&phy1>; + phy-mode = "rgmii"; + mdio { + #address-cells = <0x1>; + #size-cells = <0x0>; + + compatible = "snps,dwmac-mdio"; + phy1: phy@4 { + compatible = "ethernet-phy-ieee802.3-c22"; + device_type = "ethernet-phy"; + reg = <0x4>; + + #phy-cells = <0>; + }; + }; + }; diff --git a/MAINTAINERS b/MAINTAINERS index 0b19a3fb266c..fdcef63fa9a0 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -17304,7 +17304,7 @@ M: Vinod Koul R: Bhupesh Sharma L: netdev@vger.kernel.org S: Maintained -F: Documentation/devicetree/bindings/net/qcom,ethqos.txt +F: Documentation/devicetree/bindings/net/qcom,ethqos.yaml F: drivers/net/ethernet/stmicro/stmmac/dwmac-qcom-ethqos.c QUALCOMM FASTRPC DRIVER -- cgit v1.2.3 From c3d713409b53c0db59887230341973d9b1f73bd4 Mon Sep 17 00:00:00 2001 From: Matthieu Baerts Date: Fri, 14 Apr 2023 17:47:07 +0200 Subject: MAINTAINERS: add git trees for MPTCP This will help occasional developers to find our git repo without having to look at our wiki. Acked-by: Paolo Abeni Signed-off-by: Matthieu Baerts Signed-off-by: David S. Miller --- MAINTAINERS | 2 ++ 1 file changed, 2 insertions(+) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index b8b275e27cdb..1c09473685b1 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -14617,6 +14617,8 @@ L: mptcp@lists.linux.dev S: Maintained W: https://github.com/multipath-tcp/mptcp_net-next/wiki B: https://github.com/multipath-tcp/mptcp_net-next/issues +T: git https://github.com/multipath-tcp/mptcp_net-next.git export-net +T: git https://github.com/multipath-tcp/mptcp_net-next.git export F: Documentation/networking/mptcp-sysctl.rst F: include/net/mptcp.h F: include/trace/events/mptcp.h -- cgit v1.2.3 From b76eaf7d7ede35adbbb914cb2e9223a250448e52 Mon Sep 17 00:00:00 2001 From: Yanhong Wang Date: Mon, 17 Apr 2023 18:02:49 +0800 Subject: dt-bindings: net: Add support StarFive dwmac Add documentation to describe StarFive dwmac driver(GMAC). Signed-off-by: Yanhong Wang Reviewed-by: Krzysztof Kozlowski Signed-off-by: Samin Guo Signed-off-by: Paolo Abeni --- .../devicetree/bindings/net/snps,dwmac.yaml | 1 + .../bindings/net/starfive,jh7110-dwmac.yaml | 144 +++++++++++++++++++++ MAINTAINERS | 6 + 3 files changed, 151 insertions(+) create mode 100644 Documentation/devicetree/bindings/net/starfive,jh7110-dwmac.yaml (limited to 'MAINTAINERS') diff --git a/Documentation/devicetree/bindings/net/snps,dwmac.yaml b/Documentation/devicetree/bindings/net/snps,dwmac.yaml index 71151478885a..363b3e3ea3a6 100644 --- a/Documentation/devicetree/bindings/net/snps,dwmac.yaml +++ b/Documentation/devicetree/bindings/net/snps,dwmac.yaml @@ -94,6 +94,7 @@ properties: - snps,dwmac-5.20 - snps,dwxgmac - snps,dwxgmac-2.10 + - starfive,jh7110-dwmac reg: minItems: 1 diff --git a/Documentation/devicetree/bindings/net/starfive,jh7110-dwmac.yaml b/Documentation/devicetree/bindings/net/starfive,jh7110-dwmac.yaml new file mode 100644 index 000000000000..5e7cfbbebce6 --- /dev/null +++ b/Documentation/devicetree/bindings/net/starfive,jh7110-dwmac.yaml @@ -0,0 +1,144 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +# Copyright (C) 2022 StarFive Technology Co., Ltd. +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/net/starfive,jh7110-dwmac.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: StarFive JH7110 DWMAC glue layer + +maintainers: + - Emil Renner Berthing + - Samin Guo + +select: + properties: + compatible: + contains: + enum: + - starfive,jh7110-dwmac + required: + - compatible + +properties: + compatible: + items: + - enum: + - starfive,jh7110-dwmac + - const: snps,dwmac-5.20 + + reg: + maxItems: 1 + + clocks: + items: + - description: GMAC main clock + - description: GMAC AHB clock + - description: PTP clock + - description: TX clock + - description: GTX clock + + clock-names: + items: + - const: stmmaceth + - const: pclk + - const: ptp_ref + - const: tx + - const: gtx + + interrupts: + minItems: 3 + maxItems: 3 + + interrupt-names: + minItems: 3 + maxItems: 3 + + resets: + items: + - description: MAC Reset signal. + - description: AHB Reset signal. + + reset-names: + items: + - const: stmmaceth + - const: ahb + + starfive,tx-use-rgmii-clk: + description: + Tx clock is provided by external rgmii clock. + type: boolean + + starfive,syscon: + $ref: /schemas/types.yaml#/definitions/phandle-array + items: + - items: + - description: phandle to syscon that configures phy mode + - description: Offset of phy mode selection + - description: Shift of phy mode selection + description: + A phandle to syscon with two arguments that configure phy mode. + The argument one is the offset of phy mode selection, the + argument two is the shift of phy mode selection. + +required: + - compatible + - reg + - clocks + - clock-names + - interrupts + - interrupt-names + - resets + - reset-names + +allOf: + - $ref: snps,dwmac.yaml# + +unevaluatedProperties: false + +examples: + - | + ethernet@16030000 { + compatible = "starfive,jh7110-dwmac", "snps,dwmac-5.20"; + reg = <0x16030000 0x10000>; + clocks = <&clk 3>, <&clk 2>, <&clk 109>, + <&clk 6>, <&clk 111>; + clock-names = "stmmaceth", "pclk", "ptp_ref", + "tx", "gtx"; + resets = <&rst 1>, <&rst 2>; + reset-names = "stmmaceth", "ahb"; + interrupts = <7>, <6>, <5>; + interrupt-names = "macirq", "eth_wake_irq", "eth_lpi"; + phy-mode = "rgmii-id"; + snps,multicast-filter-bins = <64>; + snps,perfect-filter-entries = <8>; + rx-fifo-depth = <2048>; + tx-fifo-depth = <2048>; + snps,fixed-burst; + snps,no-pbl-x8; + snps,tso; + snps,force_thresh_dma_mode; + snps,axi-config = <&stmmac_axi_setup>; + snps,en-tx-lpi-clockgating; + snps,txpbl = <16>; + snps,rxpbl = <16>; + starfive,syscon = <&aon_syscon 0xc 0x12>; + phy-handle = <&phy0>; + + mdio { + #address-cells = <1>; + #size-cells = <0>; + compatible = "snps,dwmac-mdio"; + + phy0: ethernet-phy@0 { + reg = <0>; + }; + }; + + stmmac_axi_setup: stmmac-axi-config { + snps,lpi_en; + snps,wr_osr_lmt = <4>; + snps,rd_osr_lmt = <4>; + snps,blen = <256 128 64 32 0 0 0>; + }; + }; diff --git a/MAINTAINERS b/MAINTAINERS index 1c09473685b1..4301fe86ad35 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -19928,6 +19928,12 @@ M: Emil Renner Berthing S: Maintained F: arch/riscv/boot/dts/starfive/ +STARFIVE DWMAC GLUE LAYER +M: Emil Renner Berthing +M: Samin Guo +S: Maintained +F: Documentation/devicetree/bindings/net/starfive,jh7110-dwmac.yaml + STARFIVE JH7100 CLOCK DRIVERS M: Emil Renner Berthing S: Maintained -- cgit v1.2.3 From 4bd3bb7b452690a939c3f327104a557e1b9876b7 Mon Sep 17 00:00:00 2001 From: Samin Guo Date: Mon, 17 Apr 2023 18:02:50 +0800 Subject: net: stmmac: Add glue layer for StarFive JH7110 SoC This adds StarFive dwmac driver support on the StarFive JH7110 SoC. Tested-by: Tommaso Merciai Co-developed-by: Emil Renner Berthing Signed-off-by: Emil Renner Berthing Signed-off-by: Samin Guo Signed-off-by: Paolo Abeni --- MAINTAINERS | 1 + drivers/net/ethernet/stmicro/stmmac/Kconfig | 12 ++ drivers/net/ethernet/stmicro/stmmac/Makefile | 1 + .../net/ethernet/stmicro/stmmac/dwmac-starfive.c | 123 +++++++++++++++++++++ 4 files changed, 137 insertions(+) create mode 100644 drivers/net/ethernet/stmicro/stmmac/dwmac-starfive.c (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 4301fe86ad35..4fc57dfd5fd0 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -19933,6 +19933,7 @@ M: Emil Renner Berthing M: Samin Guo S: Maintained F: Documentation/devicetree/bindings/net/starfive,jh7110-dwmac.yaml +F: drivers/net/ethernet/stmicro/stmmac/dwmac-starfive.c STARFIVE JH7100 CLOCK DRIVERS M: Emil Renner Berthing diff --git a/drivers/net/ethernet/stmicro/stmmac/Kconfig b/drivers/net/ethernet/stmicro/stmmac/Kconfig index f77511fe4e87..5f5a997f21f3 100644 --- a/drivers/net/ethernet/stmicro/stmmac/Kconfig +++ b/drivers/net/ethernet/stmicro/stmmac/Kconfig @@ -165,6 +165,18 @@ config DWMAC_SOCFPGA for the stmmac device driver. This driver is used for arria5 and cyclone5 FPGA SoCs. +config DWMAC_STARFIVE + tristate "StarFive dwmac support" + depends on OF && (ARCH_STARFIVE || COMPILE_TEST) + select MFD_SYSCON + default m if ARCH_STARFIVE + help + Support for ethernet controllers on StarFive RISC-V SoCs + + This selects the StarFive platform specific glue layer support for + the stmmac device driver. This driver is used for StarFive JH7110 + ethernet controller. + config DWMAC_STI tristate "STi GMAC support" default ARCH_STI diff --git a/drivers/net/ethernet/stmicro/stmmac/Makefile b/drivers/net/ethernet/stmicro/stmmac/Makefile index 057e4bab5c08..8738fdbb4b2d 100644 --- a/drivers/net/ethernet/stmicro/stmmac/Makefile +++ b/drivers/net/ethernet/stmicro/stmmac/Makefile @@ -23,6 +23,7 @@ obj-$(CONFIG_DWMAC_OXNAS) += dwmac-oxnas.o obj-$(CONFIG_DWMAC_QCOM_ETHQOS) += dwmac-qcom-ethqos.o obj-$(CONFIG_DWMAC_ROCKCHIP) += dwmac-rk.o obj-$(CONFIG_DWMAC_SOCFPGA) += dwmac-altr-socfpga.o +obj-$(CONFIG_DWMAC_STARFIVE) += dwmac-starfive.o obj-$(CONFIG_DWMAC_STI) += dwmac-sti.o obj-$(CONFIG_DWMAC_STM32) += dwmac-stm32.o obj-$(CONFIG_DWMAC_SUNXI) += dwmac-sunxi.o diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-starfive.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-starfive.c new file mode 100644 index 000000000000..4963d4008485 --- /dev/null +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-starfive.c @@ -0,0 +1,123 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * StarFive DWMAC platform driver + * + * Copyright (C) 2021 Emil Renner Berthing + * Copyright (C) 2022 StarFive Technology Co., Ltd. + * + */ + +#include +#include +#include + +#include "stmmac_platform.h" + +struct starfive_dwmac { + struct device *dev; + struct clk *clk_tx; +}; + +static void starfive_dwmac_fix_mac_speed(void *priv, unsigned int speed) +{ + struct starfive_dwmac *dwmac = priv; + unsigned long rate; + int err; + + rate = clk_get_rate(dwmac->clk_tx); + + switch (speed) { + case SPEED_1000: + rate = 125000000; + break; + case SPEED_100: + rate = 25000000; + break; + case SPEED_10: + rate = 2500000; + break; + default: + dev_err(dwmac->dev, "invalid speed %u\n", speed); + break; + } + + err = clk_set_rate(dwmac->clk_tx, rate); + if (err) + dev_err(dwmac->dev, "failed to set tx rate %lu\n", rate); +} + +static int starfive_dwmac_probe(struct platform_device *pdev) +{ + struct plat_stmmacenet_data *plat_dat; + struct stmmac_resources stmmac_res; + struct starfive_dwmac *dwmac; + struct clk *clk_gtx; + int err; + + err = stmmac_get_platform_resources(pdev, &stmmac_res); + if (err) + return dev_err_probe(&pdev->dev, err, + "failed to get resources\n"); + + plat_dat = stmmac_probe_config_dt(pdev, stmmac_res.mac); + if (IS_ERR(plat_dat)) + return dev_err_probe(&pdev->dev, PTR_ERR(plat_dat), + "dt configuration failed\n"); + + dwmac = devm_kzalloc(&pdev->dev, sizeof(*dwmac), GFP_KERNEL); + if (!dwmac) + return -ENOMEM; + + dwmac->clk_tx = devm_clk_get_enabled(&pdev->dev, "tx"); + if (IS_ERR(dwmac->clk_tx)) + return dev_err_probe(&pdev->dev, PTR_ERR(dwmac->clk_tx), + "error getting tx clock\n"); + + clk_gtx = devm_clk_get_enabled(&pdev->dev, "gtx"); + if (IS_ERR(clk_gtx)) + return dev_err_probe(&pdev->dev, PTR_ERR(clk_gtx), + "error getting gtx clock\n"); + + /* Generally, the rgmii_tx clock is provided by the internal clock, + * which needs to match the corresponding clock frequency according + * to different speeds. If the rgmii_tx clock is provided by the + * external rgmii_rxin, there is no need to configure the clock + * internally, because rgmii_rxin will be adaptively adjusted. + */ + if (!device_property_read_bool(&pdev->dev, "starfive,tx-use-rgmii-clk")) + plat_dat->fix_mac_speed = starfive_dwmac_fix_mac_speed; + + dwmac->dev = &pdev->dev; + plat_dat->bsp_priv = dwmac; + plat_dat->dma_cfg->dche = true; + + err = stmmac_dvr_probe(&pdev->dev, plat_dat, &stmmac_res); + if (err) { + stmmac_remove_config_dt(pdev, plat_dat); + return err; + } + + return 0; +} + +static const struct of_device_id starfive_dwmac_match[] = { + { .compatible = "starfive,jh7110-dwmac" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, starfive_dwmac_match); + +static struct platform_driver starfive_dwmac_driver = { + .probe = starfive_dwmac_probe, + .remove = stmmac_pltfr_remove, + .driver = { + .name = "starfive-dwmac", + .pm = &stmmac_pltfr_pm_ops, + .of_match_table = starfive_dwmac_match, + }, +}; +module_platform_driver(starfive_dwmac_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("StarFive DWMAC platform driver"); +MODULE_AUTHOR("Emil Renner Berthing "); +MODULE_AUTHOR("Samin Guo "); -- cgit v1.2.3 From ed09c61eb19d9889780c791cb316ac76468f5186 Mon Sep 17 00:00:00 2001 From: Konrad Dybcio Date: Mon, 17 Apr 2023 13:35:00 +0300 Subject: dt-bindings: net: Convert ath10k to YAML Convert the ath10k bindings to YAML. Dropped properties that are absent at the current state of mainline: - qcom,msi_addr - qcom,msi_base Somewhat based on the ath11k bindings. Signed-off-by: Konrad Dybcio Reviewed-by: Krzysztof Kozlowski Signed-off-by: Kalle Valo Link: https://lore.kernel.org/r/20230406-topic-ath10k_bindings-v4-1-9f67a6bb0d56@linaro.org --- .../bindings/net/wireless/qcom,ath10k.txt | 215 ------------- .../bindings/net/wireless/qcom,ath10k.yaml | 358 +++++++++++++++++++++ MAINTAINERS | 2 +- 3 files changed, 359 insertions(+), 216 deletions(-) delete mode 100644 Documentation/devicetree/bindings/net/wireless/qcom,ath10k.txt create mode 100644 Documentation/devicetree/bindings/net/wireless/qcom,ath10k.yaml (limited to 'MAINTAINERS') diff --git a/Documentation/devicetree/bindings/net/wireless/qcom,ath10k.txt b/Documentation/devicetree/bindings/net/wireless/qcom,ath10k.txt deleted file mode 100644 index b61c2d5a0ff7..000000000000 --- a/Documentation/devicetree/bindings/net/wireless/qcom,ath10k.txt +++ /dev/null @@ -1,215 +0,0 @@ -* Qualcomm Atheros ath10k wireless devices - -Required properties: -- compatible: Should be one of the following: - * "qcom,ath10k" - * "qcom,ipq4019-wifi" - * "qcom,wcn3990-wifi" - -PCI based devices uses compatible string "qcom,ath10k" and takes calibration -data along with board specific data via "qcom,ath10k-calibration-data". -Rest of the properties are not applicable for PCI based devices. - -AHB based devices (i.e. ipq4019) uses compatible string "qcom,ipq4019-wifi" -and also uses most of the properties defined in this doc (except -"qcom,ath10k-calibration-data"). It uses "qcom,ath10k-pre-calibration-data" -to carry pre calibration data. - -In general, entry "qcom,ath10k-pre-calibration-data" and -"qcom,ath10k-calibration-data" conflict with each other and only one -can be provided per device. - -SNOC based devices (i.e. wcn3990) uses compatible string "qcom,wcn3990-wifi". - -- reg: Address and length of the register set for the device. -- reg-names: Must include the list of following reg names, - "membase" -- interrupts: reference to the list of 17 interrupt numbers for "qcom,ipq4019-wifi" - compatible target. - reference to the list of 12 interrupt numbers for "qcom,wcn3990-wifi" - compatible target. - Must contain interrupt-names property per entry for - "qcom,ath10k", "qcom,ipq4019-wifi" compatible targets. - -- interrupt-names: Must include the entries for MSI interrupt - names ("msi0" to "msi15") and legacy interrupt - name ("legacy") for "qcom,ath10k", "qcom,ipq4019-wifi" - compatible targets. - -Optional properties: -- resets: Must contain an entry for each entry in reset-names. - See ../reset/reseti.txt for details. -- reset-names: Must include the list of following reset names, - "wifi_cpu_init" - "wifi_radio_srif" - "wifi_radio_warm" - "wifi_radio_cold" - "wifi_core_warm" - "wifi_core_cold" -- clocks: List of clock specifiers, must contain an entry for each required - entry in clock-names. -- clock-names: Should contain the clock names "wifi_wcss_cmd", "wifi_wcss_ref", - "wifi_wcss_rtc" for "qcom,ipq4019-wifi" compatible target and - "cxo_ref_clk_pin" and optionally "qdss" for "qcom,wcn3990-wifi" - compatible target. -- qcom,msi_addr: MSI interrupt address. -- qcom,msi_base: Base value to add before writing MSI data into - MSI address register. -- qcom,ath10k-calibration-variant: string to search for in the board-2.bin - variant list with the same bus and device - specific ids -- qcom,ath10k-calibration-data : calibration data + board specific data - as an array, the length can vary between - hw versions. -- qcom,ath10k-pre-calibration-data : pre calibration data as an array, - the length can vary between hw versions. -- -supply: handle to the regulator device tree node - optional "supply-name" are "vdd-0.8-cx-mx", - "vdd-1.8-xo", "vdd-1.3-rfa", "vdd-3.3-ch0", - and "vdd-3.3-ch1". -- memory-region: - Usage: optional - Value type: - Definition: reference to the reserved-memory for the msa region - used by the wifi firmware running in Q6. -- iommus: - Usage: optional - Value type: - Definition: A list of phandle and IOMMU specifier pairs. -- ext-fem-name: - Usage: Optional - Value type: string - Definition: Name of external front end module used. Some valid FEM names - for example: "microsemi-lx5586", "sky85703-11" - and "sky85803" etc. -- qcom,snoc-host-cap-8bit-quirk: - Usage: Optional - Value type: - Definition: Quirk specifying that the firmware expects the 8bit version - of the host capability QMI request -- qcom,xo-cal-data: xo cal offset to be configured in xo trim register. - -- qcom,msa-fixed-perm: Boolean context flag to disable SCM call for statically - mapped msa region. - -- qcom,coexist-support : should contain eithr "0" or "1" to indicate coex - support by the hardware. -- qcom,coexist-gpio-pin : gpio pin number information to support coex - which will be used by wifi firmware. - -* Subnodes -The ath10k wifi node can contain one optional firmware subnode. -Firmware subnode is needed when the platform does not have TustZone. -The firmware subnode must have: - -- iommus: - Usage: required - Value type: - Definition: A list of phandle and IOMMU specifier pairs. - - -Example (to supply PCI based wifi block details): - -In this example, the node is defined as child node of the PCI controller. - -pci { - pcie@0 { - reg = <0 0 0 0 0>; - #interrupt-cells = <1>; - #size-cells = <2>; - #address-cells = <3>; - device_type = "pci"; - - wifi@0,0 { - reg = <0 0 0 0 0>; - qcom,ath10k-calibration-data = [ 01 02 03 ... ]; - ext-fem-name = "microsemi-lx5586"; - }; - }; -}; - -Example (to supply ipq4019 SoC wifi block details): - -wifi0: wifi@a000000 { - compatible = "qcom,ipq4019-wifi"; - reg = <0xa000000 0x200000>; - resets = <&gcc WIFI0_CPU_INIT_RESET>, - <&gcc WIFI0_RADIO_SRIF_RESET>, - <&gcc WIFI0_RADIO_WARM_RESET>, - <&gcc WIFI0_RADIO_COLD_RESET>, - <&gcc WIFI0_CORE_WARM_RESET>, - <&gcc WIFI0_CORE_COLD_RESET>; - reset-names = "wifi_cpu_init", - "wifi_radio_srif", - "wifi_radio_warm", - "wifi_radio_cold", - "wifi_core_warm", - "wifi_core_cold"; - clocks = <&gcc GCC_WCSS2G_CLK>, - <&gcc GCC_WCSS2G_REF_CLK>, - <&gcc GCC_WCSS2G_RTC_CLK>; - clock-names = "wifi_wcss_cmd", - "wifi_wcss_ref", - "wifi_wcss_rtc"; - interrupts = <0 0x20 0x1>, - <0 0x21 0x1>, - <0 0x22 0x1>, - <0 0x23 0x1>, - <0 0x24 0x1>, - <0 0x25 0x1>, - <0 0x26 0x1>, - <0 0x27 0x1>, - <0 0x28 0x1>, - <0 0x29 0x1>, - <0 0x2a 0x1>, - <0 0x2b 0x1>, - <0 0x2c 0x1>, - <0 0x2d 0x1>, - <0 0x2e 0x1>, - <0 0x2f 0x1>, - <0 0xa8 0x0>; - interrupt-names = "msi0", "msi1", "msi2", "msi3", - "msi4", "msi5", "msi6", "msi7", - "msi8", "msi9", "msi10", "msi11", - "msi12", "msi13", "msi14", "msi15", - "legacy"; - qcom,msi_addr = <0x0b006040>; - qcom,msi_base = <0x40>; - qcom,ath10k-pre-calibration-data = [ 01 02 03 ... ]; - qcom,coexist-support = <1>; - qcom,coexist-gpio-pin = <0x33>; -}; - -Example (to supply wcn3990 SoC wifi block details): - -wifi@18000000 { - compatible = "qcom,wcn3990-wifi"; - reg = <0x18800000 0x800000>; - reg-names = "membase"; - clocks = <&clock_gcc clk_rf_clk2_pin>; - clock-names = "cxo_ref_clk_pin"; - interrupts = - , - , - , - , - , - , - , - , - , - , - , - ; - vdd-0.8-cx-mx-supply = <&pm8998_l5>; - vdd-1.8-xo-supply = <&vreg_l7a_1p8>; - vdd-1.3-rfa-supply = <&vreg_l17a_1p3>; - vdd-3.3-ch0-supply = <&vreg_l25a_3p3>; - vdd-3.3-ch1-supply = <&vreg_l26a_3p3>; - memory-region = <&wifi_msa_mem>; - iommus = <&apps_smmu 0x0040 0x1>; - qcom,msa-fixed-perm; - wifi-firmware { - iommus = <&apps_iommu 0xc22 0x1>; - }; -}; diff --git a/Documentation/devicetree/bindings/net/wireless/qcom,ath10k.yaml b/Documentation/devicetree/bindings/net/wireless/qcom,ath10k.yaml new file mode 100644 index 000000000000..c85ed330426d --- /dev/null +++ b/Documentation/devicetree/bindings/net/wireless/qcom,ath10k.yaml @@ -0,0 +1,358 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/net/wireless/qcom,ath10k.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Qualcomm Technologies ath10k wireless devices + +maintainers: + - Kalle Valo + +description: + Qualcomm Technologies, Inc. IEEE 802.11ac devices. + +properties: + compatible: + enum: + - qcom,ath10k # SDIO-based devices + - qcom,ipq4019-wifi + - qcom,wcn3990-wifi # SNoC-based devices + + reg: + maxItems: 1 + + reg-names: + items: + - const: membase + + interrupts: + minItems: 12 + maxItems: 17 + + interrupt-names: + minItems: 12 + maxItems: 17 + + memory-region: + maxItems: 1 + description: + Reference to the MSA memory region used by the Wi-Fi firmware + running on the Q6 core. + + iommus: + minItems: 1 + maxItems: 2 + + clocks: + minItems: 1 + maxItems: 3 + + clock-names: + minItems: 1 + maxItems: 3 + + resets: + maxItems: 6 + + reset-names: + items: + - const: wifi_cpu_init + - const: wifi_radio_srif + - const: wifi_radio_warm + - const: wifi_radio_cold + - const: wifi_core_warm + - const: wifi_core_cold + + ext-fem-name: + $ref: /schemas/types.yaml#/definitions/string + description: Name of external front end module used. + enum: + - microsemi-lx5586 + - sky85703-11 + - sky85803 + + wifi-firmware: + type: object + additionalProperties: false + description: | + The ath10k Wi-Fi node can contain one optional firmware subnode. + Firmware subnode is needed when the platform does not have Trustzone. + properties: + iommus: + maxItems: 1 + required: + - iommus + + qcom,ath10k-calibration-data: + $ref: /schemas/types.yaml#/definitions/uint8-array + description: + Calibration data + board-specific data as a byte array. The length + can vary between hardware versions. + + qcom,ath10k-calibration-variant: + $ref: /schemas/types.yaml#/definitions/string + description: + Unique variant identifier of the calibration data in board-2.bin + for designs with colliding bus and device specific ids + + qcom,ath10k-pre-calibration-data: + $ref: /schemas/types.yaml#/definitions/uint8-array + description: + Pre-calibration data as a byte array. The length can vary between + hardware versions. + + qcom,coexist-support: + $ref: /schemas/types.yaml#/definitions/uint8 + enum: [0, 1] + description: + Indicate coex support by the hardware. + + qcom,coexist-gpio-pin: + $ref: /schemas/types.yaml#/definitions/uint32 + description: + COEX GPIO number provided to the Wi-Fi firmware. + + qcom,msa-fixed-perm: + type: boolean + description: + Whether to skip executing an SCM call that reassigns the memory + region ownership. + + qcom,smem-states: + $ref: /schemas/types.yaml#/definitions/phandle-array + description: State bits used by the AP to signal the WLAN Q6. + items: + - description: Signal bits used to enable/disable low power mode + on WCN in the case of WoW (Wake on Wireless). + + qcom,smem-state-names: + description: The names of the state bits used for SMP2P output. + items: + - const: wlan-smp2p-out + + qcom,snoc-host-cap-8bit-quirk: + type: boolean + description: + Quirk specifying that the firmware expects the 8bit version + of the host capability QMI request + + qcom,xo-cal-data: + $ref: /schemas/types.yaml#/definitions/uint32 + description: + XO cal offset to be configured in XO trim register. + + vdd-0.8-cx-mx-supply: + description: Main logic power rail + + vdd-1.8-xo-supply: + description: Crystal oscillator supply + + vdd-1.3-rfa-supply: + description: RFA supply + + vdd-3.3-ch0-supply: + description: Primary Wi-Fi antenna supply + + vdd-3.3-ch1-supply: + description: Secondary Wi-Fi antenna supply + +required: + - compatible + - reg + +additionalProperties: false + +allOf: + - if: + properties: + compatible: + contains: + enum: + - qcom,ipq4019-wifi + then: + properties: + interrupts: + minItems: 17 + maxItems: 17 + + interrupt-names: + items: + - const: msi0 + - const: msi1 + - const: msi2 + - const: msi3 + - const: msi4 + - const: msi5 + - const: msi6 + - const: msi7 + - const: msi8 + - const: msi9 + - const: msi10 + - const: msi11 + - const: msi12 + - const: msi13 + - const: msi14 + - const: msi15 + - const: legacy + + clocks: + items: + - description: Wi-Fi command clock + - description: Wi-Fi reference clock + - description: Wi-Fi RTC clock + + clock-names: + items: + - const: wifi_wcss_cmd + - const: wifi_wcss_ref + - const: wifi_wcss_rtc + + required: + - clocks + - clock-names + - interrupts + - interrupt-names + - resets + - reset-names + + - if: + properties: + compatible: + contains: + enum: + - qcom,wcn3990-wifi + + then: + properties: + clocks: + minItems: 1 + items: + - description: XO reference clock + - description: Qualcomm Debug Subsystem clock + + clock-names: + minItems: 1 + items: + - const: cxo_ref_clk_pin + - const: qdss + + interrupts: + items: + - description: CE0 + - description: CE1 + - description: CE2 + - description: CE3 + - description: CE4 + - description: CE5 + - description: CE6 + - description: CE7 + - description: CE8 + - description: CE9 + - description: CE10 + - description: CE11 + + interrupt-names: false + + required: + - interrupts + +examples: + # SNoC + - | + #include + #include + + wifi@18800000 { + compatible = "qcom,wcn3990-wifi"; + reg = <0x18800000 0x800000>; + reg-names = "membase"; + memory-region = <&wlan_msa_mem>; + clocks = <&rpmcc RPM_SMD_RF_CLK2_PIN>; + clock-names = "cxo_ref_clk_pin"; + interrupts = , + , + , + , + , + , + , + , + , + , + , + ; + iommus = <&anoc2_smmu 0x1900>, + <&anoc2_smmu 0x1901>; + qcom,snoc-host-cap-8bit-quirk; + vdd-0.8-cx-mx-supply = <&vreg_l5a_0p8>; + vdd-1.8-xo-supply = <&vreg_l7a_1p8>; + vdd-1.3-rfa-supply = <&vreg_l17a_1p3>; + vdd-3.3-ch0-supply = <&vreg_l25a_3p3>; + vdd-3.3-ch1-supply = <&vreg_l23a_3p3>; + + wifi-firmware { + iommus = <&apps_smmu 0x1c02 0x1>; + }; + }; + + # AHB + - | + #include + + wifi@a000000 { + compatible = "qcom,ipq4019-wifi"; + reg = <0xa000000 0x200000>; + resets = <&gcc WIFI0_CPU_INIT_RESET>, + <&gcc WIFI0_RADIO_SRIF_RESET>, + <&gcc WIFI0_RADIO_WARM_RESET>, + <&gcc WIFI0_RADIO_COLD_RESET>, + <&gcc WIFI0_CORE_WARM_RESET>, + <&gcc WIFI0_CORE_COLD_RESET>; + reset-names = "wifi_cpu_init", + "wifi_radio_srif", + "wifi_radio_warm", + "wifi_radio_cold", + "wifi_core_warm", + "wifi_core_cold"; + clocks = <&gcc GCC_WCSS2G_CLK>, + <&gcc GCC_WCSS2G_REF_CLK>, + <&gcc GCC_WCSS2G_RTC_CLK>; + clock-names = "wifi_wcss_cmd", + "wifi_wcss_ref", + "wifi_wcss_rtc"; + interrupts = , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + ; + interrupt-names = "msi0", + "msi1", + "msi2", + "msi3", + "msi4", + "msi5", + "msi6", + "msi7", + "msi8", + "msi9", + "msi10", + "msi11", + "msi12", + "msi13", + "msi14", + "msi15", + "legacy"; + }; diff --git a/MAINTAINERS b/MAINTAINERS index 8d5bc223f305..c659e12e0c4f 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -17196,7 +17196,7 @@ S: Supported W: https://wireless.wiki.kernel.org/en/users/Drivers/ath10k T: git git://git.kernel.org/pub/scm/linux/kernel/git/kvalo/ath.git F: drivers/net/wireless/ath/ath10k/ -F: Documentation/devicetree/bindings/net/wireless/qcom,ath10k.txt +F: Documentation/devicetree/bindings/net/wireless/qcom,ath10k.yaml QUALCOMM ATHEROS ATH11K WIRELESS DRIVER M: Kalle Valo -- cgit v1.2.3 From 3b3009ea8abb713b022d94fba95ec270cf6e7eae Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Mon, 17 Apr 2023 10:32:26 -0400 Subject: net/handshake: Create a NETLINK service for handling handshake requests When a kernel consumer needs a transport layer security session, it first needs a handshake to negotiate and establish a session. This negotiation can be done in user space via one of the several existing library implementations, or it can be done in the kernel. No in-kernel handshake implementations yet exist. In their absence, we add a netlink service that can: a. Notify a user space daemon that a handshake is needed. b. Once notified, the daemon calls the kernel back via this netlink service to get the handshake parameters, including an open socket on which to establish the session. c. Once the handshake is complete, the daemon reports the session status and other information via a second netlink operation. This operation marks that it is safe for the kernel to use the open socket and the security session established there. The notification service uses a multicast group. Each handshake mechanism (eg, tlshd) adopts its own group number so that the handshake services are completely independent of one another. The kernel can then tell via netlink_has_listeners() whether a handshake service is active and prepared to handle a handshake request. A new netlink operation, ACCEPT, acts like accept(2) in that it instantiates a file descriptor in the user space daemon's fd table. If this operation is successful, the reply carries the fd number, which can be treated as an open and ready file descriptor. While user space is performing the handshake, the kernel keeps its muddy paws off the open socket. A second new netlink operation, DONE, indicates that the user space daemon is finished with the socket and it is safe for the kernel to use again. The operation also indicates whether a session was established successfully. Signed-off-by: Chuck Lever Signed-off-by: Jakub Kicinski --- Documentation/netlink/specs/handshake.yaml | 122 +++++++++++ MAINTAINERS | 9 + include/trace/events/handshake.h | 159 ++++++++++++++ include/uapi/linux/handshake.h | 71 ++++++ net/Kconfig | 5 + net/Makefile | 1 + net/handshake/Makefile | 11 + net/handshake/genl.c | 57 +++++ net/handshake/genl.h | 23 ++ net/handshake/handshake.h | 82 +++++++ net/handshake/netlink.c | 312 ++++++++++++++++++++++++++ net/handshake/request.c | 339 +++++++++++++++++++++++++++++ net/handshake/trace.c | 20 ++ 13 files changed, 1211 insertions(+) create mode 100644 Documentation/netlink/specs/handshake.yaml create mode 100644 include/trace/events/handshake.h create mode 100644 include/uapi/linux/handshake.h create mode 100644 net/handshake/Makefile create mode 100644 net/handshake/genl.c create mode 100644 net/handshake/genl.h create mode 100644 net/handshake/handshake.h create mode 100644 net/handshake/netlink.c create mode 100644 net/handshake/request.c create mode 100644 net/handshake/trace.c (limited to 'MAINTAINERS') diff --git a/Documentation/netlink/specs/handshake.yaml b/Documentation/netlink/specs/handshake.yaml new file mode 100644 index 000000000000..0333d92b1438 --- /dev/null +++ b/Documentation/netlink/specs/handshake.yaml @@ -0,0 +1,122 @@ +# SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) +# +# Author: Chuck Lever +# +# Copyright (c) 2023, Oracle and/or its affiliates. +# + +name: handshake + +protocol: genetlink + +doc: Netlink protocol to request a transport layer security handshake. + +definitions: + - + type: enum + name: handler-class + value-start: 0 + entries: [ none, max ] + - + type: enum + name: msg-type + value-start: 0 + entries: [ unspec, clienthello, serverhello ] + - + type: enum + name: auth + value-start: 0 + entries: [ unspec, unauth, psk, x509 ] + +attribute-sets: + - + name: x509 + attributes: + - + name: cert + type: u32 + - + name: privkey + type: u32 + - + name: accept + attributes: + - + name: sockfd + type: u32 + - + name: handler-class + type: u32 + enum: handler-class + - + name: message-type + type: u32 + enum: msg-type + - + name: timeout + type: u32 + - + name: auth-mode + type: u32 + enum: auth + - + name: peer-identity + type: u32 + multi-attr: true + - + name: certificate + type: nest + nested-attributes: x509 + multi-attr: true + - + name: done + attributes: + - + name: status + type: u32 + - + name: sockfd + type: u32 + - + name: remote-auth + type: u32 + multi-attr: true + +operations: + list: + - + name: ready + doc: Notify handlers that a new handshake request is waiting + notify: accept + - + name: accept + doc: Handler retrieves next queued handshake request + attribute-set: accept + flags: [ admin-perm ] + do: + request: + attributes: + - handler-class + reply: + attributes: + - sockfd + - message-type + - timeout + - auth-mode + - peer-identity + - certificate + - + name: done + doc: Handler reports handshake completion + attribute-set: done + do: + request: + attributes: + - status + - sockfd + - remote-auth + +mcast-groups: + list: + - + name: none diff --git a/MAINTAINERS b/MAINTAINERS index 4fc57dfd5fd0..cdc7748d15b8 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -8947,6 +8947,15 @@ Q: http://patchwork.linuxtv.org/project/linux-media/list/ T: git git://linuxtv.org/anttip/media_tree.git F: drivers/media/usb/hackrf/ +HANDSHAKE UPCALL FOR TRANSPORT LAYER SECURITY +M: Chuck Lever +L: kernel-tls-handshake@lists.linux.dev +L: netdev@vger.kernel.org +S: Maintained +F: Documentation/netlink/specs/handshake.yaml +F: include/trace/events/handshake.h +F: net/handshake/ + HANTRO VPU CODEC DRIVER M: Ezequiel Garcia M: Philipp Zabel diff --git a/include/trace/events/handshake.h b/include/trace/events/handshake.h new file mode 100644 index 000000000000..8dadcab5f12a --- /dev/null +++ b/include/trace/events/handshake.h @@ -0,0 +1,159 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#undef TRACE_SYSTEM +#define TRACE_SYSTEM handshake + +#if !defined(_TRACE_HANDSHAKE_H) || defined(TRACE_HEADER_MULTI_READ) +#define _TRACE_HANDSHAKE_H + +#include +#include + +DECLARE_EVENT_CLASS(handshake_event_class, + TP_PROTO( + const struct net *net, + const struct handshake_req *req, + const struct sock *sk + ), + TP_ARGS(net, req, sk), + TP_STRUCT__entry( + __field(const void *, req) + __field(const void *, sk) + __field(unsigned int, netns_ino) + ), + TP_fast_assign( + __entry->req = req; + __entry->sk = sk; + __entry->netns_ino = net->ns.inum; + ), + TP_printk("req=%p sk=%p", + __entry->req, __entry->sk + ) +); +#define DEFINE_HANDSHAKE_EVENT(name) \ + DEFINE_EVENT(handshake_event_class, name, \ + TP_PROTO( \ + const struct net *net, \ + const struct handshake_req *req, \ + const struct sock *sk \ + ), \ + TP_ARGS(net, req, sk)) + +DECLARE_EVENT_CLASS(handshake_fd_class, + TP_PROTO( + const struct net *net, + const struct handshake_req *req, + const struct sock *sk, + int fd + ), + TP_ARGS(net, req, sk, fd), + TP_STRUCT__entry( + __field(const void *, req) + __field(const void *, sk) + __field(int, fd) + __field(unsigned int, netns_ino) + ), + TP_fast_assign( + __entry->req = req; + __entry->sk = req->hr_sk; + __entry->fd = fd; + __entry->netns_ino = net->ns.inum; + ), + TP_printk("req=%p sk=%p fd=%d", + __entry->req, __entry->sk, __entry->fd + ) +); +#define DEFINE_HANDSHAKE_FD_EVENT(name) \ + DEFINE_EVENT(handshake_fd_class, name, \ + TP_PROTO( \ + const struct net *net, \ + const struct handshake_req *req, \ + const struct sock *sk, \ + int fd \ + ), \ + TP_ARGS(net, req, sk, fd)) + +DECLARE_EVENT_CLASS(handshake_error_class, + TP_PROTO( + const struct net *net, + const struct handshake_req *req, + const struct sock *sk, + int err + ), + TP_ARGS(net, req, sk, err), + TP_STRUCT__entry( + __field(const void *, req) + __field(const void *, sk) + __field(int, err) + __field(unsigned int, netns_ino) + ), + TP_fast_assign( + __entry->req = req; + __entry->sk = sk; + __entry->err = err; + __entry->netns_ino = net->ns.inum; + ), + TP_printk("req=%p sk=%p err=%d", + __entry->req, __entry->sk, __entry->err + ) +); +#define DEFINE_HANDSHAKE_ERROR(name) \ + DEFINE_EVENT(handshake_error_class, name, \ + TP_PROTO( \ + const struct net *net, \ + const struct handshake_req *req, \ + const struct sock *sk, \ + int err \ + ), \ + TP_ARGS(net, req, sk, err)) + + +/* + * Request lifetime events + */ + +DEFINE_HANDSHAKE_EVENT(handshake_submit); +DEFINE_HANDSHAKE_ERROR(handshake_submit_err); +DEFINE_HANDSHAKE_EVENT(handshake_cancel); +DEFINE_HANDSHAKE_EVENT(handshake_cancel_none); +DEFINE_HANDSHAKE_EVENT(handshake_cancel_busy); +DEFINE_HANDSHAKE_EVENT(handshake_destruct); + + +TRACE_EVENT(handshake_complete, + TP_PROTO( + const struct net *net, + const struct handshake_req *req, + const struct sock *sk, + int status + ), + TP_ARGS(net, req, sk, status), + TP_STRUCT__entry( + __field(const void *, req) + __field(const void *, sk) + __field(int, status) + __field(unsigned int, netns_ino) + ), + TP_fast_assign( + __entry->req = req; + __entry->sk = sk; + __entry->status = status; + __entry->netns_ino = net->ns.inum; + ), + TP_printk("req=%p sk=%p status=%d", + __entry->req, __entry->sk, __entry->status + ) +); + +/* + * Netlink events + */ + +DEFINE_HANDSHAKE_ERROR(handshake_notify_err); +DEFINE_HANDSHAKE_FD_EVENT(handshake_cmd_accept); +DEFINE_HANDSHAKE_ERROR(handshake_cmd_accept_err); +DEFINE_HANDSHAKE_FD_EVENT(handshake_cmd_done); +DEFINE_HANDSHAKE_ERROR(handshake_cmd_done_err); + +#endif /* _TRACE_HANDSHAKE_H */ + +#include diff --git a/include/uapi/linux/handshake.h b/include/uapi/linux/handshake.h new file mode 100644 index 000000000000..7f66ff489b87 --- /dev/null +++ b/include/uapi/linux/handshake.h @@ -0,0 +1,71 @@ +/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) */ +/* Do not edit directly, auto-generated from: */ +/* Documentation/netlink/specs/handshake.yaml */ +/* YNL-GEN uapi header */ + +#ifndef _UAPI_LINUX_HANDSHAKE_H +#define _UAPI_LINUX_HANDSHAKE_H + +#define HANDSHAKE_FAMILY_NAME "handshake" +#define HANDSHAKE_FAMILY_VERSION 1 + +enum handshake_handler_class { + HANDSHAKE_HANDLER_CLASS_NONE, + HANDSHAKE_HANDLER_CLASS_MAX, +}; + +enum handshake_msg_type { + HANDSHAKE_MSG_TYPE_UNSPEC, + HANDSHAKE_MSG_TYPE_CLIENTHELLO, + HANDSHAKE_MSG_TYPE_SERVERHELLO, +}; + +enum handshake_auth { + HANDSHAKE_AUTH_UNSPEC, + HANDSHAKE_AUTH_UNAUTH, + HANDSHAKE_AUTH_PSK, + HANDSHAKE_AUTH_X509, +}; + +enum { + HANDSHAKE_A_X509_CERT = 1, + HANDSHAKE_A_X509_PRIVKEY, + + __HANDSHAKE_A_X509_MAX, + HANDSHAKE_A_X509_MAX = (__HANDSHAKE_A_X509_MAX - 1) +}; + +enum { + HANDSHAKE_A_ACCEPT_SOCKFD = 1, + HANDSHAKE_A_ACCEPT_HANDLER_CLASS, + HANDSHAKE_A_ACCEPT_MESSAGE_TYPE, + HANDSHAKE_A_ACCEPT_TIMEOUT, + HANDSHAKE_A_ACCEPT_AUTH_MODE, + HANDSHAKE_A_ACCEPT_PEER_IDENTITY, + HANDSHAKE_A_ACCEPT_CERTIFICATE, + + __HANDSHAKE_A_ACCEPT_MAX, + HANDSHAKE_A_ACCEPT_MAX = (__HANDSHAKE_A_ACCEPT_MAX - 1) +}; + +enum { + HANDSHAKE_A_DONE_STATUS = 1, + HANDSHAKE_A_DONE_SOCKFD, + HANDSHAKE_A_DONE_REMOTE_AUTH, + + __HANDSHAKE_A_DONE_MAX, + HANDSHAKE_A_DONE_MAX = (__HANDSHAKE_A_DONE_MAX - 1) +}; + +enum { + HANDSHAKE_CMD_READY = 1, + HANDSHAKE_CMD_ACCEPT, + HANDSHAKE_CMD_DONE, + + __HANDSHAKE_CMD_MAX, + HANDSHAKE_CMD_MAX = (__HANDSHAKE_CMD_MAX - 1) +}; + +#define HANDSHAKE_MCGRP_NONE "none" + +#endif /* _UAPI_LINUX_HANDSHAKE_H */ diff --git a/net/Kconfig b/net/Kconfig index f806722bccf4..4b800706cc76 100644 --- a/net/Kconfig +++ b/net/Kconfig @@ -68,6 +68,11 @@ source "net/iucv/Kconfig" source "net/smc/Kconfig" source "net/xdp/Kconfig" +config NET_HANDSHAKE + bool + depends on SUNRPC || NVME_TARGET_TCP || NVME_TCP + default y + config INET bool "TCP/IP networking" help diff --git a/net/Makefile b/net/Makefile index 87592009366f..4c4dc535453d 100644 --- a/net/Makefile +++ b/net/Makefile @@ -79,3 +79,4 @@ obj-$(CONFIG_NET_NCSI) += ncsi/ obj-$(CONFIG_XDP_SOCKETS) += xdp/ obj-$(CONFIG_MPTCP) += mptcp/ obj-$(CONFIG_MCTP) += mctp/ +obj-$(CONFIG_NET_HANDSHAKE) += handshake/ diff --git a/net/handshake/Makefile b/net/handshake/Makefile new file mode 100644 index 000000000000..d38736de45da --- /dev/null +++ b/net/handshake/Makefile @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Makefile for the Generic HANDSHAKE service +# +# Author: Chuck Lever +# +# Copyright (c) 2023, Oracle and/or its affiliates. +# + +obj-y += handshake.o +handshake-y := genl.o netlink.o request.o trace.o diff --git a/net/handshake/genl.c b/net/handshake/genl.c new file mode 100644 index 000000000000..652f37d19bd6 --- /dev/null +++ b/net/handshake/genl.c @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) +/* Do not edit directly, auto-generated from: */ +/* Documentation/netlink/specs/handshake.yaml */ +/* YNL-GEN kernel source */ + +#include +#include + +#include "genl.h" + +#include + +/* HANDSHAKE_CMD_ACCEPT - do */ +static const struct nla_policy handshake_accept_nl_policy[HANDSHAKE_A_ACCEPT_HANDLER_CLASS + 1] = { + [HANDSHAKE_A_ACCEPT_HANDLER_CLASS] = NLA_POLICY_MAX(NLA_U32, 1), +}; + +/* HANDSHAKE_CMD_DONE - do */ +static const struct nla_policy handshake_done_nl_policy[HANDSHAKE_A_DONE_REMOTE_AUTH + 1] = { + [HANDSHAKE_A_DONE_STATUS] = { .type = NLA_U32, }, + [HANDSHAKE_A_DONE_SOCKFD] = { .type = NLA_U32, }, + [HANDSHAKE_A_DONE_REMOTE_AUTH] = { .type = NLA_U32, }, +}; + +/* Ops table for handshake */ +static const struct genl_split_ops handshake_nl_ops[] = { + { + .cmd = HANDSHAKE_CMD_ACCEPT, + .doit = handshake_nl_accept_doit, + .policy = handshake_accept_nl_policy, + .maxattr = HANDSHAKE_A_ACCEPT_HANDLER_CLASS, + .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO, + }, + { + .cmd = HANDSHAKE_CMD_DONE, + .doit = handshake_nl_done_doit, + .policy = handshake_done_nl_policy, + .maxattr = HANDSHAKE_A_DONE_REMOTE_AUTH, + .flags = GENL_CMD_CAP_DO, + }, +}; + +static const struct genl_multicast_group handshake_nl_mcgrps[] = { + [HANDSHAKE_NLGRP_NONE] = { "none", }, +}; + +struct genl_family handshake_nl_family __ro_after_init = { + .name = HANDSHAKE_FAMILY_NAME, + .version = HANDSHAKE_FAMILY_VERSION, + .netnsok = true, + .parallel_ops = true, + .module = THIS_MODULE, + .split_ops = handshake_nl_ops, + .n_split_ops = ARRAY_SIZE(handshake_nl_ops), + .mcgrps = handshake_nl_mcgrps, + .n_mcgrps = ARRAY_SIZE(handshake_nl_mcgrps), +}; diff --git a/net/handshake/genl.h b/net/handshake/genl.h new file mode 100644 index 000000000000..a1eb7ccccc7f --- /dev/null +++ b/net/handshake/genl.h @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) */ +/* Do not edit directly, auto-generated from: */ +/* Documentation/netlink/specs/handshake.yaml */ +/* YNL-GEN kernel header */ + +#ifndef _LINUX_HANDSHAKE_GEN_H +#define _LINUX_HANDSHAKE_GEN_H + +#include +#include + +#include + +int handshake_nl_accept_doit(struct sk_buff *skb, struct genl_info *info); +int handshake_nl_done_doit(struct sk_buff *skb, struct genl_info *info); + +enum { + HANDSHAKE_NLGRP_NONE, +}; + +extern struct genl_family handshake_nl_family; + +#endif /* _LINUX_HANDSHAKE_GEN_H */ diff --git a/net/handshake/handshake.h b/net/handshake/handshake.h new file mode 100644 index 000000000000..52568dbe24f1 --- /dev/null +++ b/net/handshake/handshake.h @@ -0,0 +1,82 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Generic netlink handshake service + * + * Author: Chuck Lever + * + * Copyright (c) 2023, Oracle and/or its affiliates. + */ + +#ifndef _INTERNAL_HANDSHAKE_H +#define _INTERNAL_HANDSHAKE_H + +/* Per-net namespace context */ +struct handshake_net { + spinlock_t hn_lock; /* protects next 3 fields */ + int hn_pending; + int hn_pending_max; + struct list_head hn_requests; + + unsigned long hn_flags; +}; + +enum hn_flags_bits { + HANDSHAKE_F_NET_DRAINING, +}; + +struct handshake_proto; + +/* One handshake request */ +struct handshake_req { + struct list_head hr_list; + struct rhash_head hr_rhash; + unsigned long hr_flags; + const struct handshake_proto *hr_proto; + struct sock *hr_sk; + void (*hr_odestruct)(struct sock *sk); + + /* Always the last field */ + char hr_priv[]; +}; + +enum hr_flags_bits { + HANDSHAKE_F_REQ_COMPLETED, +}; + +/* Invariants for all handshake requests for one transport layer + * security protocol + */ +struct handshake_proto { + int hp_handler_class; + size_t hp_privsize; + + int (*hp_accept)(struct handshake_req *req, + struct genl_info *info, int fd); + void (*hp_done)(struct handshake_req *req, + unsigned int status, + struct genl_info *info); + void (*hp_destroy)(struct handshake_req *req); +}; + +/* netlink.c */ +int handshake_genl_notify(struct net *net, const struct handshake_proto *proto, + gfp_t flags); +struct nlmsghdr *handshake_genl_put(struct sk_buff *msg, + struct genl_info *info); +struct handshake_net *handshake_pernet(struct net *net); + +/* request.c */ +struct handshake_req *handshake_req_alloc(const struct handshake_proto *proto, + gfp_t flags); +int handshake_req_hash_init(void); +void handshake_req_hash_destroy(void); +void *handshake_req_private(struct handshake_req *req); +struct handshake_req *handshake_req_hash_lookup(struct sock *sk); +struct handshake_req *handshake_req_next(struct handshake_net *hn, int class); +int handshake_req_submit(struct socket *sock, struct handshake_req *req, + gfp_t flags); +void handshake_complete(struct handshake_req *req, unsigned int status, + struct genl_info *info); +bool handshake_req_cancel(struct sock *sk); + +#endif /* _INTERNAL_HANDSHAKE_H */ diff --git a/net/handshake/netlink.c b/net/handshake/netlink.c new file mode 100644 index 000000000000..7264cac04047 --- /dev/null +++ b/net/handshake/netlink.c @@ -0,0 +1,312 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Generic netlink handshake service + * + * Author: Chuck Lever + * + * Copyright (c) 2023, Oracle and/or its affiliates. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include "handshake.h" +#include "genl.h" + +#include + +/** + * handshake_genl_notify - Notify handlers that a request is waiting + * @net: target network namespace + * @proto: handshake protocol + * @flags: memory allocation control flags + * + * Returns zero on success or a negative errno if notification failed. + */ +int handshake_genl_notify(struct net *net, const struct handshake_proto *proto, + gfp_t flags) +{ + struct sk_buff *msg; + void *hdr; + + if (!genl_has_listeners(&handshake_nl_family, net, + proto->hp_handler_class)) + return -ESRCH; + + msg = genlmsg_new(GENLMSG_DEFAULT_SIZE, GFP_KERNEL); + if (!msg) + return -ENOMEM; + + hdr = genlmsg_put(msg, 0, 0, &handshake_nl_family, 0, + HANDSHAKE_CMD_READY); + if (!hdr) + goto out_free; + + if (nla_put_u32(msg, HANDSHAKE_A_ACCEPT_HANDLER_CLASS, + proto->hp_handler_class) < 0) { + genlmsg_cancel(msg, hdr); + goto out_free; + } + + genlmsg_end(msg, hdr); + return genlmsg_multicast_netns(&handshake_nl_family, net, msg, + 0, proto->hp_handler_class, flags); + +out_free: + nlmsg_free(msg); + return -EMSGSIZE; +} + +/** + * handshake_genl_put - Create a generic netlink message header + * @msg: buffer in which to create the header + * @info: generic netlink message context + * + * Returns a ready-to-use header, or NULL. + */ +struct nlmsghdr *handshake_genl_put(struct sk_buff *msg, + struct genl_info *info) +{ + return genlmsg_put(msg, info->snd_portid, info->snd_seq, + &handshake_nl_family, 0, info->genlhdr->cmd); +} +EXPORT_SYMBOL(handshake_genl_put); + +/* + * dup() a kernel socket for use as a user space file descriptor + * in the current process. The kernel socket must have an + * instatiated struct file. + * + * Implicit argument: "current()" + */ +static int handshake_dup(struct socket *sock) +{ + struct file *file; + int newfd; + + if (!sock->file) + return -EBADF; + + file = get_file(sock->file); + newfd = get_unused_fd_flags(O_CLOEXEC); + if (newfd < 0) { + fput(file); + return newfd; + } + + fd_install(newfd, file); + return newfd; +} + +int handshake_nl_accept_doit(struct sk_buff *skb, struct genl_info *info) +{ + struct net *net = sock_net(skb->sk); + struct handshake_net *hn = handshake_pernet(net); + struct handshake_req *req = NULL; + struct socket *sock; + int class, fd, err; + + err = -EOPNOTSUPP; + if (!hn) + goto out_status; + + err = -EINVAL; + if (GENL_REQ_ATTR_CHECK(info, HANDSHAKE_A_ACCEPT_HANDLER_CLASS)) + goto out_status; + class = nla_get_u32(info->attrs[HANDSHAKE_A_ACCEPT_HANDLER_CLASS]); + + err = -EAGAIN; + req = handshake_req_next(hn, class); + if (!req) + goto out_status; + + sock = req->hr_sk->sk_socket; + fd = handshake_dup(sock); + if (fd < 0) { + err = fd; + goto out_complete; + } + err = req->hr_proto->hp_accept(req, info, fd); + if (err) + goto out_complete; + + trace_handshake_cmd_accept(net, req, req->hr_sk, fd); + return 0; + +out_complete: + handshake_complete(req, -EIO, NULL); + fput(sock->file); +out_status: + trace_handshake_cmd_accept_err(net, req, NULL, err); + return err; +} + +int handshake_nl_done_doit(struct sk_buff *skb, struct genl_info *info) +{ + struct net *net = sock_net(skb->sk); + struct socket *sock = NULL; + struct handshake_req *req; + int fd, status, err; + + if (GENL_REQ_ATTR_CHECK(info, HANDSHAKE_A_DONE_SOCKFD)) + return -EINVAL; + fd = nla_get_u32(info->attrs[HANDSHAKE_A_DONE_SOCKFD]); + + err = 0; + sock = sockfd_lookup(fd, &err); + if (err) { + err = -EBADF; + goto out_status; + } + + req = handshake_req_hash_lookup(sock->sk); + if (!req) { + err = -EBUSY; + fput(sock->file); + goto out_status; + } + + trace_handshake_cmd_done(net, req, sock->sk, fd); + + status = -EIO; + if (info->attrs[HANDSHAKE_A_DONE_STATUS]) + status = nla_get_u32(info->attrs[HANDSHAKE_A_DONE_STATUS]); + + handshake_complete(req, status, info); + fput(sock->file); + return 0; + +out_status: + trace_handshake_cmd_done_err(net, req, sock->sk, err); + return err; +} + +static unsigned int handshake_net_id; + +static int __net_init handshake_net_init(struct net *net) +{ + struct handshake_net *hn = net_generic(net, handshake_net_id); + unsigned long tmp; + struct sysinfo si; + + /* + * Arbitrary limit to prevent handshakes that do not make + * progress from clogging up the system. The cap scales up + * with the amount of physical memory on the system. + */ + si_meminfo(&si); + tmp = si.totalram / (25 * si.mem_unit); + hn->hn_pending_max = clamp(tmp, 3UL, 50UL); + + spin_lock_init(&hn->hn_lock); + hn->hn_pending = 0; + hn->hn_flags = 0; + INIT_LIST_HEAD(&hn->hn_requests); + return 0; +} + +static void __net_exit handshake_net_exit(struct net *net) +{ + struct handshake_net *hn = net_generic(net, handshake_net_id); + struct handshake_req *req; + LIST_HEAD(requests); + + /* + * Drain the net's pending list. Requests that have been + * accepted and are in progress will be destroyed when + * the socket is closed. + */ + spin_lock(&hn->hn_lock); + set_bit(HANDSHAKE_F_NET_DRAINING, &hn->hn_flags); + list_splice_init(&requests, &hn->hn_requests); + spin_unlock(&hn->hn_lock); + + while (!list_empty(&requests)) { + req = list_first_entry(&requests, struct handshake_req, hr_list); + list_del(&req->hr_list); + + /* + * Requests on this list have not yet been + * accepted, so they do not have an fd to put. + */ + + handshake_complete(req, -ETIMEDOUT, NULL); + } +} + +static struct pernet_operations __net_initdata handshake_genl_net_ops = { + .init = handshake_net_init, + .exit = handshake_net_exit, + .id = &handshake_net_id, + .size = sizeof(struct handshake_net), +}; + +/** + * handshake_pernet - Get the handshake private per-net structure + * @net: network namespace + * + * Returns a pointer to the net's private per-net structure for the + * handshake module, or NULL if handshake_init() failed. + */ +struct handshake_net *handshake_pernet(struct net *net) +{ + return handshake_net_id ? + net_generic(net, handshake_net_id) : NULL; +} + +static int __init handshake_init(void) +{ + int ret; + + ret = handshake_req_hash_init(); + if (ret) { + pr_warn("handshake: hash initialization failed (%d)\n", ret); + return ret; + } + + ret = genl_register_family(&handshake_nl_family); + if (ret) { + pr_warn("handshake: netlink registration failed (%d)\n", ret); + handshake_req_hash_destroy(); + return ret; + } + + /* + * ORDER: register_pernet_subsys must be done last. + * + * If initialization does not make it past pernet_subsys + * registration, then handshake_net_id will remain 0. That + * shunts the handshake consumer API to return ENOTSUPP + * to prevent it from dereferencing something that hasn't + * been allocated. + */ + ret = register_pernet_subsys(&handshake_genl_net_ops); + if (ret) { + pr_warn("handshake: pernet registration failed (%d)\n", ret); + genl_unregister_family(&handshake_nl_family); + handshake_req_hash_destroy(); + } + + return ret; +} + +static void __exit handshake_exit(void) +{ + unregister_pernet_subsys(&handshake_genl_net_ops); + handshake_net_id = 0; + + handshake_req_hash_destroy(); + genl_unregister_family(&handshake_nl_family); +} + +module_init(handshake_init); +module_exit(handshake_exit); diff --git a/net/handshake/request.c b/net/handshake/request.c new file mode 100644 index 000000000000..d5b2bc6de057 --- /dev/null +++ b/net/handshake/request.c @@ -0,0 +1,339 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Handshake request lifetime events + * + * Author: Chuck Lever + * + * Copyright (c) 2023, Oracle and/or its affiliates. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include "handshake.h" + +#include + +/* + * We need both a handshake_req -> sock mapping, and a sock -> + * handshake_req mapping. Both are one-to-one. + * + * To avoid adding another pointer field to struct sock, net/handshake + * maintains a hash table, indexed by the memory address of @sock, to + * find the struct handshake_req outstanding for that socket. The + * reverse direction uses a simple pointer field in the handshake_req + * struct. + */ + +static struct rhashtable handshake_rhashtbl ____cacheline_aligned_in_smp; + +static const struct rhashtable_params handshake_rhash_params = { + .key_len = sizeof_field(struct handshake_req, hr_sk), + .key_offset = offsetof(struct handshake_req, hr_sk), + .head_offset = offsetof(struct handshake_req, hr_rhash), + .automatic_shrinking = true, +}; + +int handshake_req_hash_init(void) +{ + return rhashtable_init(&handshake_rhashtbl, &handshake_rhash_params); +} + +void handshake_req_hash_destroy(void) +{ + rhashtable_destroy(&handshake_rhashtbl); +} + +struct handshake_req *handshake_req_hash_lookup(struct sock *sk) +{ + return rhashtable_lookup_fast(&handshake_rhashtbl, &sk, + handshake_rhash_params); +} + +static bool handshake_req_hash_add(struct handshake_req *req) +{ + int ret; + + ret = rhashtable_lookup_insert_fast(&handshake_rhashtbl, + &req->hr_rhash, + handshake_rhash_params); + return ret == 0; +} + +static void handshake_req_destroy(struct handshake_req *req) +{ + if (req->hr_proto->hp_destroy) + req->hr_proto->hp_destroy(req); + rhashtable_remove_fast(&handshake_rhashtbl, &req->hr_rhash, + handshake_rhash_params); + kfree(req); +} + +static void handshake_sk_destruct(struct sock *sk) +{ + void (*sk_destruct)(struct sock *sk); + struct handshake_req *req; + + req = handshake_req_hash_lookup(sk); + if (!req) + return; + + trace_handshake_destruct(sock_net(sk), req, sk); + sk_destruct = req->hr_odestruct; + handshake_req_destroy(req); + if (sk_destruct) + sk_destruct(sk); +} + +/** + * handshake_req_alloc - Allocate a handshake request + * @proto: security protocol + * @flags: memory allocation flags + * + * Returns an initialized handshake_req or NULL. + */ +struct handshake_req *handshake_req_alloc(const struct handshake_proto *proto, + gfp_t flags) +{ + struct handshake_req *req; + + if (!proto) + return NULL; + if (proto->hp_handler_class <= HANDSHAKE_HANDLER_CLASS_NONE) + return NULL; + if (proto->hp_handler_class >= HANDSHAKE_HANDLER_CLASS_MAX) + return NULL; + if (!proto->hp_accept || !proto->hp_done) + return NULL; + + req = kzalloc(struct_size(req, hr_priv, proto->hp_privsize), flags); + if (!req) + return NULL; + + INIT_LIST_HEAD(&req->hr_list); + req->hr_proto = proto; + return req; +} +EXPORT_SYMBOL(handshake_req_alloc); + +/** + * handshake_req_private - Get per-handshake private data + * @req: handshake arguments + * + */ +void *handshake_req_private(struct handshake_req *req) +{ + return (void *)&req->hr_priv; +} +EXPORT_SYMBOL(handshake_req_private); + +static bool __add_pending_locked(struct handshake_net *hn, + struct handshake_req *req) +{ + if (WARN_ON_ONCE(!list_empty(&req->hr_list))) + return false; + hn->hn_pending++; + list_add_tail(&req->hr_list, &hn->hn_requests); + return true; +} + +static void __remove_pending_locked(struct handshake_net *hn, + struct handshake_req *req) +{ + hn->hn_pending--; + list_del_init(&req->hr_list); +} + +/* + * Returns %true if the request was found on @net's pending list, + * otherwise %false. + * + * If @req was on a pending list, it has not yet been accepted. + */ +static bool remove_pending(struct handshake_net *hn, struct handshake_req *req) +{ + bool ret = false; + + spin_lock(&hn->hn_lock); + if (!list_empty(&req->hr_list)) { + __remove_pending_locked(hn, req); + ret = true; + } + spin_unlock(&hn->hn_lock); + + return ret; +} + +struct handshake_req *handshake_req_next(struct handshake_net *hn, int class) +{ + struct handshake_req *req, *pos; + + req = NULL; + spin_lock(&hn->hn_lock); + list_for_each_entry(pos, &hn->hn_requests, hr_list) { + if (pos->hr_proto->hp_handler_class != class) + continue; + __remove_pending_locked(hn, pos); + req = pos; + break; + } + spin_unlock(&hn->hn_lock); + + return req; +} + +/** + * handshake_req_submit - Submit a handshake request + * @sock: open socket on which to perform the handshake + * @req: handshake arguments + * @flags: memory allocation flags + * + * Return values: + * %0: Request queued + * %-EINVAL: Invalid argument + * %-EBUSY: A handshake is already under way for this socket + * %-ESRCH: No handshake agent is available + * %-EAGAIN: Too many pending handshake requests + * %-ENOMEM: Failed to allocate memory + * %-EMSGSIZE: Failed to construct notification message + * %-EOPNOTSUPP: Handshake module not initialized + * + * A zero return value from handshake_req_submit() means that + * exactly one subsequent completion callback is guaranteed. + * + * A negative return value from handshake_req_submit() means that + * no completion callback will be done and that @req has been + * destroyed. + */ +int handshake_req_submit(struct socket *sock, struct handshake_req *req, + gfp_t flags) +{ + struct handshake_net *hn; + struct net *net; + int ret; + + if (!sock || !req || !sock->file) { + kfree(req); + return -EINVAL; + } + + req->hr_sk = sock->sk; + if (!req->hr_sk) { + kfree(req); + return -EINVAL; + } + req->hr_odestruct = req->hr_sk->sk_destruct; + req->hr_sk->sk_destruct = handshake_sk_destruct; + + ret = -EOPNOTSUPP; + net = sock_net(req->hr_sk); + hn = handshake_pernet(net); + if (!hn) + goto out_err; + + ret = -EAGAIN; + if (READ_ONCE(hn->hn_pending) >= hn->hn_pending_max) + goto out_err; + + spin_lock(&hn->hn_lock); + ret = -EOPNOTSUPP; + if (test_bit(HANDSHAKE_F_NET_DRAINING, &hn->hn_flags)) + goto out_unlock; + ret = -EBUSY; + if (!handshake_req_hash_add(req)) + goto out_unlock; + if (!__add_pending_locked(hn, req)) + goto out_unlock; + spin_unlock(&hn->hn_lock); + + ret = handshake_genl_notify(net, req->hr_proto, flags); + if (ret) { + trace_handshake_notify_err(net, req, req->hr_sk, ret); + if (remove_pending(hn, req)) + goto out_err; + } + + /* Prevent socket release while a handshake request is pending */ + sock_hold(req->hr_sk); + + trace_handshake_submit(net, req, req->hr_sk); + return 0; + +out_unlock: + spin_unlock(&hn->hn_lock); +out_err: + trace_handshake_submit_err(net, req, req->hr_sk, ret); + handshake_req_destroy(req); + return ret; +} +EXPORT_SYMBOL(handshake_req_submit); + +void handshake_complete(struct handshake_req *req, unsigned int status, + struct genl_info *info) +{ + struct sock *sk = req->hr_sk; + struct net *net = sock_net(sk); + + if (!test_and_set_bit(HANDSHAKE_F_REQ_COMPLETED, &req->hr_flags)) { + trace_handshake_complete(net, req, sk, status); + req->hr_proto->hp_done(req, status, info); + + /* Handshake request is no longer pending */ + sock_put(sk); + } +} + +/** + * handshake_req_cancel - Cancel an in-progress handshake + * @sk: socket on which there is an ongoing handshake + * + * Request cancellation races with request completion. To determine + * who won, callers examine the return value from this function. + * + * Return values: + * %true - Uncompleted handshake request was canceled + * %false - Handshake request already completed or not found + */ +bool handshake_req_cancel(struct sock *sk) +{ + struct handshake_req *req; + struct handshake_net *hn; + struct net *net; + + net = sock_net(sk); + req = handshake_req_hash_lookup(sk); + if (!req) { + trace_handshake_cancel_none(net, req, sk); + return false; + } + + hn = handshake_pernet(net); + if (hn && remove_pending(hn, req)) { + /* Request hadn't been accepted */ + goto out_true; + } + if (test_and_set_bit(HANDSHAKE_F_REQ_COMPLETED, &req->hr_flags)) { + /* Request already completed */ + trace_handshake_cancel_busy(net, req, sk); + return false; + } + +out_true: + trace_handshake_cancel(net, req, sk); + + /* Handshake request is no longer pending */ + sock_put(sk); + return true; +} +EXPORT_SYMBOL(handshake_req_cancel); diff --git a/net/handshake/trace.c b/net/handshake/trace.c new file mode 100644 index 000000000000..1c4d8e27e17a --- /dev/null +++ b/net/handshake/trace.c @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Trace points for transport security layer handshakes. + * + * Author: Chuck Lever + * + * Copyright (c) 2023, Oracle and/or its affiliates. + */ + +#include + +#include +#include +#include + +#include "handshake.h" + +#define CREATE_TRACE_POINTS + +#include -- cgit v1.2.3 From 2fd5532044a89d2403b543520b4902e196f7d165 Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Mon, 17 Apr 2023 10:32:33 -0400 Subject: net/handshake: Add a kernel API for requesting a TLSv1.3 handshake To enable kernel consumers of TLS to request a TLS handshake, add support to net/handshake/ to request a handshake upcall. This patch also acts as a template for adding handshake upcall support for other kernel transport layer security providers. Signed-off-by: Chuck Lever Signed-off-by: Jakub Kicinski --- Documentation/netlink/specs/handshake.yaml | 4 +- Documentation/networking/index.rst | 1 + Documentation/networking/tls-handshake.rst | 217 +++++++++++++++ MAINTAINERS | 2 + include/net/handshake.h | 43 +++ include/uapi/linux/handshake.h | 2 + net/handshake/Makefile | 2 +- net/handshake/genl.c | 3 +- net/handshake/genl.h | 1 + net/handshake/tlshd.c | 417 +++++++++++++++++++++++++++++ 10 files changed, 689 insertions(+), 3 deletions(-) create mode 100644 Documentation/networking/tls-handshake.rst create mode 100644 include/net/handshake.h create mode 100644 net/handshake/tlshd.c (limited to 'MAINTAINERS') diff --git a/Documentation/netlink/specs/handshake.yaml b/Documentation/netlink/specs/handshake.yaml index 0333d92b1438..614f1a585511 100644 --- a/Documentation/netlink/specs/handshake.yaml +++ b/Documentation/netlink/specs/handshake.yaml @@ -16,7 +16,7 @@ definitions: type: enum name: handler-class value-start: 0 - entries: [ none, max ] + entries: [ none, tlshd, max ] - type: enum name: msg-type @@ -120,3 +120,5 @@ mcast-groups: list: - name: none + - + name: tlshd diff --git a/Documentation/networking/index.rst b/Documentation/networking/index.rst index 24bb256d6d53..a164ff074356 100644 --- a/Documentation/networking/index.rst +++ b/Documentation/networking/index.rst @@ -36,6 +36,7 @@ Contents: scaling tls tls-offload + tls-handshake nfc 6lowpan 6pack diff --git a/Documentation/networking/tls-handshake.rst b/Documentation/networking/tls-handshake.rst new file mode 100644 index 000000000000..a2817a88e905 --- /dev/null +++ b/Documentation/networking/tls-handshake.rst @@ -0,0 +1,217 @@ +.. SPDX-License-Identifier: GPL-2.0 + +======================= +In-Kernel TLS Handshake +======================= + +Overview +======== + +Transport Layer Security (TLS) is a Upper Layer Protocol (ULP) that runs +over TCP. TLS provides end-to-end data integrity and confidentiality in +addition to peer authentication. + +The kernel's kTLS implementation handles the TLS record subprotocol, but +does not handle the TLS handshake subprotocol which is used to establish +a TLS session. Kernel consumers can use the API described here to +request TLS session establishment. + +There are several possible ways to provide a handshake service in the +kernel. The API described here is designed to hide the details of those +implementations so that in-kernel TLS consumers do not need to be +aware of how the handshake gets done. + + +User handshake agent +==================== + +As of this writing, there is no TLS handshake implementation in the +Linux kernel. To provide a handshake service, a handshake agent +(typically in user space) is started in each network namespace where a +kernel consumer might require a TLS handshake. Handshake agents listen +for events sent from the kernel that indicate a handshake request is +waiting. + +An open socket is passed to a handshake agent via a netlink operation, +which creates a socket descriptor in the agent's file descriptor table. +If the handshake completes successfully, the handshake agent promotes +the socket to use the TLS ULP and sets the session information using the +SOL_TLS socket options. The handshake agent returns the socket to the +kernel via a second netlink operation. + + +Kernel Handshake API +==================== + +A kernel TLS consumer initiates a client-side TLS handshake on an open +socket by invoking one of the tls_client_hello() functions. First, it +fills in a structure that contains the parameters of the request: + +.. code-block:: c + + struct tls_handshake_args { + struct socket *ta_sock; + tls_done_func_t ta_done; + void *ta_data; + unsigned int ta_timeout_ms; + key_serial_t ta_keyring; + key_serial_t ta_my_cert; + key_serial_t ta_my_privkey; + unsigned int ta_num_peerids; + key_serial_t ta_my_peerids[5]; + }; + +The @ta_sock field references an open and connected socket. The consumer +must hold a reference on the socket to prevent it from being destroyed +while the handshake is in progress. The consumer must also have +instantiated a struct file in sock->file. + + +@ta_done contains a callback function that is invoked when the handshake +has completed. Further explanation of this function is in the "Handshake +Completion" sesction below. + +The consumer can fill in the @ta_timeout_ms field to force the servicing +handshake agent to exit after a number of milliseconds. This enables the +socket to be fully closed once both the kernel and the handshake agent +have closed their endpoints. + +Authentication material such as x.509 certificates, private certificate +keys, and pre-shared keys are provided to the handshake agent in keys +that are instantiated by the consumer before making the handshake +request. The consumer can provide a private keyring that is linked into +the handshake agent's process keyring in the @ta_keyring field to prevent +access of those keys by other subsystems. + +To request an x.509-authenticated TLS session, the consumer fills in +the @ta_my_cert and @ta_my_privkey fields with the serial numbers of +keys containing an x.509 certificate and the private key for that +certificate. Then, it invokes this function: + +.. code-block:: c + + ret = tls_client_hello_x509(args, gfp_flags); + +The function returns zero when the handshake request is under way. A +zero return guarantees the callback function @ta_done will be invoked +for this socket. The function returns a negative errno if the handshake +could not be started. A negative errno guarantees the callback function +@ta_done will not be invoked on this socket. + + +To initiate a client-side TLS handshake with a pre-shared key, use: + +.. code-block:: c + + ret = tls_client_hello_psk(args, gfp_flags); + +However, in this case, the consumer fills in the @ta_my_peerids array +with serial numbers of keys containing the peer identities it wishes +to offer, and the @ta_num_peerids field with the number of array +entries it has filled in. The other fields are filled in as above. + + +To initiate an anonymous client-side TLS handshake use: + +.. code-block:: c + + ret = tls_client_hello_anon(args, gfp_flags); + +The handshake agent presents no peer identity information to the remote +during this type of handshake. Only server authentication (ie the client +verifies the server's identity) is performed during the handshake. Thus +the established session uses encryption only. + + +Consumers that are in-kernel servers use: + +.. code-block:: c + + ret = tls_server_hello_x509(args, gfp_flags); + +or + +.. code-block:: c + + ret = tls_server_hello_psk(args, gfp_flags); + +The argument structure is filled in as above. + + +If the consumer needs to cancel the handshake request, say, due to a ^C +or other exigent event, the consumer can invoke: + +.. code-block:: c + + bool tls_handshake_cancel(sock); + +This function returns true if the handshake request associated with +@sock has been canceled. The consumer's handshake completion callback +will not be invoked. If this function returns false, then the consumer's +completion callback has already been invoked. + + +Handshake Completion +==================== + +When the handshake agent has completed processing, it notifies the +kernel that the socket may be used by the consumer again. At this point, +the consumer's handshake completion callback, provided in the @ta_done +field in the tls_handshake_args structure, is invoked. + +The synopsis of this function is: + +.. code-block:: c + + typedef void (*tls_done_func_t)(void *data, int status, + key_serial_t peerid); + +The consumer provides a cookie in the @ta_data field of the +tls_handshake_args structure that is returned in the @data parameter of +this callback. The consumer uses the cookie to match the callback to the +thread waiting for the handshake to complete. + +The success status of the handshake is returned via the @status +parameter: + ++------------+----------------------------------------------+ +| status | meaning | ++============+==============================================+ +| 0 | TLS session established successfully | ++------------+----------------------------------------------+ +| -EACCESS | Remote peer rejected the handshake or | +| | authentication failed | ++------------+----------------------------------------------+ +| -ENOMEM | Temporary resource allocation failure | ++------------+----------------------------------------------+ +| -EINVAL | Consumer provided an invalid argument | ++------------+----------------------------------------------+ +| -ENOKEY | Missing authentication material | ++------------+----------------------------------------------+ +| -EIO | An unexpected fault occurred | ++------------+----------------------------------------------+ + +The @peerid parameter contains the serial number of a key containing the +remote peer's identity or the value TLS_NO_PEERID if the session is not +authenticated. + +A best practice is to close and destroy the socket immediately if the +handshake failed. + + +Other considerations +-------------------- + +While a handshake is under way, the kernel consumer must alter the +socket's sk_data_ready callback function to ignore all incoming data. +Once the handshake completion callback function has been invoked, normal +receive operation can be resumed. + +Once a TLS session is established, the consumer must provide a buffer +for and then examine the control message (CMSG) that is part of every +subsequent sock_recvmsg(). Each control message indicates whether the +received message data is TLS record data or session metadata. + +See tls.rst for details on how a kTLS consumer recognizes incoming +(decrypted) application data, alerts, and handshake packets once the +socket has been promoted to use the TLS ULP. diff --git a/MAINTAINERS b/MAINTAINERS index cdc7748d15b8..04ebde8ccb75 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -8953,6 +8953,8 @@ L: kernel-tls-handshake@lists.linux.dev L: netdev@vger.kernel.org S: Maintained F: Documentation/netlink/specs/handshake.yaml +F: Documentation/networking/tls-handshake.rst +F: include/net/handshake.h F: include/trace/events/handshake.h F: net/handshake/ diff --git a/include/net/handshake.h b/include/net/handshake.h new file mode 100644 index 000000000000..3352b1ab43b3 --- /dev/null +++ b/include/net/handshake.h @@ -0,0 +1,43 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Generic netlink HANDSHAKE service. + * + * Author: Chuck Lever + * + * Copyright (c) 2023, Oracle and/or its affiliates. + */ + +#ifndef _NET_HANDSHAKE_H +#define _NET_HANDSHAKE_H + +enum { + TLS_NO_KEYRING = 0, + TLS_NO_PEERID = 0, + TLS_NO_CERT = 0, + TLS_NO_PRIVKEY = 0, +}; + +typedef void (*tls_done_func_t)(void *data, int status, + key_serial_t peerid); + +struct tls_handshake_args { + struct socket *ta_sock; + tls_done_func_t ta_done; + void *ta_data; + unsigned int ta_timeout_ms; + key_serial_t ta_keyring; + key_serial_t ta_my_cert; + key_serial_t ta_my_privkey; + unsigned int ta_num_peerids; + key_serial_t ta_my_peerids[5]; +}; + +int tls_client_hello_anon(const struct tls_handshake_args *args, gfp_t flags); +int tls_client_hello_x509(const struct tls_handshake_args *args, gfp_t flags); +int tls_client_hello_psk(const struct tls_handshake_args *args, gfp_t flags); +int tls_server_hello_x509(const struct tls_handshake_args *args, gfp_t flags); +int tls_server_hello_psk(const struct tls_handshake_args *args, gfp_t flags); + +bool tls_handshake_cancel(struct sock *sk); + +#endif /* _NET_HANDSHAKE_H */ diff --git a/include/uapi/linux/handshake.h b/include/uapi/linux/handshake.h index 7f66ff489b87..1de4d0b95325 100644 --- a/include/uapi/linux/handshake.h +++ b/include/uapi/linux/handshake.h @@ -11,6 +11,7 @@ enum handshake_handler_class { HANDSHAKE_HANDLER_CLASS_NONE, + HANDSHAKE_HANDLER_CLASS_TLSHD, HANDSHAKE_HANDLER_CLASS_MAX, }; @@ -67,5 +68,6 @@ enum { }; #define HANDSHAKE_MCGRP_NONE "none" +#define HANDSHAKE_MCGRP_TLSHD "tlshd" #endif /* _UAPI_LINUX_HANDSHAKE_H */ diff --git a/net/handshake/Makefile b/net/handshake/Makefile index d38736de45da..a089f7e3df24 100644 --- a/net/handshake/Makefile +++ b/net/handshake/Makefile @@ -8,4 +8,4 @@ # obj-y += handshake.o -handshake-y := genl.o netlink.o request.o trace.o +handshake-y := genl.o netlink.o request.o tlshd.o trace.o diff --git a/net/handshake/genl.c b/net/handshake/genl.c index 652f37d19bd6..9f29efb1493e 100644 --- a/net/handshake/genl.c +++ b/net/handshake/genl.c @@ -12,7 +12,7 @@ /* HANDSHAKE_CMD_ACCEPT - do */ static const struct nla_policy handshake_accept_nl_policy[HANDSHAKE_A_ACCEPT_HANDLER_CLASS + 1] = { - [HANDSHAKE_A_ACCEPT_HANDLER_CLASS] = NLA_POLICY_MAX(NLA_U32, 1), + [HANDSHAKE_A_ACCEPT_HANDLER_CLASS] = NLA_POLICY_MAX(NLA_U32, 2), }; /* HANDSHAKE_CMD_DONE - do */ @@ -42,6 +42,7 @@ static const struct genl_split_ops handshake_nl_ops[] = { static const struct genl_multicast_group handshake_nl_mcgrps[] = { [HANDSHAKE_NLGRP_NONE] = { "none", }, + [HANDSHAKE_NLGRP_TLSHD] = { "tlshd", }, }; struct genl_family handshake_nl_family __ro_after_init = { diff --git a/net/handshake/genl.h b/net/handshake/genl.h index a1eb7ccccc7f..2c1f1aa6a02a 100644 --- a/net/handshake/genl.h +++ b/net/handshake/genl.h @@ -16,6 +16,7 @@ int handshake_nl_done_doit(struct sk_buff *skb, struct genl_info *info); enum { HANDSHAKE_NLGRP_NONE, + HANDSHAKE_NLGRP_TLSHD, }; extern struct genl_family handshake_nl_family; diff --git a/net/handshake/tlshd.c b/net/handshake/tlshd.c new file mode 100644 index 000000000000..1b8353296060 --- /dev/null +++ b/net/handshake/tlshd.c @@ -0,0 +1,417 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Establish a TLS session for a kernel socket consumer + * using the tlshd user space handler. + * + * Author: Chuck Lever + * + * Copyright (c) 2021-2023, Oracle and/or its affiliates. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include "handshake.h" + +struct tls_handshake_req { + void (*th_consumer_done)(void *data, int status, + key_serial_t peerid); + void *th_consumer_data; + + int th_type; + unsigned int th_timeout_ms; + int th_auth_mode; + key_serial_t th_keyring; + key_serial_t th_certificate; + key_serial_t th_privkey; + + unsigned int th_num_peerids; + key_serial_t th_peerid[5]; +}; + +static struct tls_handshake_req * +tls_handshake_req_init(struct handshake_req *req, + const struct tls_handshake_args *args) +{ + struct tls_handshake_req *treq = handshake_req_private(req); + + treq->th_timeout_ms = args->ta_timeout_ms; + treq->th_consumer_done = args->ta_done; + treq->th_consumer_data = args->ta_data; + treq->th_keyring = args->ta_keyring; + treq->th_num_peerids = 0; + treq->th_certificate = TLS_NO_CERT; + treq->th_privkey = TLS_NO_PRIVKEY; + return treq; +} + +static void tls_handshake_remote_peerids(struct tls_handshake_req *treq, + struct genl_info *info) +{ + struct nlattr *head = nlmsg_attrdata(info->nlhdr, GENL_HDRLEN); + int rem, len = nlmsg_attrlen(info->nlhdr, GENL_HDRLEN); + struct nlattr *nla; + unsigned int i; + + i = 0; + nla_for_each_attr(nla, head, len, rem) { + if (nla_type(nla) == HANDSHAKE_A_DONE_REMOTE_AUTH) + i++; + } + if (!i) + return; + treq->th_num_peerids = min_t(unsigned int, i, + ARRAY_SIZE(treq->th_peerid)); + + i = 0; + nla_for_each_attr(nla, head, len, rem) { + if (nla_type(nla) == HANDSHAKE_A_DONE_REMOTE_AUTH) + treq->th_peerid[i++] = nla_get_u32(nla); + if (i >= treq->th_num_peerids) + break; + } +} + +/** + * tls_handshake_done - callback to handle a CMD_DONE request + * @req: socket on which the handshake was performed + * @status: session status code + * @info: full results of session establishment + * + */ +static void tls_handshake_done(struct handshake_req *req, + unsigned int status, struct genl_info *info) +{ + struct tls_handshake_req *treq = handshake_req_private(req); + + treq->th_peerid[0] = TLS_NO_PEERID; + if (info) + tls_handshake_remote_peerids(treq, info); + + treq->th_consumer_done(treq->th_consumer_data, -status, + treq->th_peerid[0]); +} + +#if IS_ENABLED(CONFIG_KEYS) +static int tls_handshake_private_keyring(struct tls_handshake_req *treq) +{ + key_ref_t process_keyring_ref, keyring_ref; + int ret; + + if (treq->th_keyring == TLS_NO_KEYRING) + return 0; + + process_keyring_ref = lookup_user_key(KEY_SPEC_PROCESS_KEYRING, + KEY_LOOKUP_CREATE, + KEY_NEED_WRITE); + if (IS_ERR(process_keyring_ref)) { + ret = PTR_ERR(process_keyring_ref); + goto out; + } + + keyring_ref = lookup_user_key(treq->th_keyring, KEY_LOOKUP_CREATE, + KEY_NEED_LINK); + if (IS_ERR(keyring_ref)) { + ret = PTR_ERR(keyring_ref); + goto out_put_key; + } + + ret = key_link(key_ref_to_ptr(process_keyring_ref), + key_ref_to_ptr(keyring_ref)); + + key_ref_put(keyring_ref); +out_put_key: + key_ref_put(process_keyring_ref); +out: + return ret; +} +#else +static int tls_handshake_private_keyring(struct tls_handshake_req *treq) +{ + return 0; +} +#endif + +static int tls_handshake_put_peer_identity(struct sk_buff *msg, + struct tls_handshake_req *treq) +{ + unsigned int i; + + for (i = 0; i < treq->th_num_peerids; i++) + if (nla_put_u32(msg, HANDSHAKE_A_ACCEPT_PEER_IDENTITY, + treq->th_peerid[i]) < 0) + return -EMSGSIZE; + return 0; +} + +static int tls_handshake_put_certificate(struct sk_buff *msg, + struct tls_handshake_req *treq) +{ + struct nlattr *entry_attr; + + if (treq->th_certificate == TLS_NO_CERT && + treq->th_privkey == TLS_NO_PRIVKEY) + return 0; + + entry_attr = nla_nest_start(msg, HANDSHAKE_A_ACCEPT_CERTIFICATE); + if (!entry_attr) + return -EMSGSIZE; + + if (nla_put_u32(msg, HANDSHAKE_A_X509_CERT, + treq->th_certificate) || + nla_put_u32(msg, HANDSHAKE_A_X509_PRIVKEY, + treq->th_privkey)) { + nla_nest_cancel(msg, entry_attr); + return -EMSGSIZE; + } + + nla_nest_end(msg, entry_attr); + return 0; +} + +/** + * tls_handshake_accept - callback to construct a CMD_ACCEPT response + * @req: handshake parameters to return + * @info: generic netlink message context + * @fd: file descriptor to be returned + * + * Returns zero on success, or a negative errno on failure. + */ +static int tls_handshake_accept(struct handshake_req *req, + struct genl_info *info, int fd) +{ + struct tls_handshake_req *treq = handshake_req_private(req); + struct nlmsghdr *hdr; + struct sk_buff *msg; + int ret; + + ret = tls_handshake_private_keyring(treq); + if (ret < 0) + goto out; + + ret = -ENOMEM; + msg = genlmsg_new(GENLMSG_DEFAULT_SIZE, GFP_KERNEL); + if (!msg) + goto out; + hdr = handshake_genl_put(msg, info); + if (!hdr) + goto out_cancel; + + ret = -EMSGSIZE; + ret = nla_put_u32(msg, HANDSHAKE_A_ACCEPT_SOCKFD, fd); + if (ret < 0) + goto out_cancel; + ret = nla_put_u32(msg, HANDSHAKE_A_ACCEPT_MESSAGE_TYPE, treq->th_type); + if (ret < 0) + goto out_cancel; + if (treq->th_timeout_ms) { + ret = nla_put_u32(msg, HANDSHAKE_A_ACCEPT_TIMEOUT, treq->th_timeout_ms); + if (ret < 0) + goto out_cancel; + } + + ret = nla_put_u32(msg, HANDSHAKE_A_ACCEPT_AUTH_MODE, + treq->th_auth_mode); + if (ret < 0) + goto out_cancel; + switch (treq->th_auth_mode) { + case HANDSHAKE_AUTH_PSK: + ret = tls_handshake_put_peer_identity(msg, treq); + if (ret < 0) + goto out_cancel; + break; + case HANDSHAKE_AUTH_X509: + ret = tls_handshake_put_certificate(msg, treq); + if (ret < 0) + goto out_cancel; + break; + } + + genlmsg_end(msg, hdr); + return genlmsg_reply(msg, info); + +out_cancel: + genlmsg_cancel(msg, hdr); +out: + return ret; +} + +static const struct handshake_proto tls_handshake_proto = { + .hp_handler_class = HANDSHAKE_HANDLER_CLASS_TLSHD, + .hp_privsize = sizeof(struct tls_handshake_req), + + .hp_accept = tls_handshake_accept, + .hp_done = tls_handshake_done, +}; + +/** + * tls_client_hello_anon - request an anonymous TLS handshake on a socket + * @args: socket and handshake parameters for this request + * @flags: memory allocation control flags + * + * Return values: + * %0: Handshake request enqueue; ->done will be called when complete + * %-ESRCH: No user agent is available + * %-ENOMEM: Memory allocation failed + */ +int tls_client_hello_anon(const struct tls_handshake_args *args, gfp_t flags) +{ + struct tls_handshake_req *treq; + struct handshake_req *req; + + req = handshake_req_alloc(&tls_handshake_proto, flags); + if (!req) + return -ENOMEM; + treq = tls_handshake_req_init(req, args); + treq->th_type = HANDSHAKE_MSG_TYPE_CLIENTHELLO; + treq->th_auth_mode = HANDSHAKE_AUTH_UNAUTH; + + return handshake_req_submit(args->ta_sock, req, flags); +} +EXPORT_SYMBOL(tls_client_hello_anon); + +/** + * tls_client_hello_x509 - request an x.509-based TLS handshake on a socket + * @args: socket and handshake parameters for this request + * @flags: memory allocation control flags + * + * Return values: + * %0: Handshake request enqueue; ->done will be called when complete + * %-ESRCH: No user agent is available + * %-ENOMEM: Memory allocation failed + */ +int tls_client_hello_x509(const struct tls_handshake_args *args, gfp_t flags) +{ + struct tls_handshake_req *treq; + struct handshake_req *req; + + req = handshake_req_alloc(&tls_handshake_proto, flags); + if (!req) + return -ENOMEM; + treq = tls_handshake_req_init(req, args); + treq->th_type = HANDSHAKE_MSG_TYPE_CLIENTHELLO; + treq->th_auth_mode = HANDSHAKE_AUTH_X509; + treq->th_certificate = args->ta_my_cert; + treq->th_privkey = args->ta_my_privkey; + + return handshake_req_submit(args->ta_sock, req, flags); +} +EXPORT_SYMBOL(tls_client_hello_x509); + +/** + * tls_client_hello_psk - request a PSK-based TLS handshake on a socket + * @args: socket and handshake parameters for this request + * @flags: memory allocation control flags + * + * Return values: + * %0: Handshake request enqueue; ->done will be called when complete + * %-EINVAL: Wrong number of local peer IDs + * %-ESRCH: No user agent is available + * %-ENOMEM: Memory allocation failed + */ +int tls_client_hello_psk(const struct tls_handshake_args *args, gfp_t flags) +{ + struct tls_handshake_req *treq; + struct handshake_req *req; + unsigned int i; + + if (!args->ta_num_peerids || + args->ta_num_peerids > ARRAY_SIZE(treq->th_peerid)) + return -EINVAL; + + req = handshake_req_alloc(&tls_handshake_proto, flags); + if (!req) + return -ENOMEM; + treq = tls_handshake_req_init(req, args); + treq->th_type = HANDSHAKE_MSG_TYPE_CLIENTHELLO; + treq->th_auth_mode = HANDSHAKE_AUTH_PSK; + treq->th_num_peerids = args->ta_num_peerids; + for (i = 0; i < args->ta_num_peerids; i++) + treq->th_peerid[i] = args->ta_my_peerids[i]; + + return handshake_req_submit(args->ta_sock, req, flags); +} +EXPORT_SYMBOL(tls_client_hello_psk); + +/** + * tls_server_hello_x509 - request a server TLS handshake on a socket + * @args: socket and handshake parameters for this request + * @flags: memory allocation control flags + * + * Return values: + * %0: Handshake request enqueue; ->done will be called when complete + * %-ESRCH: No user agent is available + * %-ENOMEM: Memory allocation failed + */ +int tls_server_hello_x509(const struct tls_handshake_args *args, gfp_t flags) +{ + struct tls_handshake_req *treq; + struct handshake_req *req; + + req = handshake_req_alloc(&tls_handshake_proto, flags); + if (!req) + return -ENOMEM; + treq = tls_handshake_req_init(req, args); + treq->th_type = HANDSHAKE_MSG_TYPE_SERVERHELLO; + treq->th_auth_mode = HANDSHAKE_AUTH_X509; + treq->th_certificate = args->ta_my_cert; + treq->th_privkey = args->ta_my_privkey; + + return handshake_req_submit(args->ta_sock, req, flags); +} +EXPORT_SYMBOL(tls_server_hello_x509); + +/** + * tls_server_hello_psk - request a server TLS handshake on a socket + * @args: socket and handshake parameters for this request + * @flags: memory allocation control flags + * + * Return values: + * %0: Handshake request enqueue; ->done will be called when complete + * %-ESRCH: No user agent is available + * %-ENOMEM: Memory allocation failed + */ +int tls_server_hello_psk(const struct tls_handshake_args *args, gfp_t flags) +{ + struct tls_handshake_req *treq; + struct handshake_req *req; + + req = handshake_req_alloc(&tls_handshake_proto, flags); + if (!req) + return -ENOMEM; + treq = tls_handshake_req_init(req, args); + treq->th_type = HANDSHAKE_MSG_TYPE_SERVERHELLO; + treq->th_auth_mode = HANDSHAKE_AUTH_PSK; + treq->th_num_peerids = 1; + treq->th_peerid[0] = args->ta_my_peerids[0]; + + return handshake_req_submit(args->ta_sock, req, flags); +} +EXPORT_SYMBOL(tls_server_hello_psk); + +/** + * tls_handshake_cancel - cancel a pending handshake + * @sk: socket on which there is an ongoing handshake + * + * Request cancellation races with request completion. To determine + * who won, callers examine the return value from this function. + * + * Return values: + * %true - Uncompleted handshake request was canceled + * %false - Handshake request already completed or not found + */ +bool tls_handshake_cancel(struct sock *sk) +{ + return handshake_req_cancel(sk); +} +EXPORT_SYMBOL(tls_handshake_cancel); -- cgit v1.2.3 From e375b503943f512cdd3a7dd12849972b7a006076 Mon Sep 17 00:00:00 2001 From: Jeroen de Borst Date: Wed, 19 Apr 2023 14:05:58 -0700 Subject: gve: update MAINTAINERS This reflects role changes in our team. Signed-off-by: Jeroen de Borst Signed-off-by: Praveen Kaligineedi Link: https://lore.kernel.org/r/20230419210558.1893400-1-jeroendb@google.com Signed-off-by: Jakub Kicinski --- MAINTAINERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index c6545eb54104..eefcea6f8757 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -8690,7 +8690,7 @@ F: drivers/input/touchscreen/goodix* GOOGLE ETHERNET DRIVERS M: Jeroen de Borst -M: Catherine Sullivan +M: Praveen Kaligineedi R: Shailend Chand L: netdev@vger.kernel.org S: Supported -- cgit v1.2.3 From ddbcb22055d136f58841c73ca2226dab79eb6101 Mon Sep 17 00:00:00 2001 From: Shannon Nelson Date: Wed, 19 Apr 2023 10:04:27 -0700 Subject: pds_core: Kconfig and pds_core.rst Remaining documentation and Kconfig hook for building the driver. Signed-off-by: Shannon Nelson Acked-by: Jakub Kicinski Signed-off-by: David S. Miller --- .../networking/device_drivers/ethernet/amd/pds_core.rst | 16 ++++++++++++++++ MAINTAINERS | 9 +++++++++ drivers/net/ethernet/amd/Kconfig | 12 ++++++++++++ drivers/net/ethernet/amd/Makefile | 1 + 4 files changed, 38 insertions(+) (limited to 'MAINTAINERS') diff --git a/Documentation/networking/device_drivers/ethernet/amd/pds_core.rst b/Documentation/networking/device_drivers/ethernet/amd/pds_core.rst index b9f310de862e..9e8a16c44102 100644 --- a/Documentation/networking/device_drivers/ethernet/amd/pds_core.rst +++ b/Documentation/networking/device_drivers/ethernet/amd/pds_core.rst @@ -114,6 +114,22 @@ The driver supports a devlink health reporter for FW status:: # devlink health diagnose pci/0000:2b:00.0 reporter fw Status: healthy State: 1 Generation: 0 Recoveries: 0 +Enabling the driver +=================== + +The driver is enabled via the standard kernel configuration system, +using the make command:: + + make oldconfig/menuconfig/etc. + +The driver is located in the menu structure at: + + -> Device Drivers + -> Network device support (NETDEVICES [=y]) + -> Ethernet driver support + -> AMD devices + -> AMD/Pensando Ethernet PDS_CORE Support + Support ======= diff --git a/MAINTAINERS b/MAINTAINERS index 85571d5d8209..9420a1599a58 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1041,6 +1041,15 @@ F: drivers/gpu/drm/amd/include/vi_structs.h F: include/uapi/linux/kfd_ioctl.h F: include/uapi/linux/kfd_sysfs.h +AMD PDS CORE DRIVER +M: Shannon Nelson +M: Brett Creeley +L: netdev@vger.kernel.org +S: Supported +F: Documentation/networking/device_drivers/ethernet/amd/pds_core.rst +F: drivers/net/ethernet/amd/pds_core/ +F: include/linux/pds/ + AMD SPI DRIVER M: Sanjay R Mehta S: Maintained diff --git a/drivers/net/ethernet/amd/Kconfig b/drivers/net/ethernet/amd/Kconfig index ab42f75b9413..235fcacef5c5 100644 --- a/drivers/net/ethernet/amd/Kconfig +++ b/drivers/net/ethernet/amd/Kconfig @@ -186,4 +186,16 @@ config AMD_XGBE_HAVE_ECC bool default n +config PDS_CORE + tristate "AMD/Pensando Data Systems Core Device Support" + depends on 64BIT && PCI + help + This enables the support for the AMD/Pensando Core device family of + adapters. More specific information on this driver can be + found in + . + + To compile this driver as a module, choose M here. The module + will be called pds_core. + endif # NET_VENDOR_AMD diff --git a/drivers/net/ethernet/amd/Makefile b/drivers/net/ethernet/amd/Makefile index 42742afe9115..2dcfb84731e1 100644 --- a/drivers/net/ethernet/amd/Makefile +++ b/drivers/net/ethernet/amd/Makefile @@ -17,3 +17,4 @@ obj-$(CONFIG_PCNET32) += pcnet32.o obj-$(CONFIG_SUN3LANCE) += sun3lance.o obj-$(CONFIG_SUNLANCE) += sunlance.o obj-$(CONFIG_AMD_XGBE) += xgbe/ +obj-$(CONFIG_PDS_CORE) += pds_core/ -- cgit v1.2.3 From ab3a769b4dccec2cf60f0a0700b140991bf9afc8 Mon Sep 17 00:00:00 2001 From: Neeraj Sanjay Kale Date: Thu, 16 Mar 2023 22:52:13 +0530 Subject: dt-bindings: net: bluetooth: Add NXP bluetooth support Add binding document for NXP bluetooth chipsets attached over UART. Signed-off-by: Neeraj Sanjay Kale Reviewed-by: Krzysztof Kozlowski Signed-off-by: Luiz Augusto von Dentz --- .../bindings/net/bluetooth/nxp,88w8987-bt.yaml | 45 ++++++++++++++++++++++ MAINTAINERS | 6 +++ 2 files changed, 51 insertions(+) create mode 100644 Documentation/devicetree/bindings/net/bluetooth/nxp,88w8987-bt.yaml (limited to 'MAINTAINERS') diff --git a/Documentation/devicetree/bindings/net/bluetooth/nxp,88w8987-bt.yaml b/Documentation/devicetree/bindings/net/bluetooth/nxp,88w8987-bt.yaml new file mode 100644 index 000000000000..57e4c87cb00b --- /dev/null +++ b/Documentation/devicetree/bindings/net/bluetooth/nxp,88w8987-bt.yaml @@ -0,0 +1,45 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/net/bluetooth/nxp,88w8987-bt.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: NXP Bluetooth chips + +description: + This binding describes UART-attached NXP bluetooth chips. These chips + are dual-radio chips supporting WiFi and Bluetooth. The bluetooth + works on standard H4 protocol over 4-wire UART. The RTS and CTS lines + are used during FW download. To enable power save mode, the host + asserts break signal over UART-TX line to put the chip into power save + state. De-asserting break wakes up the BT chip. + +maintainers: + - Neeraj Sanjay Kale + +properties: + compatible: + enum: + - nxp,88w8987-bt + - nxp,88w8997-bt + + fw-init-baudrate: + description: + Chip baudrate after FW is downloaded and initialized. + This property depends on the module vendor's + configuration. If this property is not specified, + 115200 is set as default. + +required: + - compatible + +additionalProperties: false + +examples: + - | + serial { + bluetooth { + compatible = "nxp,88w8987-bt"; + fw-init-baudrate = <3000000>; + }; + }; diff --git a/MAINTAINERS b/MAINTAINERS index 6ac562e0381e..ee743e25b096 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -23237,6 +23237,12 @@ L: linux-mm@kvack.org S: Maintained F: mm/zswap.c +NXP BLUETOOTH WIRELESS DRIVERS +M: Amitkumar Karwar +M: Neeraj Kale +S: Maintained +F: Documentation/devicetree/bindings/net/bluetooth/nxp,88w8987-bt.yaml + THE REST M: Linus Torvalds L: linux-kernel@vger.kernel.org -- cgit v1.2.3 From 689ca16e523278470c38832a3010645a78c544d8 Mon Sep 17 00:00:00 2001 From: Neeraj Sanjay Kale Date: Thu, 16 Mar 2023 22:52:14 +0530 Subject: Bluetooth: NXP: Add protocol support for NXP Bluetooth chipsets MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This adds a driver based on serdev driver for the NXP BT serial protocol based on running H:4, which can enable the built-in Bluetooth device inside an NXP BT chip. This driver has Power Save feature that will put the chip into sleep state whenever there is no activity for 2000ms, and will be woken up when any activity is to be initiated over UART. This driver enables the power save feature by default by sending the vendor specific commands to the chip during setup. During setup, the driver checks if a FW is already running on the chip by waiting for the bootloader signature, and downloads device specific FW file into the chip over UART if bootloader signature is received.. Signed-off-by: Neeraj Sanjay Kale Reviewed-by: Ilpo Järvinen Signed-off-by: Luiz Augusto von Dentz --- MAINTAINERS | 1 + drivers/bluetooth/Kconfig | 12 + drivers/bluetooth/Makefile | 1 + drivers/bluetooth/btnxpuart.c | 1297 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 1311 insertions(+) create mode 100644 drivers/bluetooth/btnxpuart.c (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index ee743e25b096..c2f3ecdb47e0 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -23242,6 +23242,7 @@ M: Amitkumar Karwar M: Neeraj Kale S: Maintained F: Documentation/devicetree/bindings/net/bluetooth/nxp,88w8987-bt.yaml +F: drivers/bluetooth/btnxpuart.c THE REST M: Linus Torvalds diff --git a/drivers/bluetooth/Kconfig b/drivers/bluetooth/Kconfig index 5a1a7bec3c42..0703bdd44140 100644 --- a/drivers/bluetooth/Kconfig +++ b/drivers/bluetooth/Kconfig @@ -465,4 +465,16 @@ config BT_VIRTIO Say Y here to compile support for HCI over Virtio into the kernel or say M to compile as a module. +config BT_NXPUART + tristate "NXP protocol support" + depends on SERIAL_DEV_BUS + select CRC32 + help + NXP is serial driver required for NXP Bluetooth + devices with UART interface. + + Say Y here to compile support for NXP Bluetooth UART device into + the kernel, or say M here to compile as a module (btnxpuart). + + endmenu diff --git a/drivers/bluetooth/Makefile b/drivers/bluetooth/Makefile index e0b261f24fc9..7a5967e9ac48 100644 --- a/drivers/bluetooth/Makefile +++ b/drivers/bluetooth/Makefile @@ -29,6 +29,7 @@ obj-$(CONFIG_BT_QCA) += btqca.o obj-$(CONFIG_BT_MTK) += btmtk.o obj-$(CONFIG_BT_VIRTIO) += virtio_bt.o +obj-$(CONFIG_BT_NXPUART) += btnxpuart.o obj-$(CONFIG_BT_HCIUART_NOKIA) += hci_nokia.o diff --git a/drivers/bluetooth/btnxpuart.c b/drivers/bluetooth/btnxpuart.c new file mode 100644 index 000000000000..5f641466d695 --- /dev/null +++ b/drivers/bluetooth/btnxpuart.c @@ -0,0 +1,1297 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * NXP Bluetooth driver + * Copyright 2023 NXP + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "h4_recv.h" + +#define MANUFACTURER_NXP 37 + +#define BTNXPUART_TX_STATE_ACTIVE 1 +#define BTNXPUART_FW_DOWNLOADING 2 +#define BTNXPUART_CHECK_BOOT_SIGNATURE 3 +#define BTNXPUART_SERDEV_OPEN 4 + +#define FIRMWARE_W8987 "nxp/uartuart8987_bt.bin" +#define FIRMWARE_W8997 "nxp/uartuart8997_bt_v4.bin" +#define FIRMWARE_W9098 "nxp/uartuart9098_bt_v1.bin" +#define FIRMWARE_IW416 "nxp/uartiw416_bt_v0.bin" +#define FIRMWARE_IW612 "nxp/uartspi_n61x_v1.bin.se" + +#define CHIP_ID_W9098 0x5c03 +#define CHIP_ID_IW416 0x7201 +#define CHIP_ID_IW612 0x7601 + +#define HCI_NXP_PRI_BAUDRATE 115200 +#define HCI_NXP_SEC_BAUDRATE 3000000 + +#define MAX_FW_FILE_NAME_LEN 50 + +/* Default ps timeout period in milliseconds */ +#define PS_DEFAULT_TIMEOUT_PERIOD_MS 2000 + +/* wakeup methods */ +#define WAKEUP_METHOD_DTR 0 +#define WAKEUP_METHOD_BREAK 1 +#define WAKEUP_METHOD_EXT_BREAK 2 +#define WAKEUP_METHOD_RTS 3 +#define WAKEUP_METHOD_INVALID 0xff + +/* power save mode status */ +#define PS_MODE_DISABLE 0 +#define PS_MODE_ENABLE 1 + +/* Power Save Commands to ps_work_func */ +#define PS_CMD_EXIT_PS 1 +#define PS_CMD_ENTER_PS 2 + +/* power save state */ +#define PS_STATE_AWAKE 0 +#define PS_STATE_SLEEP 1 + +/* Bluetooth vendor command : Sleep mode */ +#define HCI_NXP_AUTO_SLEEP_MODE 0xfc23 +/* Bluetooth vendor command : Wakeup method */ +#define HCI_NXP_WAKEUP_METHOD 0xfc53 +/* Bluetooth vendor command : Set operational baudrate */ +#define HCI_NXP_SET_OPER_SPEED 0xfc09 +/* Bluetooth vendor command: Independent Reset */ +#define HCI_NXP_IND_RESET 0xfcfc + +/* Bluetooth Power State : Vendor cmd params */ +#define BT_PS_ENABLE 0x02 +#define BT_PS_DISABLE 0x03 + +/* Bluetooth Host Wakeup Methods */ +#define BT_HOST_WAKEUP_METHOD_NONE 0x00 +#define BT_HOST_WAKEUP_METHOD_DTR 0x01 +#define BT_HOST_WAKEUP_METHOD_BREAK 0x02 +#define BT_HOST_WAKEUP_METHOD_GPIO 0x03 + +/* Bluetooth Chip Wakeup Methods */ +#define BT_CTRL_WAKEUP_METHOD_DSR 0x00 +#define BT_CTRL_WAKEUP_METHOD_BREAK 0x01 +#define BT_CTRL_WAKEUP_METHOD_GPIO 0x02 +#define BT_CTRL_WAKEUP_METHOD_EXT_BREAK 0x04 +#define BT_CTRL_WAKEUP_METHOD_RTS 0x05 + +struct ps_data { + u8 target_ps_mode; /* ps mode to be set */ + u8 cur_psmode; /* current ps_mode */ + u8 ps_state; /* controller's power save state */ + u8 ps_cmd; + u8 h2c_wakeupmode; + u8 cur_h2c_wakeupmode; + u8 c2h_wakeupmode; + u8 c2h_wakeup_gpio; + u8 h2c_wakeup_gpio; + bool driver_sent_cmd; + u16 h2c_ps_interval; + u16 c2h_ps_interval; + struct hci_dev *hdev; + struct work_struct work; + struct timer_list ps_timer; +}; + +struct wakeup_cmd_payload { + u8 c2h_wakeupmode; + u8 c2h_wakeup_gpio; + u8 h2c_wakeupmode; + u8 h2c_wakeup_gpio; +} __packed; + +struct psmode_cmd_payload { + u8 ps_cmd; + __le16 c2h_ps_interval; +} __packed; + +struct btnxpuart_data { + bool fw_download_3M_baudrate; + const char *fw_name; +}; + +struct btnxpuart_dev { + struct hci_dev *hdev; + struct serdev_device *serdev; + + struct work_struct tx_work; + unsigned long tx_state; + struct sk_buff_head txq; + struct sk_buff *rx_skb; + + const struct firmware *fw; + u8 fw_name[MAX_FW_FILE_NAME_LEN]; + u32 fw_dnld_v1_offset; + u32 fw_v1_sent_bytes; + u32 fw_v3_offset_correction; + u32 fw_v1_expected_len; + wait_queue_head_t fw_dnld_done_wait_q; + wait_queue_head_t check_boot_sign_wait_q; + + u32 new_baudrate; + u32 current_baudrate; + u32 fw_init_baudrate; + bool timeout_changed; + bool baudrate_changed; + + struct ps_data psdata; + struct btnxpuart_data *nxp_data; +}; + +#define NXP_V1_FW_REQ_PKT 0xa5 +#define NXP_V1_CHIP_VER_PKT 0xaa +#define NXP_V3_FW_REQ_PKT 0xa7 +#define NXP_V3_CHIP_VER_PKT 0xab + +#define NXP_ACK_V1 0x5a +#define NXP_NAK_V1 0xbf +#define NXP_ACK_V3 0x7a +#define NXP_NAK_V3 0x7b +#define NXP_CRC_ERROR_V3 0x7c + +#define HDR_LEN 16 + +#define NXP_RECV_FW_REQ_V1 \ + .type = NXP_V1_FW_REQ_PKT, \ + .hlen = 4, \ + .loff = 0, \ + .lsize = 0, \ + .maxlen = 4 + +#define NXP_RECV_CHIP_VER_V3 \ + .type = NXP_V3_CHIP_VER_PKT, \ + .hlen = 4, \ + .loff = 0, \ + .lsize = 0, \ + .maxlen = 4 + +#define NXP_RECV_FW_REQ_V3 \ + .type = NXP_V3_FW_REQ_PKT, \ + .hlen = 9, \ + .loff = 0, \ + .lsize = 0, \ + .maxlen = 9 + +struct v1_data_req { + __le16 len; + __le16 len_comp; +} __packed; + +struct v3_data_req { + __le16 len; + __le32 offset; + __le16 error; + u8 crc; +} __packed; + +struct v3_start_ind { + __le16 chip_id; + u8 loader_ver; + u8 crc; +} __packed; + +/* UART register addresses of BT chip */ +#define CLKDIVADDR 0x7f00008f +#define UARTDIVADDR 0x7f000090 +#define UARTMCRADDR 0x7f000091 +#define UARTREINITADDR 0x7f000092 +#define UARTICRADDR 0x7f000093 +#define UARTFCRADDR 0x7f000094 + +#define MCR 0x00000022 +#define INIT 0x00000001 +#define ICR 0x000000c7 +#define FCR 0x000000c7 + +#define POLYNOMIAL8 0x07 + +struct uart_reg { + __le32 address; + __le32 value; +} __packed; + +struct uart_config { + struct uart_reg clkdiv; + struct uart_reg uartdiv; + struct uart_reg mcr; + struct uart_reg re_init; + struct uart_reg icr; + struct uart_reg fcr; + __be32 crc; +} __packed; + +struct nxp_bootloader_cmd { + __le32 header; + __le32 arg; + __le32 payload_len; + __be32 crc; +} __packed; + +static u8 crc8_table[CRC8_TABLE_SIZE]; + +/* Default configurations */ +#define DEFAULT_H2C_WAKEUP_MODE WAKEUP_METHOD_BREAK +#define DEFAULT_PS_MODE PS_MODE_ENABLE +#define FW_INIT_BAUDRATE HCI_NXP_PRI_BAUDRATE + +static struct sk_buff *nxp_drv_send_cmd(struct hci_dev *hdev, u16 opcode, + u32 plen, + void *param) +{ + struct btnxpuart_dev *nxpdev = hci_get_drvdata(hdev); + struct ps_data *psdata = &nxpdev->psdata; + struct sk_buff *skb; + + /* set flag to prevent nxp_enqueue from parsing values from this command and + * calling hci_cmd_sync_queue() again. + */ + psdata->driver_sent_cmd = true; + skb = __hci_cmd_sync(hdev, opcode, plen, param, HCI_CMD_TIMEOUT); + psdata->driver_sent_cmd = false; + + return skb; +} + +static void btnxpuart_tx_wakeup(struct btnxpuart_dev *nxpdev) +{ + if (schedule_work(&nxpdev->tx_work)) + set_bit(BTNXPUART_TX_STATE_ACTIVE, &nxpdev->tx_state); +} + +/* NXP Power Save Feature */ +static void ps_start_timer(struct btnxpuart_dev *nxpdev) +{ + struct ps_data *psdata = &nxpdev->psdata; + + if (!psdata) + return; + + if (psdata->cur_psmode == PS_MODE_ENABLE) + mod_timer(&psdata->ps_timer, jiffies + msecs_to_jiffies(psdata->h2c_ps_interval)); +} + +static void ps_cancel_timer(struct btnxpuart_dev *nxpdev) +{ + struct ps_data *psdata = &nxpdev->psdata; + + flush_work(&psdata->work); + del_timer_sync(&psdata->ps_timer); +} + +static void ps_control(struct hci_dev *hdev, u8 ps_state) +{ + struct btnxpuart_dev *nxpdev = hci_get_drvdata(hdev); + struct ps_data *psdata = &nxpdev->psdata; + int status; + + if (psdata->ps_state == ps_state || + !test_bit(BTNXPUART_SERDEV_OPEN, &nxpdev->tx_state)) + return; + + switch (psdata->cur_h2c_wakeupmode) { + case WAKEUP_METHOD_DTR: + if (ps_state == PS_STATE_AWAKE) + status = serdev_device_set_tiocm(nxpdev->serdev, TIOCM_DTR, 0); + else + status = serdev_device_set_tiocm(nxpdev->serdev, 0, TIOCM_DTR); + break; + case WAKEUP_METHOD_BREAK: + default: + if (ps_state == PS_STATE_AWAKE) + status = serdev_device_break_ctl(nxpdev->serdev, 0); + else + status = serdev_device_break_ctl(nxpdev->serdev, -1); + bt_dev_dbg(hdev, "Set UART break: %s, status=%d", + str_on_off(ps_state == PS_STATE_SLEEP), status); + break; + } + if (!status) + psdata->ps_state = ps_state; + if (ps_state == PS_STATE_AWAKE) + btnxpuart_tx_wakeup(nxpdev); +} + +static void ps_work_func(struct work_struct *work) +{ + struct ps_data *data = container_of(work, struct ps_data, work); + + if (data->ps_cmd == PS_CMD_ENTER_PS && data->cur_psmode == PS_MODE_ENABLE) + ps_control(data->hdev, PS_STATE_SLEEP); + else if (data->ps_cmd == PS_CMD_EXIT_PS) + ps_control(data->hdev, PS_STATE_AWAKE); +} + +static void ps_timeout_func(struct timer_list *t) +{ + struct ps_data *data = from_timer(data, t, ps_timer); + struct hci_dev *hdev = data->hdev; + struct btnxpuart_dev *nxpdev = hci_get_drvdata(hdev); + + if (test_bit(BTNXPUART_TX_STATE_ACTIVE, &nxpdev->tx_state)) { + ps_start_timer(nxpdev); + } else { + data->ps_cmd = PS_CMD_ENTER_PS; + schedule_work(&data->work); + } +} + +static int ps_init_work(struct hci_dev *hdev) +{ + struct btnxpuart_dev *nxpdev = hci_get_drvdata(hdev); + struct ps_data *psdata = &nxpdev->psdata; + + psdata->h2c_ps_interval = PS_DEFAULT_TIMEOUT_PERIOD_MS; + psdata->ps_state = PS_STATE_AWAKE; + psdata->target_ps_mode = DEFAULT_PS_MODE; + psdata->hdev = hdev; + psdata->c2h_wakeupmode = BT_HOST_WAKEUP_METHOD_NONE; + psdata->c2h_wakeup_gpio = 0xff; + + switch (DEFAULT_H2C_WAKEUP_MODE) { + case WAKEUP_METHOD_DTR: + psdata->h2c_wakeupmode = WAKEUP_METHOD_DTR; + break; + case WAKEUP_METHOD_BREAK: + default: + psdata->h2c_wakeupmode = WAKEUP_METHOD_BREAK; + break; + } + psdata->cur_psmode = PS_MODE_DISABLE; + psdata->cur_h2c_wakeupmode = WAKEUP_METHOD_INVALID; + INIT_WORK(&psdata->work, ps_work_func); + + return 0; +} + +static void ps_init_timer(struct hci_dev *hdev) +{ + struct btnxpuart_dev *nxpdev = hci_get_drvdata(hdev); + struct ps_data *psdata = &nxpdev->psdata; + + timer_setup(&psdata->ps_timer, ps_timeout_func, 0); +} + +static void ps_wakeup(struct btnxpuart_dev *nxpdev) +{ + struct ps_data *psdata = &nxpdev->psdata; + + if (psdata->ps_state != PS_STATE_AWAKE) { + psdata->ps_cmd = PS_CMD_EXIT_PS; + schedule_work(&psdata->work); + } +} + +static int send_ps_cmd(struct hci_dev *hdev, void *data) +{ + struct btnxpuart_dev *nxpdev = hci_get_drvdata(hdev); + struct ps_data *psdata = &nxpdev->psdata; + struct psmode_cmd_payload pcmd; + struct sk_buff *skb; + u8 *status; + + if (psdata->target_ps_mode == PS_MODE_ENABLE) + pcmd.ps_cmd = BT_PS_ENABLE; + else + pcmd.ps_cmd = BT_PS_DISABLE; + pcmd.c2h_ps_interval = __cpu_to_le16(psdata->c2h_ps_interval); + + skb = nxp_drv_send_cmd(hdev, HCI_NXP_AUTO_SLEEP_MODE, sizeof(pcmd), &pcmd); + if (IS_ERR(skb)) { + bt_dev_err(hdev, "Setting Power Save mode failed (%ld)", PTR_ERR(skb)); + return PTR_ERR(skb); + } + + status = skb_pull_data(skb, 1); + if (status) { + if (!*status) + psdata->cur_psmode = psdata->target_ps_mode; + else + psdata->target_ps_mode = psdata->cur_psmode; + if (psdata->cur_psmode == PS_MODE_ENABLE) + ps_start_timer(nxpdev); + else + ps_wakeup(nxpdev); + bt_dev_dbg(hdev, "Power Save mode response: status=%d, ps_mode=%d", + *status, psdata->cur_psmode); + } + kfree_skb(skb); + + return 0; +} + +static int send_wakeup_method_cmd(struct hci_dev *hdev, void *data) +{ + struct btnxpuart_dev *nxpdev = hci_get_drvdata(hdev); + struct ps_data *psdata = &nxpdev->psdata; + struct wakeup_cmd_payload pcmd; + struct sk_buff *skb; + u8 *status; + + pcmd.c2h_wakeupmode = psdata->c2h_wakeupmode; + pcmd.c2h_wakeup_gpio = psdata->c2h_wakeup_gpio; + switch (psdata->h2c_wakeupmode) { + case WAKEUP_METHOD_DTR: + pcmd.h2c_wakeupmode = BT_CTRL_WAKEUP_METHOD_DSR; + break; + case WAKEUP_METHOD_BREAK: + default: + pcmd.h2c_wakeupmode = BT_CTRL_WAKEUP_METHOD_BREAK; + break; + } + pcmd.h2c_wakeup_gpio = 0xff; + + skb = nxp_drv_send_cmd(hdev, HCI_NXP_WAKEUP_METHOD, sizeof(pcmd), &pcmd); + if (IS_ERR(skb)) { + bt_dev_err(hdev, "Setting wake-up method failed (%ld)", PTR_ERR(skb)); + return PTR_ERR(skb); + } + + status = skb_pull_data(skb, 1); + if (status) { + if (*status == 0) + psdata->cur_h2c_wakeupmode = psdata->h2c_wakeupmode; + else + psdata->h2c_wakeupmode = psdata->cur_h2c_wakeupmode; + bt_dev_dbg(hdev, "Set Wakeup Method response: status=%d, h2c_wakeupmode=%d", + *status, psdata->cur_h2c_wakeupmode); + } + kfree_skb(skb); + + return 0; +} + +static void ps_init(struct hci_dev *hdev) +{ + struct btnxpuart_dev *nxpdev = hci_get_drvdata(hdev); + struct ps_data *psdata = &nxpdev->psdata; + + serdev_device_set_tiocm(nxpdev->serdev, 0, TIOCM_RTS); + usleep_range(5000, 10000); + serdev_device_set_tiocm(nxpdev->serdev, TIOCM_RTS, 0); + usleep_range(5000, 10000); + + switch (psdata->h2c_wakeupmode) { + case WAKEUP_METHOD_DTR: + serdev_device_set_tiocm(nxpdev->serdev, 0, TIOCM_DTR); + serdev_device_set_tiocm(nxpdev->serdev, TIOCM_DTR, 0); + break; + case WAKEUP_METHOD_BREAK: + default: + serdev_device_break_ctl(nxpdev->serdev, -1); + usleep_range(5000, 10000); + serdev_device_break_ctl(nxpdev->serdev, 0); + usleep_range(5000, 10000); + break; + } + if (psdata->cur_h2c_wakeupmode != psdata->h2c_wakeupmode) + hci_cmd_sync_queue(hdev, send_wakeup_method_cmd, NULL, NULL); + if (psdata->cur_psmode != psdata->target_ps_mode) + hci_cmd_sync_queue(hdev, send_ps_cmd, NULL, NULL); +} + +/* NXP Firmware Download Feature */ +static int nxp_download_firmware(struct hci_dev *hdev) +{ + struct btnxpuart_dev *nxpdev = hci_get_drvdata(hdev); + int err = 0; + + nxpdev->fw_dnld_v1_offset = 0; + nxpdev->fw_v1_sent_bytes = 0; + nxpdev->fw_v1_expected_len = HDR_LEN; + nxpdev->fw_v3_offset_correction = 0; + nxpdev->baudrate_changed = false; + nxpdev->timeout_changed = false; + + serdev_device_set_baudrate(nxpdev->serdev, HCI_NXP_PRI_BAUDRATE); + serdev_device_set_flow_control(nxpdev->serdev, 0); + nxpdev->current_baudrate = HCI_NXP_PRI_BAUDRATE; + + /* Wait till FW is downloaded and CTS becomes low */ + err = wait_event_interruptible_timeout(nxpdev->fw_dnld_done_wait_q, + !test_bit(BTNXPUART_FW_DOWNLOADING, + &nxpdev->tx_state), + msecs_to_jiffies(60000)); + if (err == 0) { + bt_dev_err(hdev, "FW Download Timeout."); + return -ETIMEDOUT; + } + + serdev_device_set_flow_control(nxpdev->serdev, 1); + err = serdev_device_wait_for_cts(nxpdev->serdev, 1, 60000); + if (err < 0) { + bt_dev_err(hdev, "CTS is still high. FW Download failed."); + return err; + } + release_firmware(nxpdev->fw); + memset(nxpdev->fw_name, 0, sizeof(nxpdev->fw_name)); + + /* Allow the downloaded FW to initialize */ + usleep_range(800 * USEC_PER_MSEC, 1 * USEC_PER_SEC); + + return 0; +} + +static void nxp_send_ack(u8 ack, struct hci_dev *hdev) +{ + struct btnxpuart_dev *nxpdev = hci_get_drvdata(hdev); + u8 ack_nak[2]; + int len = 1; + + ack_nak[0] = ack; + if (ack == NXP_ACK_V3) { + ack_nak[1] = crc8(crc8_table, ack_nak, 1, 0xff); + len = 2; + } + serdev_device_write_buf(nxpdev->serdev, ack_nak, len); +} + +static bool nxp_fw_change_baudrate(struct hci_dev *hdev, u16 req_len) +{ + struct btnxpuart_dev *nxpdev = hci_get_drvdata(hdev); + struct nxp_bootloader_cmd nxp_cmd5; + struct uart_config uart_config; + + if (req_len == sizeof(nxp_cmd5)) { + nxp_cmd5.header = __cpu_to_le32(5); + nxp_cmd5.arg = 0; + nxp_cmd5.payload_len = __cpu_to_le32(sizeof(uart_config)); + /* FW expects swapped CRC bytes */ + nxp_cmd5.crc = __cpu_to_be32(crc32_be(0UL, (char *)&nxp_cmd5, + sizeof(nxp_cmd5) - 4)); + + serdev_device_write_buf(nxpdev->serdev, (u8 *)&nxp_cmd5, sizeof(nxp_cmd5)); + nxpdev->fw_v3_offset_correction += req_len; + } else if (req_len == sizeof(uart_config)) { + uart_config.clkdiv.address = __cpu_to_le32(CLKDIVADDR); + uart_config.clkdiv.value = __cpu_to_le32(0x00c00000); + uart_config.uartdiv.address = __cpu_to_le32(UARTDIVADDR); + uart_config.uartdiv.value = __cpu_to_le32(1); + uart_config.mcr.address = __cpu_to_le32(UARTMCRADDR); + uart_config.mcr.value = __cpu_to_le32(MCR); + uart_config.re_init.address = __cpu_to_le32(UARTREINITADDR); + uart_config.re_init.value = __cpu_to_le32(INIT); + uart_config.icr.address = __cpu_to_le32(UARTICRADDR); + uart_config.icr.value = __cpu_to_le32(ICR); + uart_config.fcr.address = __cpu_to_le32(UARTFCRADDR); + uart_config.fcr.value = __cpu_to_le32(FCR); + /* FW expects swapped CRC bytes */ + uart_config.crc = __cpu_to_be32(crc32_be(0UL, (char *)&uart_config, + sizeof(uart_config) - 4)); + + serdev_device_write_buf(nxpdev->serdev, (u8 *)&uart_config, sizeof(uart_config)); + serdev_device_wait_until_sent(nxpdev->serdev, 0); + nxpdev->fw_v3_offset_correction += req_len; + return true; + } + return false; +} + +static bool nxp_fw_change_timeout(struct hci_dev *hdev, u16 req_len) +{ + struct btnxpuart_dev *nxpdev = hci_get_drvdata(hdev); + struct nxp_bootloader_cmd nxp_cmd7; + + if (req_len != sizeof(nxp_cmd7)) + return false; + + nxp_cmd7.header = __cpu_to_le32(7); + nxp_cmd7.arg = __cpu_to_le32(0x70); + nxp_cmd7.payload_len = 0; + /* FW expects swapped CRC bytes */ + nxp_cmd7.crc = __cpu_to_be32(crc32_be(0UL, (char *)&nxp_cmd7, + sizeof(nxp_cmd7) - 4)); + serdev_device_write_buf(nxpdev->serdev, (u8 *)&nxp_cmd7, sizeof(nxp_cmd7)); + serdev_device_wait_until_sent(nxpdev->serdev, 0); + nxpdev->fw_v3_offset_correction += req_len; + return true; +} + +static u32 nxp_get_data_len(const u8 *buf) +{ + struct nxp_bootloader_cmd *hdr = (struct nxp_bootloader_cmd *)buf; + + return __le32_to_cpu(hdr->payload_len); +} + +static bool is_fw_downloading(struct btnxpuart_dev *nxpdev) +{ + return test_bit(BTNXPUART_FW_DOWNLOADING, &nxpdev->tx_state); +} + +static bool process_boot_signature(struct btnxpuart_dev *nxpdev) +{ + if (test_bit(BTNXPUART_CHECK_BOOT_SIGNATURE, &nxpdev->tx_state)) { + clear_bit(BTNXPUART_CHECK_BOOT_SIGNATURE, &nxpdev->tx_state); + wake_up_interruptible(&nxpdev->check_boot_sign_wait_q); + return false; + } + return is_fw_downloading(nxpdev); +} + +static int nxp_request_firmware(struct hci_dev *hdev, const char *fw_name) +{ + struct btnxpuart_dev *nxpdev = hci_get_drvdata(hdev); + int err = 0; + + if (!strlen(nxpdev->fw_name)) { + snprintf(nxpdev->fw_name, MAX_FW_FILE_NAME_LEN, "%s", fw_name); + + bt_dev_dbg(hdev, "Request Firmware: %s", nxpdev->fw_name); + err = request_firmware(&nxpdev->fw, nxpdev->fw_name, &hdev->dev); + if (err < 0) { + bt_dev_err(hdev, "Firmware file %s not found", nxpdev->fw_name); + clear_bit(BTNXPUART_FW_DOWNLOADING, &nxpdev->tx_state); + } + } + return err; +} + +/* for legacy chipsets with V1 bootloader */ +static int nxp_recv_fw_req_v1(struct hci_dev *hdev, struct sk_buff *skb) +{ + struct btnxpuart_dev *nxpdev = hci_get_drvdata(hdev); + struct btnxpuart_data *nxp_data = nxpdev->nxp_data; + struct v1_data_req *req; + u32 requested_len; + + if (!process_boot_signature(nxpdev)) + goto free_skb; + + req = (struct v1_data_req *)skb_pull_data(skb, sizeof(struct v1_data_req)); + if (!req) + goto free_skb; + + if ((req->len ^ req->len_comp) != 0xffff) { + bt_dev_dbg(hdev, "ERR: Send NAK"); + nxp_send_ack(NXP_NAK_V1, hdev); + goto free_skb; + } + nxp_send_ack(NXP_ACK_V1, hdev); + + if (nxp_data->fw_download_3M_baudrate) { + if (!nxpdev->timeout_changed) { + nxpdev->timeout_changed = nxp_fw_change_timeout(hdev, req->len); + goto free_skb; + } + if (!nxpdev->baudrate_changed) { + nxpdev->baudrate_changed = nxp_fw_change_baudrate(hdev, req->len); + if (nxpdev->baudrate_changed) { + serdev_device_set_baudrate(nxpdev->serdev, + HCI_NXP_SEC_BAUDRATE); + serdev_device_set_flow_control(nxpdev->serdev, 1); + nxpdev->current_baudrate = HCI_NXP_SEC_BAUDRATE; + } + goto free_skb; + } + } + + if (nxp_request_firmware(hdev, nxp_data->fw_name)) + goto free_skb; + + requested_len = req->len; + if (requested_len == 0) { + bt_dev_dbg(hdev, "FW Downloaded Successfully: %zu bytes", nxpdev->fw->size); + clear_bit(BTNXPUART_FW_DOWNLOADING, &nxpdev->tx_state); + wake_up_interruptible(&nxpdev->fw_dnld_done_wait_q); + goto free_skb; + } + if (requested_len & 0x01) { + /* The CRC did not match at the other end. + * Simply send the same bytes again. + */ + requested_len = nxpdev->fw_v1_sent_bytes; + bt_dev_dbg(hdev, "CRC error. Resend %d bytes of FW.", requested_len); + } else { + nxpdev->fw_dnld_v1_offset += nxpdev->fw_v1_sent_bytes; + + /* The FW bin file is made up of many blocks of + * 16 byte header and payload data chunks. If the + * FW has requested a header, read the payload length + * info from the header, before sending the header. + * In the next iteration, the FW should request the + * payload data chunk, which should be equal to the + * payload length read from header. If there is a + * mismatch, clearly the driver and FW are out of sync, + * and we need to re-send the previous header again. + */ + if (requested_len == nxpdev->fw_v1_expected_len) { + if (requested_len == HDR_LEN) + nxpdev->fw_v1_expected_len = nxp_get_data_len(nxpdev->fw->data + + nxpdev->fw_dnld_v1_offset); + else + nxpdev->fw_v1_expected_len = HDR_LEN; + } else if (requested_len == HDR_LEN) { + /* FW download out of sync. Send previous chunk again */ + nxpdev->fw_dnld_v1_offset -= nxpdev->fw_v1_sent_bytes; + nxpdev->fw_v1_expected_len = HDR_LEN; + } + } + + if (nxpdev->fw_dnld_v1_offset + requested_len <= nxpdev->fw->size) + serdev_device_write_buf(nxpdev->serdev, + nxpdev->fw->data + nxpdev->fw_dnld_v1_offset, + requested_len); + nxpdev->fw_v1_sent_bytes = requested_len; + +free_skb: + kfree_skb(skb); + return 0; +} + +static char *nxp_get_fw_name_from_chipid(struct hci_dev *hdev, u16 chipid) +{ + char *fw_name = NULL; + + switch (chipid) { + case CHIP_ID_W9098: + fw_name = FIRMWARE_W9098; + break; + case CHIP_ID_IW416: + fw_name = FIRMWARE_IW416; + break; + case CHIP_ID_IW612: + fw_name = FIRMWARE_IW612; + break; + default: + bt_dev_err(hdev, "Unknown chip signature %04x", chipid); + break; + } + return fw_name; +} + +static int nxp_recv_chip_ver_v3(struct hci_dev *hdev, struct sk_buff *skb) +{ + struct v3_start_ind *req = skb_pull_data(skb, sizeof(struct v3_start_ind)); + struct btnxpuart_dev *nxpdev = hci_get_drvdata(hdev); + + if (!process_boot_signature(nxpdev)) + goto free_skb; + + if (!nxp_request_firmware(hdev, nxp_get_fw_name_from_chipid(hdev, req->chip_id))) + nxp_send_ack(NXP_ACK_V3, hdev); + +free_skb: + kfree_skb(skb); + return 0; +} + +static int nxp_recv_fw_req_v3(struct hci_dev *hdev, struct sk_buff *skb) +{ + struct btnxpuart_dev *nxpdev = hci_get_drvdata(hdev); + struct v3_data_req *req; + + if (!process_boot_signature(nxpdev)) + goto free_skb; + + req = (struct v3_data_req *)skb_pull_data(skb, sizeof(struct v3_data_req)); + if (!req || !nxpdev->fw) + goto free_skb; + + nxp_send_ack(NXP_ACK_V3, hdev); + + if (!nxpdev->timeout_changed) { + nxpdev->timeout_changed = nxp_fw_change_timeout(hdev, req->len); + goto free_skb; + } + + if (!nxpdev->baudrate_changed) { + nxpdev->baudrate_changed = nxp_fw_change_baudrate(hdev, req->len); + if (nxpdev->baudrate_changed) { + serdev_device_set_baudrate(nxpdev->serdev, + HCI_NXP_SEC_BAUDRATE); + serdev_device_set_flow_control(nxpdev->serdev, 1); + nxpdev->current_baudrate = HCI_NXP_SEC_BAUDRATE; + } + goto free_skb; + } + + if (req->len == 0) { + bt_dev_dbg(hdev, "FW Downloaded Successfully: %zu bytes", nxpdev->fw->size); + clear_bit(BTNXPUART_FW_DOWNLOADING, &nxpdev->tx_state); + wake_up_interruptible(&nxpdev->fw_dnld_done_wait_q); + goto free_skb; + } + if (req->error) + bt_dev_dbg(hdev, "FW Download received err 0x%02x from chip", req->error); + + if (req->offset < nxpdev->fw_v3_offset_correction) { + /* This scenario should ideally never occur. But if it ever does, + * FW is out of sync and needs a power cycle. + */ + bt_dev_err(hdev, "Something went wrong during FW download. Please power cycle and try again"); + goto free_skb; + } + + serdev_device_write_buf(nxpdev->serdev, + nxpdev->fw->data + req->offset - nxpdev->fw_v3_offset_correction, + req->len); + +free_skb: + kfree_skb(skb); + return 0; +} + +static int nxp_set_baudrate_cmd(struct hci_dev *hdev, void *data) +{ + struct btnxpuart_dev *nxpdev = hci_get_drvdata(hdev); + __le32 new_baudrate = __cpu_to_le32(nxpdev->new_baudrate); + struct ps_data *psdata = &nxpdev->psdata; + struct sk_buff *skb; + u8 *status; + + if (!psdata) + return 0; + + skb = nxp_drv_send_cmd(hdev, HCI_NXP_SET_OPER_SPEED, 4, (u8 *)&new_baudrate); + if (IS_ERR(skb)) { + bt_dev_err(hdev, "Setting baudrate failed (%ld)", PTR_ERR(skb)); + return PTR_ERR(skb); + } + + status = (u8 *)skb_pull_data(skb, 1); + if (status) { + if (*status == 0) { + serdev_device_set_baudrate(nxpdev->serdev, nxpdev->new_baudrate); + nxpdev->current_baudrate = nxpdev->new_baudrate; + } + bt_dev_dbg(hdev, "Set baudrate response: status=%d, baudrate=%d", + *status, nxpdev->new_baudrate); + } + kfree_skb(skb); + + return 0; +} + +static int nxp_set_ind_reset(struct hci_dev *hdev, void *data) +{ + struct btnxpuart_dev *nxpdev = hci_get_drvdata(hdev); + struct sk_buff *skb; + u8 *status; + u8 pcmd = 0; + int err = 0; + + skb = nxp_drv_send_cmd(hdev, HCI_NXP_IND_RESET, 1, &pcmd); + if (IS_ERR(skb)) + return PTR_ERR(skb); + + status = skb_pull_data(skb, 1); + if (!status || *status) + goto free_skb; + + set_bit(BTNXPUART_FW_DOWNLOADING, &nxpdev->tx_state); + err = nxp_download_firmware(hdev); + if (err < 0) + goto free_skb; + serdev_device_set_baudrate(nxpdev->serdev, nxpdev->fw_init_baudrate); + nxpdev->current_baudrate = nxpdev->fw_init_baudrate; + if (nxpdev->current_baudrate != HCI_NXP_SEC_BAUDRATE) { + nxpdev->new_baudrate = HCI_NXP_SEC_BAUDRATE; + nxp_set_baudrate_cmd(hdev, NULL); + } + hci_cmd_sync_queue(hdev, send_wakeup_method_cmd, NULL, NULL); + hci_cmd_sync_queue(hdev, send_ps_cmd, NULL, NULL); + +free_skb: + kfree_skb(skb); + return err; +} + +/* NXP protocol */ +static int nxp_check_boot_sign(struct btnxpuart_dev *nxpdev) +{ + serdev_device_set_baudrate(nxpdev->serdev, HCI_NXP_PRI_BAUDRATE); + serdev_device_set_flow_control(nxpdev->serdev, 0); + set_bit(BTNXPUART_CHECK_BOOT_SIGNATURE, &nxpdev->tx_state); + + return wait_event_interruptible_timeout(nxpdev->check_boot_sign_wait_q, + !test_bit(BTNXPUART_CHECK_BOOT_SIGNATURE, + &nxpdev->tx_state), + msecs_to_jiffies(1000)); +} + +static int nxp_setup(struct hci_dev *hdev) +{ + struct btnxpuart_dev *nxpdev = hci_get_drvdata(hdev); + int err = 0; + + set_bit(BTNXPUART_FW_DOWNLOADING, &nxpdev->tx_state); + init_waitqueue_head(&nxpdev->fw_dnld_done_wait_q); + init_waitqueue_head(&nxpdev->check_boot_sign_wait_q); + + if (nxp_check_boot_sign(nxpdev)) { + bt_dev_dbg(hdev, "Need FW Download."); + err = nxp_download_firmware(hdev); + if (err < 0) + return err; + } else { + bt_dev_dbg(hdev, "FW already running."); + clear_bit(BTNXPUART_FW_DOWNLOADING, &nxpdev->tx_state); + } + + serdev_device_set_flow_control(nxpdev->serdev, 1); + device_property_read_u32(&nxpdev->serdev->dev, "fw-init-baudrate", + &nxpdev->fw_init_baudrate); + if (!nxpdev->fw_init_baudrate) + nxpdev->fw_init_baudrate = FW_INIT_BAUDRATE; + serdev_device_set_baudrate(nxpdev->serdev, nxpdev->fw_init_baudrate); + nxpdev->current_baudrate = nxpdev->fw_init_baudrate; + + if (nxpdev->current_baudrate != HCI_NXP_SEC_BAUDRATE) { + nxpdev->new_baudrate = HCI_NXP_SEC_BAUDRATE; + hci_cmd_sync_queue(hdev, nxp_set_baudrate_cmd, NULL, NULL); + } + + ps_init(hdev); + + return 0; +} + +static int btnxpuart_queue_skb(struct hci_dev *hdev, struct sk_buff *skb) +{ + struct btnxpuart_dev *nxpdev = hci_get_drvdata(hdev); + + /* Prepend skb with frame type */ + memcpy(skb_push(skb, 1), &hci_skb_pkt_type(skb), 1); + skb_queue_tail(&nxpdev->txq, skb); + btnxpuart_tx_wakeup(nxpdev); + return 0; +} + +static int nxp_enqueue(struct hci_dev *hdev, struct sk_buff *skb) +{ + struct btnxpuart_dev *nxpdev = hci_get_drvdata(hdev); + struct ps_data *psdata = &nxpdev->psdata; + struct hci_command_hdr *hdr; + struct psmode_cmd_payload ps_parm; + struct wakeup_cmd_payload wakeup_parm; + __le32 baudrate_parm; + + /* if vendor commands are received from user space (e.g. hcitool), update + * driver flags accordingly and ask driver to re-send the command to FW. + * In case the payload for any command does not match expected payload + * length, let the firmware and user space program handle it, or throw + * an error. + */ + if (bt_cb(skb)->pkt_type == HCI_COMMAND_PKT && !psdata->driver_sent_cmd) { + hdr = (struct hci_command_hdr *)skb->data; + if (hdr->plen != (skb->len - HCI_COMMAND_HDR_SIZE)) + return btnxpuart_queue_skb(hdev, skb); + + switch (__le16_to_cpu(hdr->opcode)) { + case HCI_NXP_AUTO_SLEEP_MODE: + if (hdr->plen == sizeof(ps_parm)) { + memcpy(&ps_parm, skb->data + HCI_COMMAND_HDR_SIZE, hdr->plen); + if (ps_parm.ps_cmd == BT_PS_ENABLE) + psdata->target_ps_mode = PS_MODE_ENABLE; + else if (ps_parm.ps_cmd == BT_PS_DISABLE) + psdata->target_ps_mode = PS_MODE_DISABLE; + psdata->c2h_ps_interval = __le16_to_cpu(ps_parm.c2h_ps_interval); + hci_cmd_sync_queue(hdev, send_ps_cmd, NULL, NULL); + goto free_skb; + } + break; + case HCI_NXP_WAKEUP_METHOD: + if (hdr->plen == sizeof(wakeup_parm)) { + memcpy(&wakeup_parm, skb->data + HCI_COMMAND_HDR_SIZE, hdr->plen); + psdata->c2h_wakeupmode = wakeup_parm.c2h_wakeupmode; + psdata->c2h_wakeup_gpio = wakeup_parm.c2h_wakeup_gpio; + psdata->h2c_wakeup_gpio = wakeup_parm.h2c_wakeup_gpio; + switch (wakeup_parm.h2c_wakeupmode) { + case BT_CTRL_WAKEUP_METHOD_DSR: + psdata->h2c_wakeupmode = WAKEUP_METHOD_DTR; + break; + case BT_CTRL_WAKEUP_METHOD_BREAK: + default: + psdata->h2c_wakeupmode = WAKEUP_METHOD_BREAK; + break; + } + hci_cmd_sync_queue(hdev, send_wakeup_method_cmd, NULL, NULL); + goto free_skb; + } + break; + case HCI_NXP_SET_OPER_SPEED: + if (hdr->plen == sizeof(baudrate_parm)) { + memcpy(&baudrate_parm, skb->data + HCI_COMMAND_HDR_SIZE, hdr->plen); + nxpdev->new_baudrate = __le32_to_cpu(baudrate_parm); + hci_cmd_sync_queue(hdev, nxp_set_baudrate_cmd, NULL, NULL); + goto free_skb; + } + break; + case HCI_NXP_IND_RESET: + if (hdr->plen == 1) { + hci_cmd_sync_queue(hdev, nxp_set_ind_reset, NULL, NULL); + goto free_skb; + } + break; + default: + break; + } + } + + return btnxpuart_queue_skb(hdev, skb); + +free_skb: + kfree_skb(skb); + return 0; +} + +static struct sk_buff *nxp_dequeue(void *data) +{ + struct btnxpuart_dev *nxpdev = (struct btnxpuart_dev *)data; + + ps_wakeup(nxpdev); + ps_start_timer(nxpdev); + return skb_dequeue(&nxpdev->txq); +} + +/* btnxpuart based on serdev */ +static void btnxpuart_tx_work(struct work_struct *work) +{ + struct btnxpuart_dev *nxpdev = container_of(work, struct btnxpuart_dev, + tx_work); + struct serdev_device *serdev = nxpdev->serdev; + struct hci_dev *hdev = nxpdev->hdev; + struct sk_buff *skb; + int len; + + while ((skb = nxp_dequeue(nxpdev))) { + len = serdev_device_write_buf(serdev, skb->data, skb->len); + hdev->stat.byte_tx += len; + + skb_pull(skb, len); + if (skb->len > 0) { + skb_queue_head(&nxpdev->txq, skb); + break; + } + + switch (hci_skb_pkt_type(skb)) { + case HCI_COMMAND_PKT: + hdev->stat.cmd_tx++; + break; + case HCI_ACLDATA_PKT: + hdev->stat.acl_tx++; + break; + case HCI_SCODATA_PKT: + hdev->stat.sco_tx++; + break; + } + + kfree_skb(skb); + } + clear_bit(BTNXPUART_TX_STATE_ACTIVE, &nxpdev->tx_state); +} + +static int btnxpuart_open(struct hci_dev *hdev) +{ + struct btnxpuart_dev *nxpdev = hci_get_drvdata(hdev); + int err = 0; + + err = serdev_device_open(nxpdev->serdev); + if (err) { + bt_dev_err(hdev, "Unable to open UART device %s", + dev_name(&nxpdev->serdev->dev)); + } else { + set_bit(BTNXPUART_SERDEV_OPEN, &nxpdev->tx_state); + } + return err; +} + +static int btnxpuart_close(struct hci_dev *hdev) +{ + struct btnxpuart_dev *nxpdev = hci_get_drvdata(hdev); + + serdev_device_close(nxpdev->serdev); + clear_bit(BTNXPUART_SERDEV_OPEN, &nxpdev->tx_state); + return 0; +} + +static int btnxpuart_flush(struct hci_dev *hdev) +{ + struct btnxpuart_dev *nxpdev = hci_get_drvdata(hdev); + + /* Flush any pending characters */ + serdev_device_write_flush(nxpdev->serdev); + skb_queue_purge(&nxpdev->txq); + + cancel_work_sync(&nxpdev->tx_work); + + kfree_skb(nxpdev->rx_skb); + nxpdev->rx_skb = NULL; + + return 0; +} + +static const struct h4_recv_pkt nxp_recv_pkts[] = { + { H4_RECV_ACL, .recv = hci_recv_frame }, + { H4_RECV_SCO, .recv = hci_recv_frame }, + { H4_RECV_EVENT, .recv = hci_recv_frame }, + { NXP_RECV_FW_REQ_V1, .recv = nxp_recv_fw_req_v1 }, + { NXP_RECV_CHIP_VER_V3, .recv = nxp_recv_chip_ver_v3 }, + { NXP_RECV_FW_REQ_V3, .recv = nxp_recv_fw_req_v3 }, +}; + +static bool is_valid_bootloader_signature(const u8 *data, size_t count) +{ + if ((*data == NXP_V1_FW_REQ_PKT && count == sizeof(struct v1_data_req) + 1) || + (*data == NXP_V3_FW_REQ_PKT && count == sizeof(struct v3_data_req) + 1) || + (*data == NXP_V3_CHIP_VER_PKT && count == sizeof(struct v3_start_ind) + 1)) + return true; + else + return false; +} + +static int btnxpuart_receive_buf(struct serdev_device *serdev, const u8 *data, + size_t count) +{ + struct btnxpuart_dev *nxpdev = serdev_device_get_drvdata(serdev); + + if (is_fw_downloading(nxpdev) && !is_valid_bootloader_signature(data, count)) { + /* Unknown bootloader signature, skip without returning error */ + return count; + } + + ps_start_timer(nxpdev); + + nxpdev->rx_skb = h4_recv_buf(nxpdev->hdev, nxpdev->rx_skb, data, count, + nxp_recv_pkts, ARRAY_SIZE(nxp_recv_pkts)); + if (IS_ERR(nxpdev->rx_skb)) { + int err = PTR_ERR(nxpdev->rx_skb); + + bt_dev_err(nxpdev->hdev, "Frame reassembly failed (%d)", err); + nxpdev->rx_skb = NULL; + return err; + } + nxpdev->hdev->stat.byte_rx += count; + return count; +} + +static void btnxpuart_write_wakeup(struct serdev_device *serdev) +{ + serdev_device_write_wakeup(serdev); +} + +static const struct serdev_device_ops btnxpuart_client_ops = { + .receive_buf = btnxpuart_receive_buf, + .write_wakeup = btnxpuart_write_wakeup, +}; + +static int nxp_serdev_probe(struct serdev_device *serdev) +{ + struct hci_dev *hdev; + struct btnxpuart_dev *nxpdev; + + nxpdev = devm_kzalloc(&serdev->dev, sizeof(*nxpdev), GFP_KERNEL); + if (!nxpdev) + return -ENOMEM; + + nxpdev->nxp_data = (struct btnxpuart_data *)device_get_match_data(&serdev->dev); + + nxpdev->serdev = serdev; + serdev_device_set_drvdata(serdev, nxpdev); + + serdev_device_set_client_ops(serdev, &btnxpuart_client_ops); + + INIT_WORK(&nxpdev->tx_work, btnxpuart_tx_work); + skb_queue_head_init(&nxpdev->txq); + + crc8_populate_msb(crc8_table, POLYNOMIAL8); + + /* Initialize and register HCI device */ + hdev = hci_alloc_dev(); + if (!hdev) { + dev_err(&serdev->dev, "Can't allocate HCI device\n"); + return -ENOMEM; + } + + nxpdev->hdev = hdev; + + hdev->bus = HCI_UART; + hci_set_drvdata(hdev, nxpdev); + + hdev->manufacturer = MANUFACTURER_NXP; + hdev->open = btnxpuart_open; + hdev->close = btnxpuart_close; + hdev->flush = btnxpuart_flush; + hdev->setup = nxp_setup; + hdev->send = nxp_enqueue; + SET_HCIDEV_DEV(hdev, &serdev->dev); + + if (hci_register_dev(hdev) < 0) { + dev_err(&serdev->dev, "Can't register HCI device\n"); + hci_free_dev(hdev); + return -ENODEV; + } + + ps_init_work(hdev); + ps_init_timer(hdev); + + return 0; +} + +static void nxp_serdev_remove(struct serdev_device *serdev) +{ + struct btnxpuart_dev *nxpdev = serdev_device_get_drvdata(serdev); + struct hci_dev *hdev = nxpdev->hdev; + + /* Restore FW baudrate to fw_init_baudrate if changed. + * This will ensure FW baudrate is in sync with + * driver baudrate in case this driver is re-inserted. + */ + if (nxpdev->current_baudrate != nxpdev->fw_init_baudrate) { + nxpdev->new_baudrate = nxpdev->fw_init_baudrate; + nxp_set_baudrate_cmd(hdev, NULL); + } + + ps_cancel_timer(nxpdev); + hci_unregister_dev(hdev); + hci_free_dev(hdev); +} + +static struct btnxpuart_data w8987_data = { + .fw_download_3M_baudrate = true, + .fw_name = FIRMWARE_W8987, +}; + +static struct btnxpuart_data w8997_data = { + .fw_download_3M_baudrate = false, + .fw_name = FIRMWARE_W8997, +}; + +static const struct of_device_id nxpuart_of_match_table[] = { + { .compatible = "nxp,88w8987-bt", .data = &w8987_data }, + { .compatible = "nxp,88w8997-bt", .data = &w8997_data }, + { } +}; +MODULE_DEVICE_TABLE(of, nxpuart_of_match_table); + +static struct serdev_device_driver nxp_serdev_driver = { + .probe = nxp_serdev_probe, + .remove = nxp_serdev_remove, + .driver = { + .name = "btnxpuart", + .of_match_table = of_match_ptr(nxpuart_of_match_table), + }, +}; + +module_serdev_device_driver(nxp_serdev_driver); + +MODULE_AUTHOR("Neeraj Sanjay Kale "); +MODULE_DESCRIPTION("NXP Bluetooth Serial driver"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3 From 60fd497c99b336e466db0aaeff44c7d12a1fc10a Mon Sep 17 00:00:00 2001 From: Paul Mackerras Date: Mon, 24 Apr 2023 09:02:32 +1000 Subject: MAINTAINERS: Remove PPP maintainer I am not currently maintaining the kernel PPP code, so remove my address from the MAINTAINERS entry for it. Signed-off-by: Paul Mackerras Signed-off-by: David S. Miller --- MAINTAINERS | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index eefcea6f8757..623a4e76fd09 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -16771,9 +16771,8 @@ F: include/uapi/linux/if_pppol2tp.h F: net/l2tp/l2tp_ppp.c PPP PROTOCOL DRIVERS AND COMPRESSORS -M: Paul Mackerras L: linux-ppp@vger.kernel.org -S: Maintained +S: Orphan F: drivers/net/ppp/ppp_* PPS SUPPORT -- cgit v1.2.3