!!!Chapter 3 The Data Link Layer

3.1 Data Link Layer Design Issues

  1. Providing a well-defined service interface to the network layer.
  2. Dealing with transmission errors.
  3. Regulating the flow of data so that slow receivers are not swamped by fast senders.

The data link layer takes the packets it gets from the network layer and encapsulate them intoframes for transmission.

A frame contains a frame header, a payload field and a frame trailer.

3.1.1 Services Provided to the Network Layer

  • 数据链路层使用物理层提供的服务,为网络层提供服务。
  • 数据链路层提供的服务可以归为以下三种基本服务:
    (1) 无确认的无连接服务 Unacknowledged connectionless service:
    源机器向目的机器发送独立的帧,而目的机器对收到的帧不作确认。事先没有建立连接,事后也不存在释放。传输质量不可靠,这类服务适用于误码率很低的情况。大多数局域网在数据链路层都使用无确认的无连接服务.
    (2)有确认的无连接服务 Acknowledged connectionless service:
    不建立连接,但所发送的帧都进行确认。所以发送方知道帧是否安全到达。这类服务适用于无线系统之类的不可靠信道。
    (3)有确认的面向连接的服务 Acknowledged connection-oriented service:
    建立连接,在连接上发送帧,所发送的每一帧都被编上号,数据链路层保证所发送的每一帧都是安装顺序已收到一次。这种服务最可靠,但最复杂,不容易实现。

3.1.2 Framing

  1. Byte count
  2. Flag bytes with byte stuffing
  3. Flag bits with bit stuffing
  4. Physical layer coding violation

http://www.baike.com/wiki/%E6%95%B0%E6%8D%AE%E9%93%BE%E8%B7%AF%E5%B1%82

3.1.3 Error Control

接收方发回特殊的控制帧,作为帧正确与否的确认帧。但是更糟糕的不是仅仅帧有错,而是帧完全没有到达,发送方可用计时器来解决这个问题。当发送一帧时,启动计时器,一定时间内帧被正确接收并返回确认帧,则计时器清零。有了超时重传,可能会造成同一帧的多次重传,这时可以给各帧编号,以解决这个问题。

3.1.4 Flow Control

通过某种反馈机制,使发送方了解接收方的接收能力,从而调整发送

3.2 Error Detection and Correction

  • Error-correcting codes: include enough redundant information to enable the receiver to deduce what the transmitted data must have been.
  • Error-detecting codes: include enough only enough redundancy to allow the receiver to deduce that an error has occurred and have it request a retransmission.

3.3 Elementary Data Link Protocols

The physical layer process and some of the data link layer process run on dedicate hardware called aNIC (Network Interface Card) 网卡

The rest of the link layer process and the network layer process run on the main CPU as part of the operating system, with the software for the link layer process often taking the form of a device driver.  驱动程序

协议设计
(1)物理层服务:
               to_physical_layer();               //用于发送帧
               from_physical_layer();             //用于接收帧
(2)数据链路层:
               wait_for_event(&event);          //等待事件发生(如帧的到来),event返回发生事情。
(3)网络层:
               from_network_layer();             //接收从网络层来的分组
               to_network_layer();                 //将分组返回给网络层
(4)数据结构:

             #define MAX_PKT 1024                                                //分组最大长度
             typedef unsigned int seq_nr;                                        //给帧编号
             typedef enum {false,true} boolean;
             typedef struct {unsigned char data[MAX_PKT];}packet;//分组数据结构
             typedef enum{data,ack,nak}frame_kind;                       //帧类型
             typedef struct{
                 frame_kind kind;
                 seq_nr seq;
                 seq_nr ack;
                 packet info;
             }frame;                                                                         //帧的数据结构

  • Utopian simplex protocol
  • simplex stop-and-wait protocol for an error-free channel

3.4 Sliding Window Protocols

piggybacking(捎带): The technique of temporarily delaying outgoing acknowledgements so that they can be hooked onto the next outgoing data frame.

  • 双工通信:
    (1)帧头部要加上一个kind字段,以区分数据帧和确认帧。
    (2)确认帧附加在数据帧上:当一个数据帧到达后,接收过程不是立即发送一个独立的控制帧,而是等到网络层有分组传来,让后把确认信息附加上去一起发送出去。这种技术叫捎带(piggybacking),捎带能较好的利用有效的信息带宽。但是,如果网络层一直没有要服务的分组,那么就会一直等待,改进的方案就是等待一个固定的毫秒数,如果等不到,数据链路层只好法一个单独的确认帧了.
  • 滑动窗口(sliding window)协议:每一个要发出的帧都包含一个序号,范围从0到某个最大值(通常是,这样序号就为n,特殊的,停-等滑动窗口协议n=1,限制序号为0和1)。任何时刻,发送进程都保持一组序号,对应于允许发送的帧。这些帧称作在发送窗口(sending window)之内。接收过程也维持一个接收窗口(receiving window),对应一组允许接收的帧。
  • 缓冲区:用于保存已发送但是未收到确认的帧,以被重传。



<think>我们正在讨论配置Root Complex的Link Control Register的Bit4(Link Disable)后,链路没有断开的问题。根据引用[1],当我们设置Link Disable位后,必须等待链路状态寄存器中的"Data Link Layer Link Active"位变为0,否则行为未定义。因此,可能的原因之一是我们没有等待链路完全禁用。 另外,我们需要检查是否有其他因素阻止了链路的禁用。以下是一些可能的原因: 1. **未正确设置寄存器**:确认我们写入的是正确的寄存器和正确的位。Link Control Register在PCI Express Capability Structure中的偏移为0x10,而Bit4是Link Disable位。同时,由于引用[3]提到,该寄存器只有RC才有效,普通设备没有,所以确保我们操作的是RC的寄存器。 2. **等待时间不足**:硬件可能需要一定时间来响应。在设置Link Disable后,必须轮询链路状态寄存器(Link Status Register,偏移0x12)的Bit1(Data Link Layer Link Active)直到它变为0。 3. **链路状态未更新**:可能由于硬件延迟,状态寄存器没有及时更新。需要确保我们读取的是最新的状态。 4. **链路正在处理未完成的事务**:在链路断开之前,硬件需要完成所有未完成的事务。如果事务一直未完成,可能会导致链路无法断开。 5. **其他控制位的干扰**:例如,如果同时设置了其他控制位(如禁用ASPM的位),可能会影响链路禁用的过程。 6. **硬件不支持**:虽然规范定义了该位,但具体的硬件实现可能不完全支持,或者存在硬件缺陷。 7. **寄存器写保护**:在某些情况下,配置空间可能被写保护,导致写入无效。 8. **链路尚未进入L0状态**:如果链路处于低功耗状态(如L1),可能无法立即响应禁用请求。 引用[1]明确提到:软件必须等待链路状态寄存器的"Data Link Layer Link Active"位读为0b,确保链路完全禁用。因此,我们应该在设置Link Disable位后,不断读取链路状态寄存器,直到该位为0。 操作步骤建议: 1. 设置Link Control Register的Bit4(Link Disable)为1。 2. 读取链路状态寄存器(Link Status Register)的Bit1(Data Link Layer Link Active),直到它变为0。 3. 如果长时间未变为0,则考虑硬件问题。 在Linux内核中,我们可以使用以下类似代码(注意:实际代码需要根据内核版本和具体硬件适配): ```c // 设置Link Disable位 pcie_capability_set_word(pdev, PCI_EXP_LNKCTL, PCI_EXP_LNKCTL_LD); // 等待链路禁用,检查Data Link Layer Link Active位变为0 int timeout = 1000; // 超时时间,根据实际情况调整 u16 lnksta; do { msleep(10); // 等待10ms pcie_capability_read_word(pdev, PCI_EXP_LNKSTA, &lnksta); if (!(lnksta & PCI_EXP_LNKSTA_DLLLA)) // 如果DLLLA为0,链路已禁用 break; } while (--timeout); if (!timeout) dev_err(&pdev->dev, "Failed to disable link\n"); ``` 其中,`PCI_EXP_LNKSTA_DLLLA`是链路状态寄存器中表示数据链路层活跃的位(位1),其值为0x200(即第9位为1,但注意在寄存器中,这个位是第1位,因为我们用u16读取,所以第1位对应的是0x0002?实际上,PCI Express规范中,链路状态寄存器的位1是DLLLA,所以掩码应为0x0002)。 实际上,在Linux内核的头文件中,`PCI_EXP_LNKSTA_DLLLA`定义为0x2000?我们需要确认一下。查阅Linux内核头文件(如`include/uapi/linux/pci_regs.h`): ```c #define PCI_EXP_LNKSTA_DLLLA 0x2000 /* Data Link Layer Link Active */ ``` 但请注意,PCI_EXP_LNKSTA寄存器的偏移是0x12,是一个16位寄存器。其中位0是Link Speed,位3:1是Link Width,位4是Undefined,位5是Training Error,位6是Slot Clock Config,而位15:7是保留位。实际上,这里可能我记错了,正确的定义应该是: 在规范中,Link Status Register(偏移0x12)的位1(即第2位,从0开始计数)是Data Link Layer Link Active(DLLLA)。因此,它的掩码应该是0x0002(即第1位为1,因为位0是第0位,位1是第1位)。 但是,在Linux内核中,为了与PCI Express Capability结构中的位置对应,通常使用如下定义(在`include/uapi/linux/pci_regs.h`中): ```c #define PCI_EXP_LNKSTA 16 /* Link Status */ #define PCI_EXP_LNKSTA_DLLLA 0x2000 /* Data Link Layer Link Active */ ``` 这个0x2000看起来是第13位?这似乎与规范不符。实际上,这可能是因为Linux内核定义的是在PCI Express Capability结构中的偏移位置,并且将整个能力结构视为一个32位寄存器组。PCI_EXP_LNKSTA的偏移是16(相对于Capability结构的起始位置),而Capability结构通常从某个配置空间偏移开始。 但是,我们通常使用`pcie_capability_read_word`函数,它会自动定位到PCI Express Capability结构的起始位置,然后加上给定的偏移(这里是PCI_EXP_LNKSTA,即0x12?实际上,在头文件中,PCI_EXP_LNKSTA定义为0x12)。因此,我们读取的是偏移0x12处的16位寄存器。 在头文件中,定义如下: ```c #define PCI_EXP_LNKCAP 0x0c /* Link Capabilities */ #define PCI_EXP_LNKCTL 0x10 /* Link Control */ #define PCI_EXP_LNKSTA 0x12 /* Link Status */ ``` 而PCI_EXP_LNKSTA_DLLLA的定义为: ```c #define PCI_EXP_LNKSTA_DLLLA 0x2000 /* Data Link Layer Link Active */ ``` 这里0x2000是16位寄存器的位13(因为0x2000等于二进制第13位为1)。但规范中,DLLLA是位1(即第1位)。这显然不一致。 重新查阅规范(PCI Express Base Specification 5.0, Chapter 7.5.3.6 Link Status Register): Link Status Register位于PCI Express Capability结构偏移12h(即16+2=18?实际上,Capability结构起始位置+12h)。该寄存器为16位,其中: - Bit 0: Current Link Speed - Bit 3:1: Negotiated Link Width - Bit 4: Undefined - Bit 5: Link Training - Bit 6: Slot Clock Configuration - Bit 7: Data Link Layer Link Active (DLLLA) [注意:规范中,该位是位7] 原来是我混淆了!实际上,在规范中,Data Link Layer Link Active(DLLLA)是位7(即第8位,从0开始计数第7位)。因此,掩码应为0x0080(即1<<7)。 但是,在Linux内核头文件中,PCI_EXP_LNKSTA_DLLLA定义为0x2000,这可能是历史原因或者不同版本规范的变化?我们需要确认内核版本。 实际上,在较新的内核版本(如5.10)中,`include/uapi/linux/pci_regs.h`中定义如下: ```c #define PCI_EXP_LNKSTA 0x12 /* Link Status */ #define PCI_EXP_LNKSTA_DLLLA 0x2000 /* Data Link Layer Link Active */ ``` 这个定义与规范不符。规范中,DLLLA是位7,掩码0x0080,而0x2000是位13(即第14位)。这可能是内核的一个错误?或者另有含义? 经过进一步检查,我发现实际上在PCI Express规范1.0a中,Link Status Register的位定义确实有变化。在规范1.0a中,DLLLA位于位15?不对,规范1.0a中是位7。 实际上,在内核中,这个定义可能是错误的?或者我记错了?为了避免混淆,我们应该按照规范中的位定义来使用。 规范中明确:DLLLA是位7(所以当我们读取16位的Link Status Register时,该位在字节中的位置是第8位,即第7位)。因此,其掩码为0x0080。 但是,为什么内核中定义为0x2000?这可能是历史遗留问题。实际上,在内核代码中,我们发现有些驱动使用0x2000,有些使用0x0080。这可能导致问题。 因此,为了确保正确,我们应该按照规范使用0x0080作为掩码。或者,我们可以使用内核定义的宏,但需要确认该宏是否正确。如果内核定义错误,可能需要使用自定义的掩码。 综上所述,如果我们在设置Link Disable位后链路未断开,除了等待之外,还有可能是由于内核使用了错误的掩码来检查DLLLA位。 建议: 1. 检查内核中PCI_EXP_LNKSTA_DLLLA的定义是否正确(是否与硬件规范一致)。 2. 如果定义不正确,则使用正确的掩码(0x0080)。 另外,引用[2]提到了Link Control 2 Register,但该寄存器与链路禁用的关系不大。 引用[3]则描述了Root Complex的错误中断使能,与链路断开也没有直接关系。 因此,总结可能的原因: 1. 未正确轮询DLLLA位(可能由于掩码错误)。 2. 硬件未在合理时间内响应(可能是硬件问题)。 3. 配置空间写入失败(写保护或硬件错误)。 4. 链路处于低功耗状态,需要先恢复到L0状态。 操作步骤修正: 1. 确保链路处于L0状态(如果处于低功耗状态,需要先退出)。 2. 写入Link Control Register的Bit4为1。 3. 使用正确的掩码(0x0080)轮询Link Status Register的DLLLA位,直到该位为0(表示链路已禁用)。 4. 如果超时,则考虑硬件故障。 最后,如果问题仍然存在,可能需要检查硬件设计或联系硬件厂商。 </think>配置 Root Complex (RC) 的 Link Control Register 的 Bit 4 (Link Disable) 后链路未断开,可能由以下原因导致: ### 主要原因分析 1. **未遵守禁用时序要求** [^1] - 规范要求:设置 Link Disable 后,**必须轮询链路状态寄存器**(Link Status Register)直到 `Data Link Layer Link Active` 位(Bit 7)变为 `0` - 典型代码缺失: ```c // 错误:未检查状态直接操作 pcie_capability_set_word(pdev, PCI_EXP_LNKCTL, PCI_EXP_LNKCTL_LD); // 正确:必须轮询状态 u16 lnksta; do { pcie_capability_read_word(pdev, PCI_EXP_LNKSTA, &lnksta); } while (lnksta & PCI_EXP_LNKSTA_DLLLA); // 等待 Bit7=0 ``` - **风险**:若未等待完成,硬件行为未定义(PCIe 规范
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值