%# -*- coding: utf-8-unix -*- \chapter{FAST软件编程入门指南} \label{app:fast} \subsection{前言} 本文档的主要阅读对象是首次使用FAST平台进行软件开发的工作人员。 文档主要描述了FAST架构软件的主要功能、如何获取软件代码、 介绍软件架构与对应代码结构、编译运行FAST代码。 详细介绍了FAST开发的主要使用函数, 描述了用户如何使用开发函数进行自己代码的开发流程, 最后提供一个完整的开发示例说明,供初学者学习使用。 \subsection{FAST能做什么} FAST平台的主要功能是软硬件配合的网络分组处理功能, 硬件的主要作用是解析报文,对报文进行查表匹配, 进行指定的动作处理,然后分发报文。 报文分发可以直接转发到硬件端口,也可以送到CPU端的UA进程再处理, UA处理完成后可以再通过硬件转发从端口输出。 软件的主要功能有两点: 一是对硬件的流表与对应动作进行配置,指定报文从硬件端口转发或送到CPU端的UA; 二是处理硬件无法处理的报文,处理完成后再转发到硬件输出。 FAST软件的编程主要包括UA编程、规则管理编程和寄存器访问控制编程。 UA编程主要处理硬件送到指定软件的分组数据; 规则管理编程主要是对硬件流表的读写管理; 寄存器访问控制编程主要是对硬件各寄存器资源的访问控制。 对硬件规则的读写与对硬件其他寄存器的读写需要了解FAST架构的虚拟地址空间定义。 \subsection{FAST代码结构} \subsubsection{目录结构} \renewcommand{\arraystretch}{1.5} \begin{table}[!ht] \small \centering \caption{FAST代码目录结构} \label{tab:a:fast_dir-structure} \begin{tabular}{|c|c|c|} \hline \heiti 序号 & \heiti 目录名称 & \heiti 说明\\ \hline 1 & include & 开发所用头文件目录\\ \hline 2 & lib & ua、寄存器读写库目录\\ \hline 4 & app & fast应用工具\\ \hline 6 & tools & 调试测试示例工具\\ \hline 7 & src & 综合示例代码测试\\ \hline \end{tabular} \end{table} \subsubsection{头文件} \begin{enumerate} \item \textbf{fast\_vaddr.h:}FAST平台硬件资源访问对应的虚拟地址空间定义; \item \textbf{fast\_type.h:}FAST平台所用数据类型定义; \item \textbf{fast\_err.h:}FAST平台错误号定义; \item \textbf{fast\_struct.h:}FAST平台数据结构类型定义; \item \textbf{fast.h:}FAST平台所有开发函数定义,包含上述所有头文件; \item \textbf{其他:}不同平台的适配头文件。 \end{enumerate} \subsubsection{库文件} \begin{enumerate} \item \textbf{libreg:}libreg主要是用来实现对硬件寄存器读写动作的封装,底层支持标准PCIe设备的寄存器读写和NetMagic08使用NMAC方式的寄存器读写。在FAST平台所有对硬件的寄存器读写全部封装为统一的接口函数,主要包括硬件资源初始化、寄存器读和寄存器写等函数。用户只需要根据此接口函数读写寄存器即可,底层具体实现用户可以不用关心; \item \textbf{librule:}librule主要是实现对硬件流表规则的软件抽象与具体实现,该规则库实现了一种与硬件对应的流表字段序列,仅是一种参考实现。用户可根据硬件查表关键字来重新定义规则字段序列,但必须要求硬件字段序列与软件字段序列一一对应,包括比特位宽与大小端定义。该库封装了规则的基本操作函数,如初始化规则、添加规则、修改规则和删除规则; \item \textbf{libua:}libua主要是用来支持多用户对不同特征流的处理开发和同一特征流在不同用户进程段流水处理开发。该库的主要功能是使用规则配置的方式设置与用户关联的特征流属性,并提供用户接收与发送该属性特征流的开发接口,实现用户对特征流的接收、处理与发送操作。该库主要封装了UA的初始化函数、流处理回调函数、分组发送函数等。 \end{enumerate} \subsubsection{开发示例} 开发示例提供了一个基于FAST架构的二层交换机实例, 综合使用了libreg和libua两个库实现了一个全软件的二层交换机功能。 \begin{enumerate} \item l2switch l2switch是基于FAST架构开发一个2层以太网交换机原型, 通过对流表默认规则的设置,将硬件所有的分组重定向到UA程序, 然后在UA的分组处理回调函数中实现对分组的2层交换, 然后将分组从指定端口发送或泛洪发送。 该示例主要演示了UA编程开发的主要方法与流程、使用的相关函数, 硬件寄存器地址的操作,以及2层交换机的实现原理。 \item xofp xofp是基于FAST架构开发一个SDN交换机原型, 通过OpenFlow协议连接SDN控制器,SDN交换机默认规则是送往SDN控制器, SDN控制器根据报文数据,配置流表转发下发给硬件,下次进来的报文直接转发输出。 该示例主要演示了SDN交换机OpenFLow协议、处理OpenFLow消息、 使用的相关函数,硬件寄存器地址的操作,以及SDN交换机的实现原理。 \end{enumerate} \subsubsection{工具} 示例程序的另一种表现就是FAST提供的一些小工具,包括单独的寄存器读写命令、规则输出命令、端口状态显示命令和端口分组计数命令。 \begin{enumerate} \item reg\_rw 该工具主要是使用libreg库实现一个对用户指定的寄存器进行读写访问操作,展示了用户如何调用libreg的相关库函数进行寄存器访问操作的方法。 \item rule\_hw 该工具主要是使用libreg库和librule库实现对硬件流表的获取并分组输出操作,展示了用户如何调用librule库的相关库函数进行规则操作。该工具目前只使用了硬件规则的读取操作,更多规则的功能操作可参考综合测试程序。 \item port\_status 该工具主要使用libreg库实现了对各端口的协商状态、链路状态等寄存器的访问并进行各状态值的解释。主要展示了不同端口状态的寄存器地址如何计算,各状态寄存器值返回如何解释,示例代码中包含了目前支持的所有状态寄存器及对应功能说明。 \item port\_counts 该工具主要使用libreg库实现了对各端口分组计数的统计显示,主要展示了不同功能计数统计的寄存器虚拟地址空间定义,各端口对应功能寄存器的计算方法。示例代码中包含了目前支持的所有功能计数统计寄存器定义与说明。 \end{enumerate} \subsubsection{综合测试} 综合测试程序包含了libreg、librule和libua库的综合开发示例功能。 首先,用户可以通过此程序了解各功能库的主要使用函数及调用顺序; 其次,用户可以通过此程序的编译参数了解不同功能库如何添加对应库功能支持。 \subsection{FAST代码运行} \subsubsection{软硬件环境} 该平台可通过采购或免费申请试用获取,用户拿到FAST平台后, 默认系统环境均已支持FAST硬件基础版本与FAST软件基础版本。 FAST硬件基础版本是指基于FAST的标准5级流水线功能; FAST软件基础版本是指FAST-XXX软件版本。 如果用户只需要了解软件功能,可以不用FAST设备, FAST软件可在通用Linux系统中进行编译与测试, 没有硬件平台的软件代码,不能进行硬件寄存器读写、规则配置与报文收发功能。 \subsubsection{代码编译} \begin{enumerate} \item 解压代码 \begin{code}[console] openbox@OpenBox~/hnxs$ tar –zxf fast-0.4.5.tar.gz \end{code} \item 配置环境 \begin{code}[console] openbox@OpenBox~/hnxs/fast-0.4.5$ ./configure \end{code} \item 编译 \begin{code}[console] openbox@OpenBox~/hnxs/fast-0.4.5$ make \end{code} \end{enumerate} \subsubsection{代码执行} \begin{enumerate} \item 读硬件版本号 \begin{code}[console] openbox@OpenBox~/hnxs/fast-0.4.5$ reg_rw rd 0 \end{code} \end{enumerate} \subsection{FAST数据结构} \subsubsection{UM数据结构} \begin{code}[c] struct um_metadata{ u64 ts:32, /**< @brief 时间戳*/ reserve:17, /**< @brief 保留*/ pktsrc:1, /**< @brief 分组的来源,0为网络接口输入,1为CPU输入*/ flowID:14; /**< @brief 流ID*/ u64 seq:8, /**< @brief 分组接收序列号*/ pst:8, /**< @brief 标准协议类型(参考硬件定义)*/ dstmid:8, /**< @brief 下一个处理分组的模块ID*/ srcmid:8, /**< @brief 最近一次处理分组的模块ID*/ len:12, /**< @brief 报文长度*/ discard:1, /**< @brief 丢弃位*/ priority:3, /**< @brief 分组优先级*/ outport:6, /**< @brief 单播:分组输出端口ID,组播/泛洪:组播/泛洪表地址索引*/ outtype:2, /**< @brief 输出类型,00:单播,01:组播,10:泛洪,11:从输入接口输出*/ inport:6, /**< @brief 分组的输入端口号*/ pktdst:1, /**< @brief 分组目的,0为网络接口输出,1为送CPU*/ pkttype:1; /**< @brief 报文类型,0:数据报文,1:控制报文。硬件识别报文类别后,会将pktsrc位交换到此,恢复硬件数据格式*/ u64 user[2]; /**< @brief 用户自定义metadata数据格式与内容 @remarks 此字段由可用户改写,但需要保证数据大小严格限定在16字节*/ }; \end{code} \subsubsection{FAST报文数据结构} \begin{code}[c] struct fast_packet { union { struct um_metadata um; /**< @brief UM模块数据格式定义 @see ::um_metadata */ struct ctl_metadata cm; /**< 控制报文格式定义*/ struct common_metadata md; /**< 公共控制信息,报文类型(0:数据,1:控制)*/ }; #ifdef OPENBOX_S28 u16 flag; /**< @brief 2字节对齐标志,主要是为了IP地址的对齐*/ #endif u8 data[1514]; /**< @brief 完整以太网报文数据,暂时不含CRC数据*/ }; \end{code} \subsection{FAST开发函数} \subsubsection{硬件资源与寄存器} \begin{enumerate} \item int fast\_init\_hw(u64 addr,u64 len); 硬件资源初始化。所有涉及到需要操作硬件寄存器、流表和动作等均需要先调用此函数, 实例化硬件资源,使软件具体访问硬件的能力。 参数:addr,在PCIe标准设备中表示硬件资源的内存映射地址, 通常情况下,用户可以直接填写0。 参数:len,在PCIe标准调和中表示硬件资源的内存地址空间大小, 通常情况下,用户可以直接填写0。 返回值:0表示硬件资源实例化正常,非零表示失败, 如果失败将无法对硬件进行任何操作,程序将自动退出。 \item void fast\_distroy\_hw(void); 硬件资源销毁。PCIe设备主要是撤销对硬件资源的映射, OpenBox-S4主要是对设备发送关闭报文。 一般情况下,用户程序退出时应该调用此函数,释放资源。 但是我们内部并没有做特殊的资源处理,均为系统可自动回收功能, 即使用户不调用此函数,当程序正常退出时,系统也能正常回收资源, 不影响程序再次运行使用。 \item u64 fast\_reg\_rd(u64 regaddr); 寄存器读函数。读取用户指定寄存器位置的值返回。 该函数内部会根据平台的不同使用不同的方法去访问硬件寄存器。 PCIe的平台以正常寄存器方式访问,而NetMagic~08需要使用NMAC报文进行交互获取。 这些不同的操作方式均已对用户屏蔽,用户无需关心该函数底层如何交互与实现, 仅需调用此函数接口来操作寄存器读操作即可。 参数:regaddr,表示用户将要读取的寄存器地址, 该地址即FAST平台所定义的虚拟地址空间,该值均使用相对偏移值的方式使用, 即FAST平台定义的值,对PCIe平台设备访问需要使用绝对地址访问才可以正常访问寄存器, 相对地址转绝对地址已经封装在函数内部, 用户对所有寄存器的操作均可只用相对值访问即可。 返回值:返回该寄存器对应的值。该值的位宽是64位, 在所有FAST平台寄存器的位宽均为64位。 \item void fast\_reg\_wr(u64 regaddr,u64 regvalue)。 寄存器写函数。将指定值写对硬件对应的寄存器位置。 该函数内部会根据平台的不同使用不同的方法去操作硬件寄存器。 PCIe的平台以正常寄存器方式访问,而NetMagic~08需要使用NMAC报文进行交互获取。 这些不同的操作方式均已对用户屏蔽,用户无需关心该函数底层如何交互与实现, 仅需调用此函数接口来操作寄存器写操作即可。 参数:regaddr,表示用户将要写入的寄存器地址, 该地址即FAST平台所定义的虚拟地址空间,该值均使用相对偏移值的方式使用, 即FAST平台定义的值, 对PCIe平台设备访问需要使用绝对地址访问才可以正常访问寄存器, 相对地址转绝对地址已经封装在函数内部, 用户对所有寄存器的操作均可只用相对值访问即可。 参数:regvalue,表示要写入上述参数寄存器位置的值。该值的位宽为64位。 \end{enumerate} \subsubsection{流表操作} \begin{enumerate} \item void init\_rule(u32 default\_action); 初始化规则。初始化规则函数的主要功能是在软件分配软件规则空间, 将硬件规则空间清零,设置硬件规则的默认动作。 该函数只在进行流表操作的应用中进行调用,而且只能被调用一次。 如果有多个应用需要操作规则, 整个系统在关机或重启前所有的应用中也只能由一个应用来调用一次。 主要是该函数会将硬件所有存在流表清空。用户需要自己非常清楚对流表的操作, 在一次流表清空后才可以对流表进行其他操作。 参数:default\_action,默认动作。当所有的流表均不能匹配时, 硬件需要执行一个默认的动作来完成这个分组的处理, 流表可以为空,但默认动作必须要有。详细的动作含义可以参考动作说明。 \item int fast\_add\_rule(struct fast\_rule *rule); 添加一条规则。添加规则函数,先将用户的规则添加在软件流表中, 经过各种比较判断后,看是否能正常添加,有无冲突或是否存在, 如果可以正常添加,则先将规则存储在软件, 然后将此条软件规则数据通过寄存器方式下发到硬件对应规则的存储位置。 参数:rule,规则对象,内部具体字段参考流规则数据结构定义。 返回值:返回当前添加规则成功后此条规则所在的索引号。 \item int fast\_modify\_rule(struct fast\_rule *rule,int idx); 修改一条已经存在的规则。此函数主要是用来修改已经存在的规则, 对其关键字、掩码、动作和优先级等进行修改。 该函数的索引值参数如果指向一条空规则,其操作流程将类似于添加规则函数。 参数:rule,新规则对象,内部具体字段参考流规则数据结构定义。 参数:idx,旧规则所在索引号。 返回值:返回输入参数的idx值说明操作成功,其他值说明操作失败 \item int fast\_del\_rule(int idx); 删除一条已经存储的规则,或将一条规则置为无效。 只需要提供规则所在的索引号,软件可通过索引号定位到对应硬件存储规则位置, 将规则的有效位置为无效,实现一条规则的删除。 删除硬件成功后,将其对应的软件规则进行清除。 参数:idx,删除规则所在索引号。 返回值:返回输入参数的idx值说明操作成功,其他值说明操作失败。 \item int read\_hw\_rule(struct fast\_rule *rule,int index); 读取一条硬件规则。将存储在硬件规则内对应索引的数据读取出来, 还原为一条软件规则数据,存储在用户提供的指针位置。 参数:rule,将要存储规则的内存指针。 参数:idx,将要读取规则所在硬件的索引号。 返回值:返回输入参数的idx值说明操作成功,其他值说明操作失败。 \item void print\_hw\_rule(void)。 打印硬件所有规则信息。此函数是上一个读取硬件规则条目信息的集合操作, 将所有硬件流规则信息一一读出,并打印输出在屏幕上。 \end{enumerate} \subsubsection{UA编程} \begin{enumerate} \item int fast\_ua\_init(int mid,fast\_ua\_recv\_callback callback); UA编程初始化函数。 \item void fast\_ua\_destroy(void); UA编程注销函数。 \item int fast\_ua\_send(struct fast\_packet *pkt,int pkt\_len); UA程序发送报文函数 \item void fast\_ua\_recv()。 启动UA接收用户特征流程序函数 \end{enumerate} \subsection{FAST开发流程} 基于FAST的软件代码开发有三种方式: 第一种是基于FAST的源代码进行修改,整个代码结构不变, 可以直接使用原工程中的所有Makefile文件, 修改完成后,直接make即可得到用户所需要的可执行文件; 第二种是用户根据自己需要,选择需要的开发库与头文件, 自己重新组织新的代码目录与结构进行开发, 原工程中的Makefile文件均不可再用,需要自己重写Makefile文件。 \subsubsection{修改源码开发} \begin{enumerate} \item 以FAST的rule\_rw工具作为修改示例, 修改fast/tools/rule\_rw/目录下main\_rule.c文件; \item 修改main\_rule\_test函数, 将两个端口互转硬件规则改为根据以太网类型来转发。 \begin{code}[c] void main_rule_test(int argc,char *argv[]) { int i = 0,p1 = 1,p2 = 2,idx1 = 0,idx2 = 1; struct fast_rule rule[2] = {{0},{0}};//初始化两条全空的规则 if(argc == 3) { p1 = atoi(argv[1]); p2 = atoi(argv[2]); } //给规则的各字段赋值 rule[i].key.type = htole16(0x0800); //以太网类型 rule[i].key.port = p1; rule[i].priority =0xA; rule[i].action = ACTION_PORT<<28|p2;//动作字段的涵义请参考fast_type.h rule[i].md5[0] = i+1; //给规则对应字段设置掩码,掩码为1表示使用,为0表示忽略 rule[i].mask.type = 0xFFFF; rule[i].mask.port = 0x3F;/*6bit*/ i++; rule[i].key.type = htole16(0x0800); rule[i].key.port = p2; rule[i].priority =0xB; rule[i].action = ACTION_PORT<<28|p1;//动作字段的涵义请参考fast_type.h rule[i].md5[0] = i+1; //给规则对应字段设置掩码,掩码为1表示使用,为0表示忽略 rule[i].mask.type = 0xFFFF; rule[i].mask.port = 0x3F; if(argc == 3) { fast_modify_rule(&rule[i-1],idx1); fast_modify_rule(&rule[i],idx2); printf("Row[%d],Port[%d]----->Port[%d]\n",idx1,p1,p2); printf("Row[%d],Port[%d]----->Port[%d]\n",idx2,p2,p1); } } \end{code} 修改保存退出,打开命令终端,跳到fast目录下,执行交叉编译。 \begin{code}[console] # cd fast-0.4.5 # make \end{code} 编译完成后将把tools/rule\_hw目录下rule\_hw工具放到OpenBox-S4设备, 执行修改过的rule\_hw工具。 \begin{code}[console] # ./rule_hw 0 1 \end{code} \end{enumerate} \subsubsection{编译代码} 在fast代码的根目录执行编译命令。 \begin{code}[console] # make clean # make \end{code}