Skip to content

Commit

Permalink
NET: am79c961: ensure multicast filter is correctly set at open
Browse files Browse the repository at this point in the history
We were clearing out the multicast filter whenever the interface was
upped, and not setting the mode bits correctly.  This can cause
problems if there are any multicast addresses already set at this
point, or if ALLMULTI was set.

Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
Signed-off-by: David S. Miller <davem@davemloft.net>
  • Loading branch information
Russell King - ARM Linux authored and davem330 committed Jun 11, 2011
1 parent d814dee commit bfc6501
Showing 1 changed file with 40 additions and 37 deletions.
77 changes: 40 additions & 37 deletions drivers/net/arm/am79c961a.c
Original file line number Diff line number Diff line change
Expand Up @@ -196,13 +196,50 @@ am79c961_ramtest(struct net_device *dev, unsigned int val)
return errorcount;
}

static void am79c961_mc_hash(char *addr, u16 *hash)
{
if (addr[0] & 0x01) {
int idx, bit;
u32 crc;

crc = ether_crc_le(ETH_ALEN, addr);

idx = crc >> 30;
bit = (crc >> 26) & 15;

hash[idx] |= 1 << bit;
}
}

static unsigned int am79c961_get_rx_mode(struct net_device *dev, u16 *hash)
{
unsigned int mode = MODE_PORT_10BT;

if (dev->flags & IFF_PROMISC) {
mode |= MODE_PROMISC;
memset(hash, 0xff, 4 * sizeof(*hash));
} else if (dev->flags & IFF_ALLMULTI) {
memset(hash, 0xff, 4 * sizeof(*hash));
} else {
struct netdev_hw_addr *ha;

memset(hash, 0, 4 * sizeof(*hash));

netdev_for_each_mc_addr(ha, dev)
am79c961_mc_hash(ha->addr, hash);
}

return mode;
}

static void
am79c961_init_for_open(struct net_device *dev)
{
struct dev_priv *priv = netdev_priv(dev);
unsigned long flags;
unsigned char *p;
u_int hdr_addr, first_free_addr;
u16 multi_hash[4], mode = am79c961_get_rx_mode(dev, multi_hash);
int i;

/*
Expand All @@ -218,16 +255,12 @@ am79c961_init_for_open(struct net_device *dev)
write_ireg (dev->base_addr, 2, 0x0000); /* MODE register selects media */

for (i = LADRL; i <= LADRH; i++)
write_rreg (dev->base_addr, i, 0);
write_rreg (dev->base_addr, i, multi_hash[i - LADRL]);

for (i = PADRL, p = dev->dev_addr; i <= PADRH; i++, p += 2)
write_rreg (dev->base_addr, i, p[0] | (p[1] << 8));

i = MODE_PORT_10BT;
if (dev->flags & IFF_PROMISC)
i |= MODE_PROMISC;

write_rreg (dev->base_addr, MODE, i);
write_rreg (dev->base_addr, MODE, mode);
write_rreg (dev->base_addr, POLLINT, 0);
write_rreg (dev->base_addr, SIZERXR, -RX_BUFFERS);
write_rreg (dev->base_addr, SIZETXR, -TX_BUFFERS);
Expand Down Expand Up @@ -340,46 +373,16 @@ am79c961_close(struct net_device *dev)
return 0;
}

static void am79c961_mc_hash(char *addr, unsigned short *hash)
{
if (addr[0] & 0x01) {
int idx, bit;
u32 crc;

crc = ether_crc_le(ETH_ALEN, addr);

idx = crc >> 30;
bit = (crc >> 26) & 15;

hash[idx] |= 1 << bit;
}
}

/*
* Set or clear promiscuous/multicast mode filter for this adapter.
*/
static void am79c961_setmulticastlist (struct net_device *dev)
{
struct dev_priv *priv = netdev_priv(dev);
unsigned long flags;
unsigned short multi_hash[4], mode;
u16 multi_hash[4], mode = am79c961_get_rx_mode(dev, multi_hash);
int i, stopped;

mode = MODE_PORT_10BT;

if (dev->flags & IFF_PROMISC) {
mode |= MODE_PROMISC;
} else if (dev->flags & IFF_ALLMULTI) {
memset(multi_hash, 0xff, sizeof(multi_hash));
} else {
struct netdev_hw_addr *ha;

memset(multi_hash, 0x00, sizeof(multi_hash));

netdev_for_each_mc_addr(ha, dev)
am79c961_mc_hash(ha->addr, multi_hash);
}

spin_lock_irqsave(&priv->chip_lock, flags);

stopped = read_rreg(dev->base_addr, CSR0) & CSR0_STOP;
Expand Down

0 comments on commit bfc6501

Please sign in to comment.