diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2012-03-20 21:04:47 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2012-03-20 21:04:47 -0700 |
commit | 3b59bf081622b6446db77ad06c93fe23677bc533 (patch) | |
tree | 3f4bb5a27c90cc86994a1f6d3c53fbf9208003cb /drivers/net/ethernet/ti/cpsw_ale.c | |
parent | e45836fafe157df137a837093037f741ad8f4c90 (diff) | |
parent | bbdb32cb5b73597386913d052165423b9d736145 (diff) |
Merge git://git.kernel.org/pub/scm/linux/kernel/git/davem/net-next
Pull networking merge from David Miller:
"1) Move ixgbe driver over to purely page based buffering on receive.
From Alexander Duyck.
2) Add receive packet steering support to e1000e, from Bruce Allan.
3) Convert TCP MD5 support over to RCU, from Eric Dumazet.
4) Reduce cpu usage in handling out-of-order TCP packets on modern
systems, also from Eric Dumazet.
5) Support the IP{,V6}_UNICAST_IF socket options, making the wine
folks happy, from Erich Hoover.
6) Support VLAN trunking from guests in hyperv driver, from Haiyang
Zhang.
7) Support byte-queue-limtis in r8169, from Igor Maravic.
8) Outline code intended for IP_RECVTOS in IP_PKTOPTIONS existed but
was never properly implemented, Jiri Benc fixed that.
9) 64-bit statistics support in r8169 and 8139too, from Junchang Wang.
10) Support kernel side dump filtering by ctmark in netfilter
ctnetlink, from Pablo Neira Ayuso.
11) Support byte-queue-limits in gianfar driver, from Paul Gortmaker.
12) Add new peek socket options to assist with socket migration, from
Pavel Emelyanov.
13) Add sch_plug packet scheduler whose queue is controlled by
userland daemons using explicit freeze and release commands. From
Shriram Rajagopalan.
14) Fix FCOE checksum offload handling on transmit, from Yi Zou."
* git://git.kernel.org/pub/scm/linux/kernel/git/davem/net-next: (1846 commits)
Fix pppol2tp getsockname()
Remove printk from rds_sendmsg
ipv6: fix incorrent ipv6 ipsec packet fragment
cpsw: Hook up default ndo_change_mtu.
net: qmi_wwan: fix build error due to cdc-wdm dependecy
netdev: driver: ethernet: Add TI CPSW driver
netdev: driver: ethernet: add cpsw address lookup engine support
phy: add am79c874 PHY support
mlx4_core: fix race on comm channel
bonding: send igmp report for its master
fs_enet: Add MPC5125 FEC support and PHY interface selection
net: bpf_jit: fix BPF_S_LDX_B_MSH compilation
net: update the usage of CHECKSUM_UNNECESSARY
fcoe: use CHECKSUM_UNNECESSARY instead of CHECKSUM_PARTIAL on tx
net: do not do gso for CHECKSUM_UNNECESSARY in netif_needs_gso
ixgbe: Fix issues with SR-IOV loopback when flow control is disabled
net/hyperv: Fix the code handling tx busy
ixgbe: fix namespace issues when FCoE/DCB is not enabled
rtlwifi: Remove unused ETH_ADDR_LEN defines
igbvf: Use ETH_ALEN
...
Fix up fairly trivial conflicts in drivers/isdn/gigaset/interface.c and
drivers/net/usb/{Kconfig,qmi_wwan.c} as per David.
Diffstat (limited to 'drivers/net/ethernet/ti/cpsw_ale.c')
-rw-r--r-- | drivers/net/ethernet/ti/cpsw_ale.c | 641 |
1 files changed, 641 insertions, 0 deletions
diff --git a/drivers/net/ethernet/ti/cpsw_ale.c b/drivers/net/ethernet/ti/cpsw_ale.c new file mode 100644 index 00000000000..ca0d48a7e50 --- /dev/null +++ b/drivers/net/ethernet/ti/cpsw_ale.c @@ -0,0 +1,641 @@ +/* + * Texas Instruments 3-Port Ethernet Switch Address Lookup Engine + * + * Copyright (C) 2012 Texas Instruments + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include <linux/kernel.h> +#include <linux/platform_device.h> +#include <linux/seq_file.h> +#include <linux/slab.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/stat.h> +#include <linux/sysfs.h> + +#include "cpsw_ale.h" + +#define BITMASK(bits) (BIT(bits) - 1) +#define ALE_ENTRY_BITS 68 +#define ALE_ENTRY_WORDS DIV_ROUND_UP(ALE_ENTRY_BITS, 32) + +#define ALE_VERSION_MAJOR(rev) ((rev >> 8) & 0xff) +#define ALE_VERSION_MINOR(rev) (rev & 0xff) + +/* ALE Registers */ +#define ALE_IDVER 0x00 +#define ALE_CONTROL 0x08 +#define ALE_PRESCALE 0x10 +#define ALE_UNKNOWNVLAN 0x18 +#define ALE_TABLE_CONTROL 0x20 +#define ALE_TABLE 0x34 +#define ALE_PORTCTL 0x40 + +#define ALE_TABLE_WRITE BIT(31) + +#define ALE_TYPE_FREE 0 +#define ALE_TYPE_ADDR 1 +#define ALE_TYPE_VLAN 2 +#define ALE_TYPE_VLAN_ADDR 3 + +#define ALE_UCAST_PERSISTANT 0 +#define ALE_UCAST_UNTOUCHED 1 +#define ALE_UCAST_OUI 2 +#define ALE_UCAST_TOUCHED 3 + +static inline int cpsw_ale_get_field(u32 *ale_entry, u32 start, u32 bits) +{ + int idx; + + idx = start / 32; + start -= idx * 32; + idx = 2 - idx; /* flip */ + return (ale_entry[idx] >> start) & BITMASK(bits); +} + +static inline void cpsw_ale_set_field(u32 *ale_entry, u32 start, u32 bits, + u32 value) +{ + int idx; + + value &= BITMASK(bits); + idx = start / 32; + start -= idx * 32; + idx = 2 - idx; /* flip */ + ale_entry[idx] &= ~(BITMASK(bits) << start); + ale_entry[idx] |= (value << start); +} + +#define DEFINE_ALE_FIELD(name, start, bits) \ +static inline int cpsw_ale_get_##name(u32 *ale_entry) \ +{ \ + return cpsw_ale_get_field(ale_entry, start, bits); \ +} \ +static inline void cpsw_ale_set_##name(u32 *ale_entry, u32 value) \ +{ \ + cpsw_ale_set_field(ale_entry, start, bits, value); \ +} + +DEFINE_ALE_FIELD(entry_type, 60, 2) +DEFINE_ALE_FIELD(vlan_id, 48, 12) +DEFINE_ALE_FIELD(mcast_state, 62, 2) +DEFINE_ALE_FIELD(port_mask, 66, 3) +DEFINE_ALE_FIELD(super, 65, 1) +DEFINE_ALE_FIELD(ucast_type, 62, 2) +DEFINE_ALE_FIELD(port_num, 66, 2) +DEFINE_ALE_FIELD(blocked, 65, 1) +DEFINE_ALE_FIELD(secure, 64, 1) +DEFINE_ALE_FIELD(vlan_untag_force, 24, 3) +DEFINE_ALE_FIELD(vlan_reg_mcast, 16, 3) +DEFINE_ALE_FIELD(vlan_unreg_mcast, 8, 3) +DEFINE_ALE_FIELD(vlan_member_list, 0, 3) +DEFINE_ALE_FIELD(mcast, 40, 1) + +/* The MAC address field in the ALE entry cannot be macroized as above */ +static inline void cpsw_ale_get_addr(u32 *ale_entry, u8 *addr) +{ + int i; + + for (i = 0; i < 6; i++) + addr[i] = cpsw_ale_get_field(ale_entry, 40 - 8*i, 8); +} + +static inline void cpsw_ale_set_addr(u32 *ale_entry, u8 *addr) +{ + int i; + + for (i = 0; i < 6; i++) + cpsw_ale_set_field(ale_entry, 40 - 8*i, 8, addr[i]); +} + +static int cpsw_ale_read(struct cpsw_ale *ale, int idx, u32 *ale_entry) +{ + int i; + + WARN_ON(idx > ale->params.ale_entries); + + __raw_writel(idx, ale->params.ale_regs + ALE_TABLE_CONTROL); + + for (i = 0; i < ALE_ENTRY_WORDS; i++) + ale_entry[i] = __raw_readl(ale->params.ale_regs + + ALE_TABLE + 4 * i); + + return idx; +} + +static int cpsw_ale_write(struct cpsw_ale *ale, int idx, u32 *ale_entry) +{ + int i; + + WARN_ON(idx > ale->params.ale_entries); + + for (i = 0; i < ALE_ENTRY_WORDS; i++) + __raw_writel(ale_entry[i], ale->params.ale_regs + + ALE_TABLE + 4 * i); + + __raw_writel(idx | ALE_TABLE_WRITE, ale->params.ale_regs + + ALE_TABLE_CONTROL); + + return idx; +} + +static int cpsw_ale_match_addr(struct cpsw_ale *ale, u8 *addr) +{ + u32 ale_entry[ALE_ENTRY_WORDS]; + int type, idx; + + for (idx = 0; idx < ale->params.ale_entries; idx++) { + u8 entry_addr[6]; + + cpsw_ale_read(ale, idx, ale_entry); + type = cpsw_ale_get_entry_type(ale_entry); + if (type != ALE_TYPE_ADDR && type != ALE_TYPE_VLAN_ADDR) + continue; + cpsw_ale_get_addr(ale_entry, entry_addr); + if (memcmp(entry_addr, addr, 6) == 0) + return idx; + } + return -ENOENT; +} + +static int cpsw_ale_match_free(struct cpsw_ale *ale) +{ + u32 ale_entry[ALE_ENTRY_WORDS]; + 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_FREE) + return idx; + } + return -ENOENT; +} + +static int cpsw_ale_find_ageable(struct cpsw_ale *ale) +{ + u32 ale_entry[ALE_ENTRY_WORDS]; + 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_ADDR && type != ALE_TYPE_VLAN_ADDR) + continue; + if (cpsw_ale_get_mcast(ale_entry)) + continue; + type = cpsw_ale_get_ucast_type(ale_entry); + if (type != ALE_UCAST_PERSISTANT && + type != ALE_UCAST_OUI) + return idx; + } + return -ENOENT; +} + +static void cpsw_ale_flush_mcast(struct cpsw_ale *ale, u32 *ale_entry, + int port_mask) +{ + int mask; + + mask = cpsw_ale_get_port_mask(ale_entry); + if ((mask & port_mask) == 0) + return; /* ports dont intersect, not interested */ + mask &= ~port_mask; + + /* free if only remaining port is host port */ + if (mask == BIT(ale->params.ale_ports)) + cpsw_ale_set_entry_type(ale_entry, ALE_TYPE_FREE); + else + cpsw_ale_set_port_mask(ale_entry, mask); +} + +static void cpsw_ale_flush_ucast(struct cpsw_ale *ale, u32 *ale_entry, + int port_mask) +{ + int port; + + port = cpsw_ale_get_port_num(ale_entry); + if ((BIT(port) & port_mask) == 0) + return; /* ports dont intersect, not interested */ + cpsw_ale_set_entry_type(ale_entry, ALE_TYPE_FREE); +} + +int cpsw_ale_flush(struct cpsw_ale *ale, int port_mask) +{ + u32 ale_entry[ALE_ENTRY_WORDS]; + int ret, idx; + + for (idx = 0; idx < ale->params.ale_entries; idx++) { + cpsw_ale_read(ale, idx, ale_entry); + ret = cpsw_ale_get_entry_type(ale_entry); + if (ret != ALE_TYPE_ADDR && ret != ALE_TYPE_VLAN_ADDR) + continue; + + if (cpsw_ale_get_mcast(ale_entry)) + cpsw_ale_flush_mcast(ale, ale_entry, port_mask); + else + cpsw_ale_flush_ucast(ale, ale_entry, port_mask); + + cpsw_ale_write(ale, idx, ale_entry); + } + return 0; +} + +int cpsw_ale_add_ucast(struct cpsw_ale *ale, u8 *addr, int port, int flags) +{ + u32 ale_entry[ALE_ENTRY_WORDS] = {0, 0, 0}; + int idx; + + cpsw_ale_set_entry_type(ale_entry, ALE_TYPE_ADDR); + cpsw_ale_set_addr(ale_entry, addr); + cpsw_ale_set_ucast_type(ale_entry, ALE_UCAST_PERSISTANT); + cpsw_ale_set_secure(ale_entry, (flags & ALE_SECURE) ? 1 : 0); + cpsw_ale_set_blocked(ale_entry, (flags & ALE_BLOCKED) ? 1 : 0); + cpsw_ale_set_port_num(ale_entry, port); + + idx = cpsw_ale_match_addr(ale, addr); + if (idx < 0) + idx = cpsw_ale_match_free(ale); + if (idx < 0) + idx = cpsw_ale_find_ageable(ale); + if (idx < 0) + return -ENOMEM; + + cpsw_ale_write(ale, idx, ale_entry); + return 0; +} + +int cpsw_ale_del_ucast(struct cpsw_ale *ale, u8 *addr, int port) +{ + u32 ale_entry[ALE_ENTRY_WORDS] = {0, 0, 0}; + int idx; + + idx = cpsw_ale_match_addr(ale, addr); + if (idx < 0) + return -ENOENT; + + cpsw_ale_set_entry_type(ale_entry, ALE_TYPE_FREE); + cpsw_ale_write(ale, idx, ale_entry); + return 0; +} + +int cpsw_ale_add_mcast(struct cpsw_ale *ale, u8 *addr, int port_mask, + int super, int mcast_state) +{ + u32 ale_entry[ALE_ENTRY_WORDS] = {0, 0, 0}; + int idx, mask; + + idx = cpsw_ale_match_addr(ale, addr); + if (idx >= 0) + cpsw_ale_read(ale, idx, ale_entry); + + cpsw_ale_set_entry_type(ale_entry, ALE_TYPE_ADDR); + cpsw_ale_set_addr(ale_entry, addr); + cpsw_ale_set_super(ale_entry, super); + cpsw_ale_set_mcast_state(ale_entry, mcast_state); + + mask = cpsw_ale_get_port_mask(ale_entry); + port_mask |= mask; + cpsw_ale_set_port_mask(ale_entry, port_mask); + + if (idx < 0) + idx = cpsw_ale_match_free(ale); + if (idx < 0) + idx = cpsw_ale_find_ageable(ale); + if (idx < 0) + return -ENOMEM; + + cpsw_ale_write(ale, idx, ale_entry); + return 0; +} + +int cpsw_ale_del_mcast(struct cpsw_ale *ale, u8 *addr, int port_mask) +{ + u32 ale_entry[ALE_ENTRY_WORDS] = {0, 0, 0}; + int idx; + + idx = cpsw_ale_match_addr(ale, addr); + if (idx < 0) + return -EINVAL; + + cpsw_ale_read(ale, idx, ale_entry); + + if (port_mask) + cpsw_ale_set_port_mask(ale_entry, port_mask); + else + cpsw_ale_set_entry_type(ale_entry, ALE_TYPE_FREE); + + cpsw_ale_write(ale, idx, ale_entry); + return 0; +} + +struct ale_control_info { + const char *name; + int offset, port_offset; + int shift, port_shift; + int bits; +}; + +static const struct ale_control_info ale_controls[ALE_NUM_CONTROLS] = { + [ALE_ENABLE] = { + .name = "enable", + .offset = ALE_CONTROL, + .port_offset = 0, + .shift = 31, + .port_shift = 0, + .bits = 1, + }, + [ALE_CLEAR] = { + .name = "clear", + .offset = ALE_CONTROL, + .port_offset = 0, + .shift = 30, + .port_shift = 0, + .bits = 1, + }, + [ALE_AGEOUT] = { + .name = "ageout", + .offset = ALE_CONTROL, + .port_offset = 0, + .shift = 29, + .port_shift = 0, + .bits = 1, + }, + [ALE_VLAN_NOLEARN] = { + .name = "vlan_nolearn", + .offset = ALE_CONTROL, + .port_offset = 0, + .shift = 7, + .port_shift = 0, + .bits = 1, + }, + [ALE_NO_PORT_VLAN] = { + .name = "no_port_vlan", + .offset = ALE_CONTROL, + .port_offset = 0, + .shift = 6, + .port_shift = 0, + .bits = 1, + }, + [ALE_OUI_DENY] = { + .name = "oui_deny", + .offset = ALE_CONTROL, + .port_offset = 0, + .shift = 5, + .port_shift = 0, + .bits = 1, + }, + [ALE_BYPASS] = { + .name = "bypass", + .offset = ALE_CONTROL, + .port_offset = 0, + .shift = 4, + .port_shift = 0, + .bits = 1, + }, + [ALE_RATE_LIMIT_TX] = { + .name = "rate_limit_tx", + .offset = ALE_CONTROL, + .port_offset = 0, + .shift = 3, + .port_shift = 0, + .bits = 1, + }, + [ALE_VLAN_AWARE] = { + .name = "vlan_aware", + .offset = ALE_CONTROL, + .port_offset = 0, + .shift = 2, + .port_shift = 0, + .bits = 1, + }, + [ALE_AUTH_ENABLE] = { + .name = "auth_enable", + .offset = ALE_CONTROL, + .port_offset = 0, + .shift = 1, + .port_shift = 0, + .bits = 1, + }, + [ALE_RATE_LIMIT] = { + .name = "rate_limit", + .offset = ALE_CONTROL, + .port_offset = 0, + .shift = 0, + .port_shift = 0, + .bits = 1, + }, + [ALE_PORT_STATE] = { + .name = "port_state", + .offset = ALE_PORTCTL, + .port_offset = 4, + .shift = 0, + .port_shift = 0, + .bits = 2, + }, + [ALE_PORT_DROP_UNTAGGED] = { + .name = "drop_untagged", + .offset = ALE_PORTCTL, + .port_offset = 4, + .shift = 2, + .port_shift = 0, + .bits = 1, + }, + [ALE_PORT_DROP_UNKNOWN_VLAN] = { + .name = "drop_unknown", + .offset = ALE_PORTCTL, + .port_offset = 4, + .shift = 3, + .port_shift = 0, + .bits = 1, + }, + [ALE_PORT_NOLEARN] = { + .name = "nolearn", + .offset = ALE_PORTCTL, + .port_offset = 4, + .shift = 4, + .port_shift = 0, + .bits = 1, + }, + [ALE_PORT_MCAST_LIMIT] = { + .name = "mcast_limit", + .offset = ALE_PORTCTL, + .port_offset = 4, + .shift = 16, + .port_shift = 0, + .bits = 8, + }, + [ALE_PORT_BCAST_LIMIT] = { + .name = "bcast_limit", + .offset = ALE_PORTCTL, + .port_offset = 4, + .shift = 24, + .port_shift = 0, + .bits = 8, + }, + [ALE_PORT_UNKNOWN_VLAN_MEMBER] = { + .name = "unknown_vlan_member", + .offset = ALE_UNKNOWNVLAN, + .port_offset = 0, + .shift = 0, + .port_shift = 0, + .bits = 6, + }, + [ALE_PORT_UNKNOWN_MCAST_FLOOD] = { + .name = "unknown_mcast_flood", + .offset = ALE_UNKNOWNVLAN, + .port_offset = 0, + .shift = 8, + .port_shift = 0, + .bits = 6, + }, + [ALE_PORT_UNKNOWN_REG_MCAST_FLOOD] = { + .name = "unknown_reg_flood", + .offset = ALE_UNKNOWNVLAN, + .port_offset = 0, + .shift = 16, + .port_shift = 0, + .bits = 6, + }, + [ALE_PORT_UNTAGGED_EGRESS] = { + .name = "untagged_egress", + .offset = ALE_UNKNOWNVLAN, + .port_offset = 0, + .shift = 24, + .port_shift = 0, + .bits = 6, + }, +}; + +int cpsw_ale_control_set(struct cpsw_ale *ale, int port, int control, + int value) +{ + const struct ale_control_info *info; + int offset, shift; + u32 tmp, mask; + + if (control < 0 || control >= ARRAY_SIZE(ale_controls)) + return -EINVAL; + + info = &ale_controls[control]; + if (info->port_offset == 0 && info->port_shift == 0) + port = 0; /* global, port is a dont care */ + + if (port < 0 || port > ale->params.ale_ports) + return -EINVAL; + + mask = BITMASK(info->bits); + if (value & ~mask) + return -EINVAL; + + offset = info->offset + (port * info->port_offset); + shift = info->shift + (port * info->port_shift); + + tmp = __raw_readl(ale->params.ale_regs + offset); + tmp = (tmp & ~(mask << shift)) | (value << shift); + __raw_writel(tmp, ale->params.ale_regs + offset); + + return 0; +} + +int cpsw_ale_control_get(struct cpsw_ale *ale, int port, int control) +{ + const struct ale_control_info *info; + int offset, shift; + u32 tmp; + + if (control < 0 || control >= ARRAY_SIZE(ale_controls)) + return -EINVAL; + + info = &ale_controls[control]; + if (info->port_offset == 0 && info->port_shift == 0) + port = 0; /* global, port is a dont care */ + + if (port < 0 || port > ale->params.ale_ports) + return -EINVAL; + + offset = info->offset + (port * info->port_offset); + shift = info->shift + (port * info->port_shift); + + tmp = __raw_readl(ale->params.ale_regs + offset) >> shift; + return tmp & BITMASK(info->bits); +} + +static void cpsw_ale_timer(unsigned long arg) +{ + struct cpsw_ale *ale = (struct cpsw_ale *)arg; + + cpsw_ale_control_set(ale, 0, ALE_AGEOUT, 1); + + if (ale->ageout) { + ale->timer.expires = jiffies + ale->ageout; + add_timer(&ale->timer); + } +} + +int cpsw_ale_set_ageout(struct cpsw_ale *ale, int ageout) +{ + del_timer_sync(&ale->timer); + ale->ageout = ageout * HZ; + if (ale->ageout) { + ale->timer.expires = jiffies + ale->ageout; + add_timer(&ale->timer); + } + return 0; +} + +void cpsw_ale_start(struct cpsw_ale *ale) +{ + u32 rev; + + rev = __raw_readl(ale->params.ale_regs + ALE_IDVER); + dev_dbg(ale->params.dev, "initialized cpsw ale revision %d.%d\n", + ALE_VERSION_MAJOR(rev), ALE_VERSION_MINOR(rev)); + cpsw_ale_control_set(ale, 0, ALE_ENABLE, 1); + cpsw_ale_control_set(ale, 0, ALE_CLEAR, 1); + + init_timer(&ale->timer); + ale->timer.data = (unsigned long)ale; + ale->timer.function = cpsw_ale_timer; + if (ale->ageout) { + ale->timer.expires = jiffies + ale->ageout; + add_timer(&ale->timer); + } +} + +void cpsw_ale_stop(struct cpsw_ale *ale) +{ + del_timer_sync(&ale->timer); +} + +struct cpsw_ale *cpsw_ale_create(struct cpsw_ale_params *params) +{ + struct cpsw_ale *ale; + + ale = kzalloc(sizeof(*ale), GFP_KERNEL); + if (!ale) + return NULL; + + ale->params = *params; + ale->ageout = ale->params.ale_ageout * HZ; + + return ale; +} + +int cpsw_ale_destroy(struct cpsw_ale *ale) +{ + if (!ale) + return -EINVAL; + cpsw_ale_stop(ale); + cpsw_ale_control_set(ale, 0, ALE_ENABLE, 0); + kfree(ale); + return 0; +} |