mac下一页456888(mac下一页快捷键)

昨天把网卡的配置信息读进来了,利用配置信息中的class_code,我们找到了pc上的网卡设备。

昨天的教程:在自制操作系统上写网卡驱动(2): 网卡的I/O配置

既然找到了网卡设备,今天就可以来使用网卡了。

那么如何使用呢?

给网卡内部的寄存器写内容就可以控制网卡了。

控制网卡,就是使用网卡收发数据,触发中断等。

网卡内部的寄存器就是网卡的"遥控器",网卡的"控制面板",不同的寄存器就相当于遥控器上的按钮。

具体怎么做呢?

  1. 通过读取PCI配置中的基址寄存器base address Register,BAR的值,来得到网卡内部的寄存器的地址net_card_base_addr,这就是网卡的内部寄存器的地址,往net_card_base_addr上写内容,就是往网卡内部的寄存器里写内容。
  2. 网卡内部都有啥寄存器?这个要看具体网卡的使用手册,就是根据网卡的型号找到对应的datasheet
  3. 在datasheet上,会详细描述各个寄存器的功能,通过设置这些寄存器的值,就可以完成数据的收发,是否触发CPU的中断等功能。

读取网卡的BAR

网卡的BAR就存在于PCI配置信息中,它是PCI配置信息的第4–9行,如下图所示:

mac下一页456888(mac下一页快捷键)

PCI配置信息

第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,它的值显示出来为:

mac下一页456888(mac下一页快捷键)

class_code=02时为网卡,其BAR为0xc101

我们查询到,网卡设备对应的bar为0x0000 c101.那么这个数值代表着什么呢?是怎样对应于网卡内部的“控制面板”呢?

mac下一页456888(mac下一页快捷键)

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,就是访问网卡内部的寄存器。

具体的访问代码为:

//读取网卡内部寄存器的值
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

经过搜索,在这里找到了具体解释:

mac下一页456888(mac下一页快捷键)

10EC是Realtek公司

那么8029是什么?8029是芯片的型号,所以,连起来,就是Realtek 8029。

把"Realtek 8029"作为关键字搜索后,发现这款网卡的核心芯片就是 RTL8029芯片,

那么就可以去搜索RTL8029芯片的datasheet了。这个还是比较容易搜索到。

最终我找到了这个:

mac下一页456888(mac下一页快捷键)

RTL8029AS的芯片手册。

虽然是8029AS,不是8029,但是估计也是8029芯片的升级版,应该跟8029操作起来差不多。这是总体介绍:

mac下一页456888(mac下一页快捷键)

在这个芯片手册中,关于寄存器是这样说的:

mac下一页456888(mac下一页快捷键)

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时,可以这样操作:

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.

mac下一页456888(mac下一页快捷键)

设置CR

我们先调用page_select(2),然后再io_in8(base_addr)得到CR,然后把CR的值放到显示变量bar里进行显示。结果如下:

mac下一页456888(mac下一页快捷键)

可以看到在表格的BAR列,最后一行的值为0x00000080,就是说CR的值为0x80,也就意味着CR的bit8,bit7位10.

那么page_select(3)之后是怎样的呢?

代码改为:

mac下一页456888(mac下一页快捷键)

对应的结果为:0x000000c0.

mac下一页456888(mac下一页快捷键)

这个结果说明我们对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;
}

mac下一页456888(mac下一页快捷键)

mac下一页456888(mac下一页快捷键)

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,关闭网卡。即不再接收和发送任何的数据包。

mac下一页456888(mac下一页快捷键)

PSTART设置了网卡内部用于接收数据的内存区域的开始地址。

PSTOP设置了网卡内部用于存储数据的内存区域的结束地址。

BNRY,接收的最后一个数据的地址。BNRY是boundary的缩写,表示接收数据的边界。

TPSR,发送数据的地址,它是Transmet Page Start Register的缩写。

这4个寄存器设置了收到数据的存放位置。

需要注意的是,这里的数据都是按page页为单位处理的。

每256个字节bytes为一页page.

所以PSTART=0x4C页,PSTOP=0x80页,那么这里一共包含0x80-0x4C=52页。

mac下一页456888(mac下一页快捷键)

代码中设置了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,发送数据的配置寄存器

mac下一页456888(mac下一页快捷键)

	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:

mac下一页456888(mac下一页快捷键)

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中的位,都是选择位。

下一行代码是是关于中断的寄存器:

mac下一页456888(mac下一页快捷键)

IMR,与 ISR连用。每个bits对应一个中断interrupt.

既然与ISR有关了,我们就把ISR看了:

mac下一页456888(mac下一页快捷键)

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

mac下一页456888(mac下一页快捷键)

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;
}

mac下一页456888(mac下一页快捷键)

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下一页456888(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/

创业项目群,学习操作 18个小项目,添加 公众号:李佰秒 微信:2646648137  备注:小项目

本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 3300536702@qq.com 举报,一经查实,本站将立刻删除。
如若转载,请注明出处:https://www.cqzhaolin.cn/5340.html