正在法国EURECOM进行为期两个月的访问,本博客将持续更新关于OAI RAN侧源码分析,CRAN开发进展,以及我参与的PHY、FAPI开发工作等,按照计划将在回国前完成源码分析文档。
核心侧的开发进展可关注科良的github:https://github.com/dukl
整体流程图及源码分析文档:见我的github:
https://github.com/KujouOct/OAI-analysis
OpenAirInterface LTE源码分析
目录
0、OAI基本概念
1、Top level executable
2、PHY
3、MAC
4、RLC
5、PDCP
6、nFAPI
7、CRAN
理解本文档所述内容需要提前了解:
OAI核心网搭建方法;(see on gitlab)(see on csdn) (see detailed ver in English)
OAI接入网搭建方法;(see on gitlab)(see on csdn) (see detailed ver in English)
LTE协议栈基本内容;
全局流程图:see this visio
0.1~0.6 略
0.7 配置要求:
本文档基于OAI develop分支截止2019.7的最新代码进行分析。接入侧已实现3GPP R10所述的全部功能,R14所述的部分功能,相较于上版源码分析已实现X2 handover,LTE-M等新功能。当前OAI decelop-nr分支已完成物理层下行全部工作,有望在19年年底实现全部物理层功能。本文档将介绍OAI接入侧整体架构及源码流程。
OAI接入侧的基本配置要求:
系统要求为Ubuntu16.04/18.04,使用low-latency内核,内核版本推荐4.15及以上;
射频硬件支持USRP,BladeRF,EXMIMO,LMSSDR,IRIS等,当然只要有驱动程序并不限于这些硬件;4G可使用USRP B2xx,NR需要使用USRP N3xx或同等性能射频。
CPU要求至少有两个物理/逻辑核心用于分别挂载射频线程和OAI各线程,当总核心数高于4时可以通过手动配置CPU Affinity提升性能。推荐Intel 7代Core i7及以上,NR分支推荐Intel8代 Core i7,高频段推荐使用Core i9;
OAI接入侧整体架构:
OAI接入侧根据协议栈层次及功能分为各个模块进行独立开发,顶层根据配置通过调用不同的模块来实现对传统LTE架构以及CRAN等CU/DU分离架构的支持,通过迭代各模块功能的方式实现LTE向NR的过渡。在整体结构上OAI接入侧代码目前分为三大部分,分别为Radio-Cloud Center (RCC)、Radio-Access Unit (RAU)和Remote Radio-Unit(RRU)。其中RCC部分主要包括PDCP层、RRC功能模块;RAU部分主要包括RLC层、MAC层、物理层预编码功能模块;RRU部分主要包括调制解调、信源信道编码等物理层协议栈功能。
工程上,OAI 的main函数位于/targets/RT/USER/目录下,其中 lte-softmodem.c是lte-eNB的可执行文件,lte-uesoftmodem.c是lte-UE的。 本文档主要关注eNB的代码,射频硬件默认为USRP。
本节分析OAI RAN侧顶层代码流程及配置文件各参数意义,不涉及具体协议栈流程。OAI顶层为运行所需参数进行初始化,包括参数声明,资源分配,配置文件读取并配置各模块,模块线程初始化,针对CRAN架构、FlexRAN架构、nFAPI、CI流程等做的顶层流程调整。
OAI 顶层文件目录结构:
├──targets:
├──ARCH
├──BLADEBF 相应驱动
├──COMMON加载依赖及驱动的函数
├──ETHERNET相应驱动
├──IRIS相应驱动
├──LMSSDR相应驱动
├──rfsimulator假射频,已对5G NR分支做出相应开发
├──tcp_bridge
├──USRP相应驱动
├──COMMON
├──DOCS文档、资料、架构图
├──PROJECTS一些配置文件、bash脚本、matlab脚本
├──RT RealTime,包含顶层的函数
├──SCRIPTS安装ASN1的脚本等
├──TEST
oaienv 编译运行OAI RAN时的环境配置
eNB主程序启动后首先加载配置文件:
OAI eNB配置文件关键参数详解:
以enb.band7.tm1.50PRB.usrpb210.conf为例:
图1-1 配置文件结构
示例:
Active_eNBs = ( "eNB-Eurecom-LTEBox");
# Asn1_verbosity, choice in: none, info, annoying
# Asn1的冗长性
Asn1_verbosity = "none";
eNBs =
(
{
////////// Identification parameters:
eNB_ID = 0xe00; //基站ID
cell_type = "CELL_MACRO_ENB"; //基站类型,宏基站
eNB_name = "eNB-Eurecom-LTEBox";//基站名
// Tracking area code, 0x0000 and 0xfffe are reserved values
tracking_area_code = 1; //TAC,最新版本OAI已支持多MME,可配置多个TAC
plmn_list = ( { mcc = 208; mnc = 93; mnc_length = 2; } ); //PLMN配置,可配置为3位mcc+2位mnc或者2位mcc+3位mnc,通过mnc_length控制。
tr_s_preference = "local_mac"
////////// Physical parameters:
component_carriers = (
{
node_function = "3GPP_eNODEB";
node_timing = "synch_to_ext_device";//节点时间同步方式
node_synch_ref = 0; //CRAN,节点参考同步时钟源
frame_type = "FDD";//TDD or FDD
tdd_config = 3; //TDD模式下的配置方式
tdd_config_s = 0;
prefix_type = "NORMAL";//CP类型,NORMAL 或 EXTENDED
eutra_band = 7; //band配置,需要配合下面两个一起修改band和频点
downlink_frequency = 2685000000L; //下行频率
uplink_frequency_offset = -120000000; //上行offset
Nid_cell = 0;
N_RB_DL = 50; //PRB数量
Nid_cell_mbsfn = 0; //组播
nb_antenna_ports = 1; //天线端口数
nb_antennas_tx = 1; //发送天线数
nb_antennas_rx = 1; //接收天线数
tx_gain = 90; //发送增益
rx_gain = 125; //接收增益
pbch_repetition = "FALSE";//pbch信道重复,pbch本身有四次重复
}
);
srb1_parameters :
//SRB1高优先级信令承载配置,SRB1负责RRC信令,SRB2负责NAS信令
{
# timer_poll_retransmit = (ms) [5, 10, 15, 20,... 250, 300, 350, ... 500]
timer_poll_retransmit = 80;
# timer_reordering = (ms) [0,5, ... 100, 110, 120, ... ,200]
timer_reordering = 35;
# timer_reordering = (ms) [0,5, ... 250, 300, 350, ... ,500]
timer_status_prohibit = 0;
# poll_pdu = [4, 8, 16, 32 , 64, 128, 256, infinity(>10000)]
poll_pdu = 4;
# poll_byte = (kB) [25,50,75,100,125,250,375,500,750,1000,1250,1500,2000,3000,infinity(>10000)]
poll_byte = 99999;
# max_retx_threshold = [1, 2, 3, 4 , 6, 8, 16, 32]
max_retx_threshold = 4;
}
# ------- SCTP definitions
SCTP :
{
# Number of streams to use in input/output
SCTP_INSTREAMS = 2;
SCTP_OUTSTREAMS = 2;
};
////////// MME parameters:
mme_ip_address = ( { ipv4 = "10.64.93.19";//MME的IP地址
ipv6 = "192:168:30::17";
active = "yes";
preference = "ipv4";
}
);
enable_measurement_reports = "no";
///X2
enable_x2 = "no";
t_reloc_prep = 1000; /* unit: millisecond */
tx2_reloc_overall = 2000; /* unit: millisecond */
NETWORK_INTERFACES :
{
ENB_INTERFACE_NAME_FOR_S1_MME = "eno1";//eNB S1接口网卡
ENB_IPV4_ADDRESS_FOR_S1_MME = "10.64.45.62/23";//eNB S1接口IP
ENB_INTERFACE_NAME_FOR_S1U = "eno1";//eNB S1U接口网卡
ENB_IPV4_ADDRESS_FOR_S1U = "10.64.45.62/23"; //eNB S1U接口IP
ENB_PORT_FOR_S1U = 2152; # Spec 2152 根据标准定义固定为2152端口
ENB_IPV4_ADDRESS_FOR_X2C = "192.168.12.111/24";
ENB_PORT_FOR_X2C = 36422; # Spec 36422
};
}
);
# CRAN中CU、DU分离结构下CU DU 配置
DU = (
{
DU_INTERFACE_NAME_FOR_F1U = "lo";
DU_IPV4_ADDRESS_FOR_F1U = "127.0.0.1/16";
DU_PORT_FOR_F1U = 22100;
F1_U_DU_TRANSPORT_TYPE = "TCP";
}
);
CU = (
{
CU_INTERFACE_NAME_FOR_F1U = "lo";
CU_IPV4_ADDRESS_FOR_F1U = "127.0.0.1"; //Address to search the DU
CU_PORT_FOR_F1U = 22100;
F1_U_CU_TRANSPORT_TYPE = "TCP"; // One of TCP/UDP/SCTP
DU_TYPE = "LTE";
}//,
// {
// CU_INTERFACE_NAME_FOR_F1U = "eth0";
// CU_IPV4_ADDRESS_FOR_F1U = "10.64.93.142"; //Address to search the DU
// CU_PORT_FOR_F1U = 2211;
// F1_U_CU_TRANSPORT_TYPE = "TCP"; // One of TCP/UDP/SCTP
// DU_TYPE = "WiFi";
// }
);
CU_BALANCING = "ALL";
#FlexRAN配置
NETWORK_CONTROLLER :
{
FLEXRAN_ENABLED = "no";
FLEXRAN_INTERFACE_NAME = "lo";
FLEXRAN_IPV4_ADDRESS = "127.0.0.1";
FLEXRAN_PORT = 2210;
FLEXRAN_CACHE = "/mnt/oai_agent_cache";
FLEXRAN_AWAIT_RECONF = "no";
};
THREAD_STRUCT = (
{
#three config for level of parallelism "PARALLEL_SINGLE_THREAD", "PARALLEL_RU_L1_SPLIT", or "PARALLEL_RU_L1_TRX_SPLIT"
parallel_config = "PARALLEL_RU_L1_TRX_SPLIT";
#two option for worker "WORKER_DISABLE" or "WORKER_ENABLE"
worker_config = "WORKER_ENABLE";
}
);
1.1.2 设置CPU工作状态
保持CPU在C0状态以防止CPU因电源管理策略而导致的性能降低:
void set_latency_target(void) {
struct stat s;
int ret;
//查看在当前内核中CPU是否支持电源管理
if (stat("/dev/cpu_dma_latency", &s) == 0) {
latency_target_fd = open("/dev/cpu_dma_latency", O_RDWR);
//不支持,则结束
if (latency_target_fd == -1)
return;
//支持,则向内核文件中写入一个0 (32bit, i.e. 4Byte),从而令CPU保持在C0状态运行
ret = write(latency_target_fd, &latency_target_value, 4);
if (ret == 0) {
printf("# error setting cpu_dma_latency to %d!: %s\n", latency_target_value, strerror(errno));
close(latency_target_fd);
return;
}
printf("# /dev/cpu_dma_latency set to %dus\n", latency_target_value);
}
//注意,直接结束该函数,并且不要关闭latency_target_fd文件,从而一直保持电源状态,关闭后将回到自动电源管理状态。
}
1.1.3 log线程初始化
在logInit()函数中为log文件做初始化,
图1-2 log进程初始化流程
首先通过register_log_component()初始化PHY、MAC等部分的log内容;
g_log = calloc(1, sizeof(log_t));
memset(g_log,0,sizeof(log_t));
register_log_component("PHY","log",PHY);
register_log_component("MAC","log",MAC);
……
然后创建/tmp/openair.log文件,通过log_getconfig()函数从1.1.1节所述的配置文件中读取各模块log的配置,设置log显示选项(启用/禁用,颜色,线程名称,级别);
然后构建参数数组以设置每个组件的日志级别和infile选项,读取参数,进行设置,最后再设置debug mask。
注意,所有log文件的扩展名不能超过三个字母,超出部分会被截断:
if (strlen(fext) > 3) {
fext[3]=0; /* limit log file extension to 3 chars */
}
1.1.4 一些必要的函数初始化
通过1ms内CPU周期数计算出CPU频率,前提是电源管理功能已经禁用;
ts.in = rdtsc_oai();
sleep(1);
ts.diff = (rdtsc_oai()-ts.in);
cpu_freq_GHz = (double)ts.diff/1000000000;
Intertask Interface 初始化;
读取library初始化;
MSC初始化;
OPT初始化;
PDCP初始化;
之后根据当前的运行模式进行不同的初始化配置,比如FlexRAN,CRAN mode,L1 only mode。
当前OAI的ngran节点类型共有八种:
typedef enum {
ngran_eNB = 0,
ngran_ng_eNB = 1,
ngran_gNB = 2,
ngran_eNB_CU = 3,
ngran_ng_eNB_CU = 4,
ngran_gNB_CU = 5,
ngran_eNB_DU = 6,
ngran_gNB_DU = 7
} ngran_node_t;
根据节点类型将执行不同的协议栈流程,此处分析eNB类型节点。
初始化ue_pf_po_mutex, sync_cond 以及sync_mutex等:
pthread_mutex_init(&ue_pf_po_mutex, NULL);
memset (&UE_PF_PO[0][0], 0, sizeof(UE_PF_PO_t)*MAX_MOBILES_PER_ENB*MAX_NUM_CCs);
mlockall(MCL_CURRENT | MCL_FUTURE);
pthread_cond_init(&sync_cond,NULL);
pthread_mutex_init(&sync_mutex, NULL);
1.1.5 初始化eNB及RU实体:
init_eNB(get_softmodem_params()->single_thread_flag,get_softmodem_params()->wait_for_sync);
首先为所有eNB实体分配资源及初始化,初始化函数在/targets/RT/USER/lte-enb.c中实现。等待所有eNB配置都完成之后,初始化RU实体,
图1-3 RU初始化流程
传入的参数分别为:char[]配置文件路径,uint32_t时钟源, uint32_t同步源, uint32_t send_dmrs_sync。
init_RU(get_softmodem_params()->rf_config_file,get_softmodem_params()->clock_source,get_softmodem_params()->timing_source,get_softmodem_params()->send_dmrs_sync);
主要是为RU实体分配资源及进行各参数的初始化。进行完RU的参数初始化配置之后,执行set_function_spec_param(ru)函数进行硬件设备初始化及RU处理线程初始化。其中,需要根据NGFI的等级设置,进行相应的fronthaul和Front-End Processor(FEP)的配置
图1-4 硬件设备初始化流程
RU相关线程启动:
分配资源给fronthaul,将前传线程固定挂载到CPU1上,并加载射频硬件。如需添加OAI对其他硬件设备的支持,应首先在common_lib.c中添加相应的识别支持,然后再添加硬件驱动接口。由于LTE系统在时域上一个帧分为10个子帧,而USRP等设备在射频发送时要求I/Q两路数据分别输入,因此需要在初始化时分配10个子帧I/Q两路的射频发送资源内存。
图1-5 FH线程流程
该线程在OAI基站停止运行之前一直保持运行,首先根据控制端口的配置确定是在当前线程加载硬件还是等待其他线程加载;等待所有RU硬件准备就绪后等待本线程被唤醒:
if (ru->function!=eNodeB_3GPP && ru->has_ctrl_prt == 1) {
if (wait_on_condition(&ru->proc.mutex_ru,&ru->proc.cond_ru_thread,&ru->proc.instance_cnt_ru,"ru_thread")<0) break;
}
else wait_sync("ru_thread");
其中wait_sync()函数将锁住顶层sync_mutex互斥锁,然后等待顶层同步pthread_cond_signal(&sync_cond)唤醒:
while (sync_var<0) pthread_cond_wait( &sync_cond, &sync_mutex );
当被唤醒后,循环执行Fronthaul的各步流程直至基站结束运行。
该线程挂载在CPU1上,循环执行等到唤醒、调用物理层信号发送处理函数、允许rf_tx线程被唤醒等步骤,持续提供物理层发送功能。其中其调用的phy_procedures_eNB_TX()为物理层中的一个重要过程函数,将在第二章中进行分析。
thread_top_init函数将设置线程的线程名、CPU Affinity、运行时间、超时时间及运行周期。
图1-6 pthread_phy_tx线程流程
该线程在被唤醒后根据配置进行前端发送、OFDM及南向/北向接口输出。
图1-7 pthread_rf_tx线程流程
通过该线程调用物理层过程实现OAI向射频设备发送基站需要发送的数据,但该线程并不涉及硬件的实际操作,实际的射频需要通过挂载在CPU0的UHD驱动线程执行。具体调用的物理层函数见下一章。
该线程持续监测接收的数据中随机接入请求,通过调用物理层rx_prach()函数当检测到随机接入请求时进行UE上行信道测量及接入数据处理。值得注意的是OAI处理随机接入过程是单线程的,处理一个UE的接入请求时会忽略期间的UE接入请求。
图1-8 prach线程流程
该线程为前端处理接收(pront-end processor rx)线程,主要实现各个ru前端的接收数据处理。
图1-10 feptx发送线程流程
该线程为前端处理发送线程,主要根据当前时隙进行解调参考信号DRS的生成以及OFDM计算。
该线程用于输出各ru的状态信息。
图1-11 ru状态显示进程流程
eNB相关线程启动:
这两个线程分别为eNB下行及上行shared channel编解码处理线程。线程的具体处理主要涉及物理层编解码流程,将在下一章中详细讨论。
类似于1.2.4的ru_thread_prach线程,该线程为处理eNB随机接入信道。其中eNB_thread_prach_br线程当eNB使用R14及以上标准协议栈时才会启动,
图1-13 prach处理线程
eNB使用L1_thread 线程来进行空口信号接收,使用L1_thread_tx线程来进行subframe n+4的空口信号发送。
图1-14 物理层过程线程
Index Functions Details
1 logInit() 初始化log,如log等级等
2 get_options 读取命令行参数
3 netlink_init() 初始化与nasmesh.ko的socket连接
4 RCconfig_L1() eNB 侧参数配置,根据配置文件中读取到的参数配置程序中的相应参数
5 init_eNB() 初始化eNB ,类型为PHY_VARS_eNB,该结构体就表示一个基站,包含各个信道的参数,还有一些码表,状态信息等。在该函数中主要对相应的参数进行了内存的分配及函数指针的初始化
6 init_RU 初始化RRU及RCC,根据配置文件中的相关参数进行初始化
+ RCconfig_RU(); 读取配置文件中的内容,明确该基站运行RRU还是RCC,并配置相应的ip及端口等参数
set_function_spec_param(ru); 根据RRU及RCC的不同,配置相关的函数指针,如ru->fh_north_out等收发函数
init_RU_proc() 初始化RRU及RCC的处理线程
7 init_eNB_afterRU() 基站参数的初始化以及线程的初始化
+ phy_init_lte_eNB(eNB,0,0) 初始化LTE eNB 侧的帧结构Frames、初始化物理层信号处理过程中需要用到的gold序列、PCFICH/PHICH等信道的映射表、分配收发buffer的内存,分配各个信道的数组空间等
init_transport(eNB); 配置上下行信道参数,如dlsch以及ulsch信道
init_eNB_proc(inst); 初始化eNB 的处理线程
+ pthread_create( &L1_proc->pthread, attr0, L1_thread, proc ); 创建L1_thread线程,用于处理eNB 侧信号的接收
pthread_create( &L1_proc_tx->pthread, attr1, L1_thread_tx, proc); 创建L1_thread_tx线程,用于处理eNB 侧信号的发送
pthread_create( &proc->pthread_prach, attr_prach, eNB_thread_prach, eNB ); 创建线程eNB_thread_prach线程,用于处理随机接入的处理
8 phy_procedures_eNB_uespec_RX(eNB, proc); eNB 物理层接收处理函数
+ srs_procedures(eNB,proc); 参考信号处理函数,首先对参考信号进行处理,用于信道估计
uci_procedures(eNB,proc); 解析uci信息,即PUCCH信道信息
pusch_procedures(eNB,proc); 解析上行数据信息,包含从时频资源中提取PUSCH信号、信道估计、信道均衡、DFT、软解调以及PDSCH的信道译码等处理过程
9 phy_procedures_eNB_TX(eNB, proc, 1);
+ pmch_procedures(eNB,proc); 物理多播信道PMCH的信息处理,在此之前,会根据PMCH的映射进行判断是否为PMCH信息
common_signal_procedures(eNB,proc->frame_tx, proc->subframe_tx); 如果不是PMCH信息,则为物理层公用信号,如参考信号RS、同步信号PSS/SSS、PBCH等
+ generate_pilots_slot() 生成导频信号
generate_pss() 生成主同步信号
generate_sss() 生成从同步信号
generate_pbch() 生成PBCH信号
generate_dci_top() dci信息的生成,即PDCCH信道的处理过程,包括PCFICH信道的生成,dci信息的生成及编码、加扰、调制映射等处理过程
+ generate_pcfich() PCFICH作为控制信道的指示信息,在dci生成的过程中进行映射
generate_dci0() 根据不同的配置生成dci信息,并完成编码、交织以及速率匹配等处理
pdcch_scrambling() PDCCH信息的加扰
pdcch_interleaving() PDCCH信息交织
pdsch_procedures() PDSCH信道数据的生成、编码、加扰、调制以及映射过程
+ dlsch_encoding_all() PDSCH信道的编码,包含CRC比特添加、Segmentation、Turbo编码、交织以及速率匹配等
dlsch_scrambling() PDSCH信道的加扰
dlsch_modulation() PDSCH信道的调制,根据MCS的不同,调制方案不同,包括QPSK、16QAM以及64QAM,在调制过程中,直接进行资源的映射
generate_phich_top() PHICH信道信息的生成及映射
10 do_OFDM_mod OFDM调制
PHY_ofdm_mod extend cp OFDM调制
normal_prefix_mod normal cp OFDM调制
OAI顶层实现了保证各协议栈线程的正常运行、各设备驱动的载入等,在初始化配置过程中顶层过程决定了Front_End处理、RU射频位置、NGFI等级、nFAPI等级等架构从而实现对CRAN、FlexRAN等架构的支持,当前OAI CRAN的开发还不完善,OSA正致力于在顶层实现更完善且稳定的CU DU分离架构。
OAI接入侧物理层目录结构:
每个目录下至少包含defs.h用于声明结构和函数,vars.h用于变量的定义,extern.h用于跨文件变量,xxx-softmodem.c包含主函数用于实现实时硬件操作,SIMULATION / xxx用于顶层仿真
|-- PHY 包含与物理层相关的所有信号处理(用于实时处理及仿真)
| |-- CODING //TS36.212的实现,包括turbo码和卷积码的编解码,速率匹配,CRC循环冗余的生成
| |-- TESTBENCH //包含用于不同信道编码器/解码器的单独测试平台
| |-- INIT // 在vars.h中定义的变量进行初始化、分配内存
| |-- LTE_ESTIMATION // LTE信道估计
| |-- LTE_REFSIG //TS36.211中定义的用于LTE同步和导频序列的参考信号
| |-- NR_REFSIG // TS38.211中定义的用于NR同步和导频序列的参考信号
| |-- LTE_TRANSPORT //针对一些TS36.211和TS36.212中定义的传输信道和物理信道(比如dlsch和pss等)的顶层routines的实现
| |-- LTE_UE_TRANSPORT //UE侧的
| |-- NR_TRANSPORT // TS38.211,38.212,NR的
| |-- NR_UE_TRANSPORT // NR UE的
| |-- NBIoT_TRANSPORT // NB-IoT的
| |-- MODULATION // TS36.211中定义的调制解调过程的实现,包含FFT/SC-FDMA的前端处理
| |-- TOOLS // tools,例如FFT/IDDT,矩阵运算,矢量乘法等
| |-- FFTTEST //用于测试FFT
| |-- defs.h // 包括spec_defs, impl_defs 和其他各子目录的defs.h
| |-- extern.h
| |-- impl_defs.h // 非LTE的OAI实现的defs
| |-- impl_defs_lte.h // LTE实现的defs
| |-- impl_defs_top.h //一般实现的defs
| |-- spec_defs.h
| |-- spec_defs_top.h
| |-- types.h
| `-- vars.h
|-- SCHED // 物理层调度(指调度不同的函数,不是物理层资源调度,那是比如RRC的事情)
| |-- defs.h
| |-- extern.h
| |-- phy_procedures_lte_eNb.c //TS36.213定义的LTE PHY过程实现,针对eNB的
| |-- phy_procedures_lte_common.c// TS36.213定义的LTE PHY过程实现,eNB和UE共用的
| |-- prach_procedures.c //TS36.213定义的随机接入信道过程实现
| |-- ru_procedures.c //RU过程实现
| |-- fapi_l1.c //L1侧FAPI接口实现
| |-- phy_mac_stub.c //在phy-test-mode 模式时生成信道的MAC stub
| |-- rt_compat.h
| `-- vars.h
|-- SCHED_UE // LTE UE函数功能调度
| |-- phy_procedures_lte_ue.c // TS36.213定义的LTE PHY过程实现,针对UE的
| |-- pucch_pc.c //PUCCH功控
| |-- pusch_pc.c //PUSCH功控
| |-- srs_pc.c //SRS功控
|-- SCHED_NB_IOT // 同上,NB-IOT的
|-- SCHED_NR // 同上,NR gNB的
|-- SCHED_NR_UE // 同上NR UE的
|-- SIMULATION // 物理层仿真平台
| |-- LTE_PHY // LTE仿真平台,用于传输/物理信道的单独模拟
| | |-- pbchsim.c // PBCH 仿真
| | |-- pdcchsim.c // PCFICH/PHICH/PDCCH (DCI) 仿真
| | |-- dlsim.c // PDSCH 仿真
| | |-- ulsim.c // PUSCH 仿真
| | |-- pucchsim.c // PUCCH仿真
| |-- RF // RF 仿真工具
| |-- ETH_TRANSPORT
| |-- TOOLS
本章主要介绍OAI CRAN中RRU及RCC交互流程。
在处于IF4p5模式下运行RRU以及RCC时,进行到ru_thread_control函数后,开始进行RRU与RCC之间的配置,其大致过程分为以下几个步骤:
a) RCC开启,发送tick到配置文件中指定的RRU
b) RRU开启处于IDLE状态,从RCC获取其配置文件信息,并将自己的配置文件信息回传给RCC,此时状态RRU状态信息变更为CONFIG
c) 配置文件传送完毕,RRU处于READY状态。
d) RCC获取所有RRU的配置信息并得到他们的状态为Ready之后,进行时频同步(还没完成),同步完成之后RRU处于RUN状态。
e) 开始数据传输,此时RRU处于RUN状态。
在RCC以及RRU其他参数都初始化完成之后,均进入ru_thread_control函数,并且根据ru->if_south 是否为 LOCAL_RF 来区分RRU 与 RCC。
(a)RCC send_tick
当RCC处于IDLE状态时,将会循环send_tick,直到RRU收到并且send_capab。其中两者之间通过UDP包进行传输,报文格式如下(以send_tick为例详细介绍):
(1) MAC 地址 (14 byte)
RCC IP addr 192.168.10.13 MAC addr 00:90:fa:b5:ec:d6
RRU IP addr 192.168.10.11 MAC addr 00:0e:c6:a0:a5:e8
IP : 0x0800
图0-19 Wireshark send_tick抓包 MAC地址
(2) IP报头 (20 byte)
该部分包含协议version、源ip地址、目的ip地址以及协议号等信息。
图0-20 Wireshark send_tick抓包IP报头
(3) USER Datagram Protocol (UDP报头)(8byte)
该部分内容包含目的端口、源端口号、字节长度以及校验位等;
图0-21 Wireshark send_tick抓包UDP报头
(4) DATA (16byte)
最后是用户数据段,由于tick只是向RRU发送的一个标志信号,因此该数据包没有任何的实际信息,因此发送的只是一个RCC目前的状态以及数据长度,其在程序中的结构体为:
RRU_CONGIF_msg_t: RCC及RRU的配置信息结构体,其中RCC向RRU所发送的信息,就是该结构体内的内容,字节长度为1040bytes。
图0-22 OAI RRU 配置结构体
rru_config_msg_type_t:标识RCC所发送message的类型,该类型字节长度为8bytes。构体如下所示,其中发送tick时,类型为RAU_tick,因此数据段为:[00 00 00 00 00 00 00 00]。
图0-23 OAI RRU及RCC交互状态标志位
len: len字段表示发送信息的字节长度,其中len的字节长度为8bytes。在发送tick时,由于没有任何的配置信息,因此len的值就是len以及rru_config_msg_type_t的字节长度,也就是16,因此send_tick的DATA为:
【00 00 00 00 00 00 00 00 10 00 00 00 00 00 00 00】
(b)RRU send_capab
RRU收到tick信号后,会向RCC send_capab信号,表明RRU配置时的承载能力,如支持的频段、tx/rx天线的个数、支持的带宽等参数,以方便RCC配置信息的发送。其信号如下,其中前三个部分基本一致,因此下面主要介绍DATA数据段的数据格式。
图0-24 OAI RRU及RCC交互send_capab抓包
在send_capab中所发送的数据包中同样没有配置信息,但是会加上RRU的承载能力信息,因此RRU给RCC所发送的信息中,除了RRU_CONGIF_msg_t中的message type以及message len,还包含以下结构体中的信息:
图0-25 OAI RRU_capabilities结构体
【01 00 00 00 00 00 00 00 34 00 00 00 00 00 00 00 01 00 00 00 01 07 00 00 9c 3e e3 f7 ff 7f 78 00 00 00 01 00 00 00 01 00 27 9e ee 1c 00 00 00 00 0d 00 00 00】
FH_fmt_options_t:表示所选择的前端接口类型,这里选择的是IF4p5,因此FH_fmt的值为1,DATA里面的字段为01 00 00 00;
图0-26 OAI NGFI接口类型
num_bands:RRU所支持的bands number, num_bands <= 4,DATA里面的字段为01;
band_list[MAX_BANDS_PER_RRU]:RRU所支持的频段列表,其中num_bands=1,因此只有band_list[0]配置,其他均为默认,DATA字段为07 00 00 9c;
num_concurrent_bands:目前所连接的bands的个数,send_capab中没有进行配置;
max_pdschReferenceSignalPower[MAX_BANDS_PER_RRU]:pdsch参考信号能量,其中num_bands=1,因此max_pdschReferenceSignalPower[0]配置为-29,其余均为默认,DATA字段为e3 f7 ff 7f;
max_rxgain[MAX_BANDS_PER_RRU]:每个频段最大的接收增益,DATA字段为78 00 00 00;
nb_rx[MAX_BANDS_PER_RRU]:接收端口的个数,DATA字段为01 00 00 00;
nb_tx[MAX_BANDS_PER_RRU]:发送端口的个数,DATA字段为01 00 27 9e;
N_RB_DL:RRU所支持的下行带宽,send_capab中没有进行配置;
N_RB_UL:RRU所支持的下行带宽,send_capab中没有进行配置;
以上信息中,在send_capab函数中进行相应的配置,但是有的参数没有进行修改,其就是使用的默认值(随机值,接收端不检测)。其中没有进行配置的参数为:num_concurrent_bands、N_RB_DL、N_RB_UL。
(a)RCC send_config
RCC接收到RRU所发送的capab UDP报文之后,表示RRU已经就绪,RCC将根据RRU所发送的capable来发送Config报文,其中包含有对于硬件RRU的相关配置信息,如频段、发送频率、接收频率等物理参数。
RCC配置信息
UDP数据包的格式如下图所示,报文中的内容可见wireshark的抓包。
Wireshark抓包RCC配置信息
00 0e c6 a0 a5 e8 00 90 fa b5 ec d6 08 00 45 00
02 fc e7 7d 40 00 40 11 bb 0a c0 a8 0a 0d c0 a8
0a 0b c3 52 c3 52 02 e8 98 62 02 00 00 00 00 00
00 00 e0 02 00 00 00 00 00 00 03 00 00 00 01 07
00 00 9c 00 e3 f7 ff 00 78 00 00 00 01 00 00 8e 00 01 00为了字节对齐,无意义
bd 9f 27 9e ee 1c 00 00 00 00 0d 00 00 00 00 80
96 98 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 32 00 00 00 32 00 00 00 00 00
00 00 02 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
//……后续的没有进行配置,均为00
下面对send_config 报文中的DATA数据段进行详细的介绍:
(1) message type 以及message len
同send_tick以及send_capab报文类似,send_config的时候,message type 为RRU_config=2,message len为该部分字节长度(16bytes)加上RCC对RRU的配置信息, sizeof(RRU_config_t)= 720bytes,也就是736bytes,但是在RRU_config_t中,有很多参数与RRU的配置无关,因此没有进行配置;DATA中对应的数据段为:02 00 00 00 00 00 00 00 e0 02 00 00 00 00 00 00;
(2) RRU_config_t信息
RRU_config_t的结构体定义如下表中所示,其中红色字体为RRU的相关配置的信息,其中这些信息均是根据运行时的配置参数进行配置的,具体可以参考configure_ru函数,在send_config中只是进行发送。如下表中是RRU_config_t的定义,后面注释为IF4p5时的具体参数,未标明的参数基本上与RRU的物理参数配置没有太大关系,可以先不做考虑。
typedef struct RRU_config_s {
/// Fronthaul format
RU_if_south_t FH_fmt; // DATA 中对应的数据段为:03 00 00 00
/// number of EUTRA bands (<=4) configured in RRU
uint8_t num_bands; // DATA 中对应的数据段为:01
/// EUTRA band list configured in RRU
uint8_t band_list[MAX_BANDS_PER_RRU]; //DATA 中对应的数据段为:07 00 00 9c
/// TDD configuration (0-6)
uint8_t tdd_config[MAX_BANDS_PER_RRU]; //DATA 中对应的数据段为:00 e3 f7 ff
/// TDD special subframe configuration (0-10)
uint8_t tdd_config_S[MAX_BANDS_PER_RRU]; //DATA 中对应的数据段为:00 78 00 00
/// TX frequency // DATA 中对应的数据段为:2680000000 00 8e bd 9f 27 9e ee 1c 00 00 00 00 0d 00 00 00
uint32_t tx_freq[MAX_BANDS_PER_RRU];
/// RX frequency // DATA 中对应的数据段为:2560000000 00 80 96 98 00 00 00 00 00 00 00 00 00 00 00 00
uint32_t rx_freq[MAX_BANDS_PER_RRU];
/// TX attenation w.r.t. max // DATA 中对应的数据段为:00 00 00 00
uint8_t att_tx[MAX_BANDS_PER_RRU];
/// RX attenuation w.r.t. max // DATA 中对应的数据段为:00 00 00 00
uint8_t att_rx[MAX_BANDS_PER_RRU];
/// DL bandwidth
uint8_t N_RB_DL[MAX_BANDS_PER_RRU]; // DATA 中对应的数据段为:32 00 00 00
/// UL bandwidth
uint8_t N_RB_UL[MAX_BANDS_PER_RRU]; // DATA 中对应的数据段为:32 00 00 00
/// 3/4 sampling rate
uint8_t threequarter_fs[MAX_BANDS_PER_RRU]; //00 00 00 00
/// prach_FreqOffset for IF4p5
int prach_FreqOffset[MAX_BANDS_PER_RRU]; // DATA对应的数据段为02 00 00 00
/// prach_ConfigIndex for IF4p5
int prach_ConfigIndex[MAX_BANDS_PER_RRU]; // DATA对应的数据段为00 00 00 00
#if (RRC_VERSION >= MAKE_VERSION(14, 0, 0))
int emtc_prach_CElevel_enable[MAX_BANDS_PER_RRU][4];
/// emtc_prach_FreqOffset for IF4p5 per CE Level
int emtc_prach_FreqOffset[MAX_BANDS_PER_RRU][4];
/// emtc_prach_ConfigIndex for IF4p5 per CE Level
int emtc_prach_ConfigIndex[MAX_BANDS_PER_RRU][4];
#endif
/// mutex for asynch RX/TX thread
pthread_mutex_t mutex_asynch_rxtx;
/// mutex for RU access to eNB processing (PDSCH/PUSCH)
pthread_mutex_t mutex_RU;
/// mutex for RU access to eNB processing (PRACH)
pthread_mutex_t mutex_RU_PRACH;
/// mutex for RU access to eNB processing (PRACH BR)
pthread_mutex_t mutex_RU_PRACH_br;
/// mask for RUs serving eNB (PDSCH/PUSCH)
int RU_mask[10];
/// time measurements for RU arrivals
struct timespec t[10];
/// Timing statistics (RU_arrivals)
time_stats_t ru_arrival_time;
/// mask for RUs serving eNB (PRACH)
int RU_mask_prach;
} RRU_config_t;
(b)RRU send_config_ok
当RRU接收到RCC所发送的配置报文之后,开始对硬件设备(USRP B210)进行对应的配置,如果配置成功,则会向RCC发送Config Ok报文,报文中会有目前RRU的配置信息,即RRU给RCC所发送的信息为RRU_CONFIG_msg_t的所有信息。
Message type类型为RRU_config_ok,也就是3;
Message len 为RRU所发送的信息长度,其中send_config_ok的信息长度就是RRU_CONFIG_msg_t的长度,即8+8+1024=1040bytes.
Message msg中的消息在程序中是通过强制转换为RRU_config_t的信息,因此msg中的信息同send_config中的msg类似,但是由于sizeof(RRU_config_t) =720bytes,且只有很少的字节与RRU的参数信息有关。
00 90 fa b5 ec d6 00 0e c6 a0 a5 e8 08 00 45 00
04 2c 88 f4 40 00 40 11 18 64 c0 a8 0a 0b c0 a8
0a 0d c3 52 c3 52 04 18 19 19 03 00 00 00 00 00
00 00 10 04 00 00 00 00 00 00 03 00 00 00 01 07
00 00 9c 00 e3 f7 ff 00 78 00 00 00 01 00 00 8e
bd 9f 27 9e ee 1c 00 00 00 00 0d 00 00 00 00 80
96 98 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 32 00 00 00 32 00 00 00 00 00
00 00 02 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
//………后续基本上都没有进行配置及随机值
Wireshark 抓包send_config_ok
待RRU发送回Config Ok报文之后,两端的配置基本结束,因此开始时频同步,待同步之后,RCC向RRU发送Start命令以开始数据的传输。其中send start的payload同样是RRU_config_t的信息,并且从Wireshark抓包分析来看,payload除了message type变为RRU_start(4),其他的和RRU所发送的config ok的数据段完全一致。
wireshark 抓包send_start
ru_thread线程主要完成RRU及RAU间数据的处理。根据eNB所运行的类型,执行对应的处理函数。如果是RRU,则首先执行rx_rf,即从usrp接收相应的数据,然后执行DFT处理,之后发送给RAU;对于RAU,则首先从RRU receive IF4p5信息,然后进行precoding处理,最后输出到RRU。
(1)RRU侧
(a)rx_rf
rx_rf函数中RRU调用USRP读取数据的相关函数trx_read_func完成了从USRP中将数据读取到rxdata数组中,其中该数据为时域形式。另外,从USRP中读取数据是以子帧为单位进行的,即每次读取samples_per_tti个数据(该数据为32bit,2byte I路,2byte Q路),并指定从第几个数据开始该子帧数据的读取。
当硬件设备使用USRP时,函数指针trx_read_func指向trx_usrp_read,下面是该函数的的声明,其中具体实现为c++语言,调用uhd驱动中的函数接口进行数据的读取。另外,为了能够提高处理速度,程序中使用指令集加速读取数据,最后转化到了rxdata数组中。
static int trx_usrp_read(openair0_device *device, openair0_timestamp ptimestamp, void **buff, int nsamps, int cc)
/! \brief Receive samples from hardware.
PULFFT数据包结构
(2)RAU侧
(a)fh_south_in
同样是ru_thread线程,在RAU侧fh_south_in函数则代表fh_if4p5_south_in,即从RRU侧接收IF4p5的数据。该函数中主要调用了recv_IF4p5用来接收RRU所发送的数据包,并完成数据包的解析。
(b)feptx_prec
feptx_prec函数主要完成基站侧的预编码功能,按照理解,主要就是将数据映射到相应的天线端口,其中主要的调用的函数为beam_precoding。
(c)fh_if4p5_south_out
RAU侧经过precoding后,将数据通过IF4p5发送给RRU,主要调用的函数为send_IF4p5,但是数据包的类型不同,如下图所示为PDLFFT数据包。
PDLFFT数据包结构
(d)ru_thread_asynch_rxtx
ru_thread_asynch_rxtx线程主要是RRU侧接收IF4p5,然后进行IDFT处理,之后通过tx_rf发送出去。其中该线程主要调用了fh_if4p5_north_asynch_in函数,该函数首先完成了RRU recv_IF4p5,之后对相关的数据包进行解析并调用feptx_ofdm对其进行IDFT处理,最后调用tx_rf函数将数据包发送出去。
feptx_ofdm函数中首先根据循环前缀类型进行判断如何去做IDFT,当为normal CP时,执行normal_prefix_mod函数,并且在该函数中对第一个OFDM符号及后面六个OFDM符号分别进行IDFT处理。
Thanks for the help of Raymond, Florian, Irfan, Turker and Hongzhi.