This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

Linux: PRU interrupt to ARM

Tool/software: Linux

Hello everybody,

I am facing a Problem at which I need assistence. I need to implement an Interrupt from the PRU to be received in the ARM CPU using remoteproc and 4.14 Kernel.

I did not find any suitable example codes, so I am trying here. I hope someone has pieces of example code of the Interrupt initalisation for me.

Regards

Chris

  • Hello Chris,

    Please tell us a bit about your use case. Are there timing constraints? Are you just trying to send an interrupt, or do you need to send data as well? If you are sending data, what is your data rate requirement?

    Regards,

    Nick

  • Hello Nick,

    I want to send Data from PRU to ARM. I thought about saving the Data (2,5MByte/sec) in the shared Memory of the PRU and setting an interrupt if the storage is almost full (each 300 us), so the ARM can access the Data from the shared Memory and save it in the RAM Chip. A colleague of mine said, it is possible, that I need to wirte a kernel module, is it possible to solve the problem without a kernel module or do I really need one? And how do I write the code for such a module, or for interrupt detection?

    Regards,

    Chris

  • Hello Chris,

    You have several options.

    LOCATION OF DATA: PRU Memory, or somewhere else (e.g. RAM)

    If you are just using one PRU for collecting / preprocessing data, then it is pretty common to use the other PRU to move the data from the PRU's memory to somewhere else in the system to be anlyzed by the ARM. I can point you to an example if you are interested in this option.

    You could also just have the ARM move the data if you have spare processor cycles to do so.

    ARM ACCESSING DATA: userspace with sudo permissions, or kernel module

    If your use case is an embedded system where you plan to leave sudo permissions on the device, then from user space you can use mmap to expose whatever memory space the PRU data is in.

    If your design does not want the security risks of root access, then your best bet is to write a kernel module to expose the memory from kernel space. Then user space can access the memory through your kernel module. I can point you to resources in either direction as needed.

    MESSAGING BETWEEN PRU AND ARM: polling a known memory address, or using interrupts

    The simplest way for the ARM and the PRU to communicate is to have the ARM poll a known memory address. E.g., if you setup ping-pong buffers, then had the PRU update a known memory address as soon as one of the buffers was full. The ARM could poll until data was ready, process the data, then continue polling.

    You can also setup the PRU INTC to send interrupts to the ARM. I can provide more information on the PRU side setup if needed.

    Regards,

    Nick

  • Hello Nick,

    I would like to use a Kernelmodule for doing the Data transfair. I do not really have that much resources left to pull that much, so I want to use following strategy:

    PRU1 receives all the Values and sends it to PRU0 via Scratchpad.

    PRU0 edits the Data a bit and puts it into the shared Memory.

    When the shared Memory gets full the PRU1 should throw an Interrupt to the ARM (kernel) (it would be nice if you could give me more information about that)

    The Kernel module (Device File) shall catch the Interrupt (also it would be nice to get more information about that step)

    It shall allocate Memory and read the Values From the Shared Memory

    If the Processor is done with its calculating it should read the values from the devicefile.

    I hope you can help me by providing more information about said topics.

    Regards,

    Chris

  • Hello Chris,

    I am sorry for the delay. This is a fairly common use case, so I am taking some extra time to put together an example kernel driver for you and future customers. Kernel driver will be for a misc device. It will have an mmap function and a way to handle interrupts.

    I will post the "rough draft" example for you by Monday of next week. Once you get it, give me any feedback you think is appropriate - eventually I would like to publish this as an official resource for customers.

    Regards,

    Nick

  • Hello Nick,

    I got a bit further.

    By now I know how to write a Kernel Module and I also allocated the PRU Shared memory from within the Kernel device by using following instructions:

    struct page **page_name = kmalloc( sizeof (*page_name)), GFP_KERNEL);
    void * ptr = phys_to_page(phys_addr_t physical_mem);
    ptr = vmap(page_name, 1, VM_MAP, PAGE_KERNEL);

    All in All it seems quite solid and is tested for 'moderate' data stream.
    It is also quite handy, that it devides the Shared memory in its three Pages (one gets written to by the PRU, the others are there to get read).

    Now I need to get the Interrupt across. I thought about using one of the Sys events 15..31 of the PRU cause they are simple to create. I tried to map them to a channel and to a host. Within the Kernel I tried to implement a IRQ on this Host, but it does not work.

    I am really looking forward hearing from you.

    Regards,

    Chris

  • Hello Chris,

    This is a very rough template. I have not had time to check if anything compiles, etc - so if you have any problems or feedback let me know. I am out the rest of this week, but I will reply next week.

    I am trying to show two things here:
    1) Using remoteproc in your probe function to initialize the PRU and set up the PRU INTC
    2) Registering an IRQ to the system so that your function IRQ handler gets called

    pru_rproc_get (in pru_rproc.c) calls function pru_rproc_intc_dt_config, which sets up your PRU's INTC based on device tree arguments.

    template bindings documentation:

    PRU Template Driver on TI SoCs
    
    The PRU can be programmed to fulfill many functions in coordination with Linux.
    
    Each PRU Template node should define the PRU Application node properties as
    detailed in bindings ti,pru-rproc.txt.
    
    Required properties:
    --------------------
    - compatible           : should be "ti,pru-template".
    - interrupt-parent     : phandle to the PRUSS INTC node.
    - prus                 : phandle to the PRU node used.
    - firmware-name        : PRU Template firmware for the PRU core.
    - ti,pru-interrupt-map : PRU interrupt mappings, see ti,pru-rproc.txt for
                             details.
    
    
    Required properties:
    --------------------
    - interrupts        : interrupt specifier for PRU signaling the host. The
                          property should match the event defined in
                          ti,pru-interrupt-map property.
    
    Example (AM335x BeagleBone Black board):
    ---------------------------------------
    
            pru_template0 {
                    compatible = "ti,pru-template";
                    interrupt-parent = <&pruss_intc>;
                    prus = <&pru0>;
                    firmware-name = "pru_template.out";
                    ti,pru-interrupt-map = <0 21 2 2 >;
                    interrupts = <21>;
            }

    Template kernel functions:

    #include <linux/of_irq.h>
    #include <linux/of_platform.h>
    #include <linux/pruss.h>
    #include <linux/remoteproc.h>
    #include <linux/serial_core.h>
    #include <linux/tty_flip.h>
    
    ...
    
    struct pru_template {
            struct device *dev;
            struct rproc *pru;
            struct pruss *pruss;
            int pru_id;
            struct pruss_mem_region mem;
            //TODO: add something to keep track of multiple ARM interrupts 
    };
    ...
    static irqreturn_t pru_template_handle_irq(int irq, void *pru_template)
    {
            // Do something
            return IRQ_HANDLED;
    }
    ...
    static int pru_template_init_pruss(struct device_node *np, struct pru_template *pt)
    {
            u32 reg;
            int ret = 0;
    
            pt->pru = pru_rproc_get(np, 0);
            if (IS_ERR(pt->pru)) {
                    ret = PTR_ERR(pt->pru);
                    if (ret != -EPROBE_DEFER)
                            dev_err(pt->dev, "failed to get pru (%d)\n", ret);
                    return ret;
            }
    
            pt->pruss = pruss_get(pt->pru);
            if (IS_ERR(pt->pruss)) {
                    ret = PTR_ERR(pt->pruss);
                    dev_err(pt->dev, "failed to get pruss handle (%d)\n", ret);
                    goto ptt_pru;
            }
    
            pt->pru_id = pru_rproc_get_id(pt->pru);
            if (pt->pru_id < 0) {
                    dev_err(pt->dev, "failed to get pru id (%d)\n", pt->pru_id);
                    ret = -EINVAL;
                    goto put_pruss;
            }
    
            if (pt->pru_id > 1) {
                    dev_err(pt->dev, "invalid pru id (%d)\n", pt->pru_id);
                    ret = -EINVAL;
                    goto put_pruss;
            }
    
            ret = pruss_request_mem_region(pt->pruss,
                            pt->pru_id ? PRUSS_MEM_DRAM1 : PRUSS_MEM_DRAM0,
                            &pt->mem);
            if (ret) {
                    dev_err(pt->dev, "failed to get pruss mem region (%d)\n", ret);
                    goto put_pruss;
            }
    
            /* clear the mem region before firmware runs by rproc_boot() */
            memset_io(pt->mem.va, 0, pt->mem.size);
    
            ret = rproc_boot(pt->pru);
            if (ret) {
                    dev_err(pt->dev, "failed to boot pru (%d)\n", ret);
                    goto put_mem;
            }
    
            // check firmware version, magic number, etc here to ensure correct firmware was loaded
    
            return ret;
    
    put_rproc:
            rproc_shutdown(pt->pru);
    put_mem:
            pruss_release_mem_region(pt->pruss, &pt->mem);
    put_pruss:
            pruss_put(pt->pruss);
    put_pru:
            pru_rproc_put(pt->pru);
    
            return ret;
    }
    ...
    static int pru_template_probe(struct platform_device *pdev)
    {
            struct pru_template *pt;
            struct device *dev = &pdev->dev;
            struct device_node *np = dev->of_node;
            struct device_node *child;
            int id, ret;
    
            if (!np)
                    return -ENODEV; /* we don't support non DT */
    
            pt = devm_kzalloc(dev, sizeof(*pt), GFP_KERNEL);
            if (!pt)
                    return -ENOMEM;
    
            platform_set_drvdata(pdev, pt);
            pt->dev = dev;
    
            ret = pru_template_init_pruss(np, pt);
            if (ret < 0)
                    return -ENODEV;
    
            // TODO: add for loop so this code iterates over as many interrupts as are defined in device tree
            ret = of_irq_get(np, 0);
            if (ret < 0) {
                    dev_err(pt->dev, "failed to get irq (%d)\n", ret);
                    return;
            }
            // TODO: save the IRQ number somewhere
    
            ret = request_irq(ret, pru_template_handle_irq,
                            IRQF_TRIGGER_HIGH | IRQF_ONESHOT,
                            dev_name(pt->dev), pt);
            if (ret) {
                    dev_err(pt->dev, "failed to to request irq (%d)\n", ret);
                    return;
            }
    
    
            return 0;
    }
    ...
    static int pru_template_remove(struct platform_device *pdev)
    {
            struct pru_template *pt = platform_get_drvdata(pdev);
    
            // TODO: free all IRQs you defined earlier
            //for (each IRQ) {
            //        free_irq(IRQ#, pt);
            //}
    
            rproc_shutdown(pt->pru);
            pruss_release_mem_region(pt->pruss, &pt->mem);
            pruss_put(pt->pruss);
            pru_rproc_put(pt->pru);
    
            return 0;
    }

    Hope this is helpful,

    Nick