文章目录
- 1、PRU-ICSS硬件分析
- 2、PRU-ICSS功能
- 3、如何使用PRU
- 4、PRU相关驱动研究
- 4.1、pruss.c
- 4.4.1、PRU寄存器地址及功能
- 4.4.2、PRUSS结构体
- 4.4.2.1、private_data
- 4.4.2.2、pruss_mem_region
- 4.2、pruss_intc.c
- 4.3、pru_rproc.c
- 4.3.1、remoteproc core与i2c core进行对比
- 4.4、prueth.c
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+0x2000reg-names = "dram0", "dram1", "shrdram2", "cfg","iep", "mii_rt";#address-cells = ;
#size-cells = ;
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);
···
}