diff mbox series

[v1,33/43] hw/intc: Add LoongArch ls7a interrupt controller support(PCH-PIC)

Message ID 20220415094058.3584233-34-yangxiaojuan@loongson.cn (mailing list archive)
State New, archived
Headers show
Series Add LoongArch softmmu support | expand

Commit Message

Xiaojuan Yang April 15, 2022, 9:40 a.m. UTC
This patch realize the PCH-PIC interrupt controller.

Signed-off-by: Xiaojuan Yang <yangxiaojuan@loongson.cn>
Signed-off-by: Song Gao <gaosong@loongson.cn>
---
 MAINTAINERS                         |   1 +
 hw/intc/Kconfig                     |   4 +
 hw/intc/loongarch_pch_pic.c         | 488 ++++++++++++++++++++++++++++
 hw/intc/meson.build                 |   1 +
 hw/intc/trace-events                |   9 +
 hw/loongarch/Kconfig                |   1 +
 include/hw/intc/loongarch_pch_pic.h |  80 +++++
 include/hw/pci-host/ls7a.h          |  30 ++
 8 files changed, 614 insertions(+)
 create mode 100644 hw/intc/loongarch_pch_pic.c
 create mode 100644 include/hw/intc/loongarch_pch_pic.h
 create mode 100644 include/hw/pci-host/ls7a.h

Comments

Richard Henderson April 18, 2022, 3:15 a.m. UTC | #1
On 4/15/22 02:40, Xiaojuan Yang wrote:
> +static void pch_pic_update_irq(LoongArchPCHPIC *s, uint32_t mask,
> +                               int level, int hi)
> +{
> +    uint32_t val, irq;
> +
> +    if (level == 1) {
> +        if (hi) {
> +            val = mask & s->intirr_hi & (~s->int_mask_hi);
> +            irq = find_first_bit((unsigned long *)&val, 32);

This does not work -- you're accessing beyond the end of the uint32_t for all LP64 hosts. 
  I think you just want ctz32()...


> +            if (irq != 32) {
> +                s->intisr_hi |= 1ULL << irq;
> +                qemu_set_irq(s->parent_irq[s->htmsi_vector[irq + 32]], 1);
> +            }

... which should in fact only be tested if val != 0, which is to what this IF equates.

Is there a good reason that this function is treating hi and lo separately, as opposed to 
simply doing all of the computation on uint64_t?


r~
Xiaojuan Yang April 18, 2022, 9:14 a.m. UTC | #2
Hi, Richard

On 2022/4/18 上午11:15, Richard Henderson wrote:
> On 4/15/22 02:40, Xiaojuan Yang wrote:
>> +static void pch_pic_update_irq(LoongArchPCHPIC *s, uint32_t mask,
>> +                               int level, int hi)
>> +{
>> +    uint32_t val, irq;
>> +
>> +    if (level == 1) {
>> +        if (hi) {
>> +            val = mask & s->intirr_hi & (~s->int_mask_hi);
>> +            irq = find_first_bit((unsigned long *)&val, 32);
>
> This does not work -- you're accessing beyond the end of the uint32_t 
> for all LP64 hosts.  I think you just want ctz32()...
>
>
>> +            if (irq != 32) {
>> +                s->intisr_hi |= 1ULL << irq;
>> + qemu_set_irq(s->parent_irq[s->htmsi_vector[irq + 32]], 1);
>> +            }
>
> ... which should in fact only be tested if val != 0, which is to what 
> this IF equates.
>
> Is there a good reason that this function is treating hi and lo 
> separately, as opposed to simply doing all of the computation on 
> uint64_t?
>

In the part of linux kernel, pch pic driver use 32 bits for reading and 
writing.
e.g in the drivers/irqchip/irq-loongson-pch-pic.c, pch_pic_mask_irq() 
function use writel() to write pch_pic mask reg.

Thanks.
Xiaojuan
>
> r~
Richard Henderson April 18, 2022, 2:39 p.m. UTC | #3
On 4/18/22 02:14, yangxiaojuan wrote:
> Hi, Richard
> 
> On 2022/4/18 上午11:15, Richard Henderson wrote:
>> On 4/15/22 02:40, Xiaojuan Yang wrote:
>>> +static void pch_pic_update_irq(LoongArchPCHPIC *s, uint32_t mask,
>>> +                               int level, int hi)
>>> +{
>>> +    uint32_t val, irq;
>>> +
>>> +    if (level == 1) {
>>> +        if (hi) {
>>> +            val = mask & s->intirr_hi & (~s->int_mask_hi);
>>> +            irq = find_first_bit((unsigned long *)&val, 32);
>>
>> This does not work -- you're accessing beyond the end of the uint32_t for all LP64 
>> hosts.  I think you just want ctz32()...
>>
>>
>>> +            if (irq != 32) {
>>> +                s->intisr_hi |= 1ULL << irq;
>>> + qemu_set_irq(s->parent_irq[s->htmsi_vector[irq + 32]], 1);
>>> +            }
>>
>> ... which should in fact only be tested if val != 0, which is to what this IF equates.
>>
>> Is there a good reason that this function is treating hi and lo separately, as opposed 
>> to simply doing all of the computation on uint64_t?
>>
> 
> In the part of linux kernel, pch pic driver use 32 bits for reading and writing.
> e.g in the drivers/irqchip/irq-loongson-pch-pic.c, pch_pic_mask_irq() function use 
> writel() to write pch_pic mask reg.

So?  update_irq is not writel.

I expect that you should have done something (manual deposit64 or .impl.min_access_size = 
8) with the actual writel, but by the time we arrive in this subroutine we have a complete 
uint64_t.


r~
Xiaojuan Yang April 19, 2022, 7:27 a.m. UTC | #4
On 2022/4/18 下午10:39, Richard Henderson wrote:
> On 4/18/22 02:14, yangxiaojuan wrote:
>> Hi, Richard
>>
>> On 2022/4/18 上午11:15, Richard Henderson wrote:
>>> On 4/15/22 02:40, Xiaojuan Yang wrote:
>>>> +static void pch_pic_update_irq(LoongArchPCHPIC *s, uint32_t mask,
>>>> +                               int level, int hi)
>>>> +{
>>>> +    uint32_t val, irq;
>>>> +
>>>> +    if (level == 1) {
>>>> +        if (hi) {
>>>> +            val = mask & s->intirr_hi & (~s->int_mask_hi);
>>>> +            irq = find_first_bit((unsigned long *)&val, 32);
>>>
>>> This does not work -- you're accessing beyond the end of the 
>>> uint32_t for all LP64 hosts.  I think you just want ctz32()...
>>>
>>>
>>>> +            if (irq != 32) {
>>>> +                s->intisr_hi |= 1ULL << irq;
>>>> + qemu_set_irq(s->parent_irq[s->htmsi_vector[irq + 32]], 1);
>>>> +            }
>>>
>>> ... which should in fact only be tested if val != 0, which is to 
>>> what this IF equates.
>>>
>>> Is there a good reason that this function is treating hi and lo 
>>> separately, as opposed to simply doing all of the computation on 
>>> uint64_t?
>>>
>>
>> In the part of linux kernel, pch pic driver use 32 bits for reading 
>> and writing.
>> e.g in the drivers/irqchip/irq-loongson-pch-pic.c, pch_pic_mask_irq() 
>> function use writel() to write pch_pic mask reg.
>
> So?  update_irq is not writel.
>
> I expect that you should have done something (manual deposit64 or 
> .impl.min_access_size = 8) with the actual writel, but by the time we 
> arrive in this subroutine we have a complete uint64_t.
>

In the linux kernel pch_pic driver, pch_pic_unmask_irq() use writel to 
config PCH_PIC_CLR reg:

writel(BIT(PIC_REG_BIT(d->hwirq)),
                     priv->base + PCH_PIC_CLR + PIC_REG_IDX(d->hwirq) * 4);

And writel() need u32 value.

static inline void writel(u32 value, volatile void __iomem *addr)
{
     __io_bw();
     __raw_writel((u32 __force)__cpu_to_le32(value), addr);
     __io_aw();
}

The emulate of PCH_PIC_CLR in qemu LoongArchPCHPIC struct member is 
intirr_lo/hi(we devide 64bits reg to two 32bits reg to match the linux 
kernel), it will be changed when we config clear reg or handler irq.

static void loongarch_pch_pic_low_writew(void *opaque, hwaddr addr,
                                      uint64_t data, unsigned size)
{
...
case PCH_PIC_INT_CLEAR_LO:
     if (s->intedge_lo & data) {
         s->intirr_lo &= (~data);
         pch_pic_update_irq(s, data, 0, 0);
         s->intisr_lo &= (~data);
      }
     break;
case PCH_PIC_INT_CLEAR_HI:
     if (s->intedge_hi & data) {
         s->intirr_hi &= (~data);
         pch_pic_update_irq(s, data, 0, 1);
         s->intisr_hi &= (~data);
      }
     break;
...
}

static void pch_pic_irq_handler(void *opaque, int irq, int level)
{
...
hi = (irq >= 32) ? 1 : 0;
if (hi) {
     irq = irq - 32;
}

  mask = 1ULL << irq;


  if (hi) {
     if (s->intedge_hi & mask) {
         /* Edge triggered */
         if (level) {
             if ((s->last_intirr_hi & mask) == 0) {
                 s->intirr_hi |= mask;
             }
             s->last_intirr_hi |= mask;
         } else {
             s->last_intirr_hi &= ~mask;
         }
...
pch_pic_update_irq(s, mask, level, hi);
}

And in update_irq(), we should judge intirr,which irq number is pending.
so we also need whether is intirr_lo or intirr_hi.

static void pch_pic_update_irq(LoongArchPCHPIC *s, uint32_t mask,
                                int level, int hi)
{
     uint32_t val, irq;

     if (level) {
         if (hi) {
             val = mask & s->intirr_hi & (~s->int_mask_hi);
             if (val) {
                 irq = ctz32(val);
                 s->intisr_hi |= 1ULL << irq;
qemu_set_irq(s->parent_irq[s->htmsi_vector[irq + 32]], 1);
             }
......
}

Thanks.
Xiaojuan
>
> r~
Richard Henderson April 19, 2022, 5:14 p.m. UTC | #5
On 4/19/22 00:27, yangxiaojuan wrote:
> On 2022/4/18 下午10:39, Richard Henderson wrote:
>>>> Is there a good reason that this function is treating hi and lo separately, as opposed 
>>>> to simply doing all of the computation on uint64_t?
>>>>
>>>
>>> In the part of linux kernel, pch pic driver use 32 bits for reading and writing.
>>> e.g in the drivers/irqchip/irq-loongson-pch-pic.c, pch_pic_mask_irq() function use 
>>> writel() to write pch_pic mask reg.
>>
>> So?  update_irq is not writel.
>>
>> I expect that you should have done something (manual deposit64 or .impl.min_access_size 
>> = 8) with the actual writel, but by the time we arrive in this subroutine we have a 
>> complete uint64_t.
>>
> 
> In the linux kernel pch_pic driver, pch_pic_unmask_irq() use writel to config PCH_PIC_CLR reg:
> 
> writel(BIT(PIC_REG_BIT(d->hwirq)),
>                      priv->base + PCH_PIC_CLR + PIC_REG_IDX(d->hwirq) * 4);
> 
> And writel() need u32 value.
> 
> static inline void writel(u32 value, volatile void __iomem *addr)
> {
>      __io_bw();
>      __raw_writel((u32 __force)__cpu_to_le32(value), addr);
>      __io_aw();
> }

Are we talking past one another?
I said just above: it does not matter what the kernel is doing.


> The emulate of PCH_PIC_CLR in qemu LoongArchPCHPIC struct member is intirr_lo/hi(we devide 
> 64bits reg to two 32bits reg to match the linux kernel), it will be changed when we config 
> clear reg or handler irq.
> 
> static void loongarch_pch_pic_low_writew(void *opaque, hwaddr addr,
>                                       uint64_t data, unsigned size)
> {
> ...
> case PCH_PIC_INT_CLEAR_LO:
>      if (s->intedge_lo & data) {
>          s->intirr_lo &= (~data);
>          pch_pic_update_irq(s, data, 0, 0);
>          s->intisr_lo &= (~data);
>       }
>      break;
> case PCH_PIC_INT_CLEAR_HI:
>      if (s->intedge_hi & data) {
>          s->intirr_hi &= (~data);
>          pch_pic_update_irq(s, data, 0, 1);
>          s->intisr_hi &= (~data);
>       }
>      break;

One can just as easily do

     case PCH_PIC_INT_CLEAR_LO:
         data = (uint32_t)data;
         goto do_clear;
     case PCH_PIC_INT_CLEAR_HI:
         data <<= 32;
     do_clear:
         s->intrr &= ~data;
         pch_pic_update_irq(s...);
         s->intrs &= ~data;

with the values internal to qemu be represented with uint64_t instead of a pair of 
uint32_t.  Which would in fact be *much* clearer to read, and would seem to cut down the 
number of code lines required by half.


r~
Xiaojuan Yang April 21, 2022, 3:10 a.m. UTC | #6
On 2022/4/20 上午1:14, Richard Henderson wrote:
>
>> The emulate of PCH_PIC_CLR in qemu LoongArchPCHPIC struct member is 
>> intirr_lo/hi(we devide 64bits reg to two 32bits reg to match the 
>> linux kernel), it will be changed when we config clear reg or handler 
>> irq.
>>
>> static void loongarch_pch_pic_low_writew(void *opaque, hwaddr addr,
>>                                       uint64_t data, unsigned size)
>> {
>> ...
>> case PCH_PIC_INT_CLEAR_LO:
>>      if (s->intedge_lo & data) {
>>          s->intirr_lo &= (~data);
>>          pch_pic_update_irq(s, data, 0, 0);
>>          s->intisr_lo &= (~data);
>>       }
>>      break;
>> case PCH_PIC_INT_CLEAR_HI:
>>      if (s->intedge_hi & data) {
>>          s->intirr_hi &= (~data);
>>          pch_pic_update_irq(s, data, 0, 1);
>>          s->intisr_hi &= (~data);
>>       }
>>      break;
>
> One can just as easily do
>
>     case PCH_PIC_INT_CLEAR_LO:
>         data = (uint32_t)data;
>         goto do_clear;
>     case PCH_PIC_INT_CLEAR_HI:
>         data <<= 32;
>     do_clear:
>         s->intrr &= ~data;
>         pch_pic_update_irq(s...);
>         s->intrs &= ~data;
>
> with the values internal to qemu be represented with uint64_t instead 
> of a pair of uint32_t.  Which would in fact be *much* clearer to read, 
> and would seem to cut down the number of code lines required by half. 
Sorry, I didn't understand your means before.
I will fix it in this way. Repalcing pch_pic uint32 registers with 
uint64 and fix its' reading/writing options to keep consistency with the 
document.

Thanks.
Xiaojuan
diff mbox series

Patch

diff --git a/MAINTAINERS b/MAINTAINERS
index 3f5aaeeeba..fadb90a9d5 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1136,6 +1136,7 @@  F: hw/loongarch/
 F: include/hw/loongarch/loongarch.h
 F: include/hw/intc/loongarch_*.h
 F: hw/intc/loongarch_*.c
+F: include/hw/pci-host/ls7a.h
 
 M68K Machines
 -------------
diff --git a/hw/intc/Kconfig b/hw/intc/Kconfig
index 6c7e82da64..1fbba2e728 100644
--- a/hw/intc/Kconfig
+++ b/hw/intc/Kconfig
@@ -87,3 +87,7 @@  config M68K_IRQC
 
 config LOONGARCH_IPI
     bool
+
+config LOONGARCH_PCH_PIC
+    bool
+    select UNIMP
diff --git a/hw/intc/loongarch_pch_pic.c b/hw/intc/loongarch_pch_pic.c
new file mode 100644
index 0000000000..3865015974
--- /dev/null
+++ b/hw/intc/loongarch_pch_pic.c
@@ -0,0 +1,488 @@ 
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * QEMU Loongson 7A1000 I/O interrupt controller.
+ *
+ * Copyright (C) 2021 Loongson Technology Corporation Limited
+ */
+
+#include "qemu/osdep.h"
+#include "hw/sysbus.h"
+#include "hw/loongarch/loongarch.h"
+#include "hw/irq.h"
+#include "hw/intc/loongarch_pch_pic.h"
+#include "migration/vmstate.h"
+#include "trace.h"
+
+static void pch_pic_update_irq(LoongArchPCHPIC *s, uint32_t mask,
+                               int level, int hi)
+{
+    uint32_t val, irq;
+
+    if (level == 1) {
+        if (hi) {
+            val = mask & s->intirr_hi & (~s->int_mask_hi);
+            irq = find_first_bit((unsigned long *)&val, 32);
+            if (irq != 32) {
+                s->intisr_hi |= 1ULL << irq;
+                qemu_set_irq(s->parent_irq[s->htmsi_vector[irq + 32]], 1);
+            }
+        } else {
+            val = mask & s->intirr_lo & (~s->int_mask_lo);
+            irq = find_first_bit((unsigned long *)&val, 32);
+            if (irq != 32) {
+                s->intisr_lo |= 1ULL << irq;
+                qemu_set_irq(s->parent_irq[s->htmsi_vector[irq]], 1);
+            }
+        }
+    } else {
+        if (hi) {
+            val = mask & s->intisr_hi;
+            irq = find_first_bit((unsigned long *)&val, 32);
+            if (irq != 32) {
+                s->intisr_hi &= ~(0x1ULL << irq);
+                qemu_set_irq(s->parent_irq[s->htmsi_vector[irq + 32]], 0);
+            }
+        } else {
+            val = mask & s->intisr_lo;
+            irq = find_first_bit((unsigned long *)&val, 32);
+            if (irq != 32) {
+                s->intisr_lo &= ~(0x1ULL << irq);
+                qemu_set_irq(s->parent_irq[s->htmsi_vector[irq]], 0);
+            }
+        }
+    }
+}
+
+static void pch_pic_irq_handler(void *opaque, int irq, int level)
+{
+    LoongArchPCHPIC *s = LOONGARCH_PCH_PIC(opaque);
+    int hi = 0;
+    uint32_t mask;
+
+    assert(irq < PCH_PIC_IRQ_NUM);
+    trace_loongarch_pch_pic_irq_handler(irq, level);
+
+    hi = (irq >= 32) ? 1 : 0;
+    if (hi) {
+        irq = irq - 32;
+    }
+
+    mask = 1ULL << irq;
+
+    if (hi) {
+        if (s->intedge_hi & mask) {
+            /* Edge triggered */
+            if (level) {
+                if ((s->last_intirr_hi & mask) == 0) {
+                    s->intirr_hi |= mask;
+                }
+                s->last_intirr_hi |= mask;
+            } else {
+                s->last_intirr_hi &= ~mask;
+            }
+        } else {
+            /* Level triggered */
+            if (level) {
+                s->intirr_hi |= mask;
+                s->last_intirr_hi |= mask;
+            } else {
+                s->intirr_hi &= ~mask;
+                s->last_intirr_hi &= ~mask;
+            }
+        }
+    } else {
+        if (s->intedge_lo & mask) {
+            /* Edge triggered */
+            if (level) {
+                if ((s->last_intirr_lo & mask) == 0) {
+                    s->intirr_lo |= mask;
+                }
+                s->last_intirr_lo |= mask;
+            } else {
+                s->last_intirr_lo &= ~mask;
+            }
+        } else {
+            /* Level triggered */
+            if (level) {
+                s->intirr_lo |= mask;
+                s->last_intirr_lo |= mask;
+            } else {
+                s->intirr_lo &= ~mask;
+                s->last_intirr_lo &= ~mask;
+            }
+
+        }
+    }
+    pch_pic_update_irq(s, mask, level, hi);
+}
+
+static uint64_t loongarch_pch_pic_low_readw(void *opaque, hwaddr addr,
+                                            unsigned size)
+{
+    LoongArchPCHPIC *s = LOONGARCH_PCH_PIC(opaque);
+    uint64_t val = 0;
+    uint32_t offset = addr & 0xfff;
+
+    switch (offset) {
+    case PCH_PIC_INT_ID_LO:
+        val = PCH_PIC_INT_ID_VAL;
+        break;
+    case PCH_PIC_INT_ID_HI:
+        val = PCH_PIC_INT_ID_NUM;
+        break;
+    case PCH_PIC_INT_MASK_LO:
+        val = s->int_mask_lo;
+        break;
+    case PCH_PIC_INT_MASK_HI:
+        val = s->int_mask_hi;
+        break;
+    case PCH_PIC_INT_EDGE_LO:
+        val = s->intedge_lo;
+        break;
+    case PCH_PIC_INT_EDGE_HI:
+        val = s->intedge_hi;
+        break;
+    case PCH_PIC_HTMSI_EN_LO:
+        val = s->htmsi_en_lo;
+        break;
+    case PCH_PIC_HTMSI_EN_HI:
+        val = s->htmsi_en_hi;
+        break;
+    case PCH_PIC_AUTO_CTRL0_LO:
+    case PCH_PIC_AUTO_CTRL0_HI:
+    case PCH_PIC_AUTO_CTRL1_LO:
+    case PCH_PIC_AUTO_CTRL1_HI:
+        break;
+    default:
+        break;
+    }
+
+    trace_loongarch_pch_pic_low_readw(size, (uint32_t)addr, val);
+    return val;
+}
+
+static void loongarch_pch_pic_low_writew(void *opaque, hwaddr addr,
+                                         uint64_t data, unsigned size)
+{
+    LoongArchPCHPIC *s = LOONGARCH_PCH_PIC(opaque);
+    uint32_t offset, old;
+    offset = addr & 0xfff;
+
+    trace_loongarch_pch_pic_low_writew(size, (uint32_t)addr, data);
+
+    switch (offset) {
+    case PCH_PIC_INT_MASK_LO:
+        old = s->int_mask_lo;
+        s->int_mask_lo = data;
+        if (old & ~data) {
+            pch_pic_update_irq(s, (old & ~data), 1, 0);
+        } else if (~old & data) {
+            pch_pic_update_irq(s, (~old & data), 0, 0);
+        }
+        break;
+    case PCH_PIC_INT_MASK_HI:
+        old = s->int_mask_hi;
+        s->int_mask_hi = data;
+        if (old & ~data) {
+            pch_pic_update_irq(s, (old & ~data), 1, 1);
+        } else if (~old & data) {
+            pch_pic_update_irq(s, (~old & data), 0, 1);
+        }
+        break;
+    case PCH_PIC_INT_EDGE_LO:
+        s->intedge_lo = data;
+        break;
+    case PCH_PIC_INT_EDGE_HI:
+        s->intedge_hi = data;
+        break;
+    case PCH_PIC_INT_CLEAR_LO:
+        if (s->intedge_lo & data) {
+            s->intirr_lo &= (~data);
+            pch_pic_update_irq(s, data, 0, 0);
+            s->intisr_lo &= (~data);
+        }
+        break;
+    case PCH_PIC_INT_CLEAR_HI:
+        if (s->intedge_hi & data) {
+            s->intirr_hi &= (~data);
+            pch_pic_update_irq(s, data, 0, 1);
+            s->intisr_hi &= (~data);
+        }
+        break;
+    case PCH_PIC_HTMSI_EN_LO:
+        s->htmsi_en_lo = data;
+        break;
+    case PCH_PIC_HTMSI_EN_HI:
+        s->htmsi_en_hi = data;
+        break;
+    case PCH_PIC_AUTO_CTRL0_LO:
+    case PCH_PIC_AUTO_CTRL0_HI:
+    case PCH_PIC_AUTO_CTRL1_LO:
+    case PCH_PIC_AUTO_CTRL1_HI:
+        break;
+    default:
+        break;
+    }
+}
+
+static uint64_t loongarch_pch_pic_high_readw(void *opaque, hwaddr addr,
+                                        unsigned size)
+{
+    LoongArchPCHPIC *s = LOONGARCH_PCH_PIC(opaque);
+    uint64_t val = 0;
+    uint32_t offset = addr & 0xfff;
+
+    switch (offset) {
+    case STATUS_LO_START:
+        val = s->intisr_lo & (~s->int_mask_lo);
+        break;
+    case STATUS_HI_START:
+        val = s->intisr_hi & (~s->int_mask_hi);
+        break;
+    case POL_LO_START:
+        val = s->int_polarity_lo;
+        break;
+    case POL_HI_START:
+        val = s->int_polarity_hi;
+        break;
+    default:
+        break;
+    }
+
+    trace_loongarch_pch_pic_high_readw(size, (uint32_t)addr, val);
+    return val;
+}
+
+static void loongarch_pch_pic_high_writew(void *opaque, hwaddr addr,
+                                     uint64_t data, unsigned size)
+{
+    LoongArchPCHPIC *s = LOONGARCH_PCH_PIC(opaque);
+    uint32_t offset;
+    offset = addr & 0xfff;
+
+    trace_loongarch_pch_pic_high_writew(size, (uint32_t)addr, data);
+
+    switch (offset) {
+    case STATUS_LO_START:
+        s->intisr_lo = data;
+        break;
+    case STATUS_HI_START:
+        s->intisr_hi = data;
+        break;
+    case POL_LO_START:
+        s->int_polarity_lo = data;
+        break;
+    case POL_HI_START:
+        s->int_polarity_hi = data;
+        break;
+    default:
+        break;
+    }
+}
+
+static uint64_t loongarch_pch_pic_readb(void *opaque, hwaddr addr,
+                                        unsigned size)
+{
+    LoongArchPCHPIC *s = LOONGARCH_PCH_PIC(opaque);
+    uint64_t val = 0;
+    uint32_t offset = (addr & 0xfff) + PCH_PIC_ROUTE_ENTRY_OFFSET;
+    int64_t offset_tmp;
+
+    switch (offset) {
+    case PCH_PIC_HTMSI_VEC_OFFSET ... PCH_PIC_HTMSI_VEC_END:
+        offset_tmp = offset - PCH_PIC_HTMSI_VEC_OFFSET;
+        if (offset_tmp >= 0 && offset_tmp < 64) {
+            val = s->htmsi_vector[offset_tmp];
+        }
+        break;
+    case PCH_PIC_ROUTE_ENTRY_OFFSET ... PCH_PIC_ROUTE_ENTRY_END:
+        offset_tmp = offset - PCH_PIC_ROUTE_ENTRY_OFFSET;
+        if (offset_tmp >= 0 && offset_tmp < 64) {
+            val = s->route_entry[offset_tmp];
+        }
+        break;
+    default:
+        break;
+    }
+
+    trace_loongarch_pch_pic_readb(size, (uint32_t)addr, val);
+    return val;
+}
+
+static void loongarch_pch_pic_writeb(void *opaque, hwaddr addr,
+                                     uint64_t data, unsigned size)
+{
+    LoongArchPCHPIC *s = LOONGARCH_PCH_PIC(opaque);
+    int32_t offset_tmp;
+    uint32_t offset = (addr & 0xfff) + PCH_PIC_ROUTE_ENTRY_OFFSET;
+
+    trace_loongarch_pch_pic_writeb(size, (uint32_t)addr, data);
+
+    switch (offset) {
+    case PCH_PIC_HTMSI_VEC_OFFSET ... PCH_PIC_HTMSI_VEC_END:
+        offset_tmp = offset - PCH_PIC_HTMSI_VEC_OFFSET;
+        if (offset_tmp >= 0 && offset_tmp < 64) {
+            s->htmsi_vector[offset_tmp] = (uint8_t)(data & 0xff);
+        }
+        break;
+    case PCH_PIC_ROUTE_ENTRY_OFFSET ... PCH_PIC_ROUTE_ENTRY_END:
+        offset_tmp = offset - PCH_PIC_ROUTE_ENTRY_OFFSET;
+        if (offset_tmp >= 0 && offset_tmp < 64) {
+            s->route_entry[offset_tmp] = (uint8_t)(data & 0xff);
+        }
+        break;
+    default:
+        break;
+    }
+}
+
+static const MemoryRegionOps loongarch_pch_pic_reg32_low_ops = {
+    .read = loongarch_pch_pic_low_readw,
+    .write = loongarch_pch_pic_low_writew,
+    .valid = {
+        .min_access_size = 4,
+        .max_access_size = 8,
+    },
+    .impl = {
+        .min_access_size = 4,
+        .max_access_size = 4,
+    },
+    .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static const MemoryRegionOps loongarch_pch_pic_reg32_high_ops = {
+    .read = loongarch_pch_pic_high_readw,
+    .write = loongarch_pch_pic_high_writew,
+    .valid = {
+        .min_access_size = 4,
+        .max_access_size = 8,
+    },
+    .impl = {
+        .min_access_size = 4,
+        .max_access_size = 4,
+    },
+    .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static const MemoryRegionOps loongarch_pch_pic_reg8_ops = {
+    .read = loongarch_pch_pic_readb,
+    .write = loongarch_pch_pic_writeb,
+    .valid = {
+        .min_access_size = 1,
+        .max_access_size = 1,
+    },
+    .impl = {
+        .min_access_size = 1,
+        .max_access_size = 1,
+    },
+    .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static void loongarch_pch_pic_reset(DeviceState *d)
+{
+    LoongArchPCHPIC *s = LOONGARCH_PCH_PIC(d);
+    int i;
+
+    s->int_mask_lo = -1;
+    s->int_mask_hi = -1;
+    s->htmsi_en_lo = 0x0;
+    s->htmsi_en_hi = 0x0;
+    s->intedge_lo  = 0x0;
+    s->intedge_hi  = 0x0;
+    s->intclr_lo   = 0x0;
+    s->intclr_hi   = 0x0;
+    s->auto_crtl0_lo = 0x0;
+    s->auto_crtl0_hi = 0x0;
+    s->auto_crtl1_lo = 0x0;
+    s->auto_crtl1_hi = 0x0;
+    for (i = 0; i < 64; i++) {
+        s->route_entry[i] = 0x1;
+        s->htmsi_vector[i] = 0x0;
+    }
+    s->intirr_lo = 0x0;
+    s->intirr_hi = 0x0;
+    s->intisr_lo = 0x0;
+    s->intisr_hi = 0x0;
+    s->last_intirr_lo = 0x0;
+    s->last_intirr_hi = 0x0;
+    s->int_polarity_lo = 0x0;
+    s->int_polarity_hi = 0x0;
+}
+
+static void loongarch_pch_pic_init(Object *obj)
+{
+    LoongArchPCHPIC *s = LOONGARCH_PCH_PIC(obj);
+    SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+    int i;
+
+    memory_region_init_io(&s->iomem32_low, obj,
+                          &loongarch_pch_pic_reg32_low_ops,
+                          s, PCH_PIC_NAME(.reg32_part1), 0x100);
+    memory_region_init_io(&s->iomem8, obj, &loongarch_pch_pic_reg8_ops,
+                          s, PCH_PIC_NAME(.reg8), 0x2a0);
+    memory_region_init_io(&s->iomem32_high, obj,
+                          &loongarch_pch_pic_reg32_high_ops,
+                          s, PCH_PIC_NAME(.reg32_part2), 0xc60);
+    sysbus_init_mmio(sbd, &s->iomem32_low);
+    sysbus_init_mmio(sbd, &s->iomem8);
+    sysbus_init_mmio(sbd, &s->iomem32_high);
+
+    for (i = 0; i < PCH_PIC_IRQ_NUM; i++) {
+        sysbus_init_irq(sbd, &s->parent_irq[i]);
+    }
+    qdev_init_gpio_in(DEVICE(obj), pch_pic_irq_handler, PCH_PIC_IRQ_NUM);
+}
+
+static const VMStateDescription vmstate_loongarch_pch_pic = {
+    .name = TYPE_LOONGARCH_PCH_PIC,
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .fields = (VMStateField[]) {
+        VMSTATE_UINT32(int_mask_lo, LoongArchPCHPIC),
+        VMSTATE_UINT32(int_mask_hi, LoongArchPCHPIC),
+        VMSTATE_UINT32(htmsi_en_lo, LoongArchPCHPIC),
+        VMSTATE_UINT32(htmsi_en_hi, LoongArchPCHPIC),
+        VMSTATE_UINT32(intedge_lo, LoongArchPCHPIC),
+        VMSTATE_UINT32(intedge_hi, LoongArchPCHPIC),
+        VMSTATE_UINT32(intclr_lo, LoongArchPCHPIC),
+        VMSTATE_UINT32(intclr_hi, LoongArchPCHPIC),
+        VMSTATE_UINT32(auto_crtl0_lo, LoongArchPCHPIC),
+        VMSTATE_UINT32(auto_crtl0_hi, LoongArchPCHPIC),
+        VMSTATE_UINT32(auto_crtl1_lo, LoongArchPCHPIC),
+        VMSTATE_UINT32(auto_crtl1_hi, LoongArchPCHPIC),
+        VMSTATE_UINT8_ARRAY(route_entry, LoongArchPCHPIC, 64),
+        VMSTATE_UINT8_ARRAY(htmsi_vector, LoongArchPCHPIC, 64),
+        VMSTATE_UINT32(last_intirr_lo, LoongArchPCHPIC),
+        VMSTATE_UINT32(last_intirr_hi, LoongArchPCHPIC),
+        VMSTATE_UINT32(intirr_lo, LoongArchPCHPIC),
+        VMSTATE_UINT32(intirr_hi, LoongArchPCHPIC),
+        VMSTATE_UINT32(intisr_lo, LoongArchPCHPIC),
+        VMSTATE_UINT32(intisr_hi, LoongArchPCHPIC),
+        VMSTATE_UINT32(int_polarity_lo, LoongArchPCHPIC),
+        VMSTATE_UINT32(int_polarity_hi, LoongArchPCHPIC),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+static void loongarch_pch_pic_class_init(ObjectClass *klass, void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+
+    dc->reset = loongarch_pch_pic_reset;
+    dc->vmsd = &vmstate_loongarch_pch_pic;
+}
+
+static const TypeInfo loongarch_pch_pic_info = {
+    .name          = TYPE_LOONGARCH_PCH_PIC,
+    .parent        = TYPE_SYS_BUS_DEVICE,
+    .instance_size = sizeof(LoongArchPCHPIC),
+    .instance_init = loongarch_pch_pic_init,
+    .class_init    = loongarch_pch_pic_class_init,
+};
+
+static void loongarch_pch_pic_register_types(void)
+{
+    type_register_static(&loongarch_pch_pic_info);
+}
+
+type_init(loongarch_pch_pic_register_types)
diff --git a/hw/intc/meson.build b/hw/intc/meson.build
index bf5ab44a78..960ce81a92 100644
--- a/hw/intc/meson.build
+++ b/hw/intc/meson.build
@@ -63,3 +63,4 @@  specific_ss.add(when: ['CONFIG_KVM', 'CONFIG_XIVE'],
 specific_ss.add(when: 'CONFIG_GOLDFISH_PIC', if_true: files('goldfish_pic.c'))
 specific_ss.add(when: 'CONFIG_M68K_IRQC', if_true: files('m68k_irqc.c'))
 specific_ss.add(when: 'CONFIG_LOONGARCH_IPI', if_true: files('loongarch_ipi.c'))
+specific_ss.add(when: 'CONFIG_LOONGARCH_PCH_PIC', if_true: files('loongarch_pch_pic.c'))
diff --git a/hw/intc/trace-events b/hw/intc/trace-events
index 6ae8917d99..830669b547 100644
--- a/hw/intc/trace-events
+++ b/hw/intc/trace-events
@@ -279,3 +279,12 @@  sh_intc_set(int id, int enable) "setting interrupt group %d to %d"
 # loongarch_ipi.c
 loongarch_ipi_read(unsigned size, uint64_t addr, unsigned long val) "size: %u addr: 0x%"PRIx64 "val: 0x%"PRIx64
 loongarch_ipi_write(unsigned size, uint64_t addr, unsigned long val) "size: %u addr: 0x%"PRIx64 "val: 0x%"PRIx64
+
+# loongarch_pch_pic.c
+loongarch_pch_pic_irq_handler(int irq, int level) "irq %d level %d"
+loongarch_pch_pic_low_readw(unsigned size, uint32_t addr, unsigned long val) "size: %u addr: 0x%"PRIx32 "val: 0x%" PRIx64
+loongarch_pch_pic_low_writew(unsigned size, uint32_t addr, unsigned long val) "size: %u addr: 0x%"PRIx32 "val: 0x%" PRIx64
+loongarch_pch_pic_high_readw(unsigned size, uint32_t addr, unsigned long val) "size: %u addr: 0x%"PRIx32 "val: 0x%" PRIx64
+loongarch_pch_pic_high_writew(unsigned size, uint32_t addr, unsigned long val) "size: %u addr: 0x%"PRIx32 "val: 0x%" PRIx64
+loongarch_pch_pic_readb(unsigned size, uint32_t addr, unsigned long val) "size: %u addr: 0x%"PRIx32 "val: 0x%" PRIx64
+loongarch_pch_pic_writeb(unsigned size, uint32_t addr, unsigned long val) "size: %u addr: 0x%"PRIx32 "val: 0x%" PRIx64
diff --git a/hw/loongarch/Kconfig b/hw/loongarch/Kconfig
index f0dad3329a..2df45f7e8f 100644
--- a/hw/loongarch/Kconfig
+++ b/hw/loongarch/Kconfig
@@ -3,3 +3,4 @@  config LOONGARCH_VIRT
     select PCI
     select PCI_EXPRESS_GENERIC_BRIDGE
     select LOONGARCH_IPI
+    select LOONGARCH_PCH_PIC
diff --git a/include/hw/intc/loongarch_pch_pic.h b/include/hw/intc/loongarch_pch_pic.h
new file mode 100644
index 0000000000..68604e2708
--- /dev/null
+++ b/include/hw/intc/loongarch_pch_pic.h
@@ -0,0 +1,80 @@ 
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * LoongArch 7A1000 I/O interrupt controller definitions
+ *
+ * Copyright (c) 2021 Loongson Technology Corporation Limited
+ */
+
+#define TYPE_LOONGARCH_PCH_PIC "loongarch_pch_pic"
+#define PCH_PIC_NAME(name) TYPE_LOONGARCH_PCH_PIC#name
+OBJECT_DECLARE_SIMPLE_TYPE(LoongArchPCHPIC, LOONGARCH_PCH_PIC)
+
+#define PCH_PIC_IRQ_START               0
+#define PCH_PIC_IRQ_END                 63
+#define PCH_PIC_IRQ_NUM                 64
+#define PCH_PIC_INT_ID_VAL              0x7000000UL
+#define PCH_PIC_INT_ID_NUM              0x3f0001UL
+
+#define PCH_PIC_INT_ID_LO               0x00
+#define PCH_PIC_INT_ID_HI               0x04
+#define PCH_PIC_INT_MASK_LO             0x20
+#define PCH_PIC_INT_MASK_HI             0x24
+#define PCH_PIC_HTMSI_EN_LO             0x40
+#define PCH_PIC_HTMSI_EN_HI             0x44
+#define PCH_PIC_INT_EDGE_LO             0x60
+#define PCH_PIC_INT_EDGE_HI             0x64
+#define PCH_PIC_INT_CLEAR_LO            0x80
+#define PCH_PIC_INT_CLEAR_HI            0x84
+#define PCH_PIC_AUTO_CTRL0_LO           0xc0
+#define PCH_PIC_AUTO_CTRL0_HI           0xc4
+#define PCH_PIC_AUTO_CTRL1_LO           0xe0
+#define PCH_PIC_AUTO_CTRL1_HI           0xe4
+#define PCH_PIC_ROUTE_ENTRY_OFFSET      0x100
+#define PCH_PIC_ROUTE_ENTRY_END         0x13f
+#define PCH_PIC_HTMSI_VEC_OFFSET        0x200
+#define PCH_PIC_HTMSI_VEC_END           0x23f
+#define PCH_PIC_INT_STATUS_LO           0x3a0
+#define PCH_PIC_INT_STATUS_HI           0x3a4
+#define PCH_PIC_INT_POL_LO              0x3e0
+#define PCH_PIC_INT_POL_HI              0x3e4
+
+#define STATUS_LO_START                 0
+#define STATUS_HI_START                 0x4
+#define POL_LO_START                    0x40
+#define POL_HI_START                    0x44
+
+struct LoongArchPCHPIC {
+    SysBusDevice parent_obj;
+    qemu_irq parent_irq[64];
+    uint32_t int_mask_lo; /*0x020 interrupt mask register*/
+    uint32_t int_mask_hi;
+    uint32_t htmsi_en_lo; /*0x040 1=msi*/
+    uint32_t htmsi_en_hi;
+    uint32_t intedge_lo; /*0x060 edge=1 level  =0*/
+    uint32_t intedge_hi; /*0x060 edge=1 level  =0*/
+    uint32_t intclr_lo; /*0x080 for clean edge int,set 1 clean,set 0 is noused*/
+    uint32_t intclr_hi; /*0x080 for clean edge int,set 1 clean,set 0 is noused*/
+    uint32_t auto_crtl0_lo; /*0x0c0*/
+    uint32_t auto_crtl0_hi; /*0x0c0*/
+    uint32_t auto_crtl1_lo; /*0x0e0*/
+    uint32_t auto_crtl1_hi; /*0x0e0*/
+    uint32_t last_intirr_lo;    /* edge detection */
+    uint32_t last_intirr_hi;    /* edge detection */
+    uint32_t intirr_lo; /* 0x380 interrupt request register */
+    uint32_t intirr_hi; /* 0x380 interrupt request register */
+    uint32_t intisr_lo; /* 0x3a0 interrupt service register */
+    uint32_t intisr_hi; /* 0x3a0 interrupt service register */
+    /*
+     * 0x3e0 interrupt level polarity selection
+     * register 0 for high level trigger
+     */
+    uint32_t int_polarity_lo;
+    uint32_t int_polarity_hi;
+
+    uint8_t route_entry[64]; /*0x100 - 0x138*/
+    uint8_t htmsi_vector[64]; /*0x200 - 0x238*/
+
+    MemoryRegion iomem32_low;
+    MemoryRegion iomem32_high;
+    MemoryRegion iomem8;
+};
diff --git a/include/hw/pci-host/ls7a.h b/include/hw/pci-host/ls7a.h
new file mode 100644
index 0000000000..bf80e99ce1
--- /dev/null
+++ b/include/hw/pci-host/ls7a.h
@@ -0,0 +1,30 @@ 
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * QEMU LoongArch CPU
+ *
+ * Copyright (c) 2021 Loongson Technology Corporation Limited
+ */
+
+#ifndef HW_LS7A_H
+#define HW_LS7A_H
+
+#include "hw/pci/pci.h"
+#include "hw/pci/pcie_host.h"
+#include "hw/pci-host/pam.h"
+#include "qemu/units.h"
+#include "qemu/range.h"
+#include "qom/object.h"
+
+#define LS7A_PCH_REG_BASE       0x10000000UL
+#define LS7A_IOAPIC_REG_BASE    (LS7A_PCH_REG_BASE)
+#define LS7A_PCH_MSI_ADDR_LOW   0x2FF00000UL
+
+/*
+ * According to the kernel pch irq start from 64 offset
+ * 0 ~ 16 irqs used for non-pci device while 16 ~ 64 irqs
+ * used for pci device.
+ */
+#define PCH_PIC_IRQ_OFFSET      64
+#define LS7A_DEVICE_IRQS        16
+#define LS7A_PCI_IRQS           48
+#endif