Merge branch 'NCSI-vlan-filtering'

Samuel Mendoza-Jonas says:

====================
NCSI VLAN Filtering Support

This series (mainly patch 2) adds VLAN filtering to the NCSI implementation.
A fair amount of code already exists in the NCSI stack for VLAN filtering but
none of it is actually hooked up. This goes the final mile and fixes a few
bugs in the existing code found along the way (patch 1).

Patch 3 adds the appropriate flag and callbacks to the ftgmac100 driver to
enable filtering as it's a large consumer of NCSI (and what I've been
testing on).

v3:	- Add comment describing change to ncsi_find_filter()
	- Catch NULL in clear_one_vid() from ncsi_get_filter()
	- Simplify state changes when kicking updated channel
====================

Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
David S. Miller 2017-08-28 16:49:49 -07:00
commit 33b86ba057
7 changed files with 339 additions and 11 deletions

View File

@ -1623,6 +1623,8 @@ static const struct net_device_ops ftgmac100_netdev_ops = {
#ifdef CONFIG_NET_POLL_CONTROLLER #ifdef CONFIG_NET_POLL_CONTROLLER
.ndo_poll_controller = ftgmac100_poll_controller, .ndo_poll_controller = ftgmac100_poll_controller,
#endif #endif
.ndo_vlan_rx_add_vid = ncsi_vlan_rx_add_vid,
.ndo_vlan_rx_kill_vid = ncsi_vlan_rx_kill_vid,
}; };
static int ftgmac100_setup_mdio(struct net_device *netdev) static int ftgmac100_setup_mdio(struct net_device *netdev)
@ -1837,6 +1839,9 @@ static int ftgmac100_probe(struct platform_device *pdev)
NETIF_F_GRO | NETIF_F_SG | NETIF_F_HW_VLAN_CTAG_RX | NETIF_F_GRO | NETIF_F_SG | NETIF_F_HW_VLAN_CTAG_RX |
NETIF_F_HW_VLAN_CTAG_TX; NETIF_F_HW_VLAN_CTAG_TX;
if (priv->use_ncsi)
netdev->hw_features |= NETIF_F_HW_VLAN_CTAG_FILTER;
/* AST2400 doesn't have working HW checksum generation */ /* AST2400 doesn't have working HW checksum generation */
if (np && (of_device_is_compatible(np, "aspeed,ast2400-mac"))) if (np && (of_device_is_compatible(np, "aspeed,ast2400-mac")))
netdev->hw_features &= ~NETIF_F_HW_CSUM; netdev->hw_features &= ~NETIF_F_HW_CSUM;

View File

@ -28,6 +28,8 @@ struct ncsi_dev {
}; };
#ifdef CONFIG_NET_NCSI #ifdef CONFIG_NET_NCSI
int ncsi_vlan_rx_add_vid(struct net_device *dev, __be16 proto, u16 vid);
int ncsi_vlan_rx_kill_vid(struct net_device *dev, __be16 proto, u16 vid);
struct ncsi_dev *ncsi_register_dev(struct net_device *dev, struct ncsi_dev *ncsi_register_dev(struct net_device *dev,
void (*notifier)(struct ncsi_dev *nd)); void (*notifier)(struct ncsi_dev *nd));
int ncsi_start_dev(struct ncsi_dev *nd); int ncsi_start_dev(struct ncsi_dev *nd);

View File

@ -180,6 +180,7 @@ struct ncsi_channel {
#define NCSI_CHANNEL_INACTIVE 1 #define NCSI_CHANNEL_INACTIVE 1
#define NCSI_CHANNEL_ACTIVE 2 #define NCSI_CHANNEL_ACTIVE 2
#define NCSI_CHANNEL_INVISIBLE 3 #define NCSI_CHANNEL_INVISIBLE 3
bool reconfigure_needed;
spinlock_t lock; /* Protect filters etc */ spinlock_t lock; /* Protect filters etc */
struct ncsi_package *package; struct ncsi_package *package;
struct ncsi_channel_version version; struct ncsi_channel_version version;
@ -235,6 +236,9 @@ enum {
ncsi_dev_state_probe_dp, ncsi_dev_state_probe_dp,
ncsi_dev_state_config_sp = 0x0301, ncsi_dev_state_config_sp = 0x0301,
ncsi_dev_state_config_cis, ncsi_dev_state_config_cis,
ncsi_dev_state_config_clear_vids,
ncsi_dev_state_config_svf,
ncsi_dev_state_config_ev,
ncsi_dev_state_config_sma, ncsi_dev_state_config_sma,
ncsi_dev_state_config_ebf, ncsi_dev_state_config_ebf,
#if IS_ENABLED(CONFIG_IPV6) #if IS_ENABLED(CONFIG_IPV6)
@ -253,6 +257,12 @@ enum {
ncsi_dev_state_suspend_done ncsi_dev_state_suspend_done
}; };
struct vlan_vid {
struct list_head list;
__be16 proto;
u16 vid;
};
struct ncsi_dev_priv { struct ncsi_dev_priv {
struct ncsi_dev ndev; /* Associated NCSI device */ struct ncsi_dev ndev; /* Associated NCSI device */
unsigned int flags; /* NCSI device flags */ unsigned int flags; /* NCSI device flags */
@ -276,6 +286,7 @@ struct ncsi_dev_priv {
struct work_struct work; /* For channel management */ struct work_struct work; /* For channel management */
struct packet_type ptype; /* NCSI packet Rx handler */ struct packet_type ptype; /* NCSI packet Rx handler */
struct list_head node; /* Form NCSI device list */ struct list_head node; /* Form NCSI device list */
struct list_head vlan_vids; /* List of active VLAN IDs */
}; };
struct ncsi_cmd_arg { struct ncsi_cmd_arg {

View File

@ -139,9 +139,9 @@ static int ncsi_cmd_handler_svf(struct sk_buff *skb,
struct ncsi_cmd_svf_pkt *cmd; struct ncsi_cmd_svf_pkt *cmd;
cmd = skb_put_zero(skb, sizeof(*cmd)); cmd = skb_put_zero(skb, sizeof(*cmd));
cmd->vlan = htons(nca->words[0]); cmd->vlan = htons(nca->words[1]);
cmd->index = nca->bytes[2]; cmd->index = nca->bytes[6];
cmd->enable = nca->bytes[3]; cmd->enable = nca->bytes[7];
ncsi_cmd_build_header(&cmd->cmd.common, nca); ncsi_cmd_build_header(&cmd->cmd.common, nca);
return 0; return 0;
@ -153,7 +153,7 @@ static int ncsi_cmd_handler_ev(struct sk_buff *skb,
struct ncsi_cmd_ev_pkt *cmd; struct ncsi_cmd_ev_pkt *cmd;
cmd = skb_put_zero(skb, sizeof(*cmd)); cmd = skb_put_zero(skb, sizeof(*cmd));
cmd->mode = nca->bytes[0]; cmd->mode = nca->bytes[3];
ncsi_cmd_build_header(&cmd->cmd.common, nca); ncsi_cmd_build_header(&cmd->cmd.common, nca);
return 0; return 0;
@ -228,7 +228,7 @@ static struct ncsi_cmd_handler {
{ NCSI_PKT_CMD_AE, 8, ncsi_cmd_handler_ae }, { NCSI_PKT_CMD_AE, 8, ncsi_cmd_handler_ae },
{ NCSI_PKT_CMD_SL, 8, ncsi_cmd_handler_sl }, { NCSI_PKT_CMD_SL, 8, ncsi_cmd_handler_sl },
{ NCSI_PKT_CMD_GLS, 0, ncsi_cmd_handler_default }, { NCSI_PKT_CMD_GLS, 0, ncsi_cmd_handler_default },
{ NCSI_PKT_CMD_SVF, 4, ncsi_cmd_handler_svf }, { NCSI_PKT_CMD_SVF, 8, ncsi_cmd_handler_svf },
{ NCSI_PKT_CMD_EV, 4, ncsi_cmd_handler_ev }, { NCSI_PKT_CMD_EV, 4, ncsi_cmd_handler_ev },
{ NCSI_PKT_CMD_DV, 0, ncsi_cmd_handler_default }, { NCSI_PKT_CMD_DV, 0, ncsi_cmd_handler_default },
{ NCSI_PKT_CMD_SMA, 8, ncsi_cmd_handler_sma }, { NCSI_PKT_CMD_SMA, 8, ncsi_cmd_handler_sma },

View File

@ -38,6 +38,25 @@ static inline int ncsi_filter_size(int table)
return sizes[table]; return sizes[table];
} }
u32 *ncsi_get_filter(struct ncsi_channel *nc, int table, int index)
{
struct ncsi_channel_filter *ncf;
int size;
ncf = nc->filters[table];
if (!ncf)
return NULL;
size = ncsi_filter_size(table);
if (size < 0)
return NULL;
return ncf->data + size * index;
}
/* Find the first active filter in a filter table that matches the given
* data parameter. If data is NULL, this returns the first active filter.
*/
int ncsi_find_filter(struct ncsi_channel *nc, int table, void *data) int ncsi_find_filter(struct ncsi_channel *nc, int table, void *data)
{ {
struct ncsi_channel_filter *ncf; struct ncsi_channel_filter *ncf;
@ -58,7 +77,7 @@ int ncsi_find_filter(struct ncsi_channel *nc, int table, void *data)
index = -1; index = -1;
while ((index = find_next_bit(bitmap, ncf->total, index + 1)) while ((index = find_next_bit(bitmap, ncf->total, index + 1))
< ncf->total) { < ncf->total) {
if (!memcmp(ncf->data + size * index, data, size)) { if (!data || !memcmp(ncf->data + size * index, data, size)) {
spin_unlock_irqrestore(&nc->lock, flags); spin_unlock_irqrestore(&nc->lock, flags);
return index; return index;
} }
@ -639,6 +658,95 @@ error:
nd->state = ncsi_dev_state_functional; nd->state = ncsi_dev_state_functional;
} }
/* Check the VLAN filter bitmap for a set filter, and construct a
* "Set VLAN Filter - Disable" packet if found.
*/
static int clear_one_vid(struct ncsi_dev_priv *ndp, struct ncsi_channel *nc,
struct ncsi_cmd_arg *nca)
{
int index;
u32 *data;
u16 vid;
index = ncsi_find_filter(nc, NCSI_FILTER_VLAN, NULL);
if (index < 0) {
/* Filter table empty */
return -1;
}
data = ncsi_get_filter(nc, NCSI_FILTER_VLAN, index);
if (!data) {
netdev_err(ndp->ndev.dev,
"ncsi: failed to retrieve filter %d\n", index);
/* Set the VLAN id to 0 - this will still disable the entry in
* the filter table, but we won't know what it was.
*/
vid = 0;
} else {
vid = *(u16 *)data;
}
netdev_printk(KERN_DEBUG, ndp->ndev.dev,
"ncsi: removed vlan tag %u at index %d\n",
vid, index + 1);
ncsi_remove_filter(nc, NCSI_FILTER_VLAN, index);
nca->type = NCSI_PKT_CMD_SVF;
nca->words[1] = vid;
/* HW filter index starts at 1 */
nca->bytes[6] = index + 1;
nca->bytes[7] = 0x00;
return 0;
}
/* Find an outstanding VLAN tag and constuct a "Set VLAN Filter - Enable"
* packet.
*/
static int set_one_vid(struct ncsi_dev_priv *ndp, struct ncsi_channel *nc,
struct ncsi_cmd_arg *nca)
{
struct vlan_vid *vlan = NULL;
int index = 0;
list_for_each_entry_rcu(vlan, &ndp->vlan_vids, list) {
index = ncsi_find_filter(nc, NCSI_FILTER_VLAN, &vlan->vid);
if (index < 0) {
/* New tag to add */
netdev_printk(KERN_DEBUG, ndp->ndev.dev,
"ncsi: new vlan id to set: %u\n",
vlan->vid);
break;
}
netdev_printk(KERN_DEBUG, ndp->ndev.dev,
"vid %u already at filter pos %d\n",
vlan->vid, index);
}
if (!vlan || index >= 0) {
netdev_printk(KERN_DEBUG, ndp->ndev.dev,
"no vlan ids left to set\n");
return -1;
}
index = ncsi_add_filter(nc, NCSI_FILTER_VLAN, &vlan->vid);
if (index < 0) {
netdev_err(ndp->ndev.dev,
"Failed to add new VLAN tag, error %d\n", index);
return -1;
}
netdev_printk(KERN_DEBUG, ndp->ndev.dev,
"ncsi: set vid %u in packet, index %u\n",
vlan->vid, index + 1);
nca->type = NCSI_PKT_CMD_SVF;
nca->words[1] = vlan->vid;
/* HW filter index starts at 1 */
nca->bytes[6] = index + 1;
nca->bytes[7] = 0x01;
return 0;
}
static void ncsi_configure_channel(struct ncsi_dev_priv *ndp) static void ncsi_configure_channel(struct ncsi_dev_priv *ndp)
{ {
struct ncsi_dev *nd = &ndp->ndev; struct ncsi_dev *nd = &ndp->ndev;
@ -683,8 +791,11 @@ static void ncsi_configure_channel(struct ncsi_dev_priv *ndp)
if (ret) if (ret)
goto error; goto error;
nd->state = ncsi_dev_state_config_sma; nd->state = ncsi_dev_state_config_clear_vids;
break; break;
case ncsi_dev_state_config_clear_vids:
case ncsi_dev_state_config_svf:
case ncsi_dev_state_config_ev:
case ncsi_dev_state_config_sma: case ncsi_dev_state_config_sma:
case ncsi_dev_state_config_ebf: case ncsi_dev_state_config_ebf:
#if IS_ENABLED(CONFIG_IPV6) #if IS_ENABLED(CONFIG_IPV6)
@ -699,11 +810,40 @@ static void ncsi_configure_channel(struct ncsi_dev_priv *ndp)
nca.package = np->id; nca.package = np->id;
nca.channel = nc->id; nca.channel = nc->id;
/* Clear any active filters on the channel before setting */
if (nd->state == ncsi_dev_state_config_clear_vids) {
ret = clear_one_vid(ndp, nc, &nca);
if (ret) {
nd->state = ncsi_dev_state_config_svf;
schedule_work(&ndp->work);
break;
}
/* Repeat */
nd->state = ncsi_dev_state_config_clear_vids;
/* Add known VLAN tags to the filter */
} else if (nd->state == ncsi_dev_state_config_svf) {
ret = set_one_vid(ndp, nc, &nca);
if (ret) {
nd->state = ncsi_dev_state_config_ev;
schedule_work(&ndp->work);
break;
}
/* Repeat */
nd->state = ncsi_dev_state_config_svf;
/* Enable/Disable the VLAN filter */
} else if (nd->state == ncsi_dev_state_config_ev) {
if (list_empty(&ndp->vlan_vids)) {
nca.type = NCSI_PKT_CMD_DV;
} else {
nca.type = NCSI_PKT_CMD_EV;
nca.bytes[3] = NCSI_CAP_VLAN_NO;
}
nd->state = ncsi_dev_state_config_sma;
} else if (nd->state == ncsi_dev_state_config_sma) {
/* Use first entry in unicast filter table. Note that /* Use first entry in unicast filter table. Note that
* the MAC filter table starts from entry 1 instead of * the MAC filter table starts from entry 1 instead of
* 0. * 0.
*/ */
if (nd->state == ncsi_dev_state_config_sma) {
nca.type = NCSI_PKT_CMD_SMA; nca.type = NCSI_PKT_CMD_SMA;
for (index = 0; index < 6; index++) for (index = 0; index < 6; index++)
nca.bytes[index] = dev->dev_addr[index]; nca.bytes[index] = dev->dev_addr[index];
@ -751,6 +891,25 @@ static void ncsi_configure_channel(struct ncsi_dev_priv *ndp)
break; break;
case ncsi_dev_state_config_done: case ncsi_dev_state_config_done:
spin_lock_irqsave(&nc->lock, flags); spin_lock_irqsave(&nc->lock, flags);
if (nc->reconfigure_needed) {
/* This channel's configuration has been updated
* part-way during the config state - start the
* channel configuration over
*/
nc->reconfigure_needed = false;
nc->state = NCSI_CHANNEL_INACTIVE;
spin_unlock_irqrestore(&nc->lock, flags);
spin_lock_irqsave(&ndp->lock, flags);
list_add_tail_rcu(&nc->link, &ndp->channel_queue);
spin_unlock_irqrestore(&ndp->lock, flags);
netdev_printk(KERN_DEBUG, dev,
"Dirty NCSI channel state reset\n");
ncsi_process_next_channel(ndp);
break;
}
if (nc->modes[NCSI_MODE_LINK].data[2] & 0x1) { if (nc->modes[NCSI_MODE_LINK].data[2] & 0x1) {
hot_nc = nc; hot_nc = nc;
nc->state = NCSI_CHANNEL_ACTIVE; nc->state = NCSI_CHANNEL_ACTIVE;
@ -1191,6 +1350,148 @@ static struct notifier_block ncsi_inet6addr_notifier = {
}; };
#endif /* CONFIG_IPV6 */ #endif /* CONFIG_IPV6 */
static int ncsi_kick_channels(struct ncsi_dev_priv *ndp)
{
struct ncsi_dev *nd = &ndp->ndev;
struct ncsi_channel *nc;
struct ncsi_package *np;
unsigned long flags;
unsigned int n = 0;
NCSI_FOR_EACH_PACKAGE(ndp, np) {
NCSI_FOR_EACH_CHANNEL(np, nc) {
spin_lock_irqsave(&nc->lock, flags);
/* Channels may be busy, mark dirty instead of
* kicking if;
* a) not ACTIVE (configured)
* b) in the channel_queue (to be configured)
* c) it's ndev is in the config state
*/
if (nc->state != NCSI_CHANNEL_ACTIVE) {
if ((ndp->ndev.state & 0xff00) ==
ncsi_dev_state_config ||
!list_empty(&nc->link)) {
netdev_printk(KERN_DEBUG, nd->dev,
"ncsi: channel %p marked dirty\n",
nc);
nc->reconfigure_needed = true;
}
spin_unlock_irqrestore(&nc->lock, flags);
continue;
}
spin_unlock_irqrestore(&nc->lock, flags);
ncsi_stop_channel_monitor(nc);
spin_lock_irqsave(&nc->lock, flags);
nc->state = NCSI_CHANNEL_INACTIVE;
spin_unlock_irqrestore(&nc->lock, flags);
spin_lock_irqsave(&ndp->lock, flags);
list_add_tail_rcu(&nc->link, &ndp->channel_queue);
spin_unlock_irqrestore(&ndp->lock, flags);
netdev_printk(KERN_DEBUG, nd->dev,
"ncsi: kicked channel %p\n", nc);
n++;
}
}
return n;
}
int ncsi_vlan_rx_add_vid(struct net_device *dev, __be16 proto, u16 vid)
{
struct ncsi_channel_filter *ncf;
struct ncsi_dev_priv *ndp;
unsigned int n_vids = 0;
struct vlan_vid *vlan;
struct ncsi_dev *nd;
bool found = false;
if (vid == 0)
return 0;
nd = ncsi_find_dev(dev);
if (!nd) {
netdev_warn(dev, "ncsi: No net_device?\n");
return 0;
}
ndp = TO_NCSI_DEV_PRIV(nd);
ncf = ndp->hot_channel->filters[NCSI_FILTER_VLAN];
/* Add the VLAN id to our internal list */
list_for_each_entry_rcu(vlan, &ndp->vlan_vids, list) {
n_vids++;
if (vlan->vid == vid) {
netdev_printk(KERN_DEBUG, dev,
"vid %u already registered\n", vid);
return 0;
}
}
if (n_vids >= ncf->total) {
netdev_info(dev,
"NCSI Channel supports up to %u VLAN tags but %u are already set\n",
ncf->total, n_vids);
return -EINVAL;
}
vlan = kzalloc(sizeof(*vlan), GFP_KERNEL);
if (!vlan)
return -ENOMEM;
vlan->proto = proto;
vlan->vid = vid;
list_add_rcu(&vlan->list, &ndp->vlan_vids);
netdev_printk(KERN_DEBUG, dev, "Added new vid %u\n", vid);
found = ncsi_kick_channels(ndp) != 0;
return found ? ncsi_process_next_channel(ndp) : 0;
}
int ncsi_vlan_rx_kill_vid(struct net_device *dev, __be16 proto, u16 vid)
{
struct vlan_vid *vlan, *tmp;
struct ncsi_dev_priv *ndp;
struct ncsi_dev *nd;
bool found = false;
if (vid == 0)
return 0;
nd = ncsi_find_dev(dev);
if (!nd) {
netdev_warn(dev, "ncsi: no net_device?\n");
return 0;
}
ndp = TO_NCSI_DEV_PRIV(nd);
/* Remove the VLAN id from our internal list */
list_for_each_entry_safe(vlan, tmp, &ndp->vlan_vids, list)
if (vlan->vid == vid) {
netdev_printk(KERN_DEBUG, dev,
"vid %u found, removing\n", vid);
list_del_rcu(&vlan->list);
found = true;
kfree(vlan);
}
if (!found) {
netdev_err(dev, "ncsi: vid %u wasn't registered!\n", vid);
return -EINVAL;
}
found = ncsi_kick_channels(ndp) != 0;
return found ? ncsi_process_next_channel(ndp) : 0;
}
struct ncsi_dev *ncsi_register_dev(struct net_device *dev, struct ncsi_dev *ncsi_register_dev(struct net_device *dev,
void (*handler)(struct ncsi_dev *ndev)) void (*handler)(struct ncsi_dev *ndev))
{ {
@ -1215,6 +1516,7 @@ struct ncsi_dev *ncsi_register_dev(struct net_device *dev,
nd->handler = handler; nd->handler = handler;
ndp->pending_req_num = 0; ndp->pending_req_num = 0;
INIT_LIST_HEAD(&ndp->channel_queue); INIT_LIST_HEAD(&ndp->channel_queue);
INIT_LIST_HEAD(&ndp->vlan_vids);
INIT_WORK(&ndp->work, ncsi_dev_work); INIT_WORK(&ndp->work, ncsi_dev_work);
/* Initialize private NCSI device */ /* Initialize private NCSI device */

View File

@ -104,7 +104,7 @@ struct ncsi_cmd_svf_pkt {
unsigned char index; /* VLAN table index */ unsigned char index; /* VLAN table index */
unsigned char enable; /* Enable or disable */ unsigned char enable; /* Enable or disable */
__be32 checksum; /* Checksum */ __be32 checksum; /* Checksum */
unsigned char pad[14]; unsigned char pad[18];
}; };
/* Enable VLAN */ /* Enable VLAN */

View File

@ -354,7 +354,8 @@ static int ncsi_rsp_handler_svf(struct ncsi_request *nr)
/* Add or remove the VLAN filter */ /* Add or remove the VLAN filter */
if (!(cmd->enable & 0x1)) { if (!(cmd->enable & 0x1)) {
ret = ncsi_remove_filter(nc, NCSI_FILTER_VLAN, cmd->index); /* HW indexes from 1 */
ret = ncsi_remove_filter(nc, NCSI_FILTER_VLAN, cmd->index - 1);
} else { } else {
vlan = ntohs(cmd->vlan); vlan = ntohs(cmd->vlan);
ret = ncsi_add_filter(nc, NCSI_FILTER_VLAN, &vlan); ret = ncsi_add_filter(nc, NCSI_FILTER_VLAN, &vlan);
@ -693,7 +694,14 @@ static int ncsi_rsp_handler_gc(struct ncsi_request *nr)
ncf->index = i; ncf->index = i;
ncf->total = cnt; ncf->total = cnt;
ncf->bitmap = 0x0ul; if (i == NCSI_FILTER_VLAN) {
/* Set VLAN filters active so they are cleared in
* first configuration state
*/
ncf->bitmap = U64_MAX;
} else {
ncf->bitmap = 0x0ul;
}
nc->filters[i] = ncf; nc->filters[i] = ncf;
} }