在30天自制操作系统上编写网卡驱动「3」:读取网卡的MAC地址
haoteby 2024-11-26 03:28 25 浏览
昨天把网卡的配置信息读进来了,利用配置信息中的class_code,我们找到了pc上的网卡设备。
昨天的教程:在自制操作系统上写网卡驱动(2): 网卡的I/O配置
既然找到了网卡设备,今天就可以来使用网卡了。
那么如何使用呢?
给网卡内部的寄存器写内容就可以控制网卡了。
控制网卡,就是使用网卡收发数据,触发中断等。
网卡内部的寄存器就是网卡的"遥控器",网卡的"控制面板",不同的寄存器就相当于遥控器上的按钮。
具体怎么做呢?
- 通过读取PCI配置中的基址寄存器base address register,BAR的值,来得到网卡内部的寄存器的地址net_card_base_addr,这就是网卡的内部寄存器的地址,往net_card_base_addr上写内容,就是往网卡内部的寄存器里写内容。
- 网卡内部都有啥寄存器?这个要看具体网卡的使用手册,就是根据网卡的型号找到对应的datasheet
- 在datasheet上,会详细描述各个寄存器的功能,通过设置这些寄存器的值,就可以完成数据的收发,是否触发CPU的中断等功能。
读取网卡的BAR
网卡的BAR就存在于PCI配置信息中,它是PCI配置信息的第4--9行,如下图所示:
第4--9行分别是BA0,BA1,BA2,BA3,BA4,BA5,一个6个BAR.
既然在配置信息内部了,仍然使用我们昨天的方法去读取配置信息即可使用代码:
int offset=4; // 读取第offset行
unsigned int addr3 = addr | (offset<<2);
io_out32(0xCF8,addr3);
unsigned int abr = io_in32(0xCFC);
通过以上代码,我们读取到了配置信息中的BA0,它的值显示出来为:
我们查询到,网卡设备对应的bar为0x0000 c101.那么这个数值代表着什么呢?是怎样对应于网卡内部的“控制面板”呢?
先说结论:如上图bar=0x0000c101时,我们访问I/O端口 0xc100,就是访问网卡内部的寄存器。这个结论是如何得到的?
查看IO BAR Written With Base Address字样下面的说明:
bit0为1,表示访问网卡内部的寄存器需要用CPU的I/O口;如果bit0为0,表示访问网卡内部的寄存器可以像访问内存那样,不用通过I/O口。
当bits0为1时,网卡内部寄存器对应于CPU的I/O的地址net_card_base_addr为:bar & 0xffff ff00
取bar的第8到31位作为net_card_base_addr的第8到31位,然后把net_card_base_addr的第0到7位设置为零即可。
所以,我们这里得到结论,只要访问I/O端口0xc100,就是访问网卡内部的寄存器。
具体的访问代码为:
//读取网卡内部寄存器的值
unsigned int CR = io_in8(0xc100);
//往网卡内部寄存器里写值
unsigned int CR = io_out8(0xc100);
这里为什么要用io_in8和io_out8,为什么要用8位的端口读写函数?不用16位的?不用32位的?
因为这款网卡的内部寄存器是8位的。
这是一款什么样的网卡呢?它的内部寄存器到底是怎样的呢?
网卡内部的寄存器
我们要查询网卡的内部寄存器,首先要得到网卡的型号,怎么看呢?
通过PCI配置信息的第一行vonder_id和device_id。
比如我们这款网卡的vonder_id和device_id分别为:0x10ec和0x8029
经过搜索,在这里找到了具体解释:
那么8029是什么?8029是芯片的型号,所以,连起来,就是Realtek 8029。
把"Realtek 8029"作为关键字搜索后,发现这款网卡的核心芯片就是 RTL8029芯片,
那么就可以去搜索RTL8029芯片的datasheet了。这个还是比较容易搜索到。
最终我找到了这个:
RTL8029AS的芯片手册。
虽然是8029AS,不是8029,但是估计也是8029芯片的升级版,应该跟8029操作起来差不多。这是总体介绍:
在这个芯片手册中,关于寄存器是这样说的:
表5.1.1. Register Table就是8029内部所有的寄存器了。我们要控制网卡收发信息,触发中断信号,就得给这些寄存器设置合适的值。
表中,每个寄存器的意义在datasheet中都有详细说明。
我们先来看一下如何访问表中的寄存器,比如访问某个单独的寄存器:CR寄存器。
先看表中的第1列No(Hex),它表示序号,CR寄存器的No为00,这说明通过0xc100+0x00就可以访问到CR寄存器了。
比如表中有个寄存器FIFO,它的No为06,这意味着通过0xc100+0x06就可以访问CR寄存器了。
但是注意到,这个表每一行的几个寄存器都同时对应着一个No,那么问题就来了,当我们in_io8(0xc100+0x06)时,具体读的是哪一个寄存器呢?
这个还要看CR寄存器内的值。如果CR寄存器的第8,7bits为00,我们此时执行的是读操作,也就是in_io8(0xc100+0x06),那么此时操作的就是FIFO,
如果此时执行的是写操作,那么此时操作寄存器就是TBCR1.
如果CR寄存器的第8,7bits为01,我们此时执行的是读操作,也就是in_io8(0xc100+0x06),那么此时操作的就是PAR5,如果此时执行的是写操作,那么此时操作寄存器就还是PAR5.
如果CR寄存器的第8,7bits为10,我们此时执行的是读操作,也就是in_io8(0xc100+0x06),那么此时操作的就是PAR5,如果此时执行的是写操作,那么此时操作寄存器就还是PAR5.
所以说:某个寄存器的方位方式,由这个寄存器所在的行头和列头的值共同决定。
比如PSTART,我们可以看其所在行01,其所在的列为:Page2-[R],那么要操作这个寄存器,要把CR寄存器的第8,7bit设置为2,即0x10,然后用io_in8(0xc100+0x01)来操作这个寄存器。
所以,我们在访问寄存器前,总是需要先设置一下CR寄存器,那么如何设置这个寄存器呢?
CR的行为00,CR存在于所以列,这意味着访问CR就只用io_in8(0xc100)和io_out8(0xc100,0x01)就可以了。
所以,想把CR寄存器的bit8,bit7的值设置为10时,可以这样操作:
unsigned int temp = in_in8(0xc100); // 取CR原来的值
temp=temp&0x3F;
temp=temp|0x80; // 把bit8,bit7设置为10
in_out8(0xc100+0x00,temp) //temp的值给到CR
由于CR寄存器的bit8,bit7对应着Page0,Page1,Page2,Page3,为了方便的操作各寄存器,我们写了一个改变page的函数page_select:
//输入参数pagenumber,取之可以是0,1,2,3
void page_select(unsigned char pagenumber )
{
unsigned int temp;
temp=io_in8(0xc100);
temp=temp&0x3B;
pagenumber=pagenumber<<6;// 将pagenumber向左移动6为,从bit2,bit1,移动到bit8,bit7
temp=temp|pagenumber;
io_out8(0xc100,temp);
return;
}
这样,当我们需要操作寄存器PSTART的时候,就可以
page_select(2);
io_in8(0xc100+0x01);
这就大大简化了代码的编写,当然还可以继续简化,比如
void operation(unsigned char pagenumber,unsigned char No)
{
page_select(pagenumber);
io_in8(0xc100+No);
}
operation(2,0x01);//只用一句就可以完成对PSTART的操作了
不过我们这里似乎没有必要做太复杂。太复杂了程序的可读性就变差了。如果以后有必要就再说吧。
到这里,我不仅感概,拿到这个datasheet真是太好了,终于可以实现对网卡的自由操作了。
先试试page_select韩式是否能够工作:先调用page_select(2),然后再查看0xc100处的值,看其bit8,bit7是否被设置为10.
我们先调用page_select(2),然后再io_in8(base_addr)得到CR,然后把CR的值放到显示变量bar里进行显示。结果如下:
可以看到在表格的BAR列,最后一行的值为0x00000080,就是说CR的值为0x80,也就意味着CR的bit8,bit7位10.
那么page_select(3)之后是怎样的呢?
代码改为:
对应的结果为:0x000000c0.
这个结果说明我们对RTL8029网卡芯片内的寄存器CR设置成功了。
这就意味着,我们可以对RTL8029网卡芯片内的任何寄存器进行操作了。
这还意味着我们可以对不限于网卡,可以是显卡,也可以是其他卡比如显卡,比如声卡进行芯片内的寄存器设置了。
回到我们的RTL8029芯片。
是时候按照datasheet上的说明,来初始化网卡,获取网卡的mac地址,等操作了。
这些操作是数据接收和发送的基础步骤。
发送数据前的准备工作:读取网卡的MAC地址
网卡的初始化init比较繁琐,需要设置一大堆的寄存器。
我先把代码放出来,然后再解释这些代码:
// 先关闭网卡,然后设置接收数据的内存区域信息,中断信息,以及如果对接收的数据过滤
void netcard_init()
{
unsigned int base_addr=0xC100;// 访问网卡的内部寄存器,只用访问的I/O地址0xC100即可
io_out8(base_addr,0x21);// 设置page0,并且停止显卡的一切操作。这里涉及到CR寄存器的所有8位的意义,在datasheet中有详细解释;
io_out8(base_addr+1,0x4c);// 写PSTART寄存器,即接收数据的内存的开始地址为0x4c页
io_out8(base_addr+2,0x80);// 写PSTOP寄存器,即接收数据的内存的结束地址为0x80页
io_out8(base_addr+3,0x4c);// 当前接收到的最后一个数据所在的页
io_out8(base_addr+4,0x45);// 当前发送的第一数据所在的页
io_out8(base_addr+0xc,0xcc);//RCR:决定哪些数据接收,哪些数据不接收
io_out8(base_addr+0xd,0xe0);//TCR:决定哪些数据发送,哪些数据不发送
io_out8(base_addr+0xe,0xc8);//DCR:决定处理数据时按照怎样的顺序,怎样的长度,是否loopback
io_out8(base_addr+0xf,0x00);//IMR:决定开启哪些中断,这些设置为00,关闭所有中断
// 开始去设置page1的寄存器
page_select(1);
io_out8(base_addr+7,0x4c+1); // CURR, FIFO中,要存储的下一页数据的地址
io_out8(base_addr+8,0x00); // MAR0 // 多播地址的过滤
io_out8(base_addr+9,0x41); // MAR1
io_out8(base_addr+10,0x00);// MAR2
io_out8(base_addr+11,0x80);// MAR3
io_out8(base_addr+12,0x00);// MAR4
io_out8(base_addr+13,0x00);// MAR5
io_out8(base_addr+14,0x00);// MAR6
io_out8(base_addr+15,0x00);// MAR7
// 初始化完成,开启网卡
io_out8(base_addr,0x22); //与开始的0x21对应,0x21是关闭网卡,0x22是开启网卡
return;
}
可以对照CR寄存器的详细解释,看0x21。 其中PS1,PS0=00,即page0.
RD2,RD1,RD0位100,即Abort/Complete remote DMA
然后TXP=0,即has no effect.
STA=0,这一位不控制任何事情,controls nothing.
STP=1,关闭网卡。即不再接收和发送任何的数据包。
PSTART设置了网卡内部用于接收数据的内存区域的开始地址。
PSTOP设置了网卡内部用于存储数据的内存区域的结束地址。
BNRY,接收的最后一个数据的地址。BNRY是boundary的缩写,表示接收数据的边界。
TPSR,发送数据的地址,它是Transmet Page Start Register的缩写。
这4个寄存器设置了收到数据的存放位置。
需要注意的是,这里的数据都是按page页为单位处理的。
每256个字节bytes为一页page.
所以PSTART=0x4C页,PSTOP=0x80页,那么这里一共包含0x80-0x4C=52页。
这意味着MON=0,PRO=0,AM=1,AB=1,AR=0,SEP=0
MON=0, 数据校验关
PRO=0, MAC地址匹配的数据才接收
AM=1,接受有多个目的地的数据
AB=1,接受广播的数据
AR=0, 不接受少于64 bytes的数据
SEP=0,不接受有错误的数据。
这个寄存器果然如其名RCR,Receive Configuration Register,接收配置寄存器。用来配置哪些数据不接收,哪些数据接收。
那么再看一个寄存器TCR,发送数据的配置寄存器
io_out8(base_addr+0xd,0xe0);//TCR:决定哪些数据发送,哪些数据不发送
0xe0意味着OFST=0,ATD=0,LB1=0,LB0=0,CRC=0,
OFST=0, 冲突偏移功能关闭
ATD=0,关闭自动transmitter
LB1=0,LB0=0,正常操作,没有Loopback
CRC=0,开启CRC校验
下一行代码对应的是DCR:
io_out8(base_addr+0xe,0xc8);//DCR
0xc8意味着:FT1=1,FT0=0,ARM=0,LS=1,LAS=0,BOS=0,WTS=0
FT1=1,FT0=0,FIFO的阈值设置寄存器的bit1和 bit0位
ARM=0, 发送没有被执行的包命令
LS=1,正常操作,不进行Loopback
LAS=0,16-位的DMA
BOS=0,MS byte placed on MD15-8,LS byte on MD7-0, 高位和低位的顺序
WTS=0: byte-wide DMA transfer ,DMA传输时的单位是byte还是word
DCR中的位,都是选择位。
下一行代码是是关于中断的寄存器:
IMR,与 ISR连用。每个bits对应一个中断interrupt.
既然与ISR有关了,我们就把ISR看了:
ISR配置在什么情况下触发中断的。
RST:reset或者接收数据满时触发中断
RDC: 远程DMA操作完成时,触发中断。
CNT:技术吻合时触发中断。
OVW: 接收数据太多,太快,数据缓冲区满时,触发中断
TXE:因为冲突造成发送数据取消,触发中断。
RXE:接收数据时,CRC错误,帧对齐错误,包丢失时触发中断
PTX:发送成功,产生中断
PRX:接收成功,产生中断
再往后的代码:
page_select(1);
io_out8(base_addr+7,0x4d); // CURR 当前正在写的页的下一页
io_out8(base_addr+8,0x00); // MAR0
io_out8(base_addr+9,0x41); // MAR1
io_out8(base_addr+10,0x00);// MAR2
io_out8(base_addr+11,0x80);// MAR3
io_out8(base_addr+12,0x00);// MAR4
io_out8(base_addr+13,0x00);// MAR5
io_out8(base_addr+14,0x00);// MAR6
io_out8(base_addr+15,0x00);// MAR7
CUPR就相当于FIFO中P, 接收到下一个数据的时候,所存放的页地址。
关于FIFO可以看这里:30天自制操作系统day07:使鼠标指针可移动
MAR0-7:多播地址寄存器,过滤那些具有多地址的数据。
网卡的初始化工作终于完成了,可以读取网卡的MAC地址了,使用如下代码:
void read_nodeid(unsigned int base_addr,union u *mynodeid)
{
unsigned char i,temp;
union u protocal;// temp var
page_select(0);
// 从dma的0x0000开始读取数据,因为mac地址在其前12个字节,所以从0x0000开始读取
io_out8(base_addr+9,0x00);//RSAR1:the high address of dma read
io_out8(base_addr+8,0x00);//RSAR0:the low address of dma read
// 一共读取0x000c个字节
io_out8(base_addr+0xb,0x00);// RBCR1: the high bits of read count.
io_out8(base_addr+0xa,0x0c);//RBCR0: the low bits of read count
io_out8(base_addr,0x0a);//CR寄存器中,开启remote read 然后开启网卡
//for循环把mac地址读取进来
for(i=0;i<6;i++)
{
temp=io_in8(base_addr+0x10);//从dma读取一个byte
if(i%2==0){
protocal.bytes.high=temp;
}
else{
protocal.bytes.low=temp;
mynodeid[i/2].word=protocal.word;
}
temp=io_in8(base_addr+0x10);//从dma读取一个byte,由于这个byte跟上个byte一样,所以不用存储
}
return;
}
RSAR0,1,这两位设置了读取remote DMA的开始地址.
RBCR0,1, 这两位设置了从remote DMA读取多少个byte.
注意到,这个代码中,使用一个联合体数据结构:
//存储mac地址中的一个字
// 利用这样的一个联合体,方便的把字的高8位,低8位拆出来。
union u {
unsigned int word;
struct{
unsigned char high;
unsigned char low;
}bytes;
char addr[2];
};
还有,为什么丢掉一个byte,因为:
remote read时,存储在0x0000-0x000b里的网卡物理地址0x52544CC118CF是这样的:
525254544C4CC1C11818CFCF
这12字节把网卡地址重复存储了一次。
不过这样存储,单和双的地址存储的是一样的。
其实存储在0x000b后面的是生产厂商的代码和产品标识代码,也是单双地址重复存储。
我们就先不去读生成厂商的代码和产品表示了。
以上代码最后读取到的MAC地址结果为::
可以看到,读取到的Mac地址为54-52-12-00-56-34。
读取到了网卡的MAC地址,就可以在发送数据包的时候,加上这个地址了。
我们理使用网卡发送数据越来越近了。
总结
今天利用配置信息中的Vendor_id=0x10ec和device_id=0x8029,我们对应了网卡的生产商为Realtek,网卡里芯片的具体型号为8029.
根据这写信息,我么找到了这款网卡芯片的操作手册datasheet.
芯片的操作手册就是芯片的使用说明了,里面详细地介绍了如何使用芯片里的寄存器来控制芯片接收数据,发送数据。一旦拿到芯片的操作手册,我们其实已经里操作网卡芯片发送数据很近了。
这里面涉及到两方面的知识:如果去操作网卡里的寄存器,以及网卡里都有哪些寄存器。
通过CPI配置信息中的BAR就可以获取到一个地址,通过这个地址,我们就可以用io_in8(地址)命令读取到网卡内的寄存器值。用io_out8(地址)往网卡的寄存器的值内写入内容,达到控制网卡的目的。
网卡的操作手册datasheet里有对网卡里的寄存器的详细说明。
我们参考着这份datasheet完成了对网卡打开,关闭,设置接收数据缓冲区,设置中断等初始化工作,并读取了属于这个网卡的mac地址。
今天的工作先到这里,后续就可以开始真正的使用网络传输协议来收发数据了。
附录
记录一些写代码过程中的bug,以及debug的过程。
在读取网卡内部的寄存器时,一开始读取总是失败的,后来更改了地址变量的类型后
读取物理地址就成功了。
由unsigned char base_addr,
unsigned int bar = io_in32(0xCFC);
unsigned char base_addr = bar&0x0000ff00;
if(class_code==2){
page_select(3);
unsigned int CR = io_in8(base_addr);
bar = CR;
}
改为了unsigned int base_addr
unsigned int bar = io_in32(0xCFC);
unsigned int base_addr = bar&0x0000ff00;
if(class_code==2){
page_select(3);
unsigned int CR = io_in8(base_addr);
bar = CR;
}
就成功了。
写代码时,主要参考了RTL8019AS的--以太网协议:http://www.doczj.com/doc/cb952888.html
这个代码让我把看到的一些介绍信息和具体的芯片联系起来,总算把datasheet看懂了个大概。
另外,对BAR的理解其实又用的知识挺多,但是我们这里只是用到了部分信息,我是通过一下地址以及图片学习了BAR的相关设置的:
一个介绍比较完整的资料是:https://www.pianshen.com/article/40881826037/
相关推荐
- 谷歌开源大模型评测框架正式发布,AI模型评测难题迎刃而解
-
近日科技巨头谷歌正式推出其开源大模型评测框架LMEval,这一创新工具为全球AI开发者和企业提供了标准化的模型评估解决方案。LMEval的发布不仅标志着AI模型评测迈入透明化时代,更通过多项核心技术...
- Android 开发中文引导-动画和图形概述
-
安卓系统提供了各种强大的API,用来将动画应用于界面元素和自定义2D和3D图形的绘制当中。下面的小节大概的描述了可用的API和系统功能并帮助你决定那个方案最适合你的需要。动画安卓框架提供了两种动画系统...
- Qt5 C++入门教程-第12章 绘图(QPainter)
-
QPainter类在Qt5中进行绘图时起着重要作用。绘图操作是通过QPainter类在响应paintEvent方法时完成的。线条在第一个示例中,我们在窗口的客户区绘制了一些线条。line...
- 文创测评︱《如意琳琅图籍》:本土原创解谜书的胜利?
-
设想这样一个场景,你打开一本书,就化身为乾隆三十六年紫禁城中的画画人周本,有一天你在故纸堆中找到一本神秘的《如意琳琅图籍》,踏上寻宝旅程,历经各种离奇复杂的故事……这是故宫与奥秘之家联手打造的创意解谜...
- gif动图制作攻略!快快收藏(求gif制作的动图)
-
有事没事斗图玩是当下人们乐此不疲的事情,手里的gif动图也渐渐成为了人们抬杠互怼的一大资本。好有趣,好炫酷,gif是怎么做出来的?我也想做。什么?你不会?没关系,我来教你!首先介绍一下制作gif动图需...
- eduis未能初始化界面 无法启动 问题解决办法
-
1.如果edius安装后启动后出现failedtoinitializeskin中文提示无法初始化界面的错误。这说明你的电脑安装了双显卡,而edius所使用的是图形显卡。可以选择edius图标右键...
- Flash Player模拟器更新:Rufffle(flash模拟器安卓下载高版本)
-
Ruffle是一个适用于WindowsPC的FlashPlayer模拟器,用Rust编写。Ruffle作为一个独立的应用程序在所有现代操作系统上原生运行,并通过使用WebAssembly在所有现代...
- 支持终身免费4G流量,星星充电7kW星际智能交流充电桩拆解
-
前言近期星星充电推出了一款星际智能交流充电桩,在正面设有灯条,可根据灯条颜色和显示直观了解充电状态,并设有屏幕显示充电状态和ui表情。充电桩支持220V/7kW充电功率,适配主流新能源车型。并支持终身...
- 乐动随心之fancy pop(乐动随心壶多少钱一个)
-
跳动飞扬的音符像是连通人与人之间心电感应的通关密码,融化陌生,拉近彼此。此次我们邀请到宅男女神江语晨,化身音乐精灵。在歌手、演员身份间游刃自如的她,为我们生动诠释了三种不同的音乐时尚风格,娴静可爱,灵...
- Asus Zenflash 手机也能玩引闪,从此相机是路人
-
在讲解Zenflash之前,不得不提索爱的K750c,这个机器采用了氙气闪光灯,让手机的拍摄上了档次,可玩性更高,不过,说实话,当时手机的摄像头像素低,成像一般,没有掀起太大的波澜,可现在,手机的Cm...
- Axure有哪些鲜为人知的使用技巧?(axure的使用教程)
-
阿拓带你飞:不管是想入门产品经理还是已经是PM的人对AXURE都很关注,它是制作产品原型的重要工具,但是有多少人了解AXURE的使用技巧?本文是来自“知乎问答”整理的回答,一起来看看那些不常用的使用技...
- 挑战黑夜 华硕ZenFlash氙气闪光灯评测
-
【机锋配件】说到摄影,相信许多朋友都非常喜欢,不管是外出游玩拍拍风景,还是和朋友之间聚会,都会掏出手机拍两张,在餐前拍照晒朋友圈更是成为了许多用户的日常爱好,就算不是专业的摄影爱好者,大家也都有一颗热...
- WPS 演示倒计时 3 步设置!从数字动画到进度条全场景教程
-
做PPT时想添加倒计时却找不到入口?WPS演示自带的"动画+计时"功能就能轻松实现——无论是课堂互动的30秒答题倒计时、商务汇报的5分钟限时讲解,还是活动暖场的动...
- flash动画an制作MG动画元素如何调节透明度,小白...
-
如何在flash动画软件里面调节mg动画元素的透明?因为flash动画软件现在已经升级为flash动画软件,所以直接用新版flash动画软件开工,基本功能都差不多,只是flash增加很多智能化、人性...