mirror of https://gitee.com/openkylin/linux.git
Merge branch 'cpsw-switchdev'
Grygorii Strashko says: ==================== net: ethernet: ti: introduce new cpsw switchdev based driver Thank you All for review of v6. There are no significant changes in this version, just fixed comments to v6. --- v6 The major change in this version is DT bindings conversation to json-schema, and fixed other comments to v5. Also added patch to clean up ALE on init and netif restart. --- v5 The major part of work done in this iteration is rebasing on top of net-next with XDP series from Ivan Khoronzhuk [3], and enable XDP support in the new CPSW switchdev driver (it was little bit painful ;(). There are mostly no functional changes in new CPSW driver, just few fixes, sync with old driver and cleanups/optimizations. So, I've kept rest of cover letter unchanged. --- This series originally based on work [1][2] done by Ilias Apalodimas <ilias.apalodimas@linaro.org>. This the RFC v5 which introduces new CPSW switchdev based driver which is operating in dual-emac mode by default, thus working as 2 individual network interfaces. The Switch mode can be enabled by configuring devlink driver parameter "switch_mode" to 1/true: devlink dev param set platform/48484000.switch \ name switch_mode value 1 cmode runtime This can be done regardless of the state of Port's netdev devices - UP/DOWN, but Port's netdev devices have to be in UP before joining the bridge to avoid overwriting of bridge configuration as CPSW switch driver completely reloads its configuration when first Port changes its state to UP. When the both interfaces joined the bridge - CPSW switch driver will start marking packets with offload_fwd_mark flag unless "ale_bypass=0". All configuration is implemented via switchdev API. The previous solution of tracking both Ports joined the bridge (from netdevice_notifier) proved to be not correct as changing CPSW switch driver mode required cleanup of ALE table and CPSW settings which happens while second Port is joined bridge and as result configuration loaded by bridge for the first Port became corrupted. The introduction of the new CPSW switchdev based driver (cpsw_new.c) is split on two parts: Part 1 - basic dual-emac driver; Part 2 switchdev support. Such approach has simplified code development and testing alot. And, I hope, it will help with better review. patches #1 - 5: preparation patches which also moves common code to cpsw_priv.c patches #6 - 9: Introduce TI CPSW switch driver based on switchdev and new DT bindings patch #10: new CPSW switchdev driver documentation patch #11: adds DT nodes for new CPSW switchdev driver added for DRA7 SoC patch #12: adds DT nodes for new cpsw switchdev driver for am571x-idk board patch #13: enables build of TI CPSW driver Most of the contents of the previous cover-letter have been added in new driver documentation, so please refer to that for configuration, testing and future work. These patches can be found at (branch contains some additional patches required for testing on top of net-next): https://github.com/grygoriyS/linux.git branch: lkml-5.4-switch-tbd-v7 changes in v7: - patch 2: added check for devm_kmalloc_array() return value - patch 6: fixed comments changes in v6: https://lkml.org/lkml/2019/11/9/108 - DT bindings converted to json-schema - netdev initialization is split on creation and registration. The netdevs registration happens now at the end of the pobe. - reworked cpsw_set_pauseparam() to use PHYlib APIs. - other comments for v5 fixed v5: https://patchwork.kernel.org/cover/11208785/ - rebase on top of net-next with XDP series from Ivan Khoronzhuk [3], and enable XDP support in the new CPSW switchdev driver cpsw driver (tested XDP_DROP only) - sync with old cpsw driver - implement comments from Ivan Khoronzhuk and Rob Herring - fixed "NETDEV WATCHDOG: .." warning after interface after interface UP/DOWN, missed TX wake in cpsw_adjust_link() v4: https://patchwork.kernel.org/cover/11010523/ - finished split of common CPSW code - added devlink support - changed CPSW mode configuration approach: from netdevice_notifier to devlink parameter - refactor and clean up ALE changes which allows to modify VLANs/MDBs entries - added missed support for port QDISC_CBS and QDISC_MQPRIO - the CPSW is split on two parts: basic dual_mac driver and switchdev support - added missed callback .ndo_get_port_parent_id() - reworked ingress frames marking in switch mode (offload_fwd_mark) - applied comments from Andrew Lunn v3: https://lwn.net/Articles/786677/ Changes in v3: - alot of work done to split properly common code between legacy and switchdev CPSW drivers and clean up code - CPSW switchdev interface updated to the current LKML switchdev interface - actually new CPSW switchdev based driver introduced - optimized dual_mac mode in new driver. Main change is that in promiscuous mode P0_UNI_FLOOD (both ports) is enabled in addition to ALLMULTI (current port) instead of ALE_BYPASS. So, port in non promiscuous mode will keep possibility of mcast and vlan filtering. - changed bridge join sequnce: now switch mode will be enabled only when both ports joined the bridge. CPSW will be switched to dual_mac mode if any port leave bridge. ALE table is completly cleared and then refiled while switching to switch mode - this simplidies code a lot, but introduces some limitation to bridge setup sequence: ip link add name br0 type bridge ip link set dev br0 type bridge ageing_time 1000 ip link set dev br0 type bridge vlan_filtering 0 <- disable echo 0 > /sys/class/net/br0/bridge/default_vlan ip link set dev sw0p1 up <- add ports ip link set dev sw0p2 up ip link set dev sw0p1 master br0 ip link set dev sw0p2 master br0 echo 1 > /sys/class/net/br0/bridge/default_vlan <- enable ip link set dev br0 type bridge vlan_filtering 1 bridge vlan add dev br0 vid 1 pvid untagged self - STP tested with vlan_filtering 1/0. To make STP work I've had to set NO_SA_UPDATE for all slave ports (see comment in code). It also required to statically register STP mcast address {0x01, 0x80, 0xc2, 0x0, 0x0, 0x0}; - allowed build both TI_CPSW and TI_CPSW_SWITCHDEV drivers - PTP can be enabled on both ports in dual_mac mode [1] https://patchwork.ozlabs.org/cover/929367/ [2] https://patches.linaro.org/cover/136709/ [3] https://patchwork.kernel.org/cover/11035813/ ==================== Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
commit
1f12177b32
|
@ -0,0 +1,240 @@
|
|||
# SPDX-License-Identifier: GPL-2.0
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/net/ti,cpsw-switch.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: TI SoC Ethernet Switch Controller (CPSW) Device Tree Bindings
|
||||
|
||||
maintainers:
|
||||
- Grygorii Strashko <grygorii.strashko@ti.com>
|
||||
- Sekhar Nori <nsekhar@ti.com>
|
||||
|
||||
description:
|
||||
The 3-port switch gigabit ethernet subsystem provides ethernet packet
|
||||
communication and can be configured as an ethernet switch. It provides the
|
||||
gigabit media independent interface (GMII),reduced gigabit media
|
||||
independent interface (RGMII), reduced media independent interface (RMII),
|
||||
the management data input output (MDIO) for physical layer device (PHY)
|
||||
management.
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
oneOf:
|
||||
- const: ti,cpsw-switch
|
||||
- items:
|
||||
- const: ti,am335x-cpsw-switch
|
||||
- const: ti,cpsw-switch
|
||||
- items:
|
||||
- const: ti,am4372-cpsw-switch
|
||||
- const: ti,cpsw-switch
|
||||
- items:
|
||||
- const: ti,dra7-cpsw-switch
|
||||
- const: ti,cpsw-switch
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
description:
|
||||
The physical base address and size of full the CPSW module IO range
|
||||
|
||||
ranges: true
|
||||
|
||||
clocks:
|
||||
maxItems: 1
|
||||
description: CPSW functional clock
|
||||
|
||||
clock-names:
|
||||
maxItems: 1
|
||||
items:
|
||||
- const: fck
|
||||
|
||||
interrupts:
|
||||
items:
|
||||
- description: RX_THRESH interrupt
|
||||
- description: RX interrupt
|
||||
- description: TX interrupt
|
||||
- description: MISC interrupt
|
||||
|
||||
interrupt-names:
|
||||
items:
|
||||
- const: "rx_thresh"
|
||||
- const: "rx"
|
||||
- const: "tx"
|
||||
- const: "misc"
|
||||
|
||||
pinctrl-names: true
|
||||
|
||||
syscon:
|
||||
$ref: /schemas/types.yaml#definitions/phandle
|
||||
description:
|
||||
Phandle to the system control device node which provides access to
|
||||
efuse IO range with MAC addresses
|
||||
|
||||
|
||||
ethernet-ports:
|
||||
type: object
|
||||
properties:
|
||||
'#address-cells':
|
||||
const: 1
|
||||
'#size-cells':
|
||||
const: 0
|
||||
|
||||
patternProperties:
|
||||
"^port@[0-9]+$":
|
||||
type: object
|
||||
minItems: 1
|
||||
maxItems: 2
|
||||
description: CPSW external ports
|
||||
|
||||
allOf:
|
||||
- $ref: ethernet-controller.yaml#
|
||||
|
||||
properties:
|
||||
reg:
|
||||
maxItems: 1
|
||||
enum: [1, 2]
|
||||
description: CPSW port number
|
||||
|
||||
phys:
|
||||
$ref: /schemas/types.yaml#definitions/phandle-array
|
||||
maxItems: 1
|
||||
description: phandle on phy-gmii-sel PHY
|
||||
|
||||
label:
|
||||
$ref: /schemas/types.yaml#/definitions/string-array
|
||||
maxItems: 1
|
||||
description: label associated with this port
|
||||
|
||||
ti,dual-emac-pvid:
|
||||
$ref: /schemas/types.yaml#/definitions/uint32
|
||||
maxItems: 1
|
||||
minimum: 1
|
||||
maximum: 1024
|
||||
description:
|
||||
Specifies default PORT VID to be used to segregate
|
||||
ports. Default value - CPSW port number.
|
||||
|
||||
required:
|
||||
- reg
|
||||
- phys
|
||||
|
||||
mdio:
|
||||
type: object
|
||||
allOf:
|
||||
- $ref: "ti,davinci-mdio.yaml#"
|
||||
description:
|
||||
CPSW MDIO bus.
|
||||
|
||||
cpts:
|
||||
type: object
|
||||
description:
|
||||
The Common Platform Time Sync (CPTS) module
|
||||
|
||||
properties:
|
||||
clocks:
|
||||
maxItems: 1
|
||||
description: CPTS reference clock
|
||||
|
||||
clock-names:
|
||||
maxItems: 1
|
||||
items:
|
||||
- const: cpts
|
||||
|
||||
cpts_clock_mult:
|
||||
$ref: /schemas/types.yaml#/definitions/uint32
|
||||
description:
|
||||
Numerator to convert input clock ticks into ns
|
||||
|
||||
cpts_clock_shift:
|
||||
$ref: /schemas/types.yaml#/definitions/uint32
|
||||
description:
|
||||
Denominator to convert input clock ticks into ns.
|
||||
Mult and shift will be calculated basing on CPTS rftclk frequency if
|
||||
both cpts_clock_shift and cpts_clock_mult properties are not provided.
|
||||
|
||||
required:
|
||||
- clocks
|
||||
- clock-names
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
- ranges
|
||||
- clocks
|
||||
- clock-names
|
||||
- interrupts
|
||||
- interrupt-names
|
||||
- '#address-cells'
|
||||
- '#size-cells'
|
||||
|
||||
examples:
|
||||
- |
|
||||
#include <dt-bindings/interrupt-controller/irq.h>
|
||||
#include <dt-bindings/interrupt-controller/arm-gic.h>
|
||||
#include <dt-bindings/clock/dra7.h>
|
||||
|
||||
mac_sw: switch@0 {
|
||||
compatible = "ti,dra7-cpsw-switch","ti,cpsw-switch";
|
||||
reg = <0x0 0x4000>;
|
||||
ranges = <0 0 0x4000>;
|
||||
clocks = <&gmac_main_clk>;
|
||||
clock-names = "fck";
|
||||
#address-cells = <1>;
|
||||
#size-cells = <1>;
|
||||
syscon = <&scm_conf>;
|
||||
inctrl-names = "default", "sleep";
|
||||
|
||||
interrupts = <GIC_SPI 334 IRQ_TYPE_LEVEL_HIGH>,
|
||||
<GIC_SPI 335 IRQ_TYPE_LEVEL_HIGH>,
|
||||
<GIC_SPI 336 IRQ_TYPE_LEVEL_HIGH>,
|
||||
<GIC_SPI 337 IRQ_TYPE_LEVEL_HIGH>;
|
||||
interrupt-names = "rx_thresh", "rx", "tx", "misc";
|
||||
|
||||
ethernet-ports {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
cpsw_port1: port@1 {
|
||||
reg = <1>;
|
||||
label = "port1";
|
||||
mac-address = [ 00 00 00 00 00 00 ];
|
||||
phys = <&phy_gmii_sel 1>;
|
||||
phy-handle = <ðphy0_sw>;
|
||||
phy-mode = "rgmii";
|
||||
ti,dual_emac_pvid = <1>;
|
||||
};
|
||||
|
||||
cpsw_port2: port@2 {
|
||||
reg = <2>;
|
||||
label = "wan";
|
||||
mac-address = [ 00 00 00 00 00 00 ];
|
||||
phys = <&phy_gmii_sel 2>;
|
||||
phy-handle = <ðphy1_sw>;
|
||||
phy-mode = "rgmii";
|
||||
ti,dual_emac_pvid = <2>;
|
||||
};
|
||||
};
|
||||
|
||||
davinci_mdio_sw: mdio@1000 {
|
||||
compatible = "ti,cpsw-mdio","ti,davinci_mdio";
|
||||
reg = <0x1000 0x100>;
|
||||
clocks = <&gmac_clkctrl DRA7_GMAC_GMAC_CLKCTRL 0>;
|
||||
clock-names = "fck";
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
bus_freq = <1000000>;
|
||||
|
||||
ethphy0_sw: ethernet-phy@0 {
|
||||
reg = <0>;
|
||||
};
|
||||
|
||||
ethphy1_sw: ethernet-phy@1 {
|
||||
reg = <1>;
|
||||
};
|
||||
};
|
||||
|
||||
cpts {
|
||||
clocks = <&gmac_clkctrl DRA7_GMAC_GMAC_CLKCTRL 25>;
|
||||
clock-names = "cpts";
|
||||
};
|
||||
};
|
|
@ -0,0 +1,209 @@
|
|||
* Texas Instruments CPSW switchdev based ethernet driver 2.0
|
||||
|
||||
- Port renaming
|
||||
On older udev versions renaming of ethX to swXpY will not be automatically
|
||||
supported
|
||||
In order to rename via udev:
|
||||
ip -d link show dev sw0p1 | grep switchid
|
||||
|
||||
SUBSYSTEM=="net", ACTION=="add", ATTR{phys_switch_id}==<switchid>, \
|
||||
ATTR{phys_port_name}!="", NAME="sw0$attr{phys_port_name}"
|
||||
|
||||
|
||||
====================
|
||||
# Dual mac mode
|
||||
====================
|
||||
- The new (cpsw_new.c) driver is operating in dual-emac mode by default, thus
|
||||
working as 2 individual network interfaces. Main differences from legacy CPSW
|
||||
driver are:
|
||||
- optimized promiscuous mode: The P0_UNI_FLOOD (both ports) is enabled in
|
||||
addition to ALLMULTI (current port) instead of ALE_BYPASS.
|
||||
So, Ports in promiscuous mode will keep possibility of mcast and vlan filtering,
|
||||
which is provides significant benefits when ports are joined to the same bridge,
|
||||
but without enabling "switch" mode, or to different bridges.
|
||||
- learning disabled on ports as it make not too much sense for
|
||||
segregated ports - no forwarding in HW.
|
||||
- enabled basic support for devlink.
|
||||
|
||||
devlink dev show
|
||||
platform/48484000.switch
|
||||
|
||||
devlink dev param show
|
||||
platform/48484000.switch:
|
||||
name switch_mode type driver-specific
|
||||
values:
|
||||
cmode runtime value false
|
||||
name ale_bypass type driver-specific
|
||||
values:
|
||||
cmode runtime value false
|
||||
|
||||
Devlink configuration parameters
|
||||
====================
|
||||
See Documentation/networking/devlink-params-ti-cpsw-switch.txt
|
||||
|
||||
====================
|
||||
# Bridging in dual mac mode
|
||||
====================
|
||||
The dual_mac mode requires two vids to be reserved for internal purposes,
|
||||
which, by default, equal CPSW Port numbers. As result, bridge has to be
|
||||
configured in vlan unaware mode or default_pvid has to be adjusted.
|
||||
|
||||
ip link add name br0 type bridge
|
||||
ip link set dev br0 type bridge vlan_filtering 0
|
||||
echo 0 > /sys/class/net/br0/bridge/default_pvid
|
||||
ip link set dev sw0p1 master br0
|
||||
ip link set dev sw0p2 master br0
|
||||
- or -
|
||||
ip link add name br0 type bridge
|
||||
ip link set dev br0 type bridge vlan_filtering 0
|
||||
echo 100 > /sys/class/net/br0/bridge/default_pvid
|
||||
ip link set dev br0 type bridge vlan_filtering 1
|
||||
ip link set dev sw0p1 master br0
|
||||
ip link set dev sw0p2 master br0
|
||||
|
||||
====================
|
||||
# Enabling "switch"
|
||||
====================
|
||||
The Switch mode can be enabled by configuring devlink driver parameter
|
||||
"switch_mode" to 1/true:
|
||||
devlink dev param set platform/48484000.switch \
|
||||
name switch_mode value 1 cmode runtime
|
||||
|
||||
This can be done regardless of the state of Port's netdev devices - UP/DOWN, but
|
||||
Port's netdev devices have to be in UP before joining to the bridge to avoid
|
||||
overwriting of bridge configuration as CPSW switch driver copletly reloads its
|
||||
configuration when first Port changes its state to UP.
|
||||
|
||||
When the both interfaces joined the bridge - CPSW switch driver will enable
|
||||
marking packets with offload_fwd_mark flag unless "ale_bypass=0"
|
||||
|
||||
All configuration is implemented via switchdev API.
|
||||
|
||||
====================
|
||||
# Bridge setup
|
||||
====================
|
||||
devlink dev param set platform/48484000.switch \
|
||||
name switch_mode value 1 cmode runtime
|
||||
|
||||
ip link add name br0 type bridge
|
||||
ip link set dev br0 type bridge ageing_time 1000
|
||||
ip link set dev sw0p1 up
|
||||
ip link set dev sw0p2 up
|
||||
ip link set dev sw0p1 master br0
|
||||
ip link set dev sw0p2 master br0
|
||||
[*] bridge vlan add dev br0 vid 1 pvid untagged self
|
||||
|
||||
[*] if vlan_filtering=1. where default_pvid=1
|
||||
|
||||
=================
|
||||
# On/off STP
|
||||
=================
|
||||
ip link set dev BRDEV type bridge stp_state 1/0
|
||||
|
||||
Note. Steps [*] are mandatory.
|
||||
|
||||
====================
|
||||
# VLAN configuration
|
||||
====================
|
||||
bridge vlan add dev br0 vid 1 pvid untagged self <---- add cpu port to VLAN 1
|
||||
|
||||
Note. This step is mandatory for bridge/default_pvid.
|
||||
|
||||
=================
|
||||
# Add extra VLANs
|
||||
=================
|
||||
1. untagged:
|
||||
bridge vlan add dev sw0p1 vid 100 pvid untagged master
|
||||
bridge vlan add dev sw0p2 vid 100 pvid untagged master
|
||||
bridge vlan add dev br0 vid 100 pvid untagged self <---- Add cpu port to VLAN100
|
||||
|
||||
2. tagged:
|
||||
bridge vlan add dev sw0p1 vid 100 master
|
||||
bridge vlan add dev sw0p2 vid 100 master
|
||||
bridge vlan add dev br0 vid 100 pvid tagged self <---- Add cpu port to VLAN100
|
||||
|
||||
====
|
||||
FDBs
|
||||
====
|
||||
FDBs are automatically added on the appropriate switch port upon detection
|
||||
|
||||
Manually adding FDBs:
|
||||
bridge fdb add aa:bb:cc:dd:ee:ff dev sw0p1 master vlan 100
|
||||
bridge fdb add aa:bb:cc:dd:ee:fe dev sw0p2 master <---- Add on all VLANs
|
||||
|
||||
====
|
||||
MDBs
|
||||
====
|
||||
MDBs are automatically added on the appropriate switch port upon detection
|
||||
|
||||
Manually adding MDBs:
|
||||
bridge mdb add dev br0 port sw0p1 grp 239.1.1.1 permanent vid 100
|
||||
bridge mdb add dev br0 port sw0p1 grp 239.1.1.1 permanent <---- Add on all VLANs
|
||||
|
||||
==================
|
||||
Multicast flooding
|
||||
==================
|
||||
CPU port mcast_flooding is always on
|
||||
|
||||
Turning flooding on/off on swithch ports:
|
||||
bridge link set dev sw0p1 mcast_flood on/off
|
||||
|
||||
==================
|
||||
Access and Trunk port
|
||||
==================
|
||||
bridge vlan add dev sw0p1 vid 100 pvid untagged master
|
||||
bridge vlan add dev sw0p2 vid 100 master
|
||||
|
||||
|
||||
bridge vlan add dev br0 vid 100 self
|
||||
ip link add link br0 name br0.100 type vlan id 100
|
||||
|
||||
Note. Setting PVID on Bridge device itself working only for
|
||||
default VLAN (default_pvid).
|
||||
|
||||
=====================
|
||||
NFS
|
||||
=====================
|
||||
The only way for NFS to work is by chrooting to a minimal environment when
|
||||
switch configuration that will affect connectivity is needed.
|
||||
Assuming you are booting NFS with eth1 interface(the script is hacky and
|
||||
it's just there to prove NFS is doable).
|
||||
|
||||
setup.sh:
|
||||
#!/bin/sh
|
||||
mkdir proc
|
||||
mount -t proc none /proc
|
||||
ifconfig br0 > /dev/null
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Setting up bridge"
|
||||
ip link add name br0 type bridge
|
||||
ip link set dev br0 type bridge ageing_time 1000
|
||||
ip link set dev br0 type bridge vlan_filtering 1
|
||||
|
||||
ip link set eth1 down
|
||||
ip link set eth1 name sw0p1
|
||||
ip link set dev sw0p1 up
|
||||
ip link set dev sw0p2 up
|
||||
ip link set dev sw0p2 master br0
|
||||
ip link set dev sw0p1 master br0
|
||||
bridge vlan add dev br0 vid 1 pvid untagged self
|
||||
ifconfig sw0p1 0.0.0.0
|
||||
udhchc -i br0
|
||||
fi
|
||||
umount /proc
|
||||
|
||||
run_nfs.sh:
|
||||
#!/bin/sh
|
||||
mkdir /tmp/root/bin -p
|
||||
mkdir /tmp/root/lib -p
|
||||
|
||||
cp -r /lib/ /tmp/root/
|
||||
cp -r /bin/ /tmp/root/
|
||||
cp /sbin/ip /tmp/root/bin
|
||||
cp /sbin/bridge /tmp/root/bin
|
||||
cp /sbin/ifconfig /tmp/root/bin
|
||||
cp /sbin/udhcpc /tmp/root/bin
|
||||
cp /path/to/setup.sh /tmp/root/bin
|
||||
chroot /tmp/root/ busybox sh /bin/setup.sh
|
||||
|
||||
run ./run_nfs.sh
|
|
@ -0,0 +1,10 @@
|
|||
ale_bypass [DEVICE, DRIVER-SPECIFIC]
|
||||
Allows to enable ALE_CONTROL(4).BYPASS mode for debug purposes.
|
||||
All packets will be sent to the Host port only if enabled.
|
||||
Type: bool
|
||||
Configuration mode: runtime
|
||||
|
||||
switch_mode [DEVICE, DRIVER-SPECIFIC]
|
||||
Enable switch mode
|
||||
Type: bool
|
||||
Configuration mode: runtime
|
|
@ -186,3 +186,30 @@ &mmc2 {
|
|||
pinctrl-1 = <&mmc2_pins_hs>;
|
||||
pinctrl-2 = <&mmc2_pins_ddr_rev20 &mmc2_iodelay_ddr_conf>;
|
||||
};
|
||||
|
||||
&mac_sw {
|
||||
pinctrl-names = "default", "sleep";
|
||||
status = "okay";
|
||||
};
|
||||
|
||||
&cpsw_port1 {
|
||||
phy-handle = <ðphy0_sw>;
|
||||
phy-mode = "rgmii";
|
||||
ti,dual-emac-pvid = <1>;
|
||||
};
|
||||
|
||||
&cpsw_port2 {
|
||||
phy-handle = <ðphy1_sw>;
|
||||
phy-mode = "rgmii";
|
||||
ti,dual-emac-pvid = <2>;
|
||||
};
|
||||
|
||||
&davinci_mdio_sw {
|
||||
ethphy0_sw: ethernet-phy@0 {
|
||||
reg = <0>;
|
||||
};
|
||||
|
||||
ethphy1_sw: ethernet-phy@1 {
|
||||
reg = <1>;
|
||||
};
|
||||
};
|
||||
|
|
|
@ -27,3 +27,8 @@ &mmc2 {
|
|||
pinctrl-1 = <&mmc2_pins_hs>;
|
||||
pinctrl-2 = <&mmc2_pins_ddr_rev20>;
|
||||
};
|
||||
|
||||
&mac {
|
||||
status = "okay";
|
||||
dual_emac;
|
||||
};
|
||||
|
|
|
@ -35,3 +35,8 @@ &mmc2 {
|
|||
pinctrl-1 = <&mmc2_pins_default>;
|
||||
pinctrl-2 = <&mmc2_pins_default>;
|
||||
};
|
||||
|
||||
&mac {
|
||||
status = "okay";
|
||||
dual_emac;
|
||||
};
|
||||
|
|
|
@ -363,11 +363,6 @@ &rtc {
|
|||
ext-clk-src;
|
||||
};
|
||||
|
||||
&mac {
|
||||
status = "okay";
|
||||
dual_emac;
|
||||
};
|
||||
|
||||
&cpsw_emac0 {
|
||||
phy-handle = <ðphy0>;
|
||||
phy-mode = "rgmii";
|
||||
|
|
|
@ -3079,6 +3079,58 @@ cpsw_emac1: slave@300 {
|
|||
phys = <&phy_gmii_sel 2>;
|
||||
};
|
||||
};
|
||||
|
||||
mac_sw: switch@0 {
|
||||
compatible = "ti,dra7-cpsw-switch","ti,cpsw-switch";
|
||||
reg = <0x0 0x4000>;
|
||||
ranges = <0 0 0x4000>;
|
||||
clocks = <&gmac_main_clk>;
|
||||
clock-names = "fck";
|
||||
#address-cells = <1>;
|
||||
#size-cells = <1>;
|
||||
syscon = <&scm_conf>;
|
||||
status = "disabled";
|
||||
|
||||
interrupts = <GIC_SPI 334 IRQ_TYPE_LEVEL_HIGH>,
|
||||
<GIC_SPI 335 IRQ_TYPE_LEVEL_HIGH>,
|
||||
<GIC_SPI 336 IRQ_TYPE_LEVEL_HIGH>,
|
||||
<GIC_SPI 337 IRQ_TYPE_LEVEL_HIGH>;
|
||||
interrupt-names = "rx_thresh", "rx", "tx", "misc";
|
||||
|
||||
ethernet-ports {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
cpsw_port1: port@1 {
|
||||
reg = <1>;
|
||||
label = "port1";
|
||||
mac-address = [ 00 00 00 00 00 00 ];
|
||||
phys = <&phy_gmii_sel 1>;
|
||||
};
|
||||
|
||||
cpsw_port2: port@2 {
|
||||
reg = <2>;
|
||||
label = "port2";
|
||||
mac-address = [ 00 00 00 00 00 00 ];
|
||||
phys = <&phy_gmii_sel 2>;
|
||||
};
|
||||
};
|
||||
|
||||
davinci_mdio_sw: mdio@1000 {
|
||||
compatible = "ti,cpsw-mdio","ti,davinci_mdio";
|
||||
clocks = <&gmac_main_clk>;
|
||||
clock-names = "fck";
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
bus_freq = <1000000>;
|
||||
reg = <0x1000 0x100>;
|
||||
};
|
||||
|
||||
cpts {
|
||||
clocks = <&gmac_clkctrl DRA7_GMAC_GMAC_CLKCTRL 25>;
|
||||
clock-names = "cpts";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
|
@ -554,3 +554,4 @@ CONFIG_DEBUG_INFO_DWARF4=y
|
|||
CONFIG_MAGIC_SYSRQ=y
|
||||
CONFIG_SCHEDSTATS=y
|
||||
# CONFIG_DEBUG_BUGVERBOSE is not set
|
||||
CONFIG_TI_CPSW_SWITCHDEV=y
|
||||
|
|
|
@ -59,9 +59,24 @@ config TI_CPSW
|
|||
To compile this driver as a module, choose M here: the module
|
||||
will be called cpsw.
|
||||
|
||||
config TI_CPSW_SWITCHDEV
|
||||
tristate "TI CPSW Switch Support with switchdev"
|
||||
depends on ARCH_DAVINCI || ARCH_OMAP2PLUS || COMPILE_TEST
|
||||
select NET_SWITCHDEV
|
||||
select TI_DAVINCI_MDIO
|
||||
select MFD_SYSCON
|
||||
select REGMAP
|
||||
select NET_DEVLINK
|
||||
imply PHY_TI_GMII_SEL
|
||||
help
|
||||
This driver supports TI's CPSW Ethernet Switch.
|
||||
|
||||
To compile this driver as a module, choose M here: the module
|
||||
will be called cpsw_new.
|
||||
|
||||
config TI_CPTS
|
||||
bool "TI Common Platform Time Sync (CPTS) Support"
|
||||
depends on TI_CPSW || TI_KEYSTONE_NETCP || COMPILE_TEST
|
||||
depends on TI_CPSW || TI_KEYSTONE_NETCP || TI_CPSW_SWITCHDEV || COMPILE_TEST
|
||||
depends on COMMON_CLK
|
||||
depends on POSIX_TIMERS
|
||||
---help---
|
||||
|
@ -73,7 +88,7 @@ config TI_CPTS
|
|||
config TI_CPTS_MOD
|
||||
tristate
|
||||
depends on TI_CPTS
|
||||
default y if TI_CPSW=y || TI_KEYSTONE_NETCP=y
|
||||
default y if TI_CPSW=y || TI_KEYSTONE_NETCP=y || TI_CPSW_SWITCHDEV=y
|
||||
select NET_PTP_CLASSIFY
|
||||
imply PTP_1588_CLOCK
|
||||
default m
|
||||
|
|
|
@ -15,6 +15,8 @@ obj-$(CONFIG_TI_CPSW_PHY_SEL) += cpsw-phy-sel.o
|
|||
obj-$(CONFIG_TI_CPTS_MOD) += cpts.o
|
||||
obj-$(CONFIG_TI_CPSW) += ti_cpsw.o
|
||||
ti_cpsw-y := cpsw.o davinci_cpdma.o cpsw_ale.o cpsw_priv.o cpsw_sl.o cpsw_ethtool.o
|
||||
obj-$(CONFIG_TI_CPSW_SWITCHDEV) += ti_cpsw_new.o
|
||||
ti_cpsw_new-y := cpsw_switchdev.o cpsw_new.o davinci_cpdma.o cpsw_ale.o cpsw_sl.o cpsw_priv.o cpsw_ethtool.o
|
||||
|
||||
obj-$(CONFIG_TI_KEYSTONE_NETCP) += keystone_netcp.o
|
||||
keystone_netcp-y := netcp_core.o cpsw_ale.o
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -5,6 +5,8 @@
|
|||
* Copyright (C) 2012 Texas Instruments
|
||||
*
|
||||
*/
|
||||
#include <linux/bitmap.h>
|
||||
#include <linux/if_vlan.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
@ -382,6 +384,7 @@ int cpsw_ale_del_mcast(struct cpsw_ale *ale, const u8 *addr, int port_mask,
|
|||
int flags, u16 vid)
|
||||
{
|
||||
u32 ale_entry[ALE_ENTRY_WORDS] = {0, 0, 0};
|
||||
int mcast_members;
|
||||
int idx;
|
||||
|
||||
idx = cpsw_ale_match_addr(ale, addr, (flags & ALE_VLAN) ? vid : 0);
|
||||
|
@ -390,11 +393,15 @@ int cpsw_ale_del_mcast(struct cpsw_ale *ale, const u8 *addr, int port_mask,
|
|||
|
||||
cpsw_ale_read(ale, idx, ale_entry);
|
||||
|
||||
if (port_mask)
|
||||
cpsw_ale_set_port_mask(ale_entry, port_mask,
|
||||
if (port_mask) {
|
||||
mcast_members = cpsw_ale_get_port_mask(ale_entry,
|
||||
ale->port_mask_bits);
|
||||
mcast_members &= ~port_mask;
|
||||
cpsw_ale_set_port_mask(ale_entry, mcast_members,
|
||||
ale->port_mask_bits);
|
||||
else
|
||||
} else {
|
||||
cpsw_ale_set_entry_type(ale_entry, ALE_TYPE_FREE);
|
||||
}
|
||||
|
||||
cpsw_ale_write(ale, idx, ale_entry);
|
||||
return 0;
|
||||
|
@ -415,7 +422,18 @@ static void cpsw_ale_set_vlan_mcast(struct cpsw_ale *ale, u32 *ale_entry,
|
|||
writel(unreg_mcast, ale->params.ale_regs + ALE_VLAN_MASK_MUX(idx));
|
||||
}
|
||||
|
||||
int cpsw_ale_add_vlan(struct cpsw_ale *ale, u16 vid, int port, int untag,
|
||||
static void cpsw_ale_set_vlan_untag(struct cpsw_ale *ale, u32 *ale_entry,
|
||||
u16 vid, int untag_mask)
|
||||
{
|
||||
cpsw_ale_set_vlan_untag_force(ale_entry,
|
||||
untag_mask, ale->vlan_field_bits);
|
||||
if (untag_mask & ALE_PORT_HOST)
|
||||
bitmap_set(ale->p0_untag_vid_mask, vid, 1);
|
||||
else
|
||||
bitmap_clear(ale->p0_untag_vid_mask, vid, 1);
|
||||
}
|
||||
|
||||
int cpsw_ale_add_vlan(struct cpsw_ale *ale, u16 vid, int port_mask, int untag,
|
||||
int reg_mcast, int unreg_mcast)
|
||||
{
|
||||
u32 ale_entry[ALE_ENTRY_WORDS] = {0, 0, 0};
|
||||
|
@ -427,8 +445,8 @@ int cpsw_ale_add_vlan(struct cpsw_ale *ale, u16 vid, int port, int untag,
|
|||
|
||||
cpsw_ale_set_entry_type(ale_entry, ALE_TYPE_VLAN);
|
||||
cpsw_ale_set_vlan_id(ale_entry, vid);
|
||||
cpsw_ale_set_vlan_untag(ale, ale_entry, vid, untag);
|
||||
|
||||
cpsw_ale_set_vlan_untag_force(ale_entry, untag, ale->vlan_field_bits);
|
||||
if (!ale->params.nu_switch_ale) {
|
||||
cpsw_ale_set_vlan_reg_mcast(ale_entry, reg_mcast,
|
||||
ale->vlan_field_bits);
|
||||
|
@ -437,7 +455,8 @@ int cpsw_ale_add_vlan(struct cpsw_ale *ale, u16 vid, int port, int untag,
|
|||
} else {
|
||||
cpsw_ale_set_vlan_mcast(ale, ale_entry, reg_mcast, unreg_mcast);
|
||||
}
|
||||
cpsw_ale_set_vlan_member_list(ale_entry, port, ale->vlan_field_bits);
|
||||
cpsw_ale_set_vlan_member_list(ale_entry, port_mask,
|
||||
ale->vlan_field_bits);
|
||||
|
||||
if (idx < 0)
|
||||
idx = cpsw_ale_match_free(ale);
|
||||
|
@ -450,6 +469,41 @@ int cpsw_ale_add_vlan(struct cpsw_ale *ale, u16 vid, int port, int untag,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static void cpsw_ale_del_vlan_modify(struct cpsw_ale *ale, u32 *ale_entry,
|
||||
u16 vid, int port_mask)
|
||||
{
|
||||
int reg_mcast, unreg_mcast;
|
||||
int members, untag;
|
||||
|
||||
members = cpsw_ale_get_vlan_member_list(ale_entry,
|
||||
ale->vlan_field_bits);
|
||||
members &= ~port_mask;
|
||||
|
||||
untag = cpsw_ale_get_vlan_untag_force(ale_entry,
|
||||
ale->vlan_field_bits);
|
||||
reg_mcast = cpsw_ale_get_vlan_reg_mcast(ale_entry,
|
||||
ale->vlan_field_bits);
|
||||
unreg_mcast = cpsw_ale_get_vlan_unreg_mcast(ale_entry,
|
||||
ale->vlan_field_bits);
|
||||
untag &= members;
|
||||
reg_mcast &= members;
|
||||
unreg_mcast &= members;
|
||||
|
||||
cpsw_ale_set_vlan_untag(ale, ale_entry, vid, untag);
|
||||
|
||||
if (!ale->params.nu_switch_ale) {
|
||||
cpsw_ale_set_vlan_reg_mcast(ale_entry, reg_mcast,
|
||||
ale->vlan_field_bits);
|
||||
cpsw_ale_set_vlan_unreg_mcast(ale_entry, unreg_mcast,
|
||||
ale->vlan_field_bits);
|
||||
} else {
|
||||
cpsw_ale_set_vlan_mcast(ale, ale_entry, reg_mcast,
|
||||
unreg_mcast);
|
||||
}
|
||||
cpsw_ale_set_vlan_member_list(ale_entry, members,
|
||||
ale->vlan_field_bits);
|
||||
}
|
||||
|
||||
int cpsw_ale_del_vlan(struct cpsw_ale *ale, u16 vid, int port_mask)
|
||||
{
|
||||
u32 ale_entry[ALE_ENTRY_WORDS] = {0, 0, 0};
|
||||
|
@ -461,16 +515,83 @@ int cpsw_ale_del_vlan(struct cpsw_ale *ale, u16 vid, int port_mask)
|
|||
|
||||
cpsw_ale_read(ale, idx, ale_entry);
|
||||
|
||||
if (port_mask)
|
||||
cpsw_ale_set_vlan_member_list(ale_entry, port_mask,
|
||||
ale->vlan_field_bits);
|
||||
else
|
||||
if (port_mask) {
|
||||
cpsw_ale_del_vlan_modify(ale, ale_entry, vid, port_mask);
|
||||
} else {
|
||||
cpsw_ale_set_vlan_untag(ale, ale_entry, vid, 0);
|
||||
cpsw_ale_set_entry_type(ale_entry, ALE_TYPE_FREE);
|
||||
}
|
||||
|
||||
cpsw_ale_write(ale, idx, ale_entry);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cpsw_ale_vlan_add_modify(struct cpsw_ale *ale, u16 vid, int port_mask,
|
||||
int untag_mask, int reg_mask, int unreg_mask)
|
||||
{
|
||||
u32 ale_entry[ALE_ENTRY_WORDS] = {0, 0, 0};
|
||||
int reg_mcast_members, unreg_mcast_members;
|
||||
int vlan_members, untag_members;
|
||||
int idx, ret = 0;
|
||||
|
||||
idx = cpsw_ale_match_vlan(ale, vid);
|
||||
if (idx >= 0)
|
||||
cpsw_ale_read(ale, idx, ale_entry);
|
||||
|
||||
vlan_members = cpsw_ale_get_vlan_member_list(ale_entry,
|
||||
ale->vlan_field_bits);
|
||||
reg_mcast_members = cpsw_ale_get_vlan_reg_mcast(ale_entry,
|
||||
ale->vlan_field_bits);
|
||||
unreg_mcast_members =
|
||||
cpsw_ale_get_vlan_unreg_mcast(ale_entry,
|
||||
ale->vlan_field_bits);
|
||||
untag_members = cpsw_ale_get_vlan_untag_force(ale_entry,
|
||||
ale->vlan_field_bits);
|
||||
|
||||
vlan_members |= port_mask;
|
||||
untag_members = (untag_members & ~port_mask) | untag_mask;
|
||||
reg_mcast_members = (reg_mcast_members & ~port_mask) | reg_mask;
|
||||
unreg_mcast_members = (unreg_mcast_members & ~port_mask) | unreg_mask;
|
||||
|
||||
ret = cpsw_ale_add_vlan(ale, vid, vlan_members, untag_members,
|
||||
reg_mcast_members, unreg_mcast_members);
|
||||
if (ret) {
|
||||
dev_err(ale->params.dev, "Unable to add vlan\n");
|
||||
return ret;
|
||||
}
|
||||
dev_dbg(ale->params.dev, "port mask 0x%x untag 0x%x\n", vlan_members,
|
||||
untag_mask);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void cpsw_ale_set_unreg_mcast(struct cpsw_ale *ale, int unreg_mcast_mask,
|
||||
bool add)
|
||||
{
|
||||
u32 ale_entry[ALE_ENTRY_WORDS];
|
||||
int unreg_members = 0;
|
||||
int type, idx;
|
||||
|
||||
for (idx = 0; idx < ale->params.ale_entries; idx++) {
|
||||
cpsw_ale_read(ale, idx, ale_entry);
|
||||
type = cpsw_ale_get_entry_type(ale_entry);
|
||||
if (type != ALE_TYPE_VLAN)
|
||||
continue;
|
||||
|
||||
unreg_members =
|
||||
cpsw_ale_get_vlan_unreg_mcast(ale_entry,
|
||||
ale->vlan_field_bits);
|
||||
if (add)
|
||||
unreg_members |= unreg_mcast_mask;
|
||||
else
|
||||
unreg_members &= ~unreg_mcast_mask;
|
||||
cpsw_ale_set_vlan_unreg_mcast(ale_entry, unreg_members,
|
||||
ale->vlan_field_bits);
|
||||
cpsw_ale_write(ale, idx, ale_entry);
|
||||
}
|
||||
}
|
||||
|
||||
void cpsw_ale_set_allmulti(struct cpsw_ale *ale, int allmulti, int port)
|
||||
{
|
||||
u32 ale_entry[ALE_ENTRY_WORDS];
|
||||
|
@ -779,6 +900,7 @@ void cpsw_ale_start(struct cpsw_ale *ale)
|
|||
void cpsw_ale_stop(struct cpsw_ale *ale)
|
||||
{
|
||||
del_timer_sync(&ale->timer);
|
||||
cpsw_ale_control_set(ale, 0, ALE_CLEAR, 1);
|
||||
cpsw_ale_control_set(ale, 0, ALE_ENABLE, 0);
|
||||
}
|
||||
|
||||
|
@ -791,6 +913,13 @@ struct cpsw_ale *cpsw_ale_create(struct cpsw_ale_params *params)
|
|||
if (!ale)
|
||||
return NULL;
|
||||
|
||||
ale->p0_untag_vid_mask =
|
||||
devm_kmalloc_array(params->dev, BITS_TO_LONGS(VLAN_N_VID),
|
||||
sizeof(unsigned long),
|
||||
GFP_KERNEL);
|
||||
if (!ale->p0_untag_vid_mask)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
ale->params = *params;
|
||||
ale->ageout = ale->params.ale_ageout * HZ;
|
||||
|
||||
|
@ -862,6 +991,7 @@ struct cpsw_ale *cpsw_ale_create(struct cpsw_ale_params *params)
|
|||
ALE_UNKNOWNVLAN_FORCE_UNTAG_EGRESS;
|
||||
}
|
||||
|
||||
cpsw_ale_control_set(ale, 0, ALE_CLEAR, 1);
|
||||
return ale;
|
||||
}
|
||||
|
||||
|
|
|
@ -35,6 +35,7 @@ struct cpsw_ale {
|
|||
u32 port_mask_bits;
|
||||
u32 port_num_bits;
|
||||
u32 vlan_field_bits;
|
||||
unsigned long *p0_untag_vid_mask;
|
||||
};
|
||||
|
||||
enum cpsw_ale_control {
|
||||
|
@ -115,4 +116,14 @@ int cpsw_ale_control_set(struct cpsw_ale *ale, int port,
|
|||
int control, int value);
|
||||
void cpsw_ale_dump(struct cpsw_ale *ale, u32 *data);
|
||||
|
||||
static inline int cpsw_ale_get_vlan_p0_untag(struct cpsw_ale *ale, u16 vid)
|
||||
{
|
||||
return test_bit(vid, ale->p0_untag_vid_mask);
|
||||
}
|
||||
|
||||
int cpsw_ale_vlan_add_modify(struct cpsw_ale *ale, u16 vid, int port_mask,
|
||||
int untag_mask, int reg_mcast, int unreg_mcast);
|
||||
void cpsw_ale_set_unreg_mcast(struct cpsw_ale *ale, int unreg_mcast_mask,
|
||||
bool add);
|
||||
|
||||
#endif
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -54,6 +54,7 @@ do { \
|
|||
|
||||
#define HOST_PORT_NUM 0
|
||||
#define CPSW_ALE_PORTS_NUM 3
|
||||
#define CPSW_SLAVE_PORTS_NUM 2
|
||||
#define SLIVER_SIZE 0x40
|
||||
|
||||
#define CPSW1_HOST_PORT_OFFSET 0x028
|
||||
|
@ -65,6 +66,7 @@ do { \
|
|||
#define CPSW1_CPTS_OFFSET 0x500
|
||||
#define CPSW1_ALE_OFFSET 0x600
|
||||
#define CPSW1_SLIVER_OFFSET 0x700
|
||||
#define CPSW1_WR_OFFSET 0x900
|
||||
|
||||
#define CPSW2_HOST_PORT_OFFSET 0x108
|
||||
#define CPSW2_SLAVE_OFFSET 0x200
|
||||
|
@ -76,6 +78,7 @@ do { \
|
|||
#define CPSW2_ALE_OFFSET 0xd00
|
||||
#define CPSW2_SLIVER_OFFSET 0xd80
|
||||
#define CPSW2_BD_OFFSET 0x2000
|
||||
#define CPSW2_WR_OFFSET 0x1200
|
||||
|
||||
#define CPDMA_RXTHRESH 0x0c0
|
||||
#define CPDMA_RXFREE 0x0e0
|
||||
|
@ -113,12 +116,15 @@ do { \
|
|||
#define IRQ_NUM 2
|
||||
#define CPSW_MAX_QUEUES 8
|
||||
#define CPSW_CPDMA_DESCS_POOL_SIZE_DEFAULT 256
|
||||
#define CPSW_ALE_AGEOUT_DEFAULT 10 /* sec */
|
||||
#define CPSW_ALE_NUM_ENTRIES 1024
|
||||
#define CPSW_FIFO_QUEUE_TYPE_SHIFT 16
|
||||
#define CPSW_FIFO_SHAPE_EN_SHIFT 16
|
||||
#define CPSW_FIFO_RATE_EN_SHIFT 20
|
||||
#define CPSW_TC_NUM 4
|
||||
#define CPSW_FIFO_SHAPERS_NUM (CPSW_TC_NUM - 1)
|
||||
#define CPSW_PCT_MASK 0x7f
|
||||
#define CPSW_BD_RAM_SIZE 0x2000
|
||||
|
||||
#define CPSW_RX_VLAN_ENCAP_HDR_PRIO_SHIFT 29
|
||||
#define CPSW_RX_VLAN_ENCAP_HDR_PRIO_MSK GENMASK(2, 0)
|
||||
|
@ -279,6 +285,7 @@ struct cpsw_slave_data {
|
|||
u8 mac_addr[ETH_ALEN];
|
||||
u16 dual_emac_res_vlan; /* Reserved VLAN for DualEMAC */
|
||||
struct phy *ifphy;
|
||||
bool disabled;
|
||||
};
|
||||
|
||||
struct cpsw_platform_data {
|
||||
|
@ -286,9 +293,9 @@ struct cpsw_platform_data {
|
|||
u32 ss_reg_ofs; /* Subsystem control register offset */
|
||||
u32 channels; /* number of cpdma channels (symmetric) */
|
||||
u32 slaves; /* number of slave cpgmac ports */
|
||||
u32 active_slave; /* time stamping, ethtool and SIOCGMIIPHY slave */
|
||||
u32 active_slave;/* time stamping, ethtool and SIOCGMIIPHY slave */
|
||||
u32 ale_entries; /* ale table size */
|
||||
u32 bd_ram_size; /*buffer descriptor ram size */
|
||||
u32 bd_ram_size; /*buffer descriptor ram size */
|
||||
u32 mac_control; /* Mac control register */
|
||||
u16 default_vlan; /* Def VLAN for ALE lookup in VLAN aware mode*/
|
||||
bool dual_emac; /* Enable Dual EMAC mode */
|
||||
|
@ -344,10 +351,15 @@ struct cpsw_common {
|
|||
bool tx_irq_disabled;
|
||||
u32 irqs_table[IRQ_NUM];
|
||||
struct cpts *cpts;
|
||||
struct devlink *devlink;
|
||||
int rx_ch_num, tx_ch_num;
|
||||
int speed;
|
||||
int usage_count;
|
||||
struct page_pool *page_pool[CPSW_MAX_QUEUES];
|
||||
u8 br_members;
|
||||
struct net_device *hw_bridge_dev;
|
||||
bool ale_bypass;
|
||||
u8 base_mac[ETH_ALEN];
|
||||
};
|
||||
|
||||
struct cpsw_priv {
|
||||
|
@ -368,19 +380,14 @@ struct cpsw_priv {
|
|||
|
||||
u32 emac_port;
|
||||
struct cpsw_common *cpsw;
|
||||
int offload_fwd_mark;
|
||||
};
|
||||
|
||||
#define ndev_to_cpsw(ndev) (((struct cpsw_priv *)netdev_priv(ndev))->cpsw)
|
||||
#define napi_to_cpsw(napi) container_of(napi, struct cpsw_common, napi)
|
||||
|
||||
#define cpsw_slave_index(cpsw, priv) \
|
||||
((cpsw->data.dual_emac) ? priv->emac_port : \
|
||||
cpsw->data.active_slave)
|
||||
|
||||
static inline int cpsw_get_slave_port(u32 slave_num)
|
||||
{
|
||||
return slave_num + 1;
|
||||
}
|
||||
extern int (*cpsw_slave_index)(struct cpsw_common *cpsw,
|
||||
struct cpsw_priv *priv);
|
||||
|
||||
struct addr_sync_ctx {
|
||||
struct net_device *ndev;
|
||||
|
@ -389,6 +396,35 @@ struct addr_sync_ctx {
|
|||
int flush; /* flush flag */
|
||||
};
|
||||
|
||||
#define CPSW_XMETA_OFFSET ALIGN(sizeof(struct xdp_frame), sizeof(long))
|
||||
|
||||
#define CPSW_XDP_CONSUMED 1
|
||||
#define CPSW_XDP_PASS 0
|
||||
|
||||
struct __aligned(sizeof(long)) cpsw_meta_xdp {
|
||||
struct net_device *ndev;
|
||||
int ch;
|
||||
};
|
||||
|
||||
/* The buf includes headroom compatible with both skb and xdpf */
|
||||
#define CPSW_HEADROOM_NA (max(XDP_PACKET_HEADROOM, NET_SKB_PAD) + NET_IP_ALIGN)
|
||||
#define CPSW_HEADROOM ALIGN(CPSW_HEADROOM_NA, sizeof(long))
|
||||
|
||||
static inline int cpsw_is_xdpf_handle(void *handle)
|
||||
{
|
||||
return (unsigned long)handle & BIT(0);
|
||||
}
|
||||
|
||||
static inline void *cpsw_xdpf_to_handle(struct xdp_frame *xdpf)
|
||||
{
|
||||
return (void *)((unsigned long)xdpf | BIT(0));
|
||||
}
|
||||
|
||||
static inline struct xdp_frame *cpsw_handle_to_xdpf(void *handle)
|
||||
{
|
||||
return (struct xdp_frame *)((unsigned long)handle & ~BIT(0));
|
||||
}
|
||||
|
||||
int cpsw_init_common(struct cpsw_common *cpsw, void __iomem *ss_regs,
|
||||
int ale_ageout, phys_addr_t desc_mem_phys,
|
||||
int descs_pool_size);
|
||||
|
@ -399,6 +435,29 @@ void cpsw_intr_disable(struct cpsw_common *cpsw);
|
|||
void cpsw_tx_handler(void *token, int len, int status);
|
||||
int cpsw_create_xdp_rxqs(struct cpsw_common *cpsw);
|
||||
void cpsw_destroy_xdp_rxqs(struct cpsw_common *cpsw);
|
||||
int cpsw_ndo_bpf(struct net_device *ndev, struct netdev_bpf *bpf);
|
||||
int cpsw_xdp_tx_frame(struct cpsw_priv *priv, struct xdp_frame *xdpf,
|
||||
struct page *page, int port);
|
||||
int cpsw_run_xdp(struct cpsw_priv *priv, int ch, struct xdp_buff *xdp,
|
||||
struct page *page, int port);
|
||||
irqreturn_t cpsw_tx_interrupt(int irq, void *dev_id);
|
||||
irqreturn_t cpsw_rx_interrupt(int irq, void *dev_id);
|
||||
int cpsw_tx_mq_poll(struct napi_struct *napi_tx, int budget);
|
||||
int cpsw_tx_poll(struct napi_struct *napi_tx, int budget);
|
||||
int cpsw_rx_mq_poll(struct napi_struct *napi_rx, int budget);
|
||||
int cpsw_rx_poll(struct napi_struct *napi_rx, int budget);
|
||||
void cpsw_rx_vlan_encap(struct sk_buff *skb);
|
||||
void soft_reset(const char *module, void __iomem *reg);
|
||||
void cpsw_set_slave_mac(struct cpsw_slave *slave, struct cpsw_priv *priv);
|
||||
void cpsw_ndo_tx_timeout(struct net_device *ndev);
|
||||
int cpsw_need_resplit(struct cpsw_common *cpsw);
|
||||
int cpsw_ndo_ioctl(struct net_device *dev, struct ifreq *req, int cmd);
|
||||
int cpsw_ndo_set_tx_maxrate(struct net_device *ndev, int queue, u32 rate);
|
||||
int cpsw_ndo_setup_tc(struct net_device *ndev, enum tc_setup_type type,
|
||||
void *type_data);
|
||||
bool cpsw_shp_is_off(struct cpsw_priv *priv);
|
||||
void cpsw_cbs_resume(struct cpsw_slave *slave, struct cpsw_priv *priv);
|
||||
void cpsw_mqprio_resume(struct cpsw_slave *slave, struct cpsw_priv *priv);
|
||||
|
||||
/* ethtool */
|
||||
u32 cpsw_get_msglevel(struct net_device *ndev);
|
||||
|
|
|
@ -0,0 +1,589 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Texas Instruments switchdev Driver
|
||||
*
|
||||
* Copyright (C) 2019 Texas Instruments
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/etherdevice.h>
|
||||
#include <linux/if_bridge.h>
|
||||
#include <linux/netdevice.h>
|
||||
#include <linux/workqueue.h>
|
||||
#include <net/switchdev.h>
|
||||
|
||||
#include "cpsw.h"
|
||||
#include "cpsw_ale.h"
|
||||
#include "cpsw_priv.h"
|
||||
#include "cpsw_switchdev.h"
|
||||
|
||||
struct cpsw_switchdev_event_work {
|
||||
struct work_struct work;
|
||||
struct switchdev_notifier_fdb_info fdb_info;
|
||||
struct cpsw_priv *priv;
|
||||
unsigned long event;
|
||||
};
|
||||
|
||||
static int cpsw_port_stp_state_set(struct cpsw_priv *priv,
|
||||
struct switchdev_trans *trans, u8 state)
|
||||
{
|
||||
struct cpsw_common *cpsw = priv->cpsw;
|
||||
u8 cpsw_state;
|
||||
int ret = 0;
|
||||
|
||||
if (switchdev_trans_ph_prepare(trans))
|
||||
return 0;
|
||||
|
||||
switch (state) {
|
||||
case BR_STATE_FORWARDING:
|
||||
cpsw_state = ALE_PORT_STATE_FORWARD;
|
||||
break;
|
||||
case BR_STATE_LEARNING:
|
||||
cpsw_state = ALE_PORT_STATE_LEARN;
|
||||
break;
|
||||
case BR_STATE_DISABLED:
|
||||
cpsw_state = ALE_PORT_STATE_DISABLE;
|
||||
break;
|
||||
case BR_STATE_LISTENING:
|
||||
case BR_STATE_BLOCKING:
|
||||
cpsw_state = ALE_PORT_STATE_BLOCK;
|
||||
break;
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
ret = cpsw_ale_control_set(cpsw->ale, priv->emac_port,
|
||||
ALE_PORT_STATE, cpsw_state);
|
||||
dev_dbg(priv->dev, "ale state: %u\n", cpsw_state);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int cpsw_port_attr_br_flags_set(struct cpsw_priv *priv,
|
||||
struct switchdev_trans *trans,
|
||||
struct net_device *orig_dev,
|
||||
unsigned long brport_flags)
|
||||
{
|
||||
struct cpsw_common *cpsw = priv->cpsw;
|
||||
bool unreg_mcast_add = false;
|
||||
|
||||
if (switchdev_trans_ph_prepare(trans))
|
||||
return 0;
|
||||
|
||||
if (brport_flags & BR_MCAST_FLOOD)
|
||||
unreg_mcast_add = true;
|
||||
dev_dbg(priv->dev, "BR_MCAST_FLOOD: %d port %u\n",
|
||||
unreg_mcast_add, priv->emac_port);
|
||||
|
||||
cpsw_ale_set_unreg_mcast(cpsw->ale, BIT(priv->emac_port),
|
||||
unreg_mcast_add);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cpsw_port_attr_br_flags_pre_set(struct net_device *netdev,
|
||||
struct switchdev_trans *trans,
|
||||
unsigned long flags)
|
||||
{
|
||||
if (flags & ~(BR_LEARNING | BR_MCAST_FLOOD))
|
||||
return -EINVAL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cpsw_port_attr_set(struct net_device *ndev,
|
||||
const struct switchdev_attr *attr,
|
||||
struct switchdev_trans *trans)
|
||||
{
|
||||
struct cpsw_priv *priv = netdev_priv(ndev);
|
||||
int ret;
|
||||
|
||||
dev_dbg(priv->dev, "attr: id %u port: %u\n", attr->id, priv->emac_port);
|
||||
|
||||
switch (attr->id) {
|
||||
case SWITCHDEV_ATTR_ID_PORT_PRE_BRIDGE_FLAGS:
|
||||
ret = cpsw_port_attr_br_flags_pre_set(ndev, trans,
|
||||
attr->u.brport_flags);
|
||||
break;
|
||||
case SWITCHDEV_ATTR_ID_PORT_STP_STATE:
|
||||
ret = cpsw_port_stp_state_set(priv, trans, attr->u.stp_state);
|
||||
dev_dbg(priv->dev, "stp state: %u\n", attr->u.stp_state);
|
||||
break;
|
||||
case SWITCHDEV_ATTR_ID_PORT_BRIDGE_FLAGS:
|
||||
ret = cpsw_port_attr_br_flags_set(priv, trans, attr->orig_dev,
|
||||
attr->u.brport_flags);
|
||||
break;
|
||||
default:
|
||||
ret = -EOPNOTSUPP;
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static u16 cpsw_get_pvid(struct cpsw_priv *priv)
|
||||
{
|
||||
struct cpsw_common *cpsw = priv->cpsw;
|
||||
u32 __iomem *port_vlan_reg;
|
||||
u32 pvid;
|
||||
|
||||
if (priv->emac_port) {
|
||||
int reg = CPSW2_PORT_VLAN;
|
||||
|
||||
if (cpsw->version == CPSW_VERSION_1)
|
||||
reg = CPSW1_PORT_VLAN;
|
||||
pvid = slave_read(cpsw->slaves + (priv->emac_port - 1), reg);
|
||||
} else {
|
||||
port_vlan_reg = &cpsw->host_port_regs->port_vlan;
|
||||
pvid = readl(port_vlan_reg);
|
||||
}
|
||||
|
||||
pvid = pvid & 0xfff;
|
||||
|
||||
return pvid;
|
||||
}
|
||||
|
||||
static void cpsw_set_pvid(struct cpsw_priv *priv, u16 vid, bool cfi, u32 cos)
|
||||
{
|
||||
struct cpsw_common *cpsw = priv->cpsw;
|
||||
void __iomem *port_vlan_reg;
|
||||
u32 pvid;
|
||||
|
||||
pvid = vid;
|
||||
pvid |= cfi ? BIT(12) : 0;
|
||||
pvid |= (cos & 0x7) << 13;
|
||||
|
||||
if (priv->emac_port) {
|
||||
int reg = CPSW2_PORT_VLAN;
|
||||
|
||||
if (cpsw->version == CPSW_VERSION_1)
|
||||
reg = CPSW1_PORT_VLAN;
|
||||
/* no barrier */
|
||||
slave_write(cpsw->slaves + (priv->emac_port - 1), pvid, reg);
|
||||
} else {
|
||||
/* CPU port */
|
||||
port_vlan_reg = &cpsw->host_port_regs->port_vlan;
|
||||
writel(pvid, port_vlan_reg);
|
||||
}
|
||||
}
|
||||
|
||||
static int cpsw_port_vlan_add(struct cpsw_priv *priv, bool untag, bool pvid,
|
||||
u16 vid, struct net_device *orig_dev)
|
||||
{
|
||||
bool cpu_port = netif_is_bridge_master(orig_dev);
|
||||
struct cpsw_common *cpsw = priv->cpsw;
|
||||
int unreg_mcast_mask = 0;
|
||||
int reg_mcast_mask = 0;
|
||||
int untag_mask = 0;
|
||||
int port_mask;
|
||||
int ret = 0;
|
||||
u32 flags;
|
||||
|
||||
if (cpu_port) {
|
||||
port_mask = BIT(HOST_PORT_NUM);
|
||||
flags = orig_dev->flags;
|
||||
unreg_mcast_mask = port_mask;
|
||||
} else {
|
||||
port_mask = BIT(priv->emac_port);
|
||||
flags = priv->ndev->flags;
|
||||
}
|
||||
|
||||
if (flags & IFF_MULTICAST)
|
||||
reg_mcast_mask = port_mask;
|
||||
|
||||
if (untag)
|
||||
untag_mask = port_mask;
|
||||
|
||||
ret = cpsw_ale_vlan_add_modify(cpsw->ale, vid, port_mask, untag_mask,
|
||||
reg_mcast_mask, unreg_mcast_mask);
|
||||
if (ret) {
|
||||
dev_err(priv->dev, "Unable to add vlan\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (cpu_port)
|
||||
cpsw_ale_add_ucast(cpsw->ale, priv->mac_addr,
|
||||
HOST_PORT_NUM, ALE_VLAN, vid);
|
||||
if (!pvid)
|
||||
return ret;
|
||||
|
||||
cpsw_set_pvid(priv, vid, 0, 0);
|
||||
|
||||
dev_dbg(priv->dev, "VID add: %s: vid:%u ports:%X\n",
|
||||
priv->ndev->name, vid, port_mask);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int cpsw_port_vlan_del(struct cpsw_priv *priv, u16 vid,
|
||||
struct net_device *orig_dev)
|
||||
{
|
||||
bool cpu_port = netif_is_bridge_master(orig_dev);
|
||||
struct cpsw_common *cpsw = priv->cpsw;
|
||||
int port_mask;
|
||||
int ret = 0;
|
||||
|
||||
if (cpu_port)
|
||||
port_mask = BIT(HOST_PORT_NUM);
|
||||
else
|
||||
port_mask = BIT(priv->emac_port);
|
||||
|
||||
ret = cpsw_ale_del_vlan(cpsw->ale, vid, port_mask);
|
||||
if (ret != 0)
|
||||
return ret;
|
||||
|
||||
/* We don't care for the return value here, error is returned only if
|
||||
* the unicast entry is not present
|
||||
*/
|
||||
if (cpu_port)
|
||||
cpsw_ale_del_ucast(cpsw->ale, priv->mac_addr,
|
||||
HOST_PORT_NUM, ALE_VLAN, vid);
|
||||
|
||||
if (vid == cpsw_get_pvid(priv))
|
||||
cpsw_set_pvid(priv, 0, 0, 0);
|
||||
|
||||
/* We don't care for the return value here, error is returned only if
|
||||
* the multicast entry is not present
|
||||
*/
|
||||
cpsw_ale_del_mcast(cpsw->ale, priv->ndev->broadcast,
|
||||
port_mask, ALE_VLAN, vid);
|
||||
dev_dbg(priv->dev, "VID del: %s: vid:%u ports:%X\n",
|
||||
priv->ndev->name, vid, port_mask);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int cpsw_port_vlans_add(struct cpsw_priv *priv,
|
||||
const struct switchdev_obj_port_vlan *vlan,
|
||||
struct switchdev_trans *trans)
|
||||
{
|
||||
bool untag = vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED;
|
||||
struct net_device *orig_dev = vlan->obj.orig_dev;
|
||||
bool cpu_port = netif_is_bridge_master(orig_dev);
|
||||
bool pvid = vlan->flags & BRIDGE_VLAN_INFO_PVID;
|
||||
u16 vid;
|
||||
|
||||
dev_dbg(priv->dev, "VID add: %s: vid:%u flags:%X\n",
|
||||
priv->ndev->name, vlan->vid_begin, vlan->flags);
|
||||
|
||||
if (cpu_port && !(vlan->flags & BRIDGE_VLAN_INFO_BRENTRY))
|
||||
return 0;
|
||||
|
||||
if (switchdev_trans_ph_prepare(trans))
|
||||
return 0;
|
||||
|
||||
for (vid = vlan->vid_begin; vid <= vlan->vid_end; vid++) {
|
||||
int err;
|
||||
|
||||
err = cpsw_port_vlan_add(priv, untag, pvid, vid, orig_dev);
|
||||
if (err)
|
||||
return err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cpsw_port_vlans_del(struct cpsw_priv *priv,
|
||||
const struct switchdev_obj_port_vlan *vlan)
|
||||
|
||||
{
|
||||
struct net_device *orig_dev = vlan->obj.orig_dev;
|
||||
u16 vid;
|
||||
|
||||
for (vid = vlan->vid_begin; vid <= vlan->vid_end; vid++) {
|
||||
int err;
|
||||
|
||||
err = cpsw_port_vlan_del(priv, vid, orig_dev);
|
||||
if (err)
|
||||
return err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cpsw_port_mdb_add(struct cpsw_priv *priv,
|
||||
struct switchdev_obj_port_mdb *mdb,
|
||||
struct switchdev_trans *trans)
|
||||
|
||||
{
|
||||
struct net_device *orig_dev = mdb->obj.orig_dev;
|
||||
bool cpu_port = netif_is_bridge_master(orig_dev);
|
||||
struct cpsw_common *cpsw = priv->cpsw;
|
||||
int port_mask;
|
||||
int err;
|
||||
|
||||
if (switchdev_trans_ph_prepare(trans))
|
||||
return 0;
|
||||
|
||||
if (cpu_port)
|
||||
port_mask = BIT(HOST_PORT_NUM);
|
||||
else
|
||||
port_mask = BIT(priv->emac_port);
|
||||
|
||||
err = cpsw_ale_add_mcast(cpsw->ale, mdb->addr, port_mask,
|
||||
ALE_VLAN, mdb->vid, 0);
|
||||
dev_dbg(priv->dev, "MDB add: %s: vid %u:%pM ports: %X\n",
|
||||
priv->ndev->name, mdb->vid, mdb->addr, port_mask);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int cpsw_port_mdb_del(struct cpsw_priv *priv,
|
||||
struct switchdev_obj_port_mdb *mdb)
|
||||
|
||||
{
|
||||
struct net_device *orig_dev = mdb->obj.orig_dev;
|
||||
bool cpu_port = netif_is_bridge_master(orig_dev);
|
||||
struct cpsw_common *cpsw = priv->cpsw;
|
||||
int del_mask;
|
||||
int err;
|
||||
|
||||
if (cpu_port)
|
||||
del_mask = BIT(HOST_PORT_NUM);
|
||||
else
|
||||
del_mask = BIT(priv->emac_port);
|
||||
|
||||
err = cpsw_ale_del_mcast(cpsw->ale, mdb->addr, del_mask,
|
||||
ALE_VLAN, mdb->vid);
|
||||
dev_dbg(priv->dev, "MDB del: %s: vid %u:%pM ports: %X\n",
|
||||
priv->ndev->name, mdb->vid, mdb->addr, del_mask);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int cpsw_port_obj_add(struct net_device *ndev,
|
||||
const struct switchdev_obj *obj,
|
||||
struct switchdev_trans *trans,
|
||||
struct netlink_ext_ack *extack)
|
||||
{
|
||||
struct switchdev_obj_port_vlan *vlan = SWITCHDEV_OBJ_PORT_VLAN(obj);
|
||||
struct switchdev_obj_port_mdb *mdb = SWITCHDEV_OBJ_PORT_MDB(obj);
|
||||
struct cpsw_priv *priv = netdev_priv(ndev);
|
||||
int err = 0;
|
||||
|
||||
dev_dbg(priv->dev, "obj_add: id %u port: %u\n",
|
||||
obj->id, priv->emac_port);
|
||||
|
||||
switch (obj->id) {
|
||||
case SWITCHDEV_OBJ_ID_PORT_VLAN:
|
||||
err = cpsw_port_vlans_add(priv, vlan, trans);
|
||||
break;
|
||||
case SWITCHDEV_OBJ_ID_PORT_MDB:
|
||||
case SWITCHDEV_OBJ_ID_HOST_MDB:
|
||||
err = cpsw_port_mdb_add(priv, mdb, trans);
|
||||
break;
|
||||
default:
|
||||
err = -EOPNOTSUPP;
|
||||
break;
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int cpsw_port_obj_del(struct net_device *ndev,
|
||||
const struct switchdev_obj *obj)
|
||||
{
|
||||
struct switchdev_obj_port_vlan *vlan = SWITCHDEV_OBJ_PORT_VLAN(obj);
|
||||
struct switchdev_obj_port_mdb *mdb = SWITCHDEV_OBJ_PORT_MDB(obj);
|
||||
struct cpsw_priv *priv = netdev_priv(ndev);
|
||||
int err = 0;
|
||||
|
||||
dev_dbg(priv->dev, "obj_del: id %u port: %u\n",
|
||||
obj->id, priv->emac_port);
|
||||
|
||||
switch (obj->id) {
|
||||
case SWITCHDEV_OBJ_ID_PORT_VLAN:
|
||||
err = cpsw_port_vlans_del(priv, vlan);
|
||||
break;
|
||||
case SWITCHDEV_OBJ_ID_PORT_MDB:
|
||||
case SWITCHDEV_OBJ_ID_HOST_MDB:
|
||||
err = cpsw_port_mdb_del(priv, mdb);
|
||||
break;
|
||||
default:
|
||||
err = -EOPNOTSUPP;
|
||||
break;
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static void cpsw_fdb_offload_notify(struct net_device *ndev,
|
||||
struct switchdev_notifier_fdb_info *rcv)
|
||||
{
|
||||
struct switchdev_notifier_fdb_info info;
|
||||
|
||||
info.addr = rcv->addr;
|
||||
info.vid = rcv->vid;
|
||||
info.offloaded = true;
|
||||
call_switchdev_notifiers(SWITCHDEV_FDB_OFFLOADED,
|
||||
ndev, &info.info, NULL);
|
||||
}
|
||||
|
||||
static void cpsw_switchdev_event_work(struct work_struct *work)
|
||||
{
|
||||
struct cpsw_switchdev_event_work *switchdev_work =
|
||||
container_of(work, struct cpsw_switchdev_event_work, work);
|
||||
struct cpsw_priv *priv = switchdev_work->priv;
|
||||
struct switchdev_notifier_fdb_info *fdb;
|
||||
struct cpsw_common *cpsw = priv->cpsw;
|
||||
int port = priv->emac_port;
|
||||
|
||||
rtnl_lock();
|
||||
switch (switchdev_work->event) {
|
||||
case SWITCHDEV_FDB_ADD_TO_DEVICE:
|
||||
fdb = &switchdev_work->fdb_info;
|
||||
|
||||
dev_dbg(cpsw->dev, "cpsw_fdb_add: MACID = %pM vid = %u flags = %u %u -- port %d\n",
|
||||
fdb->addr, fdb->vid, fdb->added_by_user,
|
||||
fdb->offloaded, port);
|
||||
|
||||
if (!fdb->added_by_user)
|
||||
break;
|
||||
if (memcmp(priv->mac_addr, (u8 *)fdb->addr, ETH_ALEN) == 0)
|
||||
port = HOST_PORT_NUM;
|
||||
|
||||
cpsw_ale_add_ucast(cpsw->ale, (u8 *)fdb->addr, port,
|
||||
fdb->vid ? ALE_VLAN : 0, fdb->vid);
|
||||
cpsw_fdb_offload_notify(priv->ndev, fdb);
|
||||
break;
|
||||
case SWITCHDEV_FDB_DEL_TO_DEVICE:
|
||||
fdb = &switchdev_work->fdb_info;
|
||||
|
||||
dev_dbg(cpsw->dev, "cpsw_fdb_del: MACID = %pM vid = %u flags = %u %u -- port %d\n",
|
||||
fdb->addr, fdb->vid, fdb->added_by_user,
|
||||
fdb->offloaded, port);
|
||||
|
||||
if (!fdb->added_by_user)
|
||||
break;
|
||||
if (memcmp(priv->mac_addr, (u8 *)fdb->addr, ETH_ALEN) == 0)
|
||||
port = HOST_PORT_NUM;
|
||||
|
||||
cpsw_ale_del_ucast(cpsw->ale, (u8 *)fdb->addr, port,
|
||||
fdb->vid ? ALE_VLAN : 0, fdb->vid);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
rtnl_unlock();
|
||||
|
||||
kfree(switchdev_work->fdb_info.addr);
|
||||
kfree(switchdev_work);
|
||||
dev_put(priv->ndev);
|
||||
}
|
||||
|
||||
/* called under rcu_read_lock() */
|
||||
static int cpsw_switchdev_event(struct notifier_block *unused,
|
||||
unsigned long event, void *ptr)
|
||||
{
|
||||
struct net_device *ndev = switchdev_notifier_info_to_dev(ptr);
|
||||
struct switchdev_notifier_fdb_info *fdb_info = ptr;
|
||||
struct cpsw_switchdev_event_work *switchdev_work;
|
||||
struct cpsw_priv *priv = netdev_priv(ndev);
|
||||
int err;
|
||||
|
||||
if (event == SWITCHDEV_PORT_ATTR_SET) {
|
||||
err = switchdev_handle_port_attr_set(ndev, ptr,
|
||||
cpsw_port_dev_check,
|
||||
cpsw_port_attr_set);
|
||||
return notifier_from_errno(err);
|
||||
}
|
||||
|
||||
if (!cpsw_port_dev_check(ndev))
|
||||
return NOTIFY_DONE;
|
||||
|
||||
switchdev_work = kzalloc(sizeof(*switchdev_work), GFP_ATOMIC);
|
||||
if (WARN_ON(!switchdev_work))
|
||||
return NOTIFY_BAD;
|
||||
|
||||
INIT_WORK(&switchdev_work->work, cpsw_switchdev_event_work);
|
||||
switchdev_work->priv = priv;
|
||||
switchdev_work->event = event;
|
||||
|
||||
switch (event) {
|
||||
case SWITCHDEV_FDB_ADD_TO_DEVICE:
|
||||
case SWITCHDEV_FDB_DEL_TO_DEVICE:
|
||||
memcpy(&switchdev_work->fdb_info, ptr,
|
||||
sizeof(switchdev_work->fdb_info));
|
||||
switchdev_work->fdb_info.addr = kzalloc(ETH_ALEN, GFP_ATOMIC);
|
||||
if (!switchdev_work->fdb_info.addr)
|
||||
goto err_addr_alloc;
|
||||
ether_addr_copy((u8 *)switchdev_work->fdb_info.addr,
|
||||
fdb_info->addr);
|
||||
dev_hold(ndev);
|
||||
break;
|
||||
default:
|
||||
kfree(switchdev_work);
|
||||
return NOTIFY_DONE;
|
||||
}
|
||||
|
||||
queue_work(system_long_wq, &switchdev_work->work);
|
||||
|
||||
return NOTIFY_DONE;
|
||||
|
||||
err_addr_alloc:
|
||||
kfree(switchdev_work);
|
||||
return NOTIFY_BAD;
|
||||
}
|
||||
|
||||
static struct notifier_block cpsw_switchdev_notifier = {
|
||||
.notifier_call = cpsw_switchdev_event,
|
||||
};
|
||||
|
||||
static int cpsw_switchdev_blocking_event(struct notifier_block *unused,
|
||||
unsigned long event, void *ptr)
|
||||
{
|
||||
struct net_device *dev = switchdev_notifier_info_to_dev(ptr);
|
||||
int err;
|
||||
|
||||
switch (event) {
|
||||
case SWITCHDEV_PORT_OBJ_ADD:
|
||||
err = switchdev_handle_port_obj_add(dev, ptr,
|
||||
cpsw_port_dev_check,
|
||||
cpsw_port_obj_add);
|
||||
return notifier_from_errno(err);
|
||||
case SWITCHDEV_PORT_OBJ_DEL:
|
||||
err = switchdev_handle_port_obj_del(dev, ptr,
|
||||
cpsw_port_dev_check,
|
||||
cpsw_port_obj_del);
|
||||
return notifier_from_errno(err);
|
||||
case SWITCHDEV_PORT_ATTR_SET:
|
||||
err = switchdev_handle_port_attr_set(dev, ptr,
|
||||
cpsw_port_dev_check,
|
||||
cpsw_port_attr_set);
|
||||
return notifier_from_errno(err);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return NOTIFY_DONE;
|
||||
}
|
||||
|
||||
static struct notifier_block cpsw_switchdev_bl_notifier = {
|
||||
.notifier_call = cpsw_switchdev_blocking_event,
|
||||
};
|
||||
|
||||
int cpsw_switchdev_register_notifiers(struct cpsw_common *cpsw)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
ret = register_switchdev_notifier(&cpsw_switchdev_notifier);
|
||||
if (ret) {
|
||||
dev_err(cpsw->dev, "register switchdev notifier fail ret:%d\n",
|
||||
ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = register_switchdev_blocking_notifier(&cpsw_switchdev_bl_notifier);
|
||||
if (ret) {
|
||||
dev_err(cpsw->dev, "register switchdev blocking notifier ret:%d\n",
|
||||
ret);
|
||||
unregister_switchdev_notifier(&cpsw_switchdev_notifier);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void cpsw_switchdev_unregister_notifiers(struct cpsw_common *cpsw)
|
||||
{
|
||||
unregister_switchdev_blocking_notifier(&cpsw_switchdev_bl_notifier);
|
||||
unregister_switchdev_notifier(&cpsw_switchdev_notifier);
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/*
|
||||
* Texas Instruments Ethernet Switch Driver
|
||||
*/
|
||||
|
||||
#ifndef DRIVERS_NET_ETHERNET_TI_CPSW_SWITCHDEV_H_
|
||||
#define DRIVERS_NET_ETHERNET_TI_CPSW_SWITCHDEV_H_
|
||||
|
||||
#include <net/switchdev.h>
|
||||
|
||||
bool cpsw_port_dev_check(const struct net_device *dev);
|
||||
int cpsw_switchdev_register_notifiers(struct cpsw_common *cpsw);
|
||||
void cpsw_switchdev_unregister_notifiers(struct cpsw_common *cpsw);
|
||||
|
||||
#endif /* DRIVERS_NET_ETHERNET_TI_CPSW_SWITCHDEV_H_ */
|
|
@ -90,8 +90,8 @@ config TWL4030_USB
|
|||
|
||||
config PHY_TI_GMII_SEL
|
||||
tristate
|
||||
default y if TI_CPSW=y
|
||||
depends on TI_CPSW || COMPILE_TEST
|
||||
default y if TI_CPSW=y || TI_CPSW_SWITCHDEV=y
|
||||
depends on TI_CPSW || TI_CPSW_SWITCHDEV || COMPILE_TEST
|
||||
select GENERIC_PHY
|
||||
select REGMAP
|
||||
default m
|
||||
|
|
Loading…
Reference in New Issue