昨天把网卡的配置信息读进来了,利用配置信息中的class_code,我们找到了pc上的网卡设备。
昨天的教程:在自制操作系统上写网卡驱动(2): 网卡的I/O配置
既然找到了网卡设备,今天就可以来使用网卡了。
那么如何使用呢?
给网卡内部的寄存器写内容就可以控制网卡了。
控制网卡,就是使用网卡收发数据,触发中断等。
网卡内部的寄存器就是网卡的&34;遥控器&34;,网卡的&34;控制面板&34;,不同的寄存器就相当于遥控器上的按钮。
具体怎么做呢?
- 通过读取PCI配置中的基址寄存器base address register,BAR的值,来得到网卡内部的寄存器的地址net_card_base_addr,这就是网卡的内部寄存器的地址,往net_card_base_addr上写内容,就是往网卡内部的寄存器里写内容。
- 网卡内部都有啥寄存器?这个要看具体网卡的使用手册,就是根据网卡的型号找到对应的datasheet
- 在datasheet上,会详细描述各个寄存器的功能,通过设置这些寄存器的值,就可以完成数据的收发,是否触发CPU的中断等功能。
网卡的BAR就存在于PCI配置信息中,它是PCI配置信息的第4--9行,如下图所示:
PCI配置信息
第4--9行分别是BA0,BA1,BA2,BA3,BA4,BA5,一个6个BAR.
既然在配置信息内部了,仍然使用我们昨天的方法去读取配置信息即可使用代码:
通过以上代码,我们读取到了配置信息中的BA0,它的值显示出来为:
class_code=02时为网卡,其BAR为0xc101
我们查询到,网卡设备对应的bar为0x0000 c101.那么这个数值代表着什么呢?是怎样对应于网卡内部的“控制面板”呢?
BAR各bits的意思
先说结论:如上图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,就是访问网卡内部的寄存器。
具体的访问代码为:
这里为什么要用io_in8和io_out8,为什么要用8位的端口读写函数?不用16位的?不用32位的?
因为这款网卡的内部寄存器是8位的。
这是一款什么样的网卡呢?它的内部寄存器到底是怎样的呢?
我们要查询网卡的内部寄存器,首先要得到网卡的型号,怎么看呢?
通过PCI配置信息的第一行vonder_id和device_id。
比如我们这款网卡的vonder_id和device_id分别为:0x10ec和0x8029
经过搜索,在这里找到了具体解释:
10EC是Realtek公司
那么8029是什么?8029是芯片的型号,所以,连起来,就是Realtek 8029。
把&34;Realtek 8029&34;作为关键字搜索后,发现这款网卡的核心芯片就是 RTL8029芯片,
那么就可以去搜索RTL8029芯片的datasheet了。这个还是比较容易搜索到。
最终我找到了这个:
RTL8029AS的芯片手册。
虽然是8029AS,不是8029,但是估计也是8029芯片的升级版,应该跟8029操作起来差不多。这是总体介绍:
在这个芯片手册中,关于寄存器是这样说的:
8029AS的内部寄存器总览
表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时,可以这样操作:
由于CR寄存器的bit8,bit7对应着Page0,Page1,Page2,Page3,为了方便的操作各寄存器,我们写了一个改变page的函数page_select:
这样,当我们需要操作寄存器PSTART的时候,就可以
这就大大简化了代码的编写,当然还可以继续简化,比如
不过我们这里似乎没有必要做太复杂。太复杂了程序的可读性就变差了。如果以后有必要就再说吧。
到这里,我不仅感概,拿到这个datasheet真是太好了,终于可以实现对网卡的自由操作了。
先试试page_select韩式是否能够工作:先调用page_select(2),然后再查看0xc100处的值,看其bit8,bit7是否被设置为10.
设置CR
我们先调用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地址,等操作了。
这些操作是数据接收和发送的基础步骤。
网卡的初始化init比较繁琐,需要设置一大堆的寄存器。
我先把代码放出来,然后再解释这些代码:
CR寄存器每一bit的作用
可以对照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页。
代码中设置了RCR=0xcc
这意味着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,发送数据的配置寄存器
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:
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:接收成功,产生中断
再往后的代码:
CUPR就相当于FIFO中P, 接收到下一个数据的时候,所存放的页地址。
关于FIFO可以看这里:30天自制操作系统day07:使鼠标指针可移动
MAR0-7:多播地址寄存器,过滤那些具有多地址的数据。
网卡的初始化工作终于完成了,可以读取网卡的MAC地址了,使用如下代码:
RSAR0,1,这两位设置了读取remote DMA的开始地址.
RBCR0,1, 这两位设置了从remote DMA读取多少个byte.
注意到,这个代码中,使用一个联合体数据结构:
还有,为什么丢掉一个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 base_addr
就成功了。
写代码时,主要参考了RTL8019AS的--以太网协议:http://www.doczj.com/origin/doc/cb952888.html
这个代码让我把看到的一些介绍信息和具体的芯片联系起来,总算把datasheet看懂了个大概。
另外,对BAR的理解其实又用的知识挺多,但是我们这里只是用到了部分信息,我是通过一下地址以及图片学习了BAR的相关设置的:
一个介绍比较完整的资料是:http://www.pianshen.com/origin/article/40881826037/