polarfire-soc / polarfire-soc-documentation

PolarFire SoC Documentation
Other
37 stars 19 forks source link

Interrupt 'level high' with UIO driver #92

Closed rroulleau closed 2 years ago

rroulleau commented 2 years ago

Hello,

I am trying to trigger an interrupt level high using the uio driver, I have written the dts entry as follow:

....
irqfabric: irq_level@0x46000000  {
     compatible = "generic-uio";
     reg = < 0x0 0x46000000 0x0 0x00001000>
     interrupt-parent = <&plic>;
     interrupts = <128 4>;
     status = "okay";
};
...

For the field interrupts 128 = interrupt ID and the 4 = active high level-sensitive. Connected to the MSS_INT_F2M in the fabric I used the coreGPIO IP (mapped at 0x46000000) from libero with interrupt port enable set and my Input interrupt type set to 'Level High'.

The first interrupt is catched ok but not the second or following interrupt
In the linux when doing

cat /proc/interrupts 
    CPU0  CPU1  CPU2   CPU3
...
4:   1      0     0     0    SiFive PLIC  128  irq_level
...

and the counter is stuck at 1 on CPU0

I am guessing I am not clearing/acknowledging the UIO driver correctly. Can someone help?

mcnamarad1971 commented 2 years ago

Hi,

I think interrupts = <128>; is better than interrupts = <128 4>; in the device tree. The PolarFire SoC interrupts in device-tree just take the interrupt itself, no details about level - they are all level.

I don't have docs to hand, but I suspect that you need to handle the interrupt on its own register map. I mean a combination of status and mask registers on the coreGPIO IP (working at an offset of a virt address based on 0x46000000).

I suspect the first interrupt is not cleared and remains level high.

rroulleau commented 2 years ago

Hello, Thanks for your feedback. I have only managed to get the interrupt thing to work by sending a "pulse" type interrupt (a clock width - edge trigger) to the MSS_INT_F2M. But in some use case we could miss out some events and I was therefore trying to use the high level-sensitive interrupt but then I am not able to clear my interrupt in the UIO driver, even though I am clearing the source interrupt signal to 0. Do you have a pseudo code that would explain the sequence to acknowledge and clear the UIO driver correctly? In my software wait_irq() function I am doing the following and to be true I retreived this code from forums but I have no spec that explains it:

fd = open("/dev/uiox", O_RDWR | O_SYNC);
wait_irq(){
   write(fd,&reenable,...); // to activate the UIO ?
...
   struct pollfd fds = (fd, POLLIN);
   stat = poll (fds, 1, timeout);  // wait for an event
   if (stat>0){
      if (fds.revents & POLLIN){
          read(fd, &reenable, ...); // acknowledge the event - reenable value = total number of events
          write(fd, &reenable,...); // reenable the UIO - is it Ok to do so here?
      }
   }
} 

This above code works "ok" with edge trigger interrupt (pulse) but NOT when I am using level-sensitive interrupt.

eppidei commented 2 years ago

Hi, just to give you some additional info you may find it useful. The interrupt controller works only on level-high sensitive interrupt triggering. So I guess the issue is somewhere else.

rroulleau commented 2 years ago

Hi, can you be more explicit on what you call the "interrupt controller". Is it the PLIC within the soc-polarfire?

eppidei commented 2 years ago

Yes, I refer to the PLIC. The acknowledgement is done at low level by the irqchip driver. The UIO generic driver disable irqs before handing off control to user who is in charge to re-enable it. There is a CAN example here if u find it useful: https://github.com/polarfire-soc/polarfire-soc-linux-examples/blob/master/can/uio-can-example.c

rroulleau commented 2 years ago

@eppidei Thanks for the link. Now looking in the DTS file, the CAN periph is declared like this:

canx: can@2010**** {
    compatible ="microchip, mpfs-can-uio";
...
    interrupt-parent = <&plic>;
    interrupts = <PLIC_INT_CANx>;  
    status = "disabled";
}

So I conclude in the CAN example the interrupt is handle by a "mpfs-can-uio" driver when in my case I use the generic-uio (see the top of the thread). compatible = "generic-uio"; Could it be an issue on the generic-uio driver on the linux distrib of the MPFS ?

rroulleau commented 2 years ago

If I look in the source code in the linux build. When I use generic-uio in my dts it is using the uio_pdrv_genirq driver/module. within this driver the irq handler function in uio_pdrv_genirq.c:

 static irqreturn_t uio_pdrv_genirq_handler(int irq, struct uio_info *dev_info)
{
        struct uio_pdrv_genirq_platdata *priv = dev_info->priv;

        /* Just disable the interrupt in the interrupt controller, and
         * remember the state so we can allow user space to enable it later.
         */

        spin_lock(&priv->lock);
        if (!__test_and_set_bit(UIO_IRQ_DISABLED, &priv->flags))
                disable_irq_nosync(irq);
        spin_unlock(&priv->lock);

        return IRQ_HANDLED;
}

In the CAN exemple my understanding microchip wrote their own specific driver compatible ="microchip, mpfs-can-uio"; and in the uio_microchip-can.c the handler is doing the following:

 static irqreturn_t mss_can_handler(int irq, struct uio_info *info)
{
        struct uio_mss_can_dev *dev_info = info->priv;
        int val;
        void __iomem *base = dev_info->mss_can_io_vaddr + dev_info->pintc_base;
        void __iomem *intren_reg = base + CAN_INT_ENABLE;
        void __iomem *intrstat_reg = base + CAN_INT_STATUS;

        val = ioread32(intren_reg);
        /* Is interrupt enabled and active ? */
        if (!(val & 0xffff) && (ioread32(intrstat_reg) & 0xffff))
                return IRQ_NONE;
        return IRQ_HANDLED;
}

In the case of the CAN irq handler it is referencing to *base=dev_info->mss_can_io_vaddr... This seems to be hardware specific to the MPFS and therefore not in the generic-uio driver. That make me think if I want to use the MSS interrupt form the Fabric I will have no option but to write my own IRQ handler like microchip did for the CAN, SPI, I2C, etc... I am not a linux guru, so any hint/clue is welcome :-) thanks.

mcnamarad1971 commented 2 years ago

yes, I think that would certainly be a reasonable way forward. You could start by copying the UIO CAN driver, including the Kconfig / Makefile in the same directory along with its device-tree stanza.

Alternatively, you could try using the generic-uio driver and doing those writes in userspace. There are examples in /opt/microchip that show you how to access hardware registers from userspace.