首页 | 新闻 | 新品 | 文库 | 方案 | 视频 | 下载 | 商城 | 开发板 | 数据中心 | 座谈新版 | 培训 | 工具 | 博客 | 论坛 | 百科 | GEC | 活动 | 主题月 | 电子展
返回列表 回复 发帖

浅谈 NCSI 及其在 Linux 上的实现(4)

浅谈 NCSI 及其在 Linux 上的实现(4)

NCSI 在 Linux 上的简单实现硬件支持要实现 NCSI,首先需要选择支持 NCSI 的 NIC,目前大部分网络芯片厂商都有支持 NCSI 的产品,比如 Broadcom 的 BCM57710,Intel 的 Intel82576 等。
在 Linux 网络协议栈中初始化 NCSI 协议族在 Linux 的网络协议栈中初始化 NCSI 协议族需要了解 Linux 的网络协议栈,这不是本文讨论的重点,本节只是简单介绍在 Linux 网络协议栈的三个层面(Socket 层、Sock 层和 sk_buff 层)上初始化 NCSI 接口。之所以只是介绍这三层,是因为 socket{}、sock{}、sk_buff{} 是 Linux 网络协议栈中最重要的数据结构,也是数据流的连接通道。比如,发送报文时,数据会由 socket{} 通过相应的 proto_ops{} 把数据传给 sock{},sock{} 又通过 proto{} 把数据传到 sk_buff;反过来,当收到报文时,sk_buff{} 通过 net_protocol{} 把数据传给 sock{},后者又通过 proto{} 把数据传给 socket{},socket{} 最后把数据传给用户层,有关这方面具体详细细节在这里不做过多赘述。
  • Socket 层,注册 net_proto_family 结构体
清单 1. 注册 net_proto_family 结构体到全局数组 net_families 中
1
2
3
4
5
6
7
#define PF_NCSI    27  /* NCSI Address Family */
static struct net_proto_family ncsi_family_ops = {
     .family =  PF_NCSI,
     .create =  ncsi_create,
     .owner  =  THIS_MODULE,
};
sock_register(&ncsi_family_ops);




  • Sock 层,填充 proto_ops 结构体,该结构体的成员变量是一些函数指针,分别对应了 NCSI 操作的函数。
清单 2. 填充 proto_ops 结构体
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
static struct proto_ops ncsi_ops = {
      .family =  PF_NCSI,
      .owner =  THIS_MODULE,
      .release =  ncsi_release,
      .bind =    sock_no_bind,
      .connect =  sock_no_connect,
      .socketpair =  sock_no_socketpair,
      .accept =  sock_no_accept,
      .getname =  sock_no_getname,
      .poll =    sock_no_poll,
      .ioctl =  sock_no_ioctl,
      .listen =  sock_no_listen,
      .shutdown =  sock_no_shutdown,
      .setsockopt =  sock_no_setsockopt,
      .getsockopt =  sock_no_getsockopt,
      .sendmsg =  ncsi_sendmsg,
      .recvmsg =  ncsi_recvmsg,
      .mmap =    sock_no_mmap,
      .sendpage =  sock_no_sendpage,
};




  • sk_buff 层,实现从 NIC 接收 NCSI packet 的功能,其中 ncsi_rcv 是从 NIC 中接收 RAW packet 的接口。
清单 3. 实现从 NIC 接收 NCSI packet 的功能
1
2
3
4
5
6
#define NCSI_PROTOCOL 0x88F8
static struct packet_type ncsi_packet_type = {
     .type =    __constant_htons(NCSI_PROTOCOL),
     .func =    ncsi_rcv,
};
dev_add_pack(&ncsi_packet_type);




利用套接字 socket 实现 NCSI 的操作在 Linux 的网络协议栈中初始化了 NCSI socket 的三元组:< 地址族,类型,具体协议 >,也正好是调用 socket 系统函数的 3 个参数。内核中这 3 个数据结构,就可以创建 sock{} 结构。
首先,在利用套接字实现 NCSI 操作之前,为了提高程序的通用性和易读性、减少不一致性,需要对 NCSI 协议族进行宏定义:
清单 4. 对 NCSI 协议族进行宏定义
1
#define AF_NCSI    27  /* NCSI 地址族定义 */




其次,为了便于对 NCSI 包头进行整体性操作,这里根据前文所述的 NCSI 包格式,定义了 NCSI 的包头结构 ncsihdr:
清单 5. NCSI 包头结构的定义
1
2
3
4
5
6
7
8
9
10
11
12
/* NCSI 包头结构的定义 */
struct ncsihdr {
unsigned char mc_id;
unsigned char hdr_rev;
unsigned char reserved0;
unsigned char cmd_iid;
unsigned char cmd;
unsigned char chnl_id;
unsigned short payload_len;
unsigned int reserved2;
unsigned int reserved3;  
};




从根本上说,NCSI 主要包括 4 种原子操作:创建 NCSI socket、关闭 NCSI socket、向已创建的 NCSI socket 发送 NCSI 命令以及从已创建的 NCSI socket 获得 NCSI 响应。一个最简单的 NCSI 命令操作流程如图 7 所示:
图 7. 一个最简单的 NCSI 命令操作流程图其他任意操作都可以通过使用这 4 种原子操作的组合来实现。为简单起见,本文仅对这 4 中最基本的 NCSI 操作给出了在 linux 下的相应实现,而对于由这 4 种操作引申而出的其它复杂操作就不做过多的赘述了。
  • (1) 创建 NCSI socket:
清单 6. 创建 NCSI socket
1
2
3
4
5
6
7
8
9
10
11
int OpenNCSISocket ()
{
// 调用 socket 函数创建一个能够进行网络通信的套接字:协议族为 AF_NCSI,套接字类型为 SOCK_RAW
   sd = socket(AF_NCSI, SOCK_RAW, 0);
   if (sd < 0)
   {
       cout << "NCSI socket creation failed" << endl;
       return -1;
   }   
   return sd;
}




  • (2) 关闭 NCSI socket:
清单 7. 关闭 NCSI socket
1
2
3
4
5
int CloseNCSISocket(int sd)
{
   close(sd);    // 调用 close 函数关闭已创建的套接字
   return 0;
}




  • (3) 向已创建的 NCSI socket 发送 NCSI 命令:
清单 8. 向已创建的 NCSI socket 发送 NCSI 命令
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
int SendRawNCSIPacket( int sd,      // 已创建的 NCSI socket
                     int Port_Num,    //NCSI 命令发送到的以太网接口号
                      unsigned char cmd,    //NCSI 命令号
                      unsigned char channel_id,  // 承载 NCSI 命令的信道号
                      unsigned char *pu8Data,  // 指向 NCSI 载荷的指针
                      unsigned short data_length//NCSI 载荷的长度
                   )
{
     int status;
     struct ncsihdr *ncsih;      //NCSI 包头
     struct iovec xmit_iovec[2];    // 创建 linux I/O 向量 iovec 结构数组
     struct msghdr xmit_message;     // 创建 linux 信息头 msghdr 结构数组
unsigned char pkt_buffer[60];      // 发送缓冲区
unsigned char *ncsi_payload ;    //NCSI 载荷指针

// 封装以太网帧头
memset(pkt_buffer, 0, 60);
memset(pkt_buffer, 0xff, 12);      // 目的地址和源地址均为 0xFF FF FF FF FF FF
pkt_buffer[12] = 0x88;
pkt_buffer[13] = 0xf8;            // 以太网帧头类型字段为 0x88f8,表示 NCSI 命令

// 封装 NCSI 包头
ncsih = (struct ncsihdr *) (pkt_buffer+14);
ncsih->mc_id = 0x0;      // 设置管理控制器 ID 为 0x0
ncsih->hdr_rev = 0x1;      // 设置 NCSI 头版本号围 0x1
ncsih->cmd_iid = inciid();     // inciid() 是一个函数,用于获得当前发送数据包的 instance id
ncsih->cmd = cmd;      // 设置 NCSI 命令的命令号
ncsih->chnl_id = channel_id;    // 设置 NCSI 命令传输的信道号
// 设置 NCSI 载荷的长度。由于网络字节序使用的都是大端模式,所以要调用 htons 进行相应的转换
ncsih->payload_len = htons(data_length);

// 封装 NCSI 载荷
ncsi_payload = (unsigned char *)(pkt_buffer + 14 + sizeof(struct ncsihdr));
if ( (pu8Data!=NULL) && (data_length!=0) )
{
           memcpy(ncsi_payload, pu8Data, data_length);
}

// 封装 xmit_message
xmit_message.msg_name = NULL;
xmit_message.msg_namelen = 0;
xmit_message.msg_iov = xmit_iovec;
xmit_message.msg_iov[0].iov_base = (void *)&Port_Num;
xmit_message.msg_iov[0].iov_len = sizeof(int);
     xmit_message.msg_iov[1].iov_base = (void *)pkt_buffer;
xmit_message.msg_iov[1].iov_len = 60;
xmit_message.msg_iovlen = 2;
xmit_message.msg_control = NULL;
xmit_message.msg_controllen = 0;
xmit_message.msg_flags = 0;

// 调用 linux 的 sendmsg 发送 NCSI 请求
status = sendmsg(sd, &xmit_message, 0);
     return status;
}




  • (4) 从已创建的 NCSI socket 获得 NCSI 响应:
清单 9. 从已创建的 NCSI socket 获得 NCSI 响应
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
int RecvRawNCSIPacket( int sd,        // 已创建的 NCSI socket
                      unsigned char *pu8NCSIpacket,  // 用于存放接收数据的缓冲区
                      int buf_size,        // 接收缓冲区的大小
                      int *Port_Num,      // 接收 NCSI 响应的以太网接口号
                      int wait_time        // 等待时间,单位毫秒
            )
{
     int status;
struct msghdr recv_message;    // 创建 linux 信息头 msghdr 结构数组
struct iovec recv_iovec[3];    // 创建 linux I/O 向量 iovec 结构数组

if ( (NULL == pu8NCSIpacket) || (0 == buf_size )
      return -1;

// 封装 recv_message
recv_message.msg_name = NULL;
recv_message.msg_namelen = 0;
recv_message.msg_iov = recv_iovec;
recv_message.msg_iov[0].iov_base = (void *)&wait_time;
recv_message.msg_iov[0].iov_len = sizeof(int);
recv_message.msg_iov[1].iov_base = (void *) pu8NCSIpacket;
recv_message.msg_iov[1].iov_len = buf_size;
recv_message.msg_iov[2].iov_base = (void *)Port_Num;
recv_message.msg_iov[2].iov_len = sizeof (int *);
recv_message.msg_iovlen = 3;
recv_message.msg_control = NULL;
recv_message.msg_controllen = 0;
recv_message.msg_flags = 0;

// 调用 linux 的 recvmsg 接收 NCSI 响应
status = recvmsg(sd, &recv_message, 0);
return status;
}

返回列表