Message ID | 20240102214549.22498-5-quic_wcheng@quicinc.com |
---|---|
State | New |
Headers |
Return-Path: <linux-kernel+bounces-14867-ouuuleilei=gmail.com@vger.kernel.org> Delivered-To: ouuuleilei@gmail.com Received: by 2002:a05:7301:6f82:b0:100:9c79:88ff with SMTP id tb2csp4700136dyb; Tue, 2 Jan 2024 13:50:49 -0800 (PST) X-Google-Smtp-Source: AGHT+IFyPFmKXTnXn2uaD10E0fMwi90vnaS+oB3VBm9NguEWZa8RyKyYXLoJWGnDcZyNcnBpUlQt X-Received: by 2002:a05:690c:2d0f:b0:5f2:684c:aad5 with SMTP id eq15-20020a05690c2d0f00b005f2684caad5mr1856709ywb.62.1704232249543; Tue, 02 Jan 2024 13:50:49 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; t=1704232249; cv=none; d=google.com; s=arc-20160816; b=ypNob14LbGschdJO86ZFYBz6+TqZ1CT2G1XvL/m3pn4lqrgdaYUt2qp7lxzFfP4YNI Ei//CVEhdheygvOEH2o0M0vXkNQtnexIvuPpkEBA57V4OI4UDZLlp03dF5wZdf1Yonfx +lRylXiPADVpb9wSsHIgu1bcFfo32Fa2W8d9ttXV3y8fGdvUxsk+ZnT/85+LcyDzaCJG uNVJRjiUx3ssfrfUoX0l5sBCqrG636TKpvlVKnLVknYoOdmcbcbCR3L2YeVcOWblohTP uno8MWTMEcJ/H4yGe9+8HZtscKXji9aQC0xkfiUPlLaD+TF8PKoHmBCP0xrcvUsvCAsf Z96A== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=mime-version:list-unsubscribe:list-subscribe:list-id:precedence :references:in-reply-to:message-id:date:subject:cc:to:from :dkim-signature; bh=dvSeSujDsWAxoUgpDofYeTPsm6P/pMlRxfIJ/11lUYU=; fh=0KzismZj+xI0Z//ZGdqKIH/FO9LtSZM7Yj4P0VtrCz0=; b=ytigeOgdymUIfvZC3XgedR5yGvfFLdox1kmI4dCS9q5w6rd5pRdFSEIquSS81g558a zuj2RtHlqyz4EJkdGociPGiW/rT7UNCmntn3uZ9/LAS4O17b4bCBLuEZLQWk1/zAYFPQ +3MJIBa1mAP1GGjzxc+sCCIFepIWB9ghfs8Ksq7WosvsPzxW+KR00LXJmg4KKHL+xUWb VrNeh7XNk1bx4GC9MRuNTn9R6p06vLZw4hcXNUI6PRSLB3koIUFylVgsf5D1UNX6vo2C Eq4ecexu2jNOtqmaHXLUbTuvs0pDsOsG2Vq9rMEL2ESxpp1pZsCWes00g5fO99H16HSj 9Ppg== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@quicinc.com header.s=qcppdkim1 header.b=f2RLNxYu; spf=pass (google.com: domain of linux-kernel+bounces-14867-ouuuleilei=gmail.com@vger.kernel.org designates 2604:1380:45d1:ec00::1 as permitted sender) smtp.mailfrom="linux-kernel+bounces-14867-ouuuleilei=gmail.com@vger.kernel.org"; dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=quicinc.com Received: from ny.mirrors.kernel.org (ny.mirrors.kernel.org. [2604:1380:45d1:ec00::1]) by mx.google.com with ESMTPS id h6-20020a0cf446000000b0067f7071032csi4487275qvm.466.2024.01.02.13.50.49 for <ouuuleilei@gmail.com> (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 02 Jan 2024 13:50:49 -0800 (PST) Received-SPF: pass (google.com: domain of linux-kernel+bounces-14867-ouuuleilei=gmail.com@vger.kernel.org designates 2604:1380:45d1:ec00::1 as permitted sender) client-ip=2604:1380:45d1:ec00::1; Authentication-Results: mx.google.com; dkim=pass header.i=@quicinc.com header.s=qcppdkim1 header.b=f2RLNxYu; spf=pass (google.com: domain of linux-kernel+bounces-14867-ouuuleilei=gmail.com@vger.kernel.org designates 2604:1380:45d1:ec00::1 as permitted sender) smtp.mailfrom="linux-kernel+bounces-14867-ouuuleilei=gmail.com@vger.kernel.org"; dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=quicinc.com Received: from smtp.subspace.kernel.org (wormhole.subspace.kernel.org [52.25.139.140]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by ny.mirrors.kernel.org (Postfix) with ESMTPS id 477C41C214E3 for <ouuuleilei@gmail.com>; Tue, 2 Jan 2024 21:50:49 +0000 (UTC) Received: from localhost.localdomain (localhost.localdomain [127.0.0.1]) by smtp.subspace.kernel.org (Postfix) with ESMTP id 1B3B3199A7; Tue, 2 Jan 2024 21:46:48 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=quicinc.com header.i=@quicinc.com header.b="f2RLNxYu" X-Original-To: linux-kernel@vger.kernel.org Received: from mx0a-0031df01.pphosted.com (mx0a-0031df01.pphosted.com [205.220.168.131]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id DBBE0171DE; Tue, 2 Jan 2024 21:46:42 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=quicinc.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=quicinc.com Received: from pps.filterd (m0279863.ppops.net [127.0.0.1]) by mx0a-0031df01.pphosted.com (8.17.1.24/8.17.1.24) with ESMTP id 402LLX2k018405; Tue, 2 Jan 2024 21:46:20 GMT DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=quicinc.com; h= from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-type; s=qcppdkim1; bh=dvSeSujDsWAxoUgpDofY eTPsm6P/pMlRxfIJ/11lUYU=; b=f2RLNxYueYV4o2pMXQKUpL4KRWZZmtKc0WUK pc/JxTOxJsZWPwme/klqSMJJoYe5kO8M6pdRco2PKBngasS4uKDcqK3YCrbflQPs J9e5HACoxXfN4VR64lB0giZ5ITeTs9/xRIvz0+gqVTfNJamltsO/rc9/qVkvthd9 bhejiPmDsFk9yRGou7PJFpRNXJx19P8pSgFd0OovItGIu9zUJk4ESXd/hdEaeRb6 ptOgEmAKkFs+j6fY4XPLT8K5zaA/wt0q1cy/Fmz/loIW+3XKzAyjEflK7vDGtwLM wZURbOx6/u8SdTql9FcDGDMPzKaXccysScxPRau8xSt8mYcwtg== Received: from nalasppmta02.qualcomm.com (Global_NAT1.qualcomm.com [129.46.96.20]) by mx0a-0031df01.pphosted.com (PPS) with ESMTPS id 3vcg419bat-1 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NOT); Tue, 02 Jan 2024 21:46:19 +0000 (GMT) Received: from nalasex01b.na.qualcomm.com (nalasex01b.na.qualcomm.com [10.47.209.197]) by NALASPPMTA02.qualcomm.com (8.17.1.5/8.17.1.5) with ESMTPS id 402LkI7O011442 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NOT); Tue, 2 Jan 2024 21:46:18 GMT Received: from hu-wcheng-lv.qualcomm.com (10.49.16.6) by nalasex01b.na.qualcomm.com (10.47.209.197) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.2.1118.40; Tue, 2 Jan 2024 13:46:18 -0800 From: Wesley Cheng <quic_wcheng@quicinc.com> To: <srinivas.kandagatla@linaro.org>, <mathias.nyman@intel.com>, <perex@perex.cz>, <conor+dt@kernel.org>, <corbet@lwn.net>, <gregkh@linuxfoundation.org>, <lgirdwood@gmail.com>, <andersson@kernel.org>, <krzysztof.kozlowski+dt@linaro.org>, <konrad.dybcio@linaro.org>, <Thinh.Nguyen@synopsys.com>, <broonie@kernel.org>, <bgoswami@quicinc.com>, <tiwai@suse.com>, <robh+dt@kernel.org>, <agross@kernel.org> CC: <linux-kernel@vger.kernel.org>, <devicetree@vger.kernel.org>, <linux-sound@vger.kernel.org>, <linux-usb@vger.kernel.org>, <linux-arm-msm@vger.kernel.org>, <linux-doc@vger.kernel.org>, <alsa-devel@alsa-project.org>, Wesley Cheng <quic_wcheng@quicinc.com> Subject: [PATCH v12 04/41] usb: host: xhci-mem: Cleanup pending secondary event ring events Date: Tue, 2 Jan 2024 13:45:12 -0800 Message-ID: <20240102214549.22498-5-quic_wcheng@quicinc.com> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20240102214549.22498-1-quic_wcheng@quicinc.com> References: <20240102214549.22498-1-quic_wcheng@quicinc.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: <linux-kernel.vger.kernel.org> List-Subscribe: <mailto:linux-kernel+subscribe@vger.kernel.org> List-Unsubscribe: <mailto:linux-kernel+unsubscribe@vger.kernel.org> MIME-Version: 1.0 Content-Type: text/plain X-ClientProxiedBy: nalasex01c.na.qualcomm.com (10.47.97.35) To nalasex01b.na.qualcomm.com (10.47.209.197) X-QCInternal: smtphost X-Proofpoint-Virus-Version: vendor=nai engine=6200 definitions=5800 signatures=585085 X-Proofpoint-GUID: nGR2oXkUThsdwGRb_FdP30C5PMFCSnck X-Proofpoint-ORIG-GUID: nGR2oXkUThsdwGRb_FdP30C5PMFCSnck X-Proofpoint-Virus-Version: vendor=baseguard engine=ICAP:2.0.272,Aquarius:18.0.997,Hydra:6.0.619,FMLib:17.11.176.26 definitions=2023-12-09_01,2023-12-07_01,2023-05-22_02 X-Proofpoint-Spam-Details: rule=outbound_notspam policy=outbound score=0 clxscore=1015 mlxscore=0 phishscore=0 spamscore=0 bulkscore=0 adultscore=0 priorityscore=1501 impostorscore=0 mlxlogscore=780 malwarescore=0 suspectscore=0 lowpriorityscore=0 classifier=spam adjust=0 reason=mlx scancount=1 engine=8.19.0-2311290000 definitions=main-2401020161 X-getmail-retrieved-from-mailbox: INBOX X-GMAIL-THRID: 1787017035188241893 X-GMAIL-MSGID: 1787017035188241893 |
Series |
Introduce QC USB SND audio offloading support
|
|
Commit Message
Wesley Cheng
Jan. 2, 2024, 9:45 p.m. UTC
As part of xHCI bus suspend, the XHCI is halted. However, if there are
pending events in the secondary event ring, it is observed that the xHCI
controller stops responding to further commands upon host or device
initiated bus resume. Iterate through all pending events and update the
dequeue pointer to the beginning of the event ring.
Signed-off-by: Wesley Cheng <quic_wcheng@quicinc.com>
---
drivers/usb/host/xhci-mem.c | 11 +++++----
drivers/usb/host/xhci-ring.c | 46 +++++++++++++++++++++++++++++++++++-
drivers/usb/host/xhci.c | 2 +-
drivers/usb/host/xhci.h | 7 ++++++
4 files changed, 59 insertions(+), 7 deletions(-)
Comments
On 2.1.2024 23.45, Wesley Cheng wrote: > As part of xHCI bus suspend, the XHCI is halted. However, if there are > pending events in the secondary event ring, it is observed that the xHCI > controller stops responding to further commands upon host or device > initiated bus resume. Iterate through all pending events and update the > dequeue pointer to the beginning of the event ring. > > Signed-off-by: Wesley Cheng <quic_wcheng@quicinc.com> ... > +/* > + * Move the event ring dequeue pointer to skip events kept in the secondary > + * event ring. This is used to ensure that pending events in the ring are > + * acknowledged, so the XHCI HCD can properly enter suspend/resume. The > + * secondary ring is typically maintained by an external component. > + */ > +void xhci_skip_sec_intr_events(struct xhci_hcd *xhci, > + struct xhci_ring *ring, struct xhci_interrupter *ir) > +{ > + union xhci_trb *erdp_trb, *current_trb; > + u64 erdp_reg; > + u32 iman_reg; > + dma_addr_t deq; > + > + /* disable irq, ack pending interrupt and ack all pending events */ > + xhci_disable_interrupter(ir); > + iman_reg = readl_relaxed(&ir->ir_set->irq_pending); > + if (iman_reg & IMAN_IP) > + writel_relaxed(iman_reg, &ir->ir_set->irq_pending); > + > + /* last acked event trb is in erdp reg */ > + erdp_reg = xhci_read_64(xhci, &ir->ir_set->erst_dequeue); > + deq = (dma_addr_t)(erdp_reg & ERST_PTR_MASK); > + if (!deq) { > + xhci_err(xhci, "event ring handling not required\n"); > + return; > + } > + > + erdp_trb = current_trb = ir->event_ring->dequeue; > + /* read cycle state of the last acked trb to find out CCS */ > + ring->cycle_state = le32_to_cpu(current_trb->event_cmd.flags) & TRB_CYCLE; > + > + while (1) { > + inc_deq(xhci, ir->event_ring); > + erdp_trb = ir->event_ring->dequeue; > + /* cycle state transition */ > + if ((le32_to_cpu(erdp_trb->event_cmd.flags) & TRB_CYCLE) != > + ring->cycle_state) > + break; > + } > + > + xhci_update_erst_dequeue(xhci, ir, current_trb, true); > +} Code above is very similar to the existing event ring processing parts of xhci_irq() and xhci_handle_event() I'll see if I can refactor the existing event ring processing, decouple it from event handling so that it could be used by primary and secondary interrupters with handlers, and this case where we just want to clear the event ring. Thanks Mathias
Hi Mathias, On 1/4/2024 6:48 AM, Mathias Nyman wrote: > On 2.1.2024 23.45, Wesley Cheng wrote: >> As part of xHCI bus suspend, the XHCI is halted. However, if there are >> pending events in the secondary event ring, it is observed that the xHCI >> controller stops responding to further commands upon host or device >> initiated bus resume. Iterate through all pending events and update the >> dequeue pointer to the beginning of the event ring. >> >> Signed-off-by: Wesley Cheng <quic_wcheng@quicinc.com> > ... >> +/* >> + * Move the event ring dequeue pointer to skip events kept in the >> secondary >> + * event ring. This is used to ensure that pending events in the >> ring are >> + * acknowledged, so the XHCI HCD can properly enter suspend/resume. The >> + * secondary ring is typically maintained by an external component. >> + */ >> +void xhci_skip_sec_intr_events(struct xhci_hcd *xhci, >> + struct xhci_ring *ring, struct xhci_interrupter *ir) >> +{ >> + union xhci_trb *erdp_trb, *current_trb; >> + u64 erdp_reg; >> + u32 iman_reg; >> + dma_addr_t deq; >> + >> + /* disable irq, ack pending interrupt and ack all pending events */ >> + xhci_disable_interrupter(ir); >> + iman_reg = readl_relaxed(&ir->ir_set->irq_pending); >> + if (iman_reg & IMAN_IP) >> + writel_relaxed(iman_reg, &ir->ir_set->irq_pending); >> + >> + /* last acked event trb is in erdp reg */ >> + erdp_reg = xhci_read_64(xhci, &ir->ir_set->erst_dequeue); >> + deq = (dma_addr_t)(erdp_reg & ERST_PTR_MASK); >> + if (!deq) { >> + xhci_err(xhci, "event ring handling not required\n"); >> + return; >> + } >> + >> + erdp_trb = current_trb = ir->event_ring->dequeue; >> + /* read cycle state of the last acked trb to find out CCS */ >> + ring->cycle_state = le32_to_cpu(current_trb->event_cmd.flags) & >> TRB_CYCLE; >> + >> + while (1) { >> + inc_deq(xhci, ir->event_ring); >> + erdp_trb = ir->event_ring->dequeue; >> + /* cycle state transition */ >> + if ((le32_to_cpu(erdp_trb->event_cmd.flags) & TRB_CYCLE) != >> + ring->cycle_state) >> + break; >> + } >> + >> + xhci_update_erst_dequeue(xhci, ir, current_trb, true); >> +} > > Code above is very similar to the existing event ring processing parts > of xhci_irq() > and xhci_handle_event() > > I'll see if I can refactor the existing event ring processing, decouple > it from > event handling so that it could be used by primary and secondary > interrupters with > handlers, and this case where we just want to clear the event ring. > Thanks, that makes sense. Will take a look as well. Thanks Wesley Cheng
Hi Mathias, On 1/8/2024 12:51 PM, Wesley Cheng wrote: > Hi Mathias, > > On 1/4/2024 6:48 AM, Mathias Nyman wrote: >> On 2.1.2024 23.45, Wesley Cheng wrote: >>> As part of xHCI bus suspend, the XHCI is halted. However, if there are >>> pending events in the secondary event ring, it is observed that the xHCI >>> controller stops responding to further commands upon host or device >>> initiated bus resume. Iterate through all pending events and update the >>> dequeue pointer to the beginning of the event ring. >>> >>> Signed-off-by: Wesley Cheng <quic_wcheng@quicinc.com> >> ... >>> +/* >>> + * Move the event ring dequeue pointer to skip events kept in the >>> secondary >>> + * event ring. This is used to ensure that pending events in the >>> ring are >>> + * acknowledged, so the XHCI HCD can properly enter suspend/resume. >>> The >>> + * secondary ring is typically maintained by an external component. >>> + */ >>> +void xhci_skip_sec_intr_events(struct xhci_hcd *xhci, >>> + struct xhci_ring *ring, struct xhci_interrupter *ir) >>> +{ >>> + union xhci_trb *erdp_trb, *current_trb; >>> + u64 erdp_reg; >>> + u32 iman_reg; >>> + dma_addr_t deq; >>> + >>> + /* disable irq, ack pending interrupt and ack all pending events */ >>> + xhci_disable_interrupter(ir); >>> + iman_reg = readl_relaxed(&ir->ir_set->irq_pending); >>> + if (iman_reg & IMAN_IP) >>> + writel_relaxed(iman_reg, &ir->ir_set->irq_pending); >>> + >>> + /* last acked event trb is in erdp reg */ >>> + erdp_reg = xhci_read_64(xhci, &ir->ir_set->erst_dequeue); >>> + deq = (dma_addr_t)(erdp_reg & ERST_PTR_MASK); >>> + if (!deq) { >>> + xhci_err(xhci, "event ring handling not required\n"); >>> + return; >>> + } >>> + >>> + erdp_trb = current_trb = ir->event_ring->dequeue; >>> + /* read cycle state of the last acked trb to find out CCS */ >>> + ring->cycle_state = le32_to_cpu(current_trb->event_cmd.flags) & >>> TRB_CYCLE; >>> + >>> + while (1) { >>> + inc_deq(xhci, ir->event_ring); >>> + erdp_trb = ir->event_ring->dequeue; >>> + /* cycle state transition */ >>> + if ((le32_to_cpu(erdp_trb->event_cmd.flags) & TRB_CYCLE) != >>> + ring->cycle_state) >>> + break; >>> + } >>> + >>> + xhci_update_erst_dequeue(xhci, ir, current_trb, true); >>> +} >> >> Code above is very similar to the existing event ring processing parts >> of xhci_irq() >> and xhci_handle_event() >> >> I'll see if I can refactor the existing event ring processing, >> decouple it from >> event handling so that it could be used by primary and secondary >> interrupters with >> handlers, and this case where we just want to clear the event ring. >> > > Thanks, that makes sense. Will take a look as well. > How about something like the below? Tested this on my set up and everything looks to be working fine. Had to add another param to struct xhci_interrupters to tell the XHCI interrupt handler to say if that particular interrupter wants to skip_events (handling). This way, its something that the class driver utilizing the interrupter will have to tell XHCI sideband. It would allow the user to determine if they want to use the interrupter to actually handle events or not on the proc running Linux. Thanks Wesley Cheng --------------------------------------------------------------------- diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c index 4460fa7e9fab..5bf74c37cbf6 100644 --- a/drivers/usb/host/xhci-mem.c +++ b/drivers/usb/host/xhci-mem.c @@ -1827,7 +1827,7 @@ xhci_remove_interrupter(struct xhci_hcd *xhci, struct xhci_interrupter *ir) tmp &= ERST_SIZE_MASK; writel(tmp, &ir->ir_set->erst_size); - xhci_write_64(xhci, ERST_EHB, &ir->ir_set->erst_dequeue); + xhci_update_erst_dequeue(xhci, ir, ir->event_ring->first_seg->trbs, true); } } @@ -1865,11 +1865,12 @@ void xhci_remove_secondary_interrupter(struct usb_hcd *hcd, struct xhci_interrup if (!ir || !ir->intr_num || ir->intr_num >= xhci->max_interrupters) xhci_dbg(xhci, "Invalid secondary interrupter, can't remove\n"); - /* fixme, should we check xhci->interrupter[intr_num] == ir */ - /* fixme locking */ - spin_lock_irq(&xhci->lock); - + /* + * Cleanup secondary interrupter to ensure there are no pending events. + * This also updates event ring dequeue pointer back to the start. + */ + xhci_skip_sec_intr_events(xhci, ir->event_ring, ir); intr_num = ir->intr_num; xhci_remove_interrupter(xhci, ir); diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index 33806ae966f9..1d69da07ffdd 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -2905,6 +2905,46 @@ static int handle_tx_event(struct xhci_hcd *xhci, return -ENODEV; } +static void xhci_clear_interrupt_pending(struct xhci_hcd *xhci, struct xhci_interrupter *ir) +{ + struct usb_hcd *hcd = xhci_to_hcd(xhci); + + if (!hcd->msi_enabled) { + u32 irq_pending; + irq_pending = readl(&ir->ir_set->irq_pending); + irq_pending |= IMAN_IP; + writel(irq_pending, &ir->ir_set->irq_pending); + } +} + +static void xhci_handle_event_trb(struct xhci_hcd *xhci, + struct xhci_interrupter *ir, union xhci_trb *event) +{ + u32 trb_type; + + trb_type = TRB_FIELD_TO_TYPE(le32_to_cpu(event->event_cmd.flags)); + + switch (trb_type) { + case TRB_COMPLETION: + handle_cmd_completion(xhci, &event->event_cmd); + break; + case TRB_PORT_STATUS: + handle_port_status(xhci, ir, event); + break; + case TRB_TRANSFER: + handle_tx_event(xhci, ir, &event->trans_event); + break; + case TRB_DEV_NOTE: + handle_device_notification(xhci, event); + break; + default: + if (trb_type >= TRB_VENDOR_DEFINED_LOW) + handle_vendor_event(xhci, event, trb_type); + else + xhci_warn(xhci, "ERROR unknown event type %d\n", trb_type); + } +} + /* * This function handles all OS-owned events on the event ring. It may drop * xhci->lock between event processing (e.g. to pass up port status changes). @@ -2914,7 +2954,6 @@ static int handle_tx_event(struct xhci_hcd *xhci, static int xhci_handle_event(struct xhci_hcd *xhci, struct xhci_interrupter *ir) { union xhci_trb *event; - u32 trb_type; /* Event ring hasn't been allocated yet. */ if (!ir || !ir->event_ring || !ir->event_ring->dequeue) { @@ -2935,28 +2974,9 @@ static int xhci_handle_event(struct xhci_hcd *xhci, struct xhci_interrupter *ir) * speculative reads of the event's flags/data below. */ rmb(); - trb_type = TRB_FIELD_TO_TYPE(le32_to_cpu(event->event_cmd.flags)); - /* FIXME: Handle more event types. */ + if (!ir->skip_events) + xhci_handle_event_trb(xhci, ir, event); - switch (trb_type) { - case TRB_COMPLETION: - handle_cmd_completion(xhci, &event->event_cmd); - break; - case TRB_PORT_STATUS: - handle_port_status(xhci, ir, event); - break; - case TRB_TRANSFER: - handle_tx_event(xhci, ir, &event->trans_event); - break; - case TRB_DEV_NOTE: - handle_device_notification(xhci, event); - break; - default: - if (trb_type >= TRB_VENDOR_DEFINED_LOW) - handle_vendor_event(xhci, event, trb_type); - else - xhci_warn(xhci, "ERROR unknown event type %d\n", trb_type); - } /* Any of the above functions may drop and re-acquire the lock, so check * to make sure a watchdog timer didn't mark the host as non-responsive. */ @@ -2980,7 +3000,7 @@ static int xhci_handle_event(struct xhci_hcd *xhci, struct xhci_interrupter *ir) * - When all events have finished * - To avoid "Event Ring Full Error" condition */ -static void xhci_update_erst_dequeue(struct xhci_hcd *xhci, +void xhci_update_erst_dequeue(struct xhci_hcd *xhci, struct xhci_interrupter *ir, union xhci_trb *event_ring_deq, bool clear_ehb) @@ -3013,6 +3033,75 @@ static void xhci_update_erst_dequeue(struct xhci_hcd *xhci, xhci_write_64(xhci, temp_64, &ir->ir_set->erst_dequeue); } +static irqreturn_t xhci_handle_events(struct xhci_hcd *xhci, struct xhci_interrupter *ir) +{ + union xhci_trb *event_ring_deq; + irqreturn_t ret = IRQ_NONE; + int event_loop = 0; + u64 temp_64; + + xhci_clear_interrupt_pending(xhci, ir); + + if (xhci->xhc_state & XHCI_STATE_DYING || + xhci->xhc_state & XHCI_STATE_HALTED) { + xhci_dbg(xhci, "xHCI dying, ignoring interrupt. " + "Shouldn't IRQs be disabled?\n"); + /* Clear the event handler busy flag (RW1C); + * the event ring should be empty. + */ + temp_64 = xhci_read_64(xhci, &ir->ir_set->erst_dequeue); + xhci_write_64(xhci, temp_64 | ERST_EHB, + &ir->ir_set->erst_dequeue); + ret = IRQ_HANDLED; + goto out; + } + + event_ring_deq = ir->event_ring->dequeue; + /* FIXME this should be a delayed service routine + * that clears the EHB. + */ + while (xhci_handle_event(xhci, ir) > 0) { + if (event_loop++ < TRBS_PER_SEGMENT / 2) + continue; + xhci_update_erst_dequeue(xhci, ir, event_ring_deq, false); + event_ring_deq = ir->event_ring->dequeue; + + /* ring is half-full, force isoc trbs to interrupt more often */ + if (xhci->isoc_bei_interval > AVOID_BEI_INTERVAL_MIN) + xhci->isoc_bei_interval = xhci->isoc_bei_interval / 2; + + event_loop = 0; + } + + xhci_update_erst_dequeue(xhci, ir, event_ring_deq, true); + ret = IRQ_HANDLED; + +out: + return ret; + +} + +/* + * Move the event ring dequeue pointer to skip events kept in the secondary + * event ring. This is used to ensure that pending events in the ring are + * acknowledged, so the XHCI HCD can properly enter suspend/resume. The + * secondary ring is typically maintained by an external component. + */ +void xhci_skip_sec_intr_events(struct xhci_hcd *xhci, + struct xhci_ring *ring, struct xhci_interrupter *ir) +{ + union xhci_trb *current_trb; + + /* disable irq, ack pending interrupt and ack all pending events */ + xhci_disable_interrupter(ir); + + current_trb = ir->event_ring->dequeue; + /* read cycle state of the last acked trb to find out CCS */ + ring->cycle_state = le32_to_cpu(current_trb->event_cmd.flags) & TRB_CYCLE; + + xhci_handle_events(xhci, ir); +} + /* * xHCI spec says we can get an interrupt, and if the HC has an error condition, * we might get bad data out of the event ring. Section 4.10.2.7 has a list of @@ -3021,12 +3110,8 @@ static void xhci_update_erst_dequeue(struct xhci_hcd *xhci, irqreturn_t xhci_irq(struct usb_hcd *hcd) { struct xhci_hcd *xhci = hcd_to_xhci(hcd); - union xhci_trb *event_ring_deq; - struct xhci_interrupter *ir; irqreturn_t ret = IRQ_NONE; - u64 temp_64; u32 status; - int event_loop = 0; spin_lock(&xhci->lock); /* Check if the xHC generated the interrupt, or the irq is shared */ @@ -3060,48 +3145,7 @@ irqreturn_t xhci_irq(struct usb_hcd *hcd) status |= STS_EINT; writel(status, &xhci->op_regs->status); - /* This is the handler of the primary interrupter */ - ir = xhci->interrupters[0]; - if (!hcd->msi_enabled) { - u32 irq_pending; - irq_pending = readl(&ir->ir_set->irq_pending); - irq_pending |= IMAN_IP; - writel(irq_pending, &ir->ir_set->irq_pending); - } - - if (xhci->xhc_state & XHCI_STATE_DYING || - xhci->xhc_state & XHCI_STATE_HALTED) { - xhci_dbg(xhci, "xHCI dying, ignoring interrupt. " - "Shouldn't IRQs be disabled?\n"); - /* Clear the event handler busy flag (RW1C); - * the event ring should be empty. - */ - temp_64 = xhci_read_64(xhci, &ir->ir_set->erst_dequeue); - xhci_write_64(xhci, temp_64 | ERST_EHB, - &ir->ir_set->erst_dequeue); - ret = IRQ_HANDLED; - goto out; - } - - event_ring_deq = ir->event_ring->dequeue; - /* FIXME this should be a delayed service routine - * that clears the EHB. - */ - while (xhci_handle_event(xhci, ir) > 0) { - if (event_loop++ < TRBS_PER_SEGMENT / 2) - continue; - xhci_update_erst_dequeue(xhci, ir, event_ring_deq, false); - event_ring_deq = ir->event_ring->dequeue; - - /* ring is half-full, force isoc trbs to interrupt more often */ - if (xhci->isoc_bei_interval > AVOID_BEI_INTERVAL_MIN) - xhci->isoc_bei_interval = xhci->isoc_bei_interval / 2; - - event_loop = 0; - } - - xhci_update_erst_dequeue(xhci, ir, event_ring_deq, true); - ret = IRQ_HANDLED; + ret = xhci_handle_events(xhci, xhci->interrupters[0]); out: spin_unlock(&xhci->lock); diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index 573ca5c4f31a..eb15c63e6775 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -333,7 +333,7 @@ static int xhci_enable_interrupter(struct xhci_interrupter *ir) return 0; } -static int xhci_disable_interrupter(struct xhci_interrupter *ir) +int xhci_disable_interrupter(struct xhci_interrupter *ir) { u32 iman; diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index e98099b960e4..a4126dfbd77a 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -1691,6 +1691,7 @@ struct xhci_interrupter { struct xhci_erst erst; struct xhci_intr_reg __iomem *ir_set; unsigned int intr_num; + bool skip_events; /* For interrupter registers save and restore over suspend/resume */ u32 s3_irq_pending; u32 s3_irq_control; @@ -2093,6 +2094,9 @@ struct xhci_interrupter * xhci_create_secondary_interrupter(struct usb_hcd *hcd, int num_seg); void xhci_remove_secondary_interrupter(struct usb_hcd *hcd, struct xhci_interrupter *ir); +void xhci_skip_sec_intr_events(struct xhci_hcd *xhci, + struct xhci_ring *ring, struct xhci_interrupter *ir); +int xhci_disable_interrupter(struct xhci_interrupter *ir); /* xHCI host controller glue */ typedef void (*xhci_get_quirks_t)(struct device *, struct xhci_hcd *); @@ -2180,6 +2184,10 @@ void inc_deq(struct xhci_hcd *xhci, struct xhci_ring *ring); unsigned int count_trbs(u64 addr, u64 len); int xhci_stop_endpoint_sync(struct xhci_hcd *xhci, struct xhci_virt_ep *ep, int suspend, gfp_t gfp_flags); +void xhci_update_erst_dequeue(struct xhci_hcd *xhci, + struct xhci_interrupter *ir, + union xhci_trb *event_ring_deq, + bool clear_ehb); /* xHCI roothub code */ void xhci_set_link_state(struct xhci_hcd *xhci, struct xhci_port *port,
On 10.1.2024 1.42, Wesley Cheng wrote: > Hi Mathias, > > On 1/8/2024 12:51 PM, Wesley Cheng wrote: >> Hi Mathias, >> >> On 1/4/2024 6:48 AM, Mathias Nyman wrote: >>> On 2.1.2024 23.45, Wesley Cheng wrote: >>>> As part of xHCI bus suspend, the XHCI is halted. However, if there are >>>> pending events in the secondary event ring, it is observed that the xHCI >>>> controller stops responding to further commands upon host or device >>>> initiated bus resume. Iterate through all pending events and update the >>>> dequeue pointer to the beginning of the event ring. >>>> >>>> Signed-off-by: Wesley Cheng <quic_wcheng@quicinc.com> >>> ... >>>> +/* >>>> + * Move the event ring dequeue pointer to skip events kept in the secondary >>>> + * event ring. This is used to ensure that pending events in the ring are >>>> + * acknowledged, so the XHCI HCD can properly enter suspend/resume. The >>>> + * secondary ring is typically maintained by an external component. >>>> + */ >>>> +void xhci_skip_sec_intr_events(struct xhci_hcd *xhci, >>>> + struct xhci_ring *ring, struct xhci_interrupter *ir) >>>> +{ >>>> + union xhci_trb *erdp_trb, *current_trb; >>>> + u64 erdp_reg; >>>> + u32 iman_reg; >>>> + dma_addr_t deq; >>>> + >>>> + /* disable irq, ack pending interrupt and ack all pending events */ >>>> + xhci_disable_interrupter(ir); >>>> + iman_reg = readl_relaxed(&ir->ir_set->irq_pending); >>>> + if (iman_reg & IMAN_IP) >>>> + writel_relaxed(iman_reg, &ir->ir_set->irq_pending); >>>> + >>>> + /* last acked event trb is in erdp reg */ >>>> + erdp_reg = xhci_read_64(xhci, &ir->ir_set->erst_dequeue); >>>> + deq = (dma_addr_t)(erdp_reg & ERST_PTR_MASK); >>>> + if (!deq) { >>>> + xhci_err(xhci, "event ring handling not required\n"); >>>> + return; >>>> + } >>>> + >>>> + erdp_trb = current_trb = ir->event_ring->dequeue; >>>> + /* read cycle state of the last acked trb to find out CCS */ >>>> + ring->cycle_state = le32_to_cpu(current_trb->event_cmd.flags) & TRB_CYCLE; >>>> + >>>> + while (1) { >>>> + inc_deq(xhci, ir->event_ring); >>>> + erdp_trb = ir->event_ring->dequeue; >>>> + /* cycle state transition */ >>>> + if ((le32_to_cpu(erdp_trb->event_cmd.flags) & TRB_CYCLE) != >>>> + ring->cycle_state) >>>> + break; >>>> + } >>>> + >>>> + xhci_update_erst_dequeue(xhci, ir, current_trb, true); >>>> +} >>> >>> Code above is very similar to the existing event ring processing parts of xhci_irq() >>> and xhci_handle_event() >>> >>> I'll see if I can refactor the existing event ring processing, decouple it from >>> event handling so that it could be used by primary and secondary interrupters with >>> handlers, and this case where we just want to clear the event ring. >>> >> >> Thanks, that makes sense. Will take a look as well. >> > > How about something like the below? Tested this on my set up and everything looks to be working fine. Had to add another param to struct xhci_interrupters to tell the XHCI interrupt handler to say if that particular interrupter wants to skip_events (handling). This way, its something that the class driver utilizing the interrupter will have to tell XHCI sideband. It would allow the user to determine if they want to use the interrupter to actually handle events or not on the proc running Linux. > Yes, I have something similar. I'll share it soon, just need to clean it up a bit fist. Thanks Mathias
Hi Mathias, On 1/15/2024 6:01 AM, Mathias Nyman wrote: > On 10.1.2024 1.42, Wesley Cheng wrote: >> Hi Mathias, >> >> On 1/8/2024 12:51 PM, Wesley Cheng wrote: >>> Hi Mathias, >>> >>> On 1/4/2024 6:48 AM, Mathias Nyman wrote: >>>> On 2.1.2024 23.45, Wesley Cheng wrote: >>>>> As part of xHCI bus suspend, the XHCI is halted. However, if there >>>>> are >>>>> pending events in the secondary event ring, it is observed that the >>>>> xHCI >>>>> controller stops responding to further commands upon host or device >>>>> initiated bus resume. Iterate through all pending events and >>>>> update the >>>>> dequeue pointer to the beginning of the event ring. >>>>> >>>>> Signed-off-by: Wesley Cheng <quic_wcheng@quicinc.com> >>>> ... >>>>> +/* >>>>> + * Move the event ring dequeue pointer to skip events kept in the >>>>> secondary >>>>> + * event ring. This is used to ensure that pending events in the >>>>> ring are >>>>> + * acknowledged, so the XHCI HCD can properly enter >>>>> suspend/resume. The >>>>> + * secondary ring is typically maintained by an external component. >>>>> + */ >>>>> +void xhci_skip_sec_intr_events(struct xhci_hcd *xhci, >>>>> + struct xhci_ring *ring, struct xhci_interrupter *ir) >>>>> +{ >>>>> + union xhci_trb *erdp_trb, *current_trb; >>>>> + u64 erdp_reg; >>>>> + u32 iman_reg; >>>>> + dma_addr_t deq; >>>>> + >>>>> + /* disable irq, ack pending interrupt and ack all pending >>>>> events */ >>>>> + xhci_disable_interrupter(ir); >>>>> + iman_reg = readl_relaxed(&ir->ir_set->irq_pending); >>>>> + if (iman_reg & IMAN_IP) >>>>> + writel_relaxed(iman_reg, &ir->ir_set->irq_pending); >>>>> + >>>>> + /* last acked event trb is in erdp reg */ >>>>> + erdp_reg = xhci_read_64(xhci, &ir->ir_set->erst_dequeue); >>>>> + deq = (dma_addr_t)(erdp_reg & ERST_PTR_MASK); >>>>> + if (!deq) { >>>>> + xhci_err(xhci, "event ring handling not required\n"); >>>>> + return; >>>>> + } >>>>> + >>>>> + erdp_trb = current_trb = ir->event_ring->dequeue; >>>>> + /* read cycle state of the last acked trb to find out CCS */ >>>>> + ring->cycle_state = le32_to_cpu(current_trb->event_cmd.flags) >>>>> & TRB_CYCLE; >>>>> + >>>>> + while (1) { >>>>> + inc_deq(xhci, ir->event_ring); >>>>> + erdp_trb = ir->event_ring->dequeue; >>>>> + /* cycle state transition */ >>>>> + if ((le32_to_cpu(erdp_trb->event_cmd.flags) & TRB_CYCLE) != >>>>> + ring->cycle_state) >>>>> + break; >>>>> + } >>>>> + >>>>> + xhci_update_erst_dequeue(xhci, ir, current_trb, true); >>>>> +} >>>> >>>> Code above is very similar to the existing event ring processing >>>> parts of xhci_irq() >>>> and xhci_handle_event() >>>> >>>> I'll see if I can refactor the existing event ring processing, >>>> decouple it from >>>> event handling so that it could be used by primary and secondary >>>> interrupters with >>>> handlers, and this case where we just want to clear the event ring. >>>> >>> >>> Thanks, that makes sense. Will take a look as well. >>> >> >> How about something like the below? Tested this on my set up and >> everything looks to be working fine. Had to add another param to >> struct xhci_interrupters to tell the XHCI interrupt handler to say if >> that particular interrupter wants to skip_events (handling). This >> way, its something that the class driver utilizing the interrupter >> will have to tell XHCI sideband. It would allow the user to determine >> if they want to use the interrupter to actually handle events or not >> on the proc running Linux. >> > > Yes, I have something similar. > I'll share it soon, just need to > clean it up a bit fist. > Sure, no worries. Will test it when its available. Thanks! Thanks Wesley Cheng
Hi Mathias, On 1/16/2024 12:24 PM, Wesley Cheng wrote: > Hi Mathias, > > On 1/15/2024 6:01 AM, Mathias Nyman wrote: >> On 10.1.2024 1.42, Wesley Cheng wrote: >>> Hi Mathias, >>> >>> On 1/8/2024 12:51 PM, Wesley Cheng wrote: >>>> Hi Mathias, >>>> >>>> On 1/4/2024 6:48 AM, Mathias Nyman wrote: >>>>> On 2.1.2024 23.45, Wesley Cheng wrote: >>>>>> As part of xHCI bus suspend, the XHCI is halted. However, if >>>>>> there are >>>>>> pending events in the secondary event ring, it is observed that >>>>>> the xHCI >>>>>> controller stops responding to further commands upon host or device >>>>>> initiated bus resume. Iterate through all pending events and >>>>>> update the >>>>>> dequeue pointer to the beginning of the event ring. >>>>>> >>>>>> Signed-off-by: Wesley Cheng <quic_wcheng@quicinc.com> >>>>> ... >>>>>> +/* >>>>>> + * Move the event ring dequeue pointer to skip events kept in the >>>>>> secondary >>>>>> + * event ring. This is used to ensure that pending events in the >>>>>> ring are >>>>>> + * acknowledged, so the XHCI HCD can properly enter >>>>>> suspend/resume. The >>>>>> + * secondary ring is typically maintained by an external component. >>>>>> + */ >>>>>> +void xhci_skip_sec_intr_events(struct xhci_hcd *xhci, >>>>>> + struct xhci_ring *ring, struct xhci_interrupter *ir) >>>>>> +{ >>>>>> + union xhci_trb *erdp_trb, *current_trb; >>>>>> + u64 erdp_reg; >>>>>> + u32 iman_reg; >>>>>> + dma_addr_t deq; >>>>>> + >>>>>> + /* disable irq, ack pending interrupt and ack all pending >>>>>> events */ >>>>>> + xhci_disable_interrupter(ir); >>>>>> + iman_reg = readl_relaxed(&ir->ir_set->irq_pending); >>>>>> + if (iman_reg & IMAN_IP) >>>>>> + writel_relaxed(iman_reg, &ir->ir_set->irq_pending); >>>>>> + >>>>>> + /* last acked event trb is in erdp reg */ >>>>>> + erdp_reg = xhci_read_64(xhci, &ir->ir_set->erst_dequeue); >>>>>> + deq = (dma_addr_t)(erdp_reg & ERST_PTR_MASK); >>>>>> + if (!deq) { >>>>>> + xhci_err(xhci, "event ring handling not required\n"); >>>>>> + return; >>>>>> + } >>>>>> + >>>>>> + erdp_trb = current_trb = ir->event_ring->dequeue; >>>>>> + /* read cycle state of the last acked trb to find out CCS */ >>>>>> + ring->cycle_state = le32_to_cpu(current_trb->event_cmd.flags) >>>>>> & TRB_CYCLE; >>>>>> + >>>>>> + while (1) { >>>>>> + inc_deq(xhci, ir->event_ring); >>>>>> + erdp_trb = ir->event_ring->dequeue; >>>>>> + /* cycle state transition */ >>>>>> + if ((le32_to_cpu(erdp_trb->event_cmd.flags) & TRB_CYCLE) != >>>>>> + ring->cycle_state) >>>>>> + break; >>>>>> + } >>>>>> + >>>>>> + xhci_update_erst_dequeue(xhci, ir, current_trb, true); >>>>>> +} >>>>> >>>>> Code above is very similar to the existing event ring processing >>>>> parts of xhci_irq() >>>>> and xhci_handle_event() >>>>> >>>>> I'll see if I can refactor the existing event ring processing, >>>>> decouple it from >>>>> event handling so that it could be used by primary and secondary >>>>> interrupters with >>>>> handlers, and this case where we just want to clear the event ring. >>>>> >>>> >>>> Thanks, that makes sense. Will take a look as well. >>>> >>> >>> How about something like the below? Tested this on my set up and >>> everything looks to be working fine. Had to add another param to >>> struct xhci_interrupters to tell the XHCI interrupt handler to say if >>> that particular interrupter wants to skip_events (handling). This >>> way, its something that the class driver utilizing the interrupter >>> will have to tell XHCI sideband. It would allow the user to >>> determine if they want to use the interrupter to actually handle >>> events or not on the proc running Linux. >>> >> >> Yes, I have something similar. >> I'll share it soon, just need to >> clean it up a bit fist. >> > > Sure, no worries. Will test it when its available. Thanks! > Was just wondering if you had the time to clean up the changes? If not, maybe you can provide a patch with whatever you have, and I can try my best to clean it up to your liking? Thanks! Thanks Wesley Cheng
On 26.1.2024 23.13, Wesley Cheng wrote: > Hi Mathias, > > On 1/16/2024 12:24 PM, Wesley Cheng wrote: >> Hi Mathias, >> >> On 1/15/2024 6:01 AM, Mathias Nyman wrote: >>> On 10.1.2024 1.42, Wesley Cheng wrote: >>>> Hi Mathias, >>>> >>>> On 1/8/2024 12:51 PM, Wesley Cheng wrote: >>>>> Hi Mathias, >>>>> >>>>> On 1/4/2024 6:48 AM, Mathias Nyman wrote: >>>>>> On 2.1.2024 23.45, Wesley Cheng wrote: >>>>>>> As part of xHCI bus suspend, the XHCI is halted. However, if there are >>>>>>> pending events in the secondary event ring, it is observed that the xHCI >>>>>>> controller stops responding to further commands upon host or device >>>>>>> initiated bus resume. Iterate through all pending events and update the >>>>>>> dequeue pointer to the beginning of the event ring. >>>>>>> >>>>>>> Signed-off-by: Wesley Cheng <quic_wcheng@quicinc.com> >>>>>> ... >>>>>>> +/* >>>>>>> + * Move the event ring dequeue pointer to skip events kept in the secondary >>>>>>> + * event ring. This is used to ensure that pending events in the ring are >>>>>>> + * acknowledged, so the XHCI HCD can properly enter suspend/resume. The >>>>>>> + * secondary ring is typically maintained by an external component. >>>>>>> + */ >>>>>>> +void xhci_skip_sec_intr_events(struct xhci_hcd *xhci, >>>>>>> + struct xhci_ring *ring, struct xhci_interrupter *ir) >>>>>>> +{ >>>>>>> + union xhci_trb *erdp_trb, *current_trb; >>>>>>> + u64 erdp_reg; >>>>>>> + u32 iman_reg; >>>>>>> + dma_addr_t deq; >>>>>>> + >>>>>>> + /* disable irq, ack pending interrupt and ack all pending events */ >>>>>>> + xhci_disable_interrupter(ir); >>>>>>> + iman_reg = readl_relaxed(&ir->ir_set->irq_pending); >>>>>>> + if (iman_reg & IMAN_IP) >>>>>>> + writel_relaxed(iman_reg, &ir->ir_set->irq_pending); >>>>>>> + >>>>>>> + /* last acked event trb is in erdp reg */ >>>>>>> + erdp_reg = xhci_read_64(xhci, &ir->ir_set->erst_dequeue); >>>>>>> + deq = (dma_addr_t)(erdp_reg & ERST_PTR_MASK); >>>>>>> + if (!deq) { >>>>>>> + xhci_err(xhci, "event ring handling not required\n"); >>>>>>> + return; >>>>>>> + } >>>>>>> + >>>>>>> + erdp_trb = current_trb = ir->event_ring->dequeue; >>>>>>> + /* read cycle state of the last acked trb to find out CCS */ >>>>>>> + ring->cycle_state = le32_to_cpu(current_trb->event_cmd.flags) & TRB_CYCLE; >>>>>>> + >>>>>>> + while (1) { >>>>>>> + inc_deq(xhci, ir->event_ring); >>>>>>> + erdp_trb = ir->event_ring->dequeue; >>>>>>> + /* cycle state transition */ >>>>>>> + if ((le32_to_cpu(erdp_trb->event_cmd.flags) & TRB_CYCLE) != >>>>>>> + ring->cycle_state) >>>>>>> + break; >>>>>>> + } >>>>>>> + >>>>>>> + xhci_update_erst_dequeue(xhci, ir, current_trb, true); >>>>>>> +} >>>>>> >>>>>> Code above is very similar to the existing event ring processing parts of xhci_irq() >>>>>> and xhci_handle_event() >>>>>> >>>>>> I'll see if I can refactor the existing event ring processing, decouple it from >>>>>> event handling so that it could be used by primary and secondary interrupters with >>>>>> handlers, and this case where we just want to clear the event ring. >>>>>> >>>>> >>>>> Thanks, that makes sense. Will take a look as well. >>>>> >>>> >>>> How about something like the below? Tested this on my set up and everything looks to be working fine. Had to add another param to struct xhci_interrupters to tell the XHCI interrupt handler to say if that particular interrupter wants to skip_events (handling). This way, its something that the class driver utilizing the interrupter will have to tell XHCI sideband. It would allow the user to determine if they want to use the interrupter to actually handle events or not on the proc running Linux. >>>> >>> >>> Yes, I have something similar. >>> I'll share it soon, just need to >>> clean it up a bit fist. >>> >> >> Sure, no worries. Will test it when its available. Thanks! >> > > Was just wondering if you had the time to clean up the changes? If not, maybe you can provide a patch with whatever you have, and I can try my best to clean it up to your liking? Thanks! Sure, got stuck fixing other issues. Code is not yet cleaned up, commit messages are not ready etc, but current work is in a fix_eventhandling branch: git://git.kernel.org/pub/scm/linux/kernel/git/mnyman/xhci.git fix_eventhandling https://git.kernel.org/pub/scm/linux/kernel/git/mnyman/xhci.git/log/?h=fix_eventhandling I was in the middle of figuring out when and where the ip_autoclear and interrupt moderation values should be set for secondary interrupters Thanks Mathias
Hi Mathias, On 1/29/2024 7:44 AM, Mathias Nyman wrote: > On 26.1.2024 23.13, Wesley Cheng wrote: >> Hi Mathias, >> >> On 1/16/2024 12:24 PM, Wesley Cheng wrote: >>> Hi Mathias, >>> >>> On 1/15/2024 6:01 AM, Mathias Nyman wrote: >>>> On 10.1.2024 1.42, Wesley Cheng wrote: >>>>> Hi Mathias, >>>>> >>>>> On 1/8/2024 12:51 PM, Wesley Cheng wrote: >>>>>> Hi Mathias, >>>>>> >>>>>> On 1/4/2024 6:48 AM, Mathias Nyman wrote: >>>>>>> On 2.1.2024 23.45, Wesley Cheng wrote: >>>>>>>> As part of xHCI bus suspend, the XHCI is halted. However, if >>>>>>>> there are >>>>>>>> pending events in the secondary event ring, it is observed that >>>>>>>> the xHCI >>>>>>>> controller stops responding to further commands upon host or device >>>>>>>> initiated bus resume. Iterate through all pending events and >>>>>>>> update the >>>>>>>> dequeue pointer to the beginning of the event ring. >>>>>>>> >>>>>>>> Signed-off-by: Wesley Cheng <quic_wcheng@quicinc.com> >>>>>>> ... >>>>>>>> +/* >>>>>>>> + * Move the event ring dequeue pointer to skip events kept in >>>>>>>> the secondary >>>>>>>> + * event ring. This is used to ensure that pending events in >>>>>>>> the ring are >>>>>>>> + * acknowledged, so the XHCI HCD can properly enter >>>>>>>> suspend/resume. The >>>>>>>> + * secondary ring is typically maintained by an external >>>>>>>> component. >>>>>>>> + */ >>>>>>>> +void xhci_skip_sec_intr_events(struct xhci_hcd *xhci, >>>>>>>> + struct xhci_ring *ring, struct xhci_interrupter *ir) >>>>>>>> +{ >>>>>>>> + union xhci_trb *erdp_trb, *current_trb; >>>>>>>> + u64 erdp_reg; >>>>>>>> + u32 iman_reg; >>>>>>>> + dma_addr_t deq; >>>>>>>> + >>>>>>>> + /* disable irq, ack pending interrupt and ack all pending >>>>>>>> events */ >>>>>>>> + xhci_disable_interrupter(ir); >>>>>>>> + iman_reg = readl_relaxed(&ir->ir_set->irq_pending); >>>>>>>> + if (iman_reg & IMAN_IP) >>>>>>>> + writel_relaxed(iman_reg, &ir->ir_set->irq_pending); >>>>>>>> + >>>>>>>> + /* last acked event trb is in erdp reg */ >>>>>>>> + erdp_reg = xhci_read_64(xhci, &ir->ir_set->erst_dequeue); >>>>>>>> + deq = (dma_addr_t)(erdp_reg & ERST_PTR_MASK); >>>>>>>> + if (!deq) { >>>>>>>> + xhci_err(xhci, "event ring handling not required\n"); >>>>>>>> + return; >>>>>>>> + } >>>>>>>> + >>>>>>>> + erdp_trb = current_trb = ir->event_ring->dequeue; >>>>>>>> + /* read cycle state of the last acked trb to find out CCS */ >>>>>>>> + ring->cycle_state = >>>>>>>> le32_to_cpu(current_trb->event_cmd.flags) & TRB_CYCLE; >>>>>>>> + >>>>>>>> + while (1) { >>>>>>>> + inc_deq(xhci, ir->event_ring); >>>>>>>> + erdp_trb = ir->event_ring->dequeue; >>>>>>>> + /* cycle state transition */ >>>>>>>> + if ((le32_to_cpu(erdp_trb->event_cmd.flags) & >>>>>>>> TRB_CYCLE) != >>>>>>>> + ring->cycle_state) >>>>>>>> + break; >>>>>>>> + } >>>>>>>> + >>>>>>>> + xhci_update_erst_dequeue(xhci, ir, current_trb, true); >>>>>>>> +} >>>>>>> >>>>>>> Code above is very similar to the existing event ring processing >>>>>>> parts of xhci_irq() >>>>>>> and xhci_handle_event() >>>>>>> >>>>>>> I'll see if I can refactor the existing event ring processing, >>>>>>> decouple it from >>>>>>> event handling so that it could be used by primary and secondary >>>>>>> interrupters with >>>>>>> handlers, and this case where we just want to clear the event ring. >>>>>>> >>>>>> >>>>>> Thanks, that makes sense. Will take a look as well. >>>>>> >>>>> >>>>> How about something like the below? Tested this on my set up and >>>>> everything looks to be working fine. Had to add another param to >>>>> struct xhci_interrupters to tell the XHCI interrupt handler to say >>>>> if that particular interrupter wants to skip_events (handling). >>>>> This way, its something that the class driver utilizing the >>>>> interrupter will have to tell XHCI sideband. It would allow the >>>>> user to determine if they want to use the interrupter to actually >>>>> handle events or not on the proc running Linux. >>>>> >>>> >>>> Yes, I have something similar. >>>> I'll share it soon, just need to >>>> clean it up a bit fist. >>>> >>> >>> Sure, no worries. Will test it when its available. Thanks! >>> >> >> Was just wondering if you had the time to clean up the changes? If >> not, maybe you can provide a patch with whatever you have, and I can >> try my best to clean it up to your liking? Thanks! > > Sure, got stuck fixing other issues. > No worries, tested the code briefly as is and it is working, with some minor modifications. > Code is not yet cleaned up, commit messages are not ready etc, but > current work is in > a fix_eventhandling branch: > > git://git.kernel.org/pub/scm/linux/kernel/git/mnyman/xhci.git > fix_eventhandling > https://git.kernel.org/pub/scm/linux/kernel/git/mnyman/xhci.git/log/?h=fix_eventhandling > > > I was in the middle of figuring out when and where the ip_autoclear and > interrupt > moderation values should be set for secondary interrupters > I set these currently when the client driver requests for the interrupter, ie xhci_sideband_create_interrupter(). If the client driver wants to actually have the secondary interrupter events handled by the OS then I added a path to call xhci_enable_interrupter() to enable that IRQ line. Likewise, based on XHCI spec Figure 4-22, the IMAN interrupt enable (IE) bit controls basically when IMOD and IP autoclear mechanisms would come into the picture, so I placed these configurations before we set the IE bit. For the most part, if we offload event ring handling to another processor, then IMOD and IE settings would be irrelevant IMO. The only pitfall with this is that it gets a bit cumbersome (although flexible) for the client driver to know what these arguments actually do within the XHCI layer. Working through your changes and will push something soon. Thanks again for sharing the changes! Thanks Wesley Cheng
diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c index 4460fa7e9fab..5bf74c37cbf6 100644 --- a/drivers/usb/host/xhci-mem.c +++ b/drivers/usb/host/xhci-mem.c @@ -1827,7 +1827,7 @@ xhci_remove_interrupter(struct xhci_hcd *xhci, struct xhci_interrupter *ir) tmp &= ERST_SIZE_MASK; writel(tmp, &ir->ir_set->erst_size); - xhci_write_64(xhci, ERST_EHB, &ir->ir_set->erst_dequeue); + xhci_update_erst_dequeue(xhci, ir, ir->event_ring->first_seg->trbs, true); } } @@ -1865,11 +1865,12 @@ void xhci_remove_secondary_interrupter(struct usb_hcd *hcd, struct xhci_interrup if (!ir || !ir->intr_num || ir->intr_num >= xhci->max_interrupters) xhci_dbg(xhci, "Invalid secondary interrupter, can't remove\n"); - /* fixme, should we check xhci->interrupter[intr_num] == ir */ - /* fixme locking */ - spin_lock_irq(&xhci->lock); - + /* + * Cleanup secondary interrupter to ensure there are no pending events. + * This also updates event ring dequeue pointer back to the start. + */ + xhci_skip_sec_intr_events(xhci, ir->event_ring, ir); intr_num = ir->intr_num; xhci_remove_interrupter(xhci, ir); diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index 33806ae966f9..448417c5d5b1 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -2980,7 +2980,7 @@ static int xhci_handle_event(struct xhci_hcd *xhci, struct xhci_interrupter *ir) * - When all events have finished * - To avoid "Event Ring Full Error" condition */ -static void xhci_update_erst_dequeue(struct xhci_hcd *xhci, +void xhci_update_erst_dequeue(struct xhci_hcd *xhci, struct xhci_interrupter *ir, union xhci_trb *event_ring_deq, bool clear_ehb) @@ -3013,6 +3013,50 @@ static void xhci_update_erst_dequeue(struct xhci_hcd *xhci, xhci_write_64(xhci, temp_64, &ir->ir_set->erst_dequeue); } +/* + * Move the event ring dequeue pointer to skip events kept in the secondary + * event ring. This is used to ensure that pending events in the ring are + * acknowledged, so the XHCI HCD can properly enter suspend/resume. The + * secondary ring is typically maintained by an external component. + */ +void xhci_skip_sec_intr_events(struct xhci_hcd *xhci, + struct xhci_ring *ring, struct xhci_interrupter *ir) +{ + union xhci_trb *erdp_trb, *current_trb; + u64 erdp_reg; + u32 iman_reg; + dma_addr_t deq; + + /* disable irq, ack pending interrupt and ack all pending events */ + xhci_disable_interrupter(ir); + iman_reg = readl_relaxed(&ir->ir_set->irq_pending); + if (iman_reg & IMAN_IP) + writel_relaxed(iman_reg, &ir->ir_set->irq_pending); + + /* last acked event trb is in erdp reg */ + erdp_reg = xhci_read_64(xhci, &ir->ir_set->erst_dequeue); + deq = (dma_addr_t)(erdp_reg & ERST_PTR_MASK); + if (!deq) { + xhci_err(xhci, "event ring handling not required\n"); + return; + } + + erdp_trb = current_trb = ir->event_ring->dequeue; + /* read cycle state of the last acked trb to find out CCS */ + ring->cycle_state = le32_to_cpu(current_trb->event_cmd.flags) & TRB_CYCLE; + + while (1) { + inc_deq(xhci, ir->event_ring); + erdp_trb = ir->event_ring->dequeue; + /* cycle state transition */ + if ((le32_to_cpu(erdp_trb->event_cmd.flags) & TRB_CYCLE) != + ring->cycle_state) + break; + } + + xhci_update_erst_dequeue(xhci, ir, current_trb, true); +} + /* * xHCI spec says we can get an interrupt, and if the HC has an error condition, * we might get bad data out of the event ring. Section 4.10.2.7 has a list of diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index 573ca5c4f31a..eb15c63e6775 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -333,7 +333,7 @@ static int xhci_enable_interrupter(struct xhci_interrupter *ir) return 0; } -static int xhci_disable_interrupter(struct xhci_interrupter *ir) +int xhci_disable_interrupter(struct xhci_interrupter *ir) { u32 iman; diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index e98099b960e4..37887c2f0653 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -2093,6 +2093,9 @@ struct xhci_interrupter * xhci_create_secondary_interrupter(struct usb_hcd *hcd, int num_seg); void xhci_remove_secondary_interrupter(struct usb_hcd *hcd, struct xhci_interrupter *ir); +void xhci_skip_sec_intr_events(struct xhci_hcd *xhci, + struct xhci_ring *ring, struct xhci_interrupter *ir); +int xhci_disable_interrupter(struct xhci_interrupter *ir); /* xHCI host controller glue */ typedef void (*xhci_get_quirks_t)(struct device *, struct xhci_hcd *); @@ -2180,6 +2183,10 @@ void inc_deq(struct xhci_hcd *xhci, struct xhci_ring *ring); unsigned int count_trbs(u64 addr, u64 len); int xhci_stop_endpoint_sync(struct xhci_hcd *xhci, struct xhci_virt_ep *ep, int suspend, gfp_t gfp_flags); +void xhci_update_erst_dequeue(struct xhci_hcd *xhci, + struct xhci_interrupter *ir, + union xhci_trb *event_ring_deq, + bool clear_ehb); /* xHCI roothub code */ void xhci_set_link_state(struct xhci_hcd *xhci, struct xhci_port *port,