From patchwork Sat Mar 1 17:20:38 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Brian Cain X-Patchwork-Id: 13997631 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id 38EAFC021B8 for ; Sat, 1 Mar 2025 17:21:47 +0000 (UTC) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1toQWb-0003BH-40; Sat, 01 Mar 2025 12:21:18 -0500 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1toQWL-00039P-HE for qemu-devel@nongnu.org; Sat, 01 Mar 2025 12:21:01 -0500 Received: from mx0a-0031df01.pphosted.com ([205.220.168.131]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1toQWI-0008Qh-Az for qemu-devel@nongnu.org; Sat, 01 Mar 2025 12:21:01 -0500 Received: from pps.filterd (m0279862.ppops.net [127.0.0.1]) by mx0a-0031df01.pphosted.com (8.18.1.2/8.18.1.2) with ESMTP id 521GTUOp015517 for ; Sat, 1 Mar 2025 17:20:55 GMT DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=qualcomm.com; h= cc:content-transfer-encoding:content-type:date:from:in-reply-to :message-id:mime-version:references:subject:to; s=qcppdkim1; bh= oL5B4J1F6NJg8yShtfpvZ5qgW1CBbx22qQqXR8Ids8U=; b=ZbU5ulx+LVWLmivB HVO5QuMLALXKe0zrhY431ZC2hJ4eRogZI5spq0plmbNY3arAefDYplhy1+/P2jWP 6BaWV7o6VW0tGVkwFsw7enFtMKaac4m4ABZ7GeI4puWbIkv+5ukMIO/8mEgBRyku W8+/flYG1Pwfk5RTKYkcShxUPqblw8sFnYcWSgh02aOhzKnLZ94ecAh6yZqEfePh jq3QC1WudguJNWqp0uXz9WrdYVErIIS1902apMbQZ3XANhSFp+97mqxH0gjQQcRJ OetR9ccJqzCF9TMwzGJ6TfTmouJ10w8K9fm8CmLCzXbCu+OCS52V3uEqICiodBhS CosdsA== Received: from mail-pj1-f71.google.com (mail-pj1-f71.google.com [209.85.216.71]) by mx0a-0031df01.pphosted.com (PPS) with ESMTPS id 453u0d11d2-1 (version=TLSv1.2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128 verify=NOT) for ; Sat, 01 Mar 2025 17:20:54 +0000 (GMT) Received: by mail-pj1-f71.google.com with SMTP id 98e67ed59e1d1-2feda472a6eso995906a91.0 for ; Sat, 01 Mar 2025 09:20:54 -0800 (PST) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1740849654; x=1741454454; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=oL5B4J1F6NJg8yShtfpvZ5qgW1CBbx22qQqXR8Ids8U=; b=ZieejCGvTb3FAWSPCVRCIO8bxV6A01Kb2PvHaWFHxgPOSl7uaok1swBR/LUS0Suzuh khObnHfQozcc9fIzAgnSPjikosG6CXaDmRrAkuSmY+rneXOy2HdLIQg23S/5BOZgoLn2 H2NqBf/1z2MVv2tIM77yh/LIz/Ki/kj6ft+29ZO62fHctNlbYtpatRBndes0Aol+7lrZ q5I8dIkDa/SHjIjPGYr2WRbPgVWMq7Q/bFPeDzqMXsSYg/BoZJCqfcytwS27TSFeOXYh Ora1K7xkYgffzp8UGGMebm+lVfg3mlHqRUfJbmHDkKHiLJO0OwxRKxhxwi4hiecS4x2y S8CA== X-Gm-Message-State: AOJu0YzDyIZytNjMPDURTs8/WPAfUzX6Lxh06wb2zUuZDU3mSOwpKkwF SvNsnNrFuZwnXSGQ+pjushaFh5Vs82GUKNT6wivYvd7jwN68VYM9kHbRZgxXXxZiWQ86H1928Df PjkUnXxALCNmfWZ/X/EKPwC2lLEM+uPOGiQZtNYY16NQmqOaL4n4aWBdyGU3DKQ== X-Gm-Gg: ASbGncsSz0/k8x5NUxlqpcAN+virSScPc41hXRZyyHjiXpAITdl9BpsjecH1D4vEe2s wj0Sf4QDyGvio383UFOcrxjc9vbIB6w20YXDmgUjroo1mPtrmPJvda/CRVUY7bCSa3AS60maM6H Dn1PhuZsTQM8vKE/MVAyySwH4IjNs559+ScpBFshuHuJ/TiAF+TaoKz6Km6dGXwkjh04tbbB7Ig gAW3FYv4jjcbNmyqnDNrCec+rNskQHYZ9oc+rAgtrl0bwP8neLvJa6zU3+ImROxut5mqKnc1sAH hh1bah8Z/xNHV+We1bJiT11d/PUEumBlkM1ojh2OsjFwRiVvJBZa/QTiW9MCKJJR X-Received: by 2002:a17:90b:3fcb:b0:2fe:9581:fbea with SMTP id 98e67ed59e1d1-2febabf414amr12122351a91.29.1740849653651; Sat, 01 Mar 2025 09:20:53 -0800 (PST) X-Google-Smtp-Source: AGHT+IHEynSQgj2M0pIkjKqPMhEDPmrM6xcQhgzEWepgWaTQsVk3u1kewsijzSDdodxinKwM/OFE8A== X-Received: by 2002:a17:90b:3fcb:b0:2fe:9581:fbea with SMTP id 98e67ed59e1d1-2febabf414amr12122299a91.29.1740849652826; Sat, 01 Mar 2025 09:20:52 -0800 (PST) Received: from hu-bcain-lv.qualcomm.com (Global_NAT1.qualcomm.com. [129.46.96.20]) by smtp.gmail.com with ESMTPSA id d9443c01a7336-223501f9dacsm51308275ad.83.2025.03.01.09.20.51 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 01 Mar 2025 09:20:52 -0800 (PST) From: Brian Cain To: qemu-devel@nongnu.org Cc: brian.cain@oss.qualcomm.com, richard.henderson@linaro.org, philmd@linaro.org, quic_mathbern@quicinc.com, ale@rev.ng, anjo@rev.ng, quic_mliebel@quicinc.com, ltaylorsimpson@gmail.com, alex.bennee@linaro.org, quic_mburton@quicinc.com, sidneym@quicinc.com, Damien Hedde , Paolo Bonzini Subject: [PATCH 1/8] hw/intc: Add l2vic interrupt controller Date: Sat, 1 Mar 2025 09:20:38 -0800 Message-Id: <20250301172045.1295412-2-brian.cain@oss.qualcomm.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20250301172045.1295412-1-brian.cain@oss.qualcomm.com> References: <20250301172045.1295412-1-brian.cain@oss.qualcomm.com> MIME-Version: 1.0 X-Proofpoint-ORIG-GUID: FerQEyqH_q6-ZnTLN5BK1_6DSbMqN8AM X-Proofpoint-GUID: FerQEyqH_q6-ZnTLN5BK1_6DSbMqN8AM X-Proofpoint-Virus-Version: vendor=baseguard engine=ICAP:2.0.293,Aquarius:18.0.1057,Hydra:6.0.680,FMLib:17.12.68.34 definitions=2025-03-01_07,2025-02-28_01,2024-11-22_01 X-Proofpoint-Spam-Details: rule=outbound_notspam policy=outbound score=0 impostorscore=0 adultscore=0 phishscore=0 malwarescore=0 spamscore=0 mlxscore=0 suspectscore=0 clxscore=1015 bulkscore=0 priorityscore=1501 mlxlogscore=999 lowpriorityscore=0 classifier=spam adjust=0 reason=mlx scancount=1 engine=8.19.0-2502100000 definitions=main-2503010140 Received-SPF: pass client-ip=205.220.168.131; envelope-from=brian.cain@oss.qualcomm.com; helo=mx0a-0031df01.pphosted.com X-Spam_score_int: -26 X-Spam_score: -2.7 X-Spam_bar: -- X-Spam_report: (-2.7 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_LOW=-0.7, RCVD_IN_VALIDITY_RPBL_BLOCKED=0.001, RCVD_IN_VALIDITY_SAFE_BLOCKED=0.001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+qemu-devel=archiver.kernel.org@nongnu.org Sender: qemu-devel-bounces+qemu-devel=archiver.kernel.org@nongnu.org From: Sid Manning Co-authored-by: Matheus Tavares Bernardino Co-authored-by: Damien Hedde Signed-off-by: Brian Cain --- MAINTAINERS | 2 + docs/devel/hexagon-l2vic.rst | 59 +++++ docs/devel/index-internals.rst | 1 + include/hw/intc/l2vic.h | 37 +++ hw/intc/l2vic.c | 417 +++++++++++++++++++++++++++++++++ hw/intc/Kconfig | 3 + hw/intc/meson.build | 2 + hw/intc/trace-events | 4 + 8 files changed, 525 insertions(+) create mode 100644 docs/devel/hexagon-l2vic.rst create mode 100644 include/hw/intc/l2vic.h create mode 100644 hw/intc/l2vic.c diff --git a/MAINTAINERS b/MAINTAINERS index 804c07bcd5..a842f7fe1b 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -232,6 +232,7 @@ Hexagon TCG CPUs M: Brian Cain S: Supported F: target/hexagon/ +F: hw/intc/l2vic.[ch] X: target/hexagon/idef-parser/ X: target/hexagon/gen_idef_parser_funcs.py F: linux-user/hexagon/ @@ -242,6 +243,7 @@ F: docker/dockerfiles/debian-hexagon-cross.docker F: gdb-xml/hexagon*.xml F: docs/system/target-hexagon.rst F: docs/devel/hexagon-sys.rst +F: docs/devel/hexagon-l2vic.rst T: git https://github.com/quic/qemu.git hex-next Hexagon idef-parser diff --git a/docs/devel/hexagon-l2vic.rst b/docs/devel/hexagon-l2vic.rst new file mode 100644 index 0000000000..0885636274 --- /dev/null +++ b/docs/devel/hexagon-l2vic.rst @@ -0,0 +1,59 @@ +Hexagon L2 Vectored Interrupt Controller +======================================== + + +.. code-block:: none + + +-------+ + | | +----------------+ + | l2vic | | hexagon core | + | | | | + | +-----| | | + ------> |VID0 >------------->irq2 -\ | + ------> | | | | | + ... > | | | | | + ------> | | | | + | +-----| | / | | \ | + | ... | | | | | | | + | +-----| | t0 t1 t2 t3 ...| + ------> |VIDN | | | + ------> | | | | + ------> | | | | + ------> | | | | + | +-----| | | + | | |Global SREG File| + | State | | | + | [ ]|<============|=>[VID ] | + | [ ]|<============|=>[VID1] | + | [ ]| | | + | [ ]| | | + | | | | + +-------+ +----------------+ + +L2VIC/Core Integration +---------------------- + +* hexagon core supports 8 external interrupt sources +* l2vic supports 1024 input interrupts mapped among 4 output interrupts +* l2vic has four output signals: { VID0, VID1, VID2, VID3 } +* l2vic device has a bank of registers per-VID that can be used to query + the status or assert new interrupts. +* Interrupts are 'steered' to threads based on { thread priority, 'EX' state, + thread interrupt mask, thread interrupt enable, global interrupt enable, + etc. }. +* Any hardware thread could conceivably handle any input interrupt, dependent + on state. +* The system register transfer instruction can read the VID0-VID3 values from + the l2vic when reading from hexagon core system registers "VID" and "VID1". +* When l2vic VID0 has multiple active interrupts, it pulses the VID0 output + IRQ and stores the IRQ number for the VID0 register field. Only after this + interrupt is cleared can the l2vic pulse the VID0 output IRQ again and provide + the next interrupt number on the VID0 register. +* The ``ciad`` instruction clears the l2vic input interrupt and un-disables the + core interrupt. If some/an l2vic VID0 interrupt is pending when this occurs, + the next interrupt should fire and any subseqeunt reads of the VID register + should reflect the newly raised interrupt. +* In QEMU, on an external interrupt or an unmasked-pending interrupt, + all vCPUs are triggered (has_work==true) and each will grab the IO lock + while considering the steering logic to determine whether they're the thread + that must handle the interrupt. diff --git a/docs/devel/index-internals.rst b/docs/devel/index-internals.rst index 27259a552c..35958f1c23 100644 --- a/docs/devel/index-internals.rst +++ b/docs/devel/index-internals.rst @@ -15,6 +15,7 @@ Details about QEMU's various subsystems including how to add features to them. clocks ebpf_rss hexagon-sys + hexagon-l2vic migration/index multi-process reset diff --git a/include/hw/intc/l2vic.h b/include/hw/intc/l2vic.h new file mode 100644 index 0000000000..ed8ccf33b1 --- /dev/null +++ b/include/hw/intc/l2vic.h @@ -0,0 +1,37 @@ +/* + * QEMU L2VIC Interrupt Controller + * + * Copyright(c) 2020-2025 Qualcomm Innovation Center, Inc. All Rights Reserved. + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#define L2VIC_VID_GRP_0 0x0 /* Read */ +#define L2VIC_VID_GRP_1 0x4 /* Read */ +#define L2VIC_VID_GRP_2 0x8 /* Read */ +#define L2VIC_VID_GRP_3 0xC /* Read */ +#define L2VIC_VID_0 0x10 /* Read SOFTWARE DEFINED */ +#define L2VIC_VID_1 0x14 /* Read SOFTWARE DEFINED NOT YET USED */ +#define L2VIC_INT_ENABLEn 0x100 /* Read/Write */ +#define L2VIC_INT_ENABLE_CLEARn 0x180 /* Write */ +#define L2VIC_INT_ENABLE_SETn 0x200 /* Write */ +#define L2VIC_INT_TYPEn 0x280 /* Read/Write */ +#define L2VIC_INT_STATUSn 0x380 /* Read */ +#define L2VIC_INT_CLEARn 0x400 /* Write */ +#define L2VIC_SOFT_INTn 0x480 /* Write */ +#define L2VIC_INT_PENDINGn 0x500 /* Read */ +#define L2VIC_INT_GRPn_0 0x600 /* Read/Write */ +#define L2VIC_INT_GRPn_1 0x680 /* Read/Write */ +#define L2VIC_INT_GRPn_2 0x700 /* Read/Write */ +#define L2VIC_INT_GRPn_3 0x780 /* Read/Write */ + +#define L2VIC_INTERRUPT_MAX 1024 +#define L2VIC_CIAD_INSTRUCTION -1 +/* + * Note about l2vic groups: + * Each interrupt to L2VIC can be configured to associate with one of + * four groups. + * Group 0 interrupts go to IRQ2 via VID 0 (SSR: 0xC2, the default) + * Group 1 interrupts go to IRQ3 via VID 1 (SSR: 0xC3) + * Group 2 interrupts go to IRQ4 via VID 2 (SSR: 0xC4) + * Group 3 interrupts go to IRQ5 via VID 3 (SSR: 0xC5) + */ diff --git a/hw/intc/l2vic.c b/hw/intc/l2vic.c new file mode 100644 index 0000000000..9df6575214 --- /dev/null +++ b/hw/intc/l2vic.c @@ -0,0 +1,417 @@ +/* + * QEMU L2VIC Interrupt Controller + * + * Arm PrimeCell PL190 Vector Interrupt Controller was used as a reference. + * Copyright(c) 2020-2025 Qualcomm Innovation Center, Inc. All Rights Reserved. + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "hw/irq.h" +#include "hw/sysbus.h" +#include "migration/vmstate.h" +#include "qemu/log.h" +#include "qemu/module.h" +#include "hw/intc/l2vic.h" +#include "trace.h" + +#define L2VICA(s, n) (s[(n) >> 2]) + +#define TYPE_L2VIC "l2vic" +#define L2VIC(obj) OBJECT_CHECK(L2VICState, (obj), TYPE_L2VIC) + +#define SLICE_MAX (L2VIC_INTERRUPT_MAX / 32) + +typedef struct L2VICState { + SysBusDevice parent_obj; + + QemuMutex active; + MemoryRegion iomem; + MemoryRegion fast_iomem; + uint32_t level; + /* + * offset 0:vid group 0 etc, 10 bits in each group + * are used: + */ + uint32_t vid_group[4]; + uint32_t vid0; + /* Clear Status of Active Edge interrupt, not used: */ + uint32_t int_clear[SLICE_MAX] QEMU_ALIGNED(16); + /* Enable interrupt source */ + uint32_t int_enable[SLICE_MAX] QEMU_ALIGNED(16); + /* Clear (set to 0) corresponding bit in int_enable */ + uint32_t int_enable_clear; + /* Set (to 1) corresponding bit in int_enable */ + uint32_t int_enable_set; + /* Present for debugging, not used */ + uint32_t int_pending[SLICE_MAX] QEMU_ALIGNED(16); + /* Generate an interrupt */ + uint32_t int_soft; + /* Which enabled interrupt is active */ + uint32_t int_status[SLICE_MAX] QEMU_ALIGNED(16); + /* Edge or Level interrupt */ + uint32_t int_type[SLICE_MAX] QEMU_ALIGNED(16); + /* L2 interrupt group 0-3 0x600-0x7FF */ + uint32_t int_group_n0[SLICE_MAX] QEMU_ALIGNED(16); + uint32_t int_group_n1[SLICE_MAX] QEMU_ALIGNED(16); + uint32_t int_group_n2[SLICE_MAX] QEMU_ALIGNED(16); + uint32_t int_group_n3[SLICE_MAX] QEMU_ALIGNED(16); + qemu_irq irq[8]; +} L2VICState; + + +/* + * Find out if this irq is associated with a group other than + * the default group + */ +static uint32_t *get_int_group(L2VICState *s, int irq) +{ + int n = irq & 0x1f; + if (n < 8) { + return s->int_group_n0; + } + if (n < 16) { + return s->int_group_n1; + } + if (n < 24) { + return s->int_group_n2; + } + return s->int_group_n3; +} + +static int find_slice(int irq) +{ + return irq / 32; +} + +static int get_vid(L2VICState *s, int irq) +{ + uint32_t *group = get_int_group(s, irq); + uint32_t slice = group[find_slice(irq)]; + /* Mask with 0x7 to remove the GRP:EN bit */ + uint32_t val = slice >> ((irq & 0x7) * 4); + if (val & 0x8) { + return val & 0x7; + } else { + return 0; + } +} + +static inline bool vid_active(L2VICState *s) + +{ + /* scan all 1024 bits in int_status arrary */ + const int size = sizeof(s->int_status) * CHAR_BIT; + const int active_irq = find_first_bit((unsigned long *)s->int_status, size); + return ((active_irq != size)) ? true : false; +} + +static bool l2vic_update(L2VICState *s, int irq) +{ + if (vid_active(s)) { + return true; + } + + bool pending = test_bit(irq, (unsigned long *)s->int_pending); + bool enable = test_bit(irq, (unsigned long *)s->int_enable); + if (pending && enable) { + int vid = get_vid(s, irq); + set_bit(irq, (unsigned long *)s->int_status); + clear_bit(irq, (unsigned long *)s->int_pending); + clear_bit(irq, (unsigned long *)s->int_enable); + /* ensure the irq line goes low after going high */ + s->vid0 = irq; + s->vid_group[get_vid(s, irq)] = irq; + + /* already low: now call pulse */ + /* pulse: calls qemu_upper() and then qemu_lower()) */ + qemu_irq_pulse(s->irq[vid + 2]); + trace_l2vic_delivered(irq, vid); + return true; + } + return false; +} + +static void l2vic_update_all(L2VICState *s) +{ + for (int i = 0; i < L2VIC_INTERRUPT_MAX; i++) { + if (l2vic_update(s, i) == true) { + /* once vid is active, no-one else can set it until ciad */ + return; + } + } +} + +static void l2vic_set_irq(void *opaque, int irq, int level) +{ + L2VICState *s = (L2VICState *)opaque; + if (level) { + qemu_mutex_lock(&s->active); + set_bit(irq, (unsigned long *)s->int_pending); + qemu_mutex_unlock(&s->active); + } + l2vic_update(s, irq); +} + +static void l2vic_write(void *opaque, hwaddr offset, uint64_t val, + unsigned size) +{ + L2VICState *s = (L2VICState *)opaque; + qemu_mutex_lock(&s->active); + trace_l2vic_reg_write((unsigned)offset, (uint32_t)val); + + if (offset == L2VIC_VID_0) { + if ((int)val != L2VIC_CIAD_INSTRUCTION) { + s->vid0 = val; + } else { + /* ciad issued: clear int_status */ + clear_bit(s->vid0, (unsigned long *)s->int_status); + } + } else if (offset >= L2VIC_INT_ENABLEn && + offset < (L2VIC_INT_ENABLE_CLEARn)) { + L2VICA(s->int_enable, offset - L2VIC_INT_ENABLEn) = val; + } else if (offset >= L2VIC_INT_ENABLE_CLEARn && + offset < L2VIC_INT_ENABLE_SETn) { + L2VICA(s->int_enable, offset - L2VIC_INT_ENABLE_CLEARn) &= ~val; + } else if (offset >= L2VIC_INT_ENABLE_SETn && offset < L2VIC_INT_TYPEn) { + L2VICA(s->int_enable, offset - L2VIC_INT_ENABLE_SETn) |= val; + } else if (offset >= L2VIC_INT_TYPEn && offset < L2VIC_INT_TYPEn + 0x80) { + L2VICA(s->int_type, offset - L2VIC_INT_TYPEn) = val; + } else if (offset >= L2VIC_INT_STATUSn && offset < L2VIC_INT_CLEARn) { + L2VICA(s->int_status, offset - L2VIC_INT_STATUSn) = val; + } else if (offset >= L2VIC_INT_CLEARn && offset < L2VIC_SOFT_INTn) { + L2VICA(s->int_clear, offset - L2VIC_INT_CLEARn) = val; + } else if (offset >= L2VIC_INT_PENDINGn && + offset < L2VIC_INT_PENDINGn + 0x80) { + L2VICA(s->int_pending, offset - L2VIC_INT_PENDINGn) = val; + } else if (offset >= L2VIC_SOFT_INTn && offset < L2VIC_INT_PENDINGn) { + L2VICA(s->int_enable, offset - L2VIC_SOFT_INTn) |= val; + /* + * Need to reverse engineer the actual irq number. + */ + int irq = find_first_bit((unsigned long *)&val, + sizeof(s->int_enable[0]) * CHAR_BIT); + hwaddr byteoffset = offset - L2VIC_SOFT_INTn; + g_assert(irq != sizeof(s->int_enable[0]) * CHAR_BIT); + irq += byteoffset * 8; + + /* The soft-int interface only works with edge-triggered interrupts */ + if (test_bit(irq, (unsigned long *)s->int_type)) { + qemu_mutex_unlock(&s->active); + l2vic_set_irq(opaque, irq, 1); + qemu_mutex_lock(&s->active); + } + } else if (offset >= L2VIC_INT_GRPn_0 && offset < L2VIC_INT_GRPn_1) { + L2VICA(s->int_group_n0, offset - L2VIC_INT_GRPn_0) = val; + } else if (offset >= L2VIC_INT_GRPn_1 && offset < L2VIC_INT_GRPn_2) { + L2VICA(s->int_group_n1, offset - L2VIC_INT_GRPn_1) = val; + } else if (offset >= L2VIC_INT_GRPn_2 && offset < L2VIC_INT_GRPn_3) { + L2VICA(s->int_group_n2, offset - L2VIC_INT_GRPn_2) = val; + } else if (offset >= L2VIC_INT_GRPn_3 && offset < L2VIC_INT_GRPn_3 + 0x80) { + L2VICA(s->int_group_n3, offset - L2VIC_INT_GRPn_3) = val; + } else { + qemu_log_mask(LOG_UNIMP, "%s: offset %x unimplemented\n", __func__, + (int)offset); + } + l2vic_update_all(s); + qemu_mutex_unlock(&s->active); + return; +} + +static uint64_t l2vic_read(void *opaque, hwaddr offset, unsigned size) +{ + uint64_t value; + L2VICState *s = (L2VICState *)opaque; + qemu_mutex_lock(&s->active); + + if (offset == L2VIC_VID_GRP_0) { + value = s->vid_group[0]; + } else if (offset == L2VIC_VID_GRP_1) { + value = s->vid_group[1]; + } else if (offset == L2VIC_VID_GRP_2) { + value = s->vid_group[2]; + } else if (offset == L2VIC_VID_GRP_3) { + value = s->vid_group[3]; + } else if (offset == L2VIC_VID_0) { + value = s->vid0; + } else if (offset >= L2VIC_INT_ENABLEn && + offset < L2VIC_INT_ENABLE_CLEARn) { + value = L2VICA(s->int_enable, offset - L2VIC_INT_ENABLEn); + } else if (offset >= L2VIC_INT_ENABLE_CLEARn && + offset < L2VIC_INT_ENABLE_SETn) { + value = 0; + } else if (offset >= L2VIC_INT_ENABLE_SETn && offset < L2VIC_INT_TYPEn) { + value = 0; + } else if (offset >= L2VIC_INT_TYPEn && offset < L2VIC_INT_TYPEn + 0x80) { + value = L2VICA(s->int_type, offset - L2VIC_INT_TYPEn); + } else if (offset >= L2VIC_INT_STATUSn && offset < L2VIC_INT_CLEARn) { + value = L2VICA(s->int_status, offset - L2VIC_INT_STATUSn); + } else if (offset >= L2VIC_INT_CLEARn && offset < L2VIC_SOFT_INTn) { + value = L2VICA(s->int_clear, offset - L2VIC_INT_CLEARn); + } else if (offset >= L2VIC_SOFT_INTn && offset < L2VIC_INT_PENDINGn) { + value = 0; + } else if (offset >= L2VIC_INT_PENDINGn && + offset < L2VIC_INT_PENDINGn + 0x80) { + value = L2VICA(s->int_pending, offset - L2VIC_INT_PENDINGn); + } else if (offset >= L2VIC_INT_GRPn_0 && offset < L2VIC_INT_GRPn_1) { + value = L2VICA(s->int_group_n0, offset - L2VIC_INT_GRPn_0); + } else if (offset >= L2VIC_INT_GRPn_1 && offset < L2VIC_INT_GRPn_2) { + value = L2VICA(s->int_group_n1, offset - L2VIC_INT_GRPn_1); + } else if (offset >= L2VIC_INT_GRPn_2 && offset < L2VIC_INT_GRPn_3) { + value = L2VICA(s->int_group_n2, offset - L2VIC_INT_GRPn_2); + } else if (offset >= L2VIC_INT_GRPn_3 && offset < L2VIC_INT_GRPn_3 + 0x80) { + value = L2VICA(s->int_group_n3, offset - L2VIC_INT_GRPn_3); + } else { + value = 0; + qemu_log_mask(LOG_GUEST_ERROR, "L2VIC: %s: offset 0x%x\n", __func__, + (int)offset); + } + + trace_l2vic_reg_read((unsigned)offset, value); + qemu_mutex_unlock(&s->active); + + return value; +} + +static const MemoryRegionOps l2vic_ops = { + .read = l2vic_read, + .write = l2vic_write, + .endianness = DEVICE_NATIVE_ENDIAN, + .valid.min_access_size = 4, + .valid.max_access_size = 4, + .valid.unaligned = false, +}; + +#define FASTL2VIC_ENABLE 0x0 +#define FASTL2VIC_DISABLE 0x1 +#define FASTL2VIC_INT 0x2 + +static void fastl2vic_write(void *opaque, hwaddr offset, uint64_t val, + unsigned size) +{ + if (offset == 0) { + uint32_t cmd = (val >> 16) & 0x3; + uint32_t irq = val & 0x3ff; + uint32_t slice = (irq / 32) * 4; + val = 1 << (irq % 32); + + if (cmd == FASTL2VIC_ENABLE) { + l2vic_write(opaque, L2VIC_INT_ENABLE_SETn + slice, val, size); + } else if (cmd == FASTL2VIC_DISABLE) { + l2vic_write(opaque, L2VIC_INT_ENABLE_CLEARn + slice, val, size); + } else if (cmd == FASTL2VIC_INT) { + l2vic_write(opaque, L2VIC_SOFT_INTn + slice, val, size); + } + qemu_log_mask(LOG_GUEST_ERROR, "%s: invalid write cmd %" PRId32 "\n", + __func__, cmd); + return; + } + qemu_log_mask(LOG_GUEST_ERROR, "%s: invalid write offset 0x%08" HWADDR_PRIx + "\n", __func__, offset); +} + +static const MemoryRegionOps fastl2vic_ops = { + .write = fastl2vic_write, + .endianness = DEVICE_NATIVE_ENDIAN, + .valid.min_access_size = 4, + .valid.max_access_size = 4, + .valid.unaligned = false, +}; + +static void l2vic_reset_hold(Object *obj, G_GNUC_UNUSED ResetType res_type) +{ + L2VICState *s = L2VIC(obj); + memset(s->int_clear, 0, sizeof(s->int_clear)); + memset(s->int_enable, 0, sizeof(s->int_enable)); + memset(s->int_pending, 0, sizeof(s->int_pending)); + memset(s->int_status, 0, sizeof(s->int_status)); + memset(s->int_type, 0, sizeof(s->int_type)); + memset(s->int_group_n0, 0, sizeof(s->int_group_n0)); + memset(s->int_group_n1, 0, sizeof(s->int_group_n1)); + memset(s->int_group_n2, 0, sizeof(s->int_group_n2)); + memset(s->int_group_n3, 0, sizeof(s->int_group_n3)); + s->int_soft = 0; + s->vid0 = 0; + + l2vic_update_all(s); +} + + +static void reset_irq_handler(void *opaque, int irq, int level) +{ + L2VICState *s = (L2VICState *)opaque; + Object *obj = OBJECT(opaque); + if (level) { + l2vic_reset_hold(obj, RESET_TYPE_COLD); + } + l2vic_update_all(s); +} + +static void l2vic_init(Object *obj) +{ + DeviceState *dev = DEVICE(obj); + L2VICState *s = L2VIC(obj); + SysBusDevice *sbd = SYS_BUS_DEVICE(obj); + int i; + + memory_region_init_io(&s->iomem, obj, &l2vic_ops, s, "l2vic", 0x1000); + sysbus_init_mmio(sbd, &s->iomem); + memory_region_init_io(&s->fast_iomem, obj, &fastl2vic_ops, s, "fast", + 0x10000); + sysbus_init_mmio(sbd, &s->fast_iomem); + + qdev_init_gpio_in(dev, l2vic_set_irq, L2VIC_INTERRUPT_MAX); + qdev_init_gpio_in_named(dev, reset_irq_handler, "reset", 1); + for (i = 0; i < 8; i++) { + sysbus_init_irq(sbd, &s->irq[i]); + } + qemu_mutex_init(&s->active); /* TODO: Remove this is an experiment */ +} + +static const VMStateDescription vmstate_l2vic = { + .name = "l2vic", + .version_id = 1, + .minimum_version_id = 1, + .fields = + (VMStateField[]){ + VMSTATE_UINT32(level, L2VICState), + VMSTATE_UINT32_ARRAY(vid_group, L2VICState, 4), + VMSTATE_UINT32(vid0, L2VICState), + VMSTATE_UINT32_ARRAY(int_enable, L2VICState, SLICE_MAX), + VMSTATE_UINT32(int_enable_clear, L2VICState), + VMSTATE_UINT32(int_enable_set, L2VICState), + VMSTATE_UINT32_ARRAY(int_type, L2VICState, SLICE_MAX), + VMSTATE_UINT32_ARRAY(int_status, L2VICState, SLICE_MAX), + VMSTATE_UINT32_ARRAY(int_clear, L2VICState, SLICE_MAX), + VMSTATE_UINT32(int_soft, L2VICState), + VMSTATE_UINT32_ARRAY(int_pending, L2VICState, SLICE_MAX), + VMSTATE_UINT32_ARRAY(int_group_n0, L2VICState, SLICE_MAX), + VMSTATE_UINT32_ARRAY(int_group_n1, L2VICState, SLICE_MAX), + VMSTATE_UINT32_ARRAY(int_group_n2, L2VICState, SLICE_MAX), + VMSTATE_UINT32_ARRAY(int_group_n3, L2VICState, SLICE_MAX), + VMSTATE_END_OF_LIST() } +}; + +static void l2vic_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + ResettableClass *rc = RESETTABLE_CLASS(klass); + + dc->vmsd = &vmstate_l2vic; + rc->phases.hold = l2vic_reset_hold; +} + +static const TypeInfo l2vic_info = { + .name = TYPE_L2VIC, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(L2VICState), + .instance_init = l2vic_init, + .class_init = l2vic_class_init, +}; + +static void l2vic_register_types(void) +{ + type_register_static(&l2vic_info); +} + +type_init(l2vic_register_types) diff --git a/hw/intc/Kconfig b/hw/intc/Kconfig index dd405bdb5d..471e02df27 100644 --- a/hw/intc/Kconfig +++ b/hw/intc/Kconfig @@ -8,6 +8,9 @@ config I8259 config PL190 bool +config L2VIC + bool + config IOAPIC bool select I8259 diff --git a/hw/intc/meson.build b/hw/intc/meson.build index 510fdfb688..919abe5eec 100644 --- a/hw/intc/meson.build +++ b/hw/intc/meson.build @@ -67,6 +67,8 @@ specific_ss.add(when: 'CONFIG_PSERIES', if_true: files('xics_spapr.c', 'spapr_xi specific_ss.add(when: 'CONFIG_XIVE', if_true: files('xive.c')) specific_ss.add(when: ['CONFIG_KVM', 'CONFIG_XIVE'], if_true: files('spapr_xive_kvm.c')) + +specific_ss.add(when: 'CONFIG_L2VIC', if_true: files('l2vic.c')) specific_ss.add(when: 'CONFIG_M68K_IRQC', if_true: files('m68k_irqc.c')) specific_ss.add(when: 'CONFIG_LOONGSON_IPI_COMMON', if_true: files('loongson_ipi_common.c')) specific_ss.add(when: 'CONFIG_LOONGSON_IPI', if_true: files('loongson_ipi.c')) diff --git a/hw/intc/trace-events b/hw/intc/trace-events index 3dcf147198..bc66260fc0 100644 --- a/hw/intc/trace-events +++ b/hw/intc/trace-events @@ -303,6 +303,10 @@ sh_intc_register(const char *s, int id, unsigned short v, int c, int m) "%s %u - sh_intc_read(unsigned size, uint64_t offset, unsigned long val) "size %u 0x%" PRIx64 " -> 0x%lx" sh_intc_write(unsigned size, uint64_t offset, unsigned long val) "size %u 0x%" PRIx64 " <- 0x%lx" sh_intc_set(int id, int enable) "setting interrupt group %d to %d" +# l2vic.c +l2vic_reg_write(unsigned int addr, uint32_t value) "addr: 0x%03x value: 0x%08"PRIx32 +l2vic_reg_read(unsigned int addr, uint32_t value) "addr: 0x%03x value: 0x%08"PRIx32 +l2vic_delivered(int irq, int vid) "l2vic: delivered %d (vid %d)" # loongson_ipi.c loongson_ipi_read(unsigned size, uint64_t addr, uint64_t val) "size: %u addr: 0x%"PRIx64 "val: 0x%"PRIx64