通过目的地址MAC地址计算出CRC值,用CRC值的高6bit(或其它位,尽量减少多个MAC地址对应同一个HASH key),在HASH表中查找对应bit,若=1则组播接收。HASH表一般用64bits,HASH算法可根据实际优化,有可能以太网的多个MAC算出同一个HASH key,但此地址过滤方式已有效过滤大部分无效帧,减少CPU负荷。32位CRC多项式如下:
x^32 + x^26 + x^23 + x^22 + x^16 + x^12 + x^11 + x^10 + x^8 + x^7 + x^5 + x^4 + x^2 + x^1 + 1
校验和解校验的电路默认初始状态都是 32‘hffffffff,即全1状态。解校验结果为 32’hc704dd7b。
几次幂有则对应位=1。
校验结果定值:在MSB模式下,输出正常的CRC值,将其按位取反后加入原数据后再次CRC得到定值,如下图所示:(MSB指从高位到低位一次CRC,LSB指从低位开始CRC)
在以太网的实际应用中做了一些变动,采用与MSB相同的CRC公式,但是把公式中的高低位对调,并改为LSB,其计算结果是正常LSB的32bit倒序,发送端发送时取反倒序发出,接收端用相同的公式连收CRC一起参与CRC,得到定值。下方代码用于2bit RMII的CRC仿真:
// Copyright (C) 1999-2008 Easics NV.
// This source file may be used and distributed without restriction
// provided that this copyright statement is not removed from the file
// and that any derivative work contains the original copyright notice
// and the associated disclaimer.
//
// THIS SOURCE FILE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS
// OR IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
// WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
//
// Purpose : synthesizable CRC function
// * polynomial: x^32 + x^26 + x^23 + x^22 + x^16 + x^12 + x^11 + x^10 + x^8 + x^7 + x^5 + x^4 + x^2 + x^1 + 1
// * data width: 8
//
// Info : tools@easics.be
// http://www.easics.com
module CRC32_D2_tb;
// polynomial: x^32 + x^26 + x^23 + x^22 + x^16 + x^12 + x^11 + x^10 + x^8 + x^7 + x^5 + x^4 + x^2 + x^1 + 1
// data width: 2
// convention: the first serial bit is D[1]
reg clk;
reg [7:0] cnt;
reg [31:0] crc_msb= 32'hFFFFFFFF;
reg [31:0] crc_lsb= 32'hFFFFFFFF;
initial begin
clk=0;
cnt=0;
end
always #5 clk = ~clk;
always@ (posedge clk)
begin
if (cnt != 31) cnt <= cnt + 1;
if(cnt==1 ) begin
crc_msb <= nextCRC32_D2(2'b00, crc_msb); //09的正常CRC MSB结果=6CC14FBB=0110-1100-1100-0001-0100-1111-1011-1011,取反正序出 ,接在09后参与CRC,得到定值
crc_lsb <= NextCRC_D2 (2'b01, crc_lsb); //09的代码CRC LSB结果=6b15842a=0110-1011-0001-0101-1000-0100-0010-1010,取反倒序出 ,接在09后参与CRC,得到定值
end
else
if(cnt==2 ) begin
crc_msb <= nextCRC32_D2(2'b00, crc_msb);
crc_lsb <= NextCRC_D2 (2'b10, crc_lsb);
end
else
if(cnt==3 ) begin
crc_msb <= nextCRC32_D2(2'b10, crc_msb);
crc_lsb <= NextCRC_D2 (2'b00, crc_lsb);
end
else
if(cnt==4 ) begin
crc_msb <= nextCRC32_D2(2'b01, crc_msb);
crc_lsb <= NextCRC_D2 (2'b00, crc_lsb);
end
//crc
if(cnt==5 ) begin
crc_msb <= nextCRC32_D2(2'b10, crc_msb);
crc_lsb <= NextCRC_D2 (2'b01, crc_lsb);
end
if(cnt==6 ) begin
crc_msb <= nextCRC32_D2(2'b01, crc_msb);
crc_lsb <= NextCRC_D2 (2'b10, crc_lsb);
end
if(cnt==7 ) begin
crc_msb <= nextCRC32_D2(2'b00, crc_msb);
crc_lsb <= NextCRC_D2 (2'b10, crc_lsb);
end
if(cnt==8 ) begin
crc_msb <= nextCRC32_D2(2'b11, crc_msb);
crc_lsb <= NextCRC_D2 (2'b00, crc_lsb);
end
if(cnt==9 ) begin
crc_msb <= nextCRC32_D2(2'b00, crc_msb);
crc_lsb <= NextCRC_D2 (2'b11, crc_lsb);
end
if(cnt==10 ) begin
crc_msb <= nextCRC32_D2(2'b11, crc_msb);
crc_lsb <= NextCRC_D2 (2'b01, crc_lsb);
end
if(cnt==11 ) begin
crc_msb <= nextCRC32_D2(2'b11, crc_msb);
crc_lsb <= NextCRC_D2 (2'b01, crc_lsb);
end
if(cnt==12 ) begin
crc_msb <= nextCRC32_D2(2'b10, crc_msb);
crc_lsb <= NextCRC_D2 (2'b01, crc_lsb);
end
if(cnt==13 ) begin
crc_msb <= nextCRC32_D2(2'b10, crc_msb);
crc_lsb <= NextCRC_D2 (2'b10, crc_lsb);
end
if(cnt==14 ) begin
crc_msb <= nextCRC32_D2(2'b11, crc_msb);
crc_lsb <= NextCRC_D2 (2'b11, crc_lsb);
end
if(cnt==15 ) begin
crc_msb <= nextCRC32_D2(2'b00, crc_msb);
crc_lsb <= NextCRC_D2 (2'b01, crc_lsb);
end
if(cnt==16 ) begin
crc_msb <= nextCRC32_D2(2'b00, crc_msb);
crc_lsb <= NextCRC_D2 (2'b11, crc_lsb);
end
if(cnt==17 ) begin
crc_msb <= nextCRC32_D2(2'b01, crc_msb);
crc_lsb <= NextCRC_D2 (2'b11, crc_lsb);
end
if(cnt==18 ) begin
crc_msb <= nextCRC32_D2(2'b00, crc_msb);
crc_lsb <= NextCRC_D2 (2'b10, crc_lsb);
end
if(cnt==19 ) begin
crc_msb <= nextCRC32_D2(2'b01, crc_msb);
crc_lsb <= NextCRC_D2 (2'b10, crc_lsb);
end
if(cnt==20 ) begin
crc_msb <= nextCRC32_D2(2'b00, crc_msb);
crc_lsb <= NextCRC_D2 (2'b10, crc_lsb);
end
end
function [31:0] nextCRC32_D2; // MSB: the first serial bit is D[1]
input [1:0] Data;
input [31:0] crc;
reg [1:0] d;
reg [31:0] c;
reg [31:0] newcrc;
begin
d = Data;
c = crc;
newcrc[0] = d[0] ^ c[30];
newcrc[1] = d[1] ^ d[0] ^ c[30] ^ c[31];
newcrc[2] = d[1] ^ d[0] ^ c[0] ^ c[30] ^ c[31];
newcrc[3] = d[1] ^ c[1] ^ c[31];
newcrc[4] = d[0] ^ c[2] ^ c[30];
newcrc[5] = d[1] ^ d[0] ^ c[3] ^ c[30] ^ c[31];
newcrc[6] = d[1] ^ c[4] ^ c[31];
newcrc[7] = d[0] ^ c[5] ^ c[30];
newcrc[8] = d[1] ^ d[0] ^ c[6] ^ c[30] ^ c[31];
newcrc[9] = d[1] ^ c[7] ^ c[31];
newcrc[10] = d[0] ^ c[8] ^ c[30];
newcrc[11] = d[1] ^ d[0] ^ c[9] ^ c[30] ^ c[31];
newcrc[12] = d[1] ^ d[0] ^ c[10] ^ c[30] ^ c[31];
newcrc[13] = d[1] ^ c[11] ^ c[31];
newcrc[14] = c[12];
newcrc[15] = c[13];
newcrc[16] = d[0] ^ c[14] ^ c[30];
newcrc[17] = d[1] ^ c[15] ^ c[31];
newcrc[18] = c[16];
newcrc[19] = c[17];
newcrc[20] = c[18];
newcrc[21] = c[19];
newcrc[22] = d[0] ^ c[20] ^ c[30];
newcrc[23] = d[1] ^ d[0] ^ c[21] ^ c[30] ^ c[31];
newcrc[24] = d[1] ^ c[22] ^ c[31];
newcrc[25] = c[23];
newcrc[26] = d[0] ^ c[24] ^ c[30];
newcrc[27] = d[1] ^ c[25] ^ c[31];
newcrc[28] = c[26];
newcrc[29] = c[27];
newcrc[30] = c[28];
newcrc[31] = c[29];
nextCRC32_D2 = newcrc;
end
endfunction
function [31:0] NextCRC_D2; //LSB the first serial bit is Din[0]
input [1 :0] Din;
input [31:0] CRC;
reg [31:0] NewCRC;
begin
NewCRC[0] = CRC[30] ^ Din[1];
NewCRC[1] = CRC[31] ^ CRC[30] ^ Din[1] ^ Din[0];
NewCRC[2] = CRC[31] ^ CRC[30] ^ CRC[0] ^ Din[1] ^ Din[0];
NewCRC[3] = CRC[31] ^ CRC[1] ^ Din[0];
NewCRC[4] = CRC[30] ^ CRC[2] ^ Din[1];
NewCRC[5] = CRC[31] ^ CRC[30] ^ CRC[3] ^ Din[1] ^ Din[0];
NewCRC[6] = CRC[31] ^ CRC[4] ^ Din[0];
NewCRC[7] = CRC[30] ^ CRC[5] ^ Din[1];
NewCRC[8] = CRC[31] ^ CRC[30] ^ CRC[6] ^ Din[1] ^ Din[0];
NewCRC[9] = CRC[31] ^ CRC[7] ^ Din[0];
NewCRC[10] = CRC[30] ^ CRC[8] ^ Din[1];
NewCRC[11] = CRC[31] ^ CRC[30] ^ CRC[9] ^ Din[1] ^ Din[0];
NewCRC[12] = CRC[31] ^ CRC[30] ^ CRC[10] ^ Din[1] ^ Din[0];
NewCRC[13] = CRC[31] ^ CRC[11] ^ Din[0];
NewCRC[14] = CRC[12];
NewCRC[15] = CRC[13];
NewCRC[16] = CRC[30] ^ CRC[14] ^ Din[1];
NewCRC[17] = CRC[31] ^ CRC[15] ^ Din[0];
NewCRC[18] = CRC[16];
NewCRC[19] = CRC[17];
NewCRC[20] = CRC[18];
NewCRC[21] = CRC[19];
NewCRC[22] = CRC[30] ^ CRC[20] ^ Din[1];
NewCRC[23] = CRC[31] ^ CRC[21] ^ CRC[30] ^ Din[1] ^ Din[0];
NewCRC[24] = CRC[31] ^ CRC[22] ^ Din[0];
参考博客一篇:
linux DM9000网卡驱动中设置多播地址功能的理解https://www.cnblogs.com/morris-tech/p/3650222.htmllinux DM9000网卡驱动中设置多播地址函数代码为:
static void
dm9000_hash_table_unlocked(struct net_device *dev)
{
board_info_t *db = netdev_priv(dev);
struct netdev_hw_addr *ha;
int i, oft;
u32 hash_val;
u16 hash_table[4];
u8 rcr = RCR_DIS_LONG | RCR_DIS_CRC | RCR_RXEN;
dm9000_dbg(db, 1, "entering %s\n", __func__);
for (i = 0, oft = DM9000_PAR; i < 6; i++, oft++)
iow(db, oft, dev->dev_addr[i]);
/* Clear Hash Table */
for (i = 0; i < 4; i++)
hash_table[i] = 0x0;
/* broadcast address */
hash_table[3] = 0x8000;
if (dev->flags & IFF_PROMISC)
rcr |= RCR_PRMSC;
if (dev->flags & IFF_ALLMULTI)
rcr |= RCR_ALL;
/* the multicast address in Hash Table : 64 bits */
netdev_for_each_mc_addr(ha, dev) {
hash_val = ether_crc_le(6, ha->addr) & 0x3f;
hash_table[hash_val / 16] |= (u16) 1 << (hash_val % 16);
}
/* Write the hash table to MAC MD table */
for (i = 0, oft = DM9000_MAR; i < 4; i++) {
iow(db, oft++, hash_table[i]);
iow(db, oft++, hash_table[i] >> 8);
}
iow(db, DM9000_RCR, rcr);
}
个人感觉函数代码中有两个地方比较难理解。第一,第21行设置广播地址,hash_table[3] = 0x8000;其将hash表最高位置位。设置多播地址列表为什么还要设置广播地址?为什么要这么设置?第二,第30-33行设置多播地址哈希表,该哈希表是如何工作的?
对于在多播地址哈希表中设置广播地址,可以看到如果代码的第23行第26行代码逻辑不成立,则网卡只接收目的地址为本机MAC地址的数据,广播数据将无法接收到。将广播地址设置到多播地址列表中,则广播地址的数据帧将会在本机处理,与广播地址相关的协议如ARP等才会得到处理。到此为什么要设置多播地址到多播地址哈希表中的目的已经清楚。那么第2个问题来了,为什么将hash_table[3]设置为0x8000便是设置了多播地址?这个与第30-33行设置多播地址哈希表的算法有关系。代码第31行hash_val = ether_crc_le(6, ha->addr) & 0x3f;通过计算mac地址的CRC校验值并与上0x3f来得到多播地址哈希表的哈希值。我们知道协议规定广播MAC地址为FF:FF:FF:FF:FF:FF。可以做个实验将广播地址的CRC校验值计算出来,得到的结果为:0xbe2612ff。部分实验代码如下:
1 #include <linux/init.h>
2 #include <linux/module.h>
3 #include <linux/crc32.h>
4 static int __init
5 hello_init(void)
6 {
7 unsigned int hash_val;
8 unsigned char mac[6] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
9 printk("hello module initialise\n");
10 hash_val = ether_crc_le(6, mac);
11 printk("hash val:%0x\n",hash_val);
12 return 0;
13 }
crc32 val
0xbe2612ff&0x3f = 0x3f = 63。该值按照代码第32行hash_table[hash_val / 16] |= (u16) 1 << (hash_val % 16);计算得到结果便是hash_table[3]的最高位置位。因此不管多播地址列表中是否有将hash_table[3]最高位置位的多播地址,都需要将hash_table[3]的最高位置位。
接下来看第二个问题,第30-33行设置多播地址哈希表。分析代码可以看到多播地址哈希表值是通过计算多播地址的CRC32校验值并与0x3f相与得到的。如果网卡允许对某个多播地址数据帧进行处理,则通过该多播地址计算出来的哈希值对应的多播地址列表相应bit位将会被置位。因此设置多播地址列表就是根据多播地址计算hash值置位硬件多播地址列表寄存器相应位。另外为什么是与0x3f相与得到结果这个与多播地址哈希表为64位有关系。如果为128位多播地址表,则应该和0x7f相与。如果将多播地哈希表设置为char型数组则第32行代码应修改为hash_table[hash_val / 8] |= (u8) 1 << (hash_val % 8);数组大小改为8。
通过这种方式设置多播地址哈希表,并不能使硬件网卡把全部与本机无关的多播地址相关数据帧丢弃,因为不同的多播地址通过上述方式计算出的哈希值可能相同。但该种方式不会丢掉任何一个本机需要处理的含有多播地址的数据帧,同时也起到硬件过滤多播地址的作用,减轻了CPU处理本机无关数据帧的负担。
那么为什么通过置位多播地址相应位便能起到过滤多播地址数据帧的作用呢?因为硬件接收到数据帧后,硬件会按照上述的方式来处理数据帧中的MAC地址字段,计算出MAC的哈希值来找到多播地址表寄存器的相应位,若果对应位置位则将该数据帧传递到上层继续处理,如果对应位未置位则丢弃该数据帧。某款TI网络芯片相关的网络开发包相关文档有如下描述(该款芯片支持512位多播地址列表):
Internally, a hash function is applied to the destination multicast address to calculate an arbitrary value between 0 and 511 and to set its bit to one. When a multicast frame reaches the Ethernet adapter, it applies the same hash function to the destination address contained in the frame and sends it to the next layer (IP layer) if the previously assigned bit is set.