文章目录
1、PRU-ICSS硬件分析
PRU-ICSS全称:Programmable Real-Time Unit Subsystem and Industrial Communication Subsystem
翻译:可编程实时单元子系统和工业通信子系统
具体这个全称的含义,到后面再进行分析,下面先对PRU进行分析。
以上是PRU-ICSS的资源概览
以上是PRU-ICSS2的外部引脚接口。
以上是PRU-ICSS的内存地址。
2、PRU-ICSS功能
PRU要实现的功能主要取决于开发的Firmware。该固件可自己开发,也可以通过从SDK上拷贝TI提供的固件,
TI官方的说法:
Basic firmware examples showing simple functionality
3、如何使用PRU
使用PRU的关键点:
-
加载固件到PRU
-
控制PRU的执行,例如:启动、停止等
-
管理PRU的资源,内存、中断对应表等
-
提供通信方法
以上由pruss, pru_rproc,rpmsg_pru Linux 驱动完成。
以上四个板块表示了加载PRU固件的必备程序,通过pru_rproc module加载Linux文件系统中的/lib/firmware/am335x-pru0-fw固件,并对PRU所需要的资源进行配置,最终完成PRU正常的运行工作。
在工作过程中PRU需要与arm core进行通信,其中两者进行通信的机制是通过在DDR中申请两块内存,分别是PRU to ARM和PRU form ARM。然后当有新的信息时,通过mailboxes进行提示两个核。在arm core当中有rpmsg_pru驱动在用户空间创建一个字符设备,通过读写字符设备实现与PRU的接收与发送信息。在PRU当中,使用RPMsg库进行通信,直接调用 pru_rpmsg_receive 和 pru_rpmsg_send即可实现与ARM CORE的通信。 详情见下图:
以上是PRU的通用的用法,具体到PRU可以实现的功能的话,需要根据PRU的固件来决定。
以上了解了PRU的基本用法,下面对其驱动进行研究。
4、PRU相关驱动研究
根据之前TI给出的框架情况,可以看到remoteproc driver是比较基础的一个驱动,这个层面的驱动主要是与驱动设备模型相关的东西,其中有remoteproc_core.c、remoteproc_debugfs.c等,这里暂不做分析。
这里重点分析pruss和pru_rproc相关的驱动。根据何工之前的关于PRU驱动分析可知,共用到了以下驱动:
device | compatible | driver |
---|---|---|
pruss | ti,am5728-pruss | pruss.c |
pruss_intc | ti,am5728-pruss-intc | pruss_intc.c |
pru2_0 | ti,am5728-pru | pru_rproc.c |
pru2_1 | ti,am5728-pru | pru_rproc.c |
pruss2_mdio | ti,davinci_mdio | davinci_mdio.c |
这里针对每个驱动所对应的硬件设备进行分析。
4.1、pruss.c
4.4.1、PRU寄存器地址及功能
pruss2: pruss@4b280000 {
compatible = "ti,am5728-pruss";
ti,hwmods = "pruss2";
reg = <0x4b280000 0x2000>,//Data 8 KiB RAM0
<0x4b282000 0x2000>,//Data 8 KiB RAM1
<0x4b290000 0x8000>,//Data 32 KiB RAM2 (shared)
<0x4b2a6000 0x2000>,//CFG
<0x4b2ae000 0x31c>,//IEP
<0x4b2b2000 0x58>;//eCAP0+0x2000
reg-names = "dram0", "dram1", "shrdram2", "cfg",
"iep", "mii_rt";
#address-cells = <1>;
#size-cells = <1>;
ranges;
status = "disabled";
...
}
在设备树中以上是pru设备的节点,在其下有多个子节点,可以判定该节点是PRU整体模块。对该的属性进行分析,属性主要包括较多的寄存器,对这些寄存器进行分析。
由以上注释可知,前三项是关于PRU RAM的地址信息,这在PRU-ICSS概览图中可以看到,具体这些RAM如何被使用,需要的驱动中具体查看,这里通过设备树传入了该设备的地址信息。
再向下,是关于CFG的地址信息,以下是该地址范围内的寄存器地址,下面不具体分析每一个寄存器每一位的具体作用,仅简单分析该地址范围内的寄存器的大体上的功能。
此类寄存器
1、配置电源
2、配置IO口
3、时钟管理
4、中断状态、中断使能、中断禁止
5、地址偏移使能
6、优先级设置
7、引脚配置
IEP全称:Industrial Ethernet Peripheral 工业以太网外围模块
这些IEP模块有工业以太网定时器,可产生16个比较事件,和一个数字IO。
4.4.2、PRUSS结构体
pruss.c主要就是对这些寄存器进行操作,以完成pru的驱动。
/**
* struct pruss - PRUSS parent structure
* @node: list node of this object
* @dev: pruss device pointer
* @mem_regions: data for each of the PRUSS memory regions
* @mem_in_use: to indicate if memory resource is in use
* @data: pointer to store PRUSS instance private data
* @host_mask: indicate which HOST IRQs are enabled
* @pru_running: flag to indicate if PRU is running
* @pru_in_use: flag to indicate if PRU is used
* @lock: mutex to serialize access to resources
* @cfg_lock: mutex to serialize access to CFG
* @in_standby: flag for storing standby status
*/
struct pruss {
struct list_head node;
struct device *dev;
struct pruss_mem_region mem_regions[PRUSS_MEM_MAX];
struct pruss_mem_region *mem_in_use[PRUSS_MEM_MAX];
const struct pruss_private_data *data;
u32 host_mask;
bool pru_running[PRUSS_NUM_PRUS];
struct rproc *pru_in_use[PRUSS_NUM_PRUS];
struct mutex lock; /* PRU resource lock */
struct mutex cfg_lock; /* PRUSS CFG register access lock */
bool in_standby;
};
该结构体就是PRUSS的根源结构体,后面对这个结构体会进行赋值,这个赋值就关系到设备树传入的设备的数据。
首先就是在probe当中的赋值操作:
/*
此处操作主要是希望通过设备树当中的“节点信息”匹配“驱动”中的data数据
因为驱动考虑兼容性,所以在驱动中有许多的data数据,通过设备树的节点信息,
来匹配驱动中的data数据。
*/
data = pruss_get_private_data(pdev);
···
pruss = devm_kzalloc(dev, sizeof(*pruss), GFP_KERNEL);
···
pruss->dev = dev;
pruss->data = data;
mutex_init(&pruss->lock);
mutex_init(&pruss->cfg_lock);
4.4.2.1、private_data
通过pruss_get_private_data(pdev)函数获取private_data,获取到的data到底是什么数据?
static struct pruss_private_data am57xx_pruss1_priv_data = {
.aux_data = am57xx_pruss1_rproc_auxdata_lookup,
};//该数据是pruss_get_private_data()返回的数据
static struct of_dev_auxdata am57xx_pruss1_rproc_auxdata_lookup[] = {
OF_DEV_AUXDATA("ti,am5728-pru", 0x4b234000, "4b234000.pru0", NULL),
OF_DEV_AUXDATA("ti,am5728-pru", 0x4b238000, "4b238000.pru1", NULL),
{ /* sentinel */ },
};
经过分析,private_data数据就是以上数据。以上数据的用途是什么?
/**
* struct pruss_private_data - PRUSS driver private data
* @aux_data: auxiliary data used for creating the child nodes
* @has_reset: flag to indicate the presence of global module reset
* @has_no_syscfg: flag to indicate the absence of PRUSS_SYSCFG functionality
* @has_no_sharedram: flag to indicate the absence of PRUSS Shared Data RAM
* @uses_wrapper: flag to indicate the dependence on PRUSS syscfg wrapper module
*/
struct pruss_private_data {
struct of_dev_auxdata *aux_data;
bool has_reset;
bool has_no_syscfg;
bool has_no_sharedram;
bool uses_wrapper;
};
通过该数据结构体可以基本了解这个数据的用途,主要是用来做一些标志位,用来表示该PRUSS是否具有某些功能
static int pruss_enable_module(struct pruss *pruss)
{
int ret;
···
if (pruss->data->has_no_syscfg)//通过data当中的数据确定是否存在syscfg,这么做是为了提高驱动的兼容性
return ret;
/* configure for smart idle & smart standby */
pruss_set_reg(pruss, PRUSS_MEM_CFG, PRUSS_CFG_SYSCFG,
PRUSS_SYSCFG_IDLE_MODE_MASK,
PRUSS_SYSCFG_IDLE_MODE_SMART);
pruss_set_reg(pruss, PRUSS_MEM_CFG, PRUSS_CFG_SYSCFG,
PRUSS_SYSCFG_STANDBY_MODE_MASK,
PRUSS_SYSCFG_STANDBY_MODE_SMART);
/* enable OCP master ports/disable MStandby */
ret = pruss_enable_ocp_master_ports(pruss);
if (ret)
pruss_disable_module(pruss);
return ret;
4.4.2.2、pruss_mem_region
/**
* struct pruss_mem_region - PRUSS memory region structure
* @va: kernel virtual address of the PRUSS memory region
* @pa: physical (bus) address of the PRUSS memory region
* @size: size of the PRUSS memory region
*/
struct pruss_mem_region {
void __iomem *va;
phys_addr_t pa;
size_t size;
};
该结构体是PRUSS结构体当中非常重要的结构体,这个结构体关系到物理地址与虚拟地址的对应关系。
for (i = 0; i < ARRAY_SIZE(mem_names); i++) {
if (data->has_no_sharedram && !strcmp(mem_names[i], "shrdram2"))
continue;
//通过该函数获取platform设备中的resource
//resource在设备树中定义,在设备树解析时将数据存入到dev中。
res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
mem_names[i]);
//通过devm_ioremap_resource函数进行虚拟地址转化。
pruss->mem_regions[i].va = devm_ioremap_resource(dev, res);
if (IS_ERR(pruss->mem_regions[i].va)) {//对虚拟地址进行判断
dev_err(dev, "failed to parse and map memory resource %d %s\n",
i, mem_names[i]);
return PTR_ERR(pruss->mem_regions[i].va);
}
pruss->mem_regions[i].pa = res->start;//设置物理地址
pruss->mem_regions[i].size = resource_size(res);//获取大小
dev_dbg(dev, "memory %8s: pa %pa size 0x%x va %p\n",
mem_names[i], &pruss->mem_regions[i].pa,
pruss->mem_regions[i].size, pruss->mem_regions[i].va);
}
以上完成资源的分配。
dev_info(&pdev->dev, "creating PRU cores and other child platform devices\n");
/**
* Similar to of_platform_bus_probe(), this function walks the device tree
* and creates devices from nodes. It differs in that it follows the modern
* convention of requiring all device nodes to have a 'compatible' property,
* and it is suitable for creating devices which are children of the root
* node (of_platform_bus_probe will only create children of the root which
* are selected by the @matches argument).
*
* New board support should be using this function instead of
* of_platform_bus_probe().
*/
ret = of_platform_populate(node, NULL, data->aux_data, &pdev->dev);
if (ret) {//完成PRUcore的设备创建,完成其子节点的platform设备的创建
dev_err(dev, "of_platform_populate failed\n");
goto err_of_fail;
}
4.2、pruss_intc.c
该驱动与pruss.c驱动相同,都是platform驱动,匹配方式也与pruss.c大致相同,首先是在驱动注册时进行匹配,匹配成功后会执行probe,在probe当中再次执行一次匹配,以确定是该驱动中的哪一个compatible数据匹配产生的probe。
static const struct of_device_id pruss_intc_of_match[] = {
{
.compatible = "ti,am3352-pruss-intc",
.data = NULL,
},
{
.compatible = "ti,am4372-pruss-intc",
.data = &am437x_pruss_intc_data,
},
{
.compatible = "ti,am5728-pruss-intc",
.data = NULL,
},
{
.compatible = "ti,k2g-pruss-intc",
.data = &k2g_pruss_intc_data,
},
{ /* sentinel */ },
};
其中可以被匹配的compatible有4个,本次5728所使用的是:.compatible = “ti,am5728-pruss-intc”,然后通过probe函数进行资源的分配,然后根据虚拟地址,将寄存器进行配置。完成初始化工作。驱动中还有相应的中断处理函数,在probe函数中也进行了配置,保证中断控制器正常工作。具体的代码这里不再赘述。
4.3、pru_rproc.c
该驱动的匹配机制基本与前两个驱动类似,匹配机制与pruss.c基本相同。
在probe中通过match获取当前匹配的是哪一个compatible,以此来决定private_data,以此来决定使用哪一个固件。
static struct pru_private_data am57xx_pru1_0_rproc_pdata = {
.id = 0,
.fw_name = "am57xx-pru1_0-fw",
.eth_fw_name = "ti-pruss/am57xx-pru0-prueth-fw.elf"
};
匹配到该数据之后,在probe函数当中会继续向下执行,该priv数据在后期会得到使用,该数据的主要作用就是提高驱动的兼容性,通过设备树做了简单的区分,然后在该驱动中进行进一步的区分,实现一个驱动可以驱动多个设备。
static int pru_rproc_probe(struct platform_device *pdev)
{
···
ret = rproc_add(pru->rproc);
···
}
4.3.1、remoteproc core与i2c core进行对比
rproc_add是注册设备的函数,类似于IIC控制器设备向IIC bus注册,IIC控制器一开始也是属于platform设备和驱动,最后在probe中进行了向IICbus的注册,该驱动则是向remoteproc_core进行注册。
通过对比IIC CORE和remoteproc core发现两者存在着不同,IIC CORE通过bus register注册了一条总线,用于挂接设备,为设备驱动提供统一的接口。但是remoteproc core并没有提供进行bus的注册,所以在文件系统的bus当中找不到关于remoteproc的文件夹。
通过这一对比,也能够更深的理解关于Linux内核当中各类core的作用,各类core的作用不仅仅是注册一个bus那么简单,bus只是core的其中一种管理方式,core的作用就是为各类驱动提供各类接口,为写设备驱动提供方便。core再通过对硬件的直接操作完成相应的动作,从而实现其承上启下的作用,类似于中间商,驱动为客户,硬件设备为商家,core就是中间商,客户告诉中间商我需要做什么,中间商再告诉商家去做什么,这样就会省掉很多的工作。
上图简单的对两个core进行了对比。
static int pru_rproc_probe(struct platform_device *pdev)
{
···
client = &pru->client;
client->dev = dev;
client->tx_done = NULL;
client->rx_callback = pru_rproc_mbox_callback;
client->tx_block = false;
client->knows_txdone = false;
pru->mbox = mbox_request_channel(client, 0);
if (IS_ERR(pru->mbox)) {
ret = PTR_ERR(pru->mbox);
pru->mbox = NULL;
dev_dbg(dev, "mbox_request_channel failed: %d\n", ret);
}
···
}
这里通过mbox进行双核的通信,这里暂时不做深入研究。
4.4、prueth.c
prueth.c是对pru进行相关配置的一个驱动,通过该驱动将pru配置为可以读写phy芯片,这样就完成了通过pru扩展网络的功能。
static int prueth_probe(struct platform_device *pdev)
{
···
/* setup netdev interfaces */
eth_node = of_get_child_by_name(np, "ethernet-mii0");
if (!eth_node) {
dev_err(dev, "no ethernet-mii0 node\n");
ret = -ENODEV;
goto free_pool;
}
ret = prueth_netdev_init(prueth, eth_node);
if (ret) {
if (ret == -EPROBE_DEFER) {
prueth->eth_node[PRUETH_PORT_MII0] = eth_node;
goto netdev_exit;
}
dev_err(dev, "netdev init %s failed: %d\n",
eth_node->name, ret);
of_node_put(eth_node);
} else {
prueth->eth_node[PRUETH_PORT_MII0] = eth_node;
}
···
}
从这里可以看到该驱动所对应的eth_node是“ethernet-mii0”
通过设备树的反编译可以得到以下内容:
pruss2_eth {
compatible = "ti,am57-prueth";
pruss = <0x12e>;
sram = <0x12f>;
interrupt-parent = <0xdd>;
ethernet-mii0 {
phy-handle = <0x130>;
phy-mode = "mii";
interrupts = <0x14 0x16>;
interrupt-names = "rx", "tx";
local-mac-address = [00 00 00 00 00 00];
};
ethernet-mii1 {
phy-handle = <0x131>;
phy-mode = "mii";
interrupts = <0x15 0x17>;
interrupt-names = "rx", "tx";
local-mac-address = [00 00 00 00 00 00];
};
};
可以看到pruss2_eth节点已经不属于pruss节点了,而是一个单独的节点。 通过以上节点属性可知,主要是关于中断、phy-mod的信息。通过该驱动中的probe函数中执行以下函数,对phy芯片进行了配置。
static int prueth_probe(struct platform_device *pdev)
{
···
ret = prueth_netdev_init(prueth, eth_node);
···
}