PRU驱动分析

1、PRU-ICSS硬件分析

PRU-ICSS全称:Programmable Real-Time Unit Subsystem and Industrial Communication Subsystem

翻译:可编程实时单元子系统和工业通信子系统

具体这个全称的含义,到后面再进行分析,下面先对PRU进行分析。

image-20200904150155451

以上是PRU-ICSS的资源概览image-20200904160326245

以上是PRU-ICSS2的外部引脚接口。

image-20200904162145200

以上是PRU-ICSS的内存地址。

2、PRU-ICSS功能

image-20200905111110461

PRU要实现的功能主要取决于开发的Firmware。该固件可自己开发,也可以通过从SDK上拷贝TI提供的固件,

TI官方的说法:

Basic firmware examples showing simple functionality

3、如何使用PRU

使用PRU的关键点:

  • 加载固件到PRU

  • 控制PRU的执行,例如:启动、停止等

  • 管理PRU的资源,内存、中断对应表等

  • 提供通信方法

    以上由pruss, pru_rproc,rpmsg_pru Linux 驱动完成。

    image-20200905150942476

以上四个板块表示了加载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的通信。 详情见下图:

image-20200905152516108

以上是PRU的通用的用法,具体到PRU可以实现的功能的话,需要根据PRU的固件来决定。

以上了解了PRU的基本用法,下面对其驱动进行研究。

4、PRU相关驱动研究

根据之前TI给出的框架情况,可以看到remoteproc driver是比较基础的一个驱动,这个层面的驱动主要是与驱动设备模型相关的东西,其中有remoteproc_core.c、remoteproc_debugfs.c等,这里暂不做分析。

这里重点分析pruss和pru_rproc相关的驱动。根据何工之前的关于PRU驱动分析可知,共用到了以下驱动:

devicecompatibledriver
prussti,am5728-prusspruss.c
pruss_intcti,am5728-pruss-intcpruss_intc.c
pru2_0ti,am5728-prupru_rproc.c
pru2_1ti,am5728-prupru_rproc.c
pruss2_mdioti,davinci_mdiodavinci_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的地址信息,以下是该地址范围内的寄存器地址,下面不具体分析每一个寄存器每一位的具体作用,仅简单分析该地址范围内的寄存器的大体上的功能。

image-20200907152506076

此类寄存器

1、配置电源

2、配置IO口

3、时钟管理

4、中断状态、中断使能、中断禁止

5、地址偏移使能

6、优先级设置

7、引脚配置

image-20200907155346484

image-20200907155358197

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就是中间商,客户告诉中间商我需要做什么,中间商再告诉商家去做什么,这样就会省掉很多的工作。

image-20200909134734868

上图简单的对两个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);
···   
}
  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

塔通天

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值