From nobody Fri Sep 25 23:54:10 2026 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 8B95D442387 for ; Mon, 7 Sep 2026 08:45:57 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=205.220.168.131 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788770758; cv=none; b=TAMDjw8m8kx+uCwTiyH8AxVHQ8TnOdSz/HCMgiGp1gxuWmmzbDFuBnfGo72LdMPAMuF53HQWlCVP1E+j5xydDRqLsuWuMlEHBHAgnjxAbgNd+W93K60sL9knkH7oNh8Owxle/uOHkkmaR0tQ/Vn3MY0OR0y8cXUgoQyLyiweuyg= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788770758; c=relaxed/simple; bh=IJohJZrbFopFvD7sBZflYi9Ha6O9iaOVUlcxWkrDY3w=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=ajtWP0lgpT5FIO1bRbZN2rQgT+Ow2Rzt5VTR82ryA3QUqq6ihzQ9qLZj6y/SrOSANjNZYr4RVw2iida1w2UAxzoP3hsnYAW8cd9C/rTXBffqqzHcPnXfhYVk/yogW8ZrVkPdDbAhV+fifYaCO+K+h+cwboiz6Jcc5D7YhNXaTlk= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=oss.qualcomm.com; spf=pass smtp.mailfrom=oss.qualcomm.com; dkim=pass (2048-bit key) header.d=qualcomm.com header.i=@qualcomm.com header.b=UY7vjuuJ; dkim=pass (2048-bit key) header.d=oss.qualcomm.com header.i=@oss.qualcomm.com header.b=caKLk2+U; arc=none smtp.client-ip=205.220.168.131 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=oss.qualcomm.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=oss.qualcomm.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=qualcomm.com header.i=@qualcomm.com header.b="UY7vjuuJ"; dkim=pass (2048-bit key) header.d=oss.qualcomm.com header.i=@oss.qualcomm.com header.b="caKLk2+U" Received: from pps.filterd (m0279862.ppops.net [127.0.0.1]) by mx0a-0031df01.pphosted.com (8.18.1.11/8.18.1.11) with ESMTP id 68777Cgf2838492 for ; Mon, 7 Sep 2026 08:45:56 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= VpvA1E1OPOXh8tlIuqcAnKYz4rMdYVWJjjeIFHKWD8s=; b=UY7vjuuJkwioeXLo jITbtUz2DEkeEPo3dBjm96Da8GuFUxe5+mj6LRN7+RnDt4ARnupUZkjNnfPthxO3 nZfiPh+c6TZxtH9xojfr6hk5YSeEAN3izNrZhYiWJRLsnIOx8jt7vTMIWg9YZpvJ Q7UEXQtS8we2b1FZ7qoIPbb05wVy50jU9odSFJERRb156iktVnih4T2DRMV0Vn9e gO7n5ZbRMvm/lD1zPY4p4Bdtnlt3BVLzVJrkZNwIKwYG5fybdW6Sq3ypna7cY7EV 8PpBnDdWQrVSo9xnXRejz59JBhXm3/m/axmtWmj4EqjRoMm04QxnAlyw/Zdw58OI lM/52Q== 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 4ggbuep1pb-1 (version=TLSv1.3 cipher=TLS_AES_128_GCM_SHA256 bits=128 verify=NOT) for ; Mon, 07 Sep 2026 08:45:56 +0000 (GMT) Received: by mail-pj1-f71.google.com with SMTP id 98e67ed59e1d1-38dc0eee75dso1048231a91.0 for ; Mon, 07 Sep 2026 01:45:56 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=oss.qualcomm.com; s=google; t=1788770756; x=1789375556; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :content-type:mime-version:subject:date:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=VpvA1E1OPOXh8tlIuqcAnKYz4rMdYVWJjjeIFHKWD8s=; b=caKLk2+Uym0Tivzo5P087Crwkg3Ak5XRD+POzABSiE2GwmRGDac4sI172qTPlWUxiv +3NIhEh2aWKKpT+yxaGgpM0O8Iu5iFU9rWsFMGQy/dNKd4vaym96qryhXXAQpwkdrEEb sq6TCy4mNkR8XI7QiHhXz99X790uJjs5wJGTxcHPjQqoYNmdFwxYbvhU4mZMljGBsQ6N 412F50CUVgEjYjJIuwpRyl6zSXT5OtUqc3mGHDld8WinkGF7LJflWMKu/3C1VDl9dLWw yS1IHdVvDgF/tqfHfUB2qXpGmw2cPMQiN79AJEwE7653kFkUob0eFQhCIMqJ3oqFrGv9 V7gA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1788770756; x=1789375556; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :content-type:mime-version:subject:date:from:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to :content-type; bh=VpvA1E1OPOXh8tlIuqcAnKYz4rMdYVWJjjeIFHKWD8s=; b=XiskGLupcL14B/2mZrwNFX6QTaDum1U6buVN/BrK/rEUEZejN81MZYFQ2Ng7C03w4n NMIBBMo1ig4AW2SvRV6AOT6dhNsA+hOyI8OIWGGu4ugxsah8Dm8hmJvPLoVmn7/HQuWu oXAJn/ZpvL0VINGLcmTUY0+V/I3X7jh3icHSv8KnfoRAb5C9/m4DrCfSm7DjNRJaI0l7 NofBkFgRMyGkePHHh5t0UBPdJpwLLz51qLE+WzzSp+g/rq1vc7E5HNMmfhr3VIzzz7m6 49mewLBgP6VfBC17wbkUBNeY4NP4Nii31N0NIjpUkY/UFAbVK3lHZvx9A/46RXZKb1/x YaxA== X-Forwarded-Encrypted: i=1; AKwUvByrOglCRUMZSYMmCnvykyNYIdQybwrGIp2YKsVVg9u47FaKu5HTyA5rnah4PBcRmdzT8wQsyWezv9BSSR8=@vger.kernel.org X-Gm-Message-State: AFuF++nG144UiCaplxLwo+l75RvJVNCjZYsG3fbrXlNN09RC5aKgu3KF kPTl1/tHIcavdbtXReIW89ETPWNfUjG2+qz47BcPs2xbRPoBJhwCQJN4GEKS5GGh6/V/66DNCww wsJVo9rYo7BneleWlt71O+t7wkiBmtDK+/v+ZUvZIpNfLofwQsFsDQejhD//3P8IRoj4= X-Gm-Gg: AYBFou2kA+BIPDBmCru7aqCXsR1jn6B/9yiYn4is4dwo+vWMwpoCpec8QNpYxu3C3LP ZKb77D/1fZEBMYV3g3uV1AMp6BC146K553BniIH61ndTQ65DJcyLIuUNt9s4FFXktXsMC15FmXc JGJQFykfqv8iQ1alLwvUHzlvYbN8XbFOGNl/SYsm3xb0McDBU+YFVyEUy9YzorUXzU7RjafynBw Tk0WgwFNMCM++64z+Q1ct0GRRXDKVo1NzYbVUjpUq3XoKskH5VmCy45GNNLKX5E+rcSNeFQUwu8 93iNeQC7nQ24QwvHW2H2009yFaOjzZWbQfM19b8OmZg0UfxP8D4yfpO+/wtZxCpaSRMNuZF5xG3 6aV7TI+S6pqppiv5opArLOL1VxFEVH9F47zchzsxHfxe9UZQ= X-Received: by 2002:a17:90b:4a0d:b0:381:bcfb:e710 with SMTP id 98e67ed59e1d1-39b3d7fe6a4mr12704840a91.3.1788770755981; Mon, 07 Sep 2026 01:45:55 -0700 (PDT) X-Received: by 2002:a17:90b:4a0d:b0:381:bcfb:e710 with SMTP id 98e67ed59e1d1-39b3d7fe6a4mr12704799a91.3.1788770755432; Mon, 07 Sep 2026 01:45:55 -0700 (PDT) Received: from hu-zijuhu-lv.qualcomm.com (Global_NAT1.qualcomm.com. [129.46.96.20]) by smtp.gmail.com with ESMTPSA id a92af1059eb24-143243f780csm23622117c88.13.2026.09.07.01.45.54 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 07 Sep 2026 01:45:55 -0700 (PDT) From: Zijun Hu Date: Mon, 07 Sep 2026 01:45:38 -0700 Subject: [PATCH v3 1/4] Bluetooth: btusb: Add recv_intr() hook to btusb_data Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260907-btusb_qcc2072-v3-1-1f65350b03b8@oss.qualcomm.com> References: <20260907-btusb_qcc2072-v3-0-1f65350b03b8@oss.qualcomm.com> In-Reply-To: <20260907-btusb_qcc2072-v3-0-1f65350b03b8@oss.qualcomm.com> To: Marcel Holtmann , Luiz Augusto von Dentz Cc: Zijun Hu , linux-bluetooth@vger.kernel.org, linux-kernel@vger.kernel.org, Zijun Hu X-Mailer: b4 0.15.2 X-Proofpoint-Spam-Details-Enc: AW1haW4tMjYwOTA3MDA5NCBTYWx0ZWRfXwc84VVWhBCvL Cm25/aa9LRXtiOrHbIgM8VLShy2YMC84TYBuW9S1GvqiQWEepM8T7QbNchg+1TxVfHzWxDVau02 1mFCERVrqFSnDUS3MuR0qor1toaGRdCQ1V+aH577GX9+S+iyrpse5TYdLskjMbBpQMrj1g8uVLp b3XAHilnZPpwW7XGt0+1VW1UDTuZVIlps2J2GzAkFVEDZIe1QG2y5gmjTZc7dU9QQoG6j6dA7YI 206OWnR0uqe2QDQGo0OGcpr8t8d92b+XnFRO2OXROAke+7Q/VQZe/x4Y4xGlfeaoE+LcCEA6fQy J1Ivs4a6b4G5N0Fv/rgStmumr7Bzl36dgSLUrS5b85QHRDnlJ4beEJSEQzGo4Esg9bs7wCBJyNG Fx3kwtkihTvtQDkPJ0re76QN8/nQTHA9z80XSmkXxOIzJkacQj7oeHuaOE1kWfM49vNcy8oj2o2 FtysqD5HVqPKt8TaHhw== X-Authority-Analysis: v=2.4 cv=P8AKQCAu c=1 sm=1 tr=0 ts=6a9e79c4 cx=c_pps a=UNFcQwm+pnOIJct1K4W+Mw==:117 a=ouPCqIW2jiPt+lZRy3xVPw==:17 a=IkcTkHD0fZMA:10 a=VdqzKS8jKosA:10 a=s4-Qcg_JpJYA:10 a=VkNPw1HP01LnGYTKEx00:22 a=u7WPNUs3qKkmUXheDGA7:22 a=_K5XuSEh1TEqbUxoQ0s3:22 a=EUspDBNiAAAA:8 a=I7BhAMs4-vxSTl9LnVYA:9 a=QEXdDO2ut3YA:10 a=uKXjsCUrEbL0IQVhDsJ9:22 X-Proofpoint-GUID: 30uOitVLSnfak95hi-qhlwgJULE49YNw X-Proofpoint-ORIG-GUID: 30uOitVLSnfak95hi-qhlwgJULE49YNw X-Proofpoint-Spam-Info: AW1haW4tMjYwOTA3MDA5NCBTYWx0ZWRfX2Xs+1OPxlVan uRY44rIv/d2UMiNFzzG0gF4HL7ol0xOZstx5bEfWZNehXjSL+awAKtlZbE0m0fQw4fPeJshtHyh 987NrclR07ty8RLXvgi7BimUjv2Yrxo= X-Proofpoint-Virus-Version: vendor=baseguard engine=ICAP:2.0.293,Aquarius:18.0.1176,Hydra:6.1.134,FMLib:17.12.100.49 definitions=2026-09-07_02,2026-09-03_01,2025-10-01_01 X-Proofpoint-Spam-Details: rule=outbound_notspam policy=outbound score=0 phishscore=0 impostorscore=0 bulkscore=0 clxscore=1015 adultscore=0 spamscore=0 lowpriorityscore=0 suspectscore=0 priorityscore=1501 malwarescore=0 classifier=typeunknown authscore=0 authtc= authcc= route=outbound adjust=0 reason=mlx scancount=1 engine=8.22.0-2606150000 definitions=main-2609070094 To allow a vendor driver to assemble and handle its non-BT frames from the interrupt endpoint. Used by Qualcomm QCC2072 support. Signed-off-by: Zijun Hu --- drivers/bluetooth/btusb.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c index 61c18402911a..39496336adf2 100644 --- a/drivers/bluetooth/btusb.c +++ b/drivers/bluetooth/btusb.c @@ -1046,16 +1046,17 @@ struct btusb_data { unsigned int air_mode; bool usb_alt6_packet_flow; int isoc_altsetting; int suspend_count; const struct usb_device_id *match_id; =20 int (*recv_event)(struct hci_dev *hdev, struct sk_buff *skb); int (*recv_acl)(struct hci_dev *hdev, struct sk_buff *skb); + int (*recv_intr)(struct btusb_data *data, void *buffer, int count); int (*recv_bulk)(struct btusb_data *data, void *buffer, int count); =20 int (*setup_on_usb)(struct hci_dev *hdev); =20 int (*suspend)(struct hci_dev *hdev); int (*resume)(struct hci_dev *hdev); int (*disconnect)(struct hci_dev *hdev); =20 @@ -1556,17 +1557,17 @@ static void btusb_intr_complete(struct urb *urb) urb->actual_length); =20 if (!test_bit(HCI_RUNNING, &hdev->flags)) return; =20 if (urb->status =3D=3D 0) { hdev->stat.byte_rx +=3D urb->actual_length; =20 - if (btusb_recv_intr(data, urb->transfer_buffer, + if (data->recv_intr(data, urb->transfer_buffer, urb->actual_length) < 0) { bt_dev_err(hdev, "corrupted event packet"); hdev->stat.err_rx++; } } else if (urb->status =3D=3D -ENOENT) { /* Avoid suspend failed when usb_kill_urb */ return; } @@ -2825,17 +2826,17 @@ static int btusb_recv_bulk_intel(struct btusb_data = *data, void *buffer, * same way as the ones received from the interrupt endpoint. * * In H:4 mode there is no interrupt endpoint and every frame on the * bulk endpoint carries an H:4 header, including the ones sent by the * bootloader, so the regular decoding applies. */ if (data->proto =3D=3D BTUSB_PROTO_LEGACY && btintel_test_flag(hdev, INTEL_BOOTLOADER)) - return btusb_recv_intr(data, buffer, count); + return data->recv_intr(data, buffer, count); =20 return btusb_recv_bulk(data, buffer, count); } =20 static int btusb_send_frame_intel(struct hci_dev *hdev, struct sk_buff *sk= b) { struct urb *urb; =20 @@ -4366,16 +4367,17 @@ static int btusb_probe(struct usb_interface *intf, init_usb_anchor(&data->intr_anchor); init_usb_anchor(&data->bulk_anchor); init_usb_anchor(&data->isoc_anchor); init_usb_anchor(&data->diag_anchor); init_usb_anchor(&data->ctrl_anchor); spin_lock_init(&data->rxlock); =20 data->recv_event =3D hci_recv_frame; + data->recv_intr =3D btusb_recv_intr; data->recv_bulk =3D btusb_recv_bulk; =20 if (id->driver_info & BTUSB_INTEL_COMBINED) { /* Allocate extra space for Intel device */ priv_size +=3D sizeof(struct btintel_data); =20 /* Override the rx handlers */ data->recv_event =3D btintel_recv_event; --=20 2.34.1 From nobody Fri Sep 25 23:54:10 2026 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 6D6D9443E53 for ; Mon, 7 Sep 2026 08:45:58 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=205.220.168.131 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788770761; cv=none; b=lAxk66KE4/N7+6xxSRAsnoXPfUBjNlnUcj+k8lZwqZTHsqu0ZKQ6fON+bcUo7N0sQsRi6iz5VVIK/dEPLWkDkN1boS3p7NgRzBg/VvgOkwsANYe5Mn0BqesIe93siLq6YcRut+S+X+fdLRoHcDhp4EG+ePMLLq0y8jYIyOYon7M= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788770761; c=relaxed/simple; bh=bEfX1ELACmc7lyhTNYzShAwd0GgmXQffKLWrTnYgTjc=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=Qz3CFz97wBA6IOLMhGfHCkaPyoTmw/Qa7xlW1/Nvcf25xYj4Btwn9RwXsipog7Zk8Mk+bYWUrVRP/GQFXIbErng7C6hKGr67aLn/tAiB867xVhZa/kvwmN3PARx6/fHhxF9wLNt8BEwIm1NwGBbyOe1scOY5+KTqHN6ZJBHt3jA= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=oss.qualcomm.com; spf=pass smtp.mailfrom=oss.qualcomm.com; dkim=pass (2048-bit key) header.d=qualcomm.com header.i=@qualcomm.com header.b=EnHYvDXc; dkim=pass (2048-bit key) header.d=oss.qualcomm.com header.i=@oss.qualcomm.com header.b=UXa5AJRW; arc=none smtp.client-ip=205.220.168.131 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=oss.qualcomm.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=oss.qualcomm.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=qualcomm.com header.i=@qualcomm.com header.b="EnHYvDXc"; dkim=pass (2048-bit key) header.d=oss.qualcomm.com header.i=@oss.qualcomm.com header.b="UXa5AJRW" Received: from pps.filterd (m0279862.ppops.net [127.0.0.1]) by mx0a-0031df01.pphosted.com (8.18.1.11/8.18.1.11) with ESMTP id 68777DIl2838543 for ; Mon, 7 Sep 2026 08:45:57 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= ueZgO7ADVyLe0V0vHbVULzVjaUIMwuE5zCfC0Y+haxo=; b=EnHYvDXcUX3Poh06 ToI8EHxHZRBdP1cCYotVwRLI4O/kJZWMWRztU493Vudc37ZBuT5SLO0Lmu62HTWd l2QjvVICy3lpPzwN2y35Cj7xUrzKrc9DHXDlhImESK5/ez479ComfTtdjjKLN+6q FMfQaACm4RsVZX+2ZQWrp8lEP//BztSsWx/YtkyPZTLDZpLGGNgPTiFkPsL+UiQa Mrv93H0INXlIJIWoMjWXZmnmsaAXH4G4xKKiC2q0jrwjCLfVp10/3K1TbRW537qK 9jTEqGHkUPaZIWFnRIpqXR/SKvmkfLYDCfHRnwcgNsvUzvwp1H4QYJcpU7u0brWN WN/oRA== Received: from mail-pj1-f69.google.com (mail-pj1-f69.google.com [209.85.216.69]) by mx0a-0031df01.pphosted.com (PPS) with ESMTPS id 4ggbuep1pn-1 (version=TLSv1.3 cipher=TLS_AES_128_GCM_SHA256 bits=128 verify=NOT) for ; Mon, 07 Sep 2026 08:45:57 +0000 (GMT) Received: by mail-pj1-f69.google.com with SMTP id 98e67ed59e1d1-396e5cc1846so960875a91.0 for ; Mon, 07 Sep 2026 01:45:57 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=oss.qualcomm.com; s=google; t=1788770757; x=1789375557; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :content-type:mime-version:subject:date:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=ueZgO7ADVyLe0V0vHbVULzVjaUIMwuE5zCfC0Y+haxo=; b=UXa5AJRWrJcdz8u6GaJUsGtOpI5FVPhFArCBuidG//atbRb/YtNDP9YPkjyX5d2rzx gMVyN0hBZXuyxkA7zWb2PUJpYcRvM4UjOuku6eQpxBRZtS4K6kTVWPCc/v4FRHB/JMn1 bQsY1GHjElKQU8u+DbG1ggwrcWVP2v6gHjWJ0VPo3biMINkYonJJ+GwoBSER/mDzGDI8 0bf1LuzkkgeWRUrVrqT2uZAT7WL3Bc1Acnrd/8OWYAbTk7Kwr1tZ8fQmTF+Ht38frKuA lzL6Hdru8BKmunRuBnG1tUoNpWbXNseyOCiDA57zr2wrW4zoc0oxZD6za7B4zxg4KeEk R+YQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1788770757; x=1789375557; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :content-type:mime-version:subject:date:from:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to :content-type; bh=ueZgO7ADVyLe0V0vHbVULzVjaUIMwuE5zCfC0Y+haxo=; b=N5HqmhyA0MHFRUlE9x7UaekUWfbhbEP3EdAAvopblPCMR0OWAwLseJXbhss6yPhaoI L5I8LVxnbmaRT8PHFbHs/wPo/u7AzjjCq7zM+cipxHiQKgMUeJutBvA2qKd9eysrUGbW CmPrVVjW0DOoHDUva1nr9BSgJ57TIdzVooeaOHFK2G3jnMNt7HWzTQaQNtElrzIeu8NL yh51lEhb0ASFoTJO7Hwi3UEOIS2EcQS20D8McQSjDaXOTHk9Dz/EF/tlvXBy2W/hTiJE NRmWyjyi5FhUOCw9U68zfNCfO/106x5/bd84KMJ9RbwLAFAi2i+SuNThPFzYEiozEh+Q hyRw== X-Forwarded-Encrypted: i=1; AKwUvBzq15CTliozbTh81QVoqTehc7rNFEkkPn8WeAs05zIU9S7qdQodBdDE4S/VO5RX9M1LSQvina9KoNTk4vM=@vger.kernel.org X-Gm-Message-State: AFuF++m/UVxRgoCY7x0NqczDVmZm4Iy+kbv9ZFOKJFS/5jLo6clwJRch n5754pDtnXMlgQh+HRZo1czwF211QcU7gyQzfpgPe5b1nVnW/8+2jPSGjizBoZ9z1++AIfXM2Ap /TY2iNMjpjsY4z5i2iPrK4s4HVLsflGTA5hoLZlXF0ytLO4H40qOtOOLxX0jT6gSnn3D0UhDTCf 4= X-Gm-Gg: AYBFou2F545V5tf3ztYr1a7Q3CTbjX46uA4wDQeSKWEre2Wcucq1/xK9J4i0DnUky+s KJ1YBiekdf3KvIMueAsfasamgLhLpCcq8+70lLxcNt3KgAXFzDduLQYtKm+91JGlFJ7PwTBJGZf eeowRcd83OLNNBZfQ2DPg4lnj2OiTf+9HA6tYwXb9C1xoalxFNBY7WKm/eXRsHOn7EVERJmPmTV mVrYvUVBqATayjSK44pNUvyf/ILNiuAmxp0UMrRaUK8GUgtxSbOVbaHwVyC6enXGrgAQK396ebM MXz4asAywCw20DmY1N9kvTiTqlpvzLttJd8wzOcmCR66XivDm6ny5oDXU/EigWHFz2aFd8qBFba zjeTUWFiuVg5bdtsnnhC3g/5b1TCMlM8TDDj5pzYbC+oZIXc= X-Received: by 2002:a17:90b:3882:b0:396:a47f:d38f with SMTP id 98e67ed59e1d1-39b268ce386mr20895094a91.3.1788770756843; Mon, 07 Sep 2026 01:45:56 -0700 (PDT) X-Received: by 2002:a17:90b:3882:b0:396:a47f:d38f with SMTP id 98e67ed59e1d1-39b268ce386mr20895053a91.3.1788770756355; Mon, 07 Sep 2026 01:45:56 -0700 (PDT) Received: from hu-zijuhu-lv.qualcomm.com (Global_NAT1.qualcomm.com. [129.46.96.20]) by smtp.gmail.com with ESMTPSA id a92af1059eb24-143243f780csm23622117c88.13.2026.09.07.01.45.55 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 07 Sep 2026 01:45:55 -0700 (PDT) From: Zijun Hu Date: Mon, 07 Sep 2026 01:45:39 -0700 Subject: [PATCH v3 2/4] Bluetooth: Add generic support for vendor packets Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260907-btusb_qcc2072-v3-2-1f65350b03b8@oss.qualcomm.com> References: <20260907-btusb_qcc2072-v3-0-1f65350b03b8@oss.qualcomm.com> In-Reply-To: <20260907-btusb_qcc2072-v3-0-1f65350b03b8@oss.qualcomm.com> To: Marcel Holtmann , Luiz Augusto von Dentz Cc: Zijun Hu , linux-bluetooth@vger.kernel.org, linux-kernel@vger.kernel.org, Zijun Hu X-Mailer: b4 0.15.2 X-Proofpoint-Spam-Details-Enc: AW1haW4tMjYwOTA3MDA5NCBTYWx0ZWRfXzPNIoU38uBjG /00PfHzyXAQxJaluIva01iRil/LuJSFnaZCe7vTd7hCrqLz8RrBtUJE678bKDCv6f5XIvMKe6Xv 1d7zBLNTSPvYfOAVW0tIdQLRZhE31c+8Yk+KO+eHNOuMISI/doR/dMsW1LM3Zl53DlYIFubHVNw GBddgXlZUBVWHzL2GhnSlec4VCwwLvlm+pjcNLtqDs1ozM4IHu/FAktYNZTnIjwzQja8Pa9jlD1 sOYoXJfYP1Fgkp4GVpAS8EfmlStXCoMNZEJLNpN/6TlIa8tAB2KE1Gd0mD4kZgCuvEEeDTvuukh 8nHZ6emo/6IrkETt13uo8sDi/PyMmOxxVOmTaXLzQl22z4IjDrp3AEWl07q0jnS8y5O8sBEQubv +XHOcWRhGyRi7weSeyy+HgnZfjTuKf0oHFlVDOZtzxKGFnHr7yrjamNKkicGcKFQ+rWw6lvwULB O1DDMa0/0gFW3E7vWfA== X-Authority-Analysis: v=2.4 cv=P8AKQCAu c=1 sm=1 tr=0 ts=6a9e79c5 cx=c_pps a=vVfyC5vLCtgYJKYeQD43oA==:117 a=ouPCqIW2jiPt+lZRy3xVPw==:17 a=IkcTkHD0fZMA:10 a=VdqzKS8jKosA:10 a=s4-Qcg_JpJYA:10 a=VkNPw1HP01LnGYTKEx00:22 a=u7WPNUs3qKkmUXheDGA7:22 a=_K5XuSEh1TEqbUxoQ0s3:22 a=EUspDBNiAAAA:8 a=rupOQ6kNMCESZWuvRX8A:9 a=QEXdDO2ut3YA:10 a=rl5im9kqc5Lf4LNbBjHf:22 X-Proofpoint-GUID: Of_FVfD4R9RmERK8KRSC9V9sS1dcz_ag X-Proofpoint-ORIG-GUID: Of_FVfD4R9RmERK8KRSC9V9sS1dcz_ag X-Proofpoint-Spam-Info: AW1haW4tMjYwOTA3MDA5NCBTYWx0ZWRfX97Z3zXFyMAC7 5S62md9r+SFph5vFH0aT2YuIKvL2x6k7ZWUwJ12J1uRgPFzWJ0+xAHXetN9ppftanfePCh8tSCL DqGW/HaTlbiaKCfT8Id6bbHOHlqdl7Q= X-Proofpoint-Virus-Version: vendor=baseguard engine=ICAP:2.0.293,Aquarius:18.0.1176,Hydra:6.1.134,FMLib:17.12.100.49 definitions=2026-09-07_02,2026-09-03_01,2025-10-01_01 X-Proofpoint-Spam-Details: rule=outbound_notspam policy=outbound score=0 phishscore=0 impostorscore=0 bulkscore=0 clxscore=1015 adultscore=0 spamscore=0 lowpriorityscore=0 suspectscore=0 priorityscore=1501 malwarescore=0 classifier=typeunknown authscore=0 authtc= authcc= route=outbound adjust=0 reason=mlx scancount=1 engine=8.22.0-2606150000 definitions=main-2609070094 The virtual HCI_VENDOR_PKT (0xff) has been defined but never used by the BT core. Now, there is a requirement to implement it, as follows: For Qualcomm multi-subsystem BT chips, the transport wire carries both BT-HCI and PERI-HCI packets, where PERI is a subsystem in the chip, take the upcoming QCC2072 as an example: Packet type BT-HCI indicator PERI-HCI indicator ----------------------------------------------------------------- CMD (Host -> Controller) 0x01 0x31 ACL Data (bidirectional) 0x02 0x32 EVENT (Controller -> Host) 0x04 0x34 There are also BT ACL packets with vendor-reserved handles found in existing device drivers, as shown below: +--------+--------+---------------------------+ | Vendor | Handle | Purpose | +--------+--------+---------------------------+ | QCOM | 0xEDD | Firmware coredump | | | 0xEDC | Firmware enhanced logging | +--------+--------+---------------------------+ | MTK | 0xFC6F | Firmware coredump | | | 0x05FF | Firmware debug logging 1 | | | 0x05FE | Firmware debug logging 2 | +--------+--------+---------------------------+ | NXP | 0xFFF | Firmware coredump | +--------+--------+---------------------------+ Implement HCI_VENDOR_PKT to generically support such vendor packets, which don't follow BT SIG's vendor-extension framework: - Log them in btmon as they appear on the wire. - Add hdev->recv_vendor_pkt() to handle them in hci_rx_work(). - Add hci_send_vendor_frame() to send them to the device driver. - Allow them to flow over HCI_CHANNEL_USER, nested inside HCI_VENDOR_PKT. Also solve the same issues that the commit below fixes for HCI_EV_VENDOR, which follows BT SIG's vendor-extension framework: commit 0bd606b31d40 ("Bluetooth: hci_event: Introduce handle_ev_vendor() for HCI_EV_VENDOR") Signed-off-by: Zijun Hu --- include/net/bluetooth/hci_core.h | 5 +++++ include/net/bluetooth/hci_mon.h | 2 ++ net/bluetooth/hci_core.c | 45 ++++++++++++++++++++++++++++++++++++= ++++ net/bluetooth/hci_sock.c | 8 +++++++ 4 files changed, 60 insertions(+) diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_c= ore.h index c12cd6873f65..b26004a05368 100644 --- a/include/net/bluetooth/hci_core.h +++ b/include/net/bluetooth/hci_core.h @@ -23,16 +23,17 @@ #ifndef __HCI_CORE_H #define __HCI_CORE_H =20 #include #include #include #include #include +#include =20 #include #include #include #include #include =20 /* HCI priority */ @@ -641,16 +642,18 @@ struct hci_dev { #endif =20 int (*open)(struct hci_dev *hdev); int (*close)(struct hci_dev *hdev); int (*flush)(struct hci_dev *hdev); int (*setup)(struct hci_dev *hdev); int (*shutdown)(struct hci_dev *hdev); int (*send)(struct hci_dev *hdev, struct sk_buff *skb); + /* Receive HCI_VENDOR_PKT */ + void (*recv_vendor_pkt)(struct hci_dev *hdev, struct sk_buff *skb); /* Handle HCI_EV_VENDOR; return true if handled, false otherwise */ bool (*handle_ev_vendor)(struct hci_dev *hdev, struct sk_buff *skb); void (*notify)(struct hci_dev *hdev, unsigned int evt); void (*hw_error)(struct hci_dev *hdev, u8 code); int (*post_init)(struct hci_dev *hdev); int (*set_diag)(struct hci_dev *hdev, bool enable); int (*set_bdaddr)(struct hci_dev *hdev, const bdaddr_t *bdaddr); void (*reset)(struct hci_dev *hdev); @@ -2395,16 +2398,18 @@ static inline int hci_check_conn_params(u16 min, u1= 6 max, u16 latency, } =20 return 0; } =20 int hci_register_cb(struct hci_cb *hcb); int hci_unregister_cb(struct hci_cb *hcb); =20 +int hci_send_vendor_frame(struct hci_dev *hdev, struct iov_iter *iter); + int __hci_cmd_send(struct hci_dev *hdev, u16 opcode, u32 plen, const void *param); =20 int hci_send_cmd(struct hci_dev *hdev, __u16 opcode, __u32 plen, const void *param); void hci_send_acl(struct hci_chan *chan, struct sk_buff *skb, __u16 flags); void hci_send_sco(struct hci_conn *conn, struct sk_buff *skb); void hci_send_iso(struct hci_conn *conn, struct sk_buff *skb); diff --git a/include/net/bluetooth/hci_mon.h b/include/net/bluetooth/hci_mo= n.h index 4b2a0af4ed58..7710688c0d30 100644 --- a/include/net/bluetooth/hci_mon.h +++ b/include/net/bluetooth/hci_mon.h @@ -45,16 +45,18 @@ struct hci_mon_hdr { #define HCI_MON_CTRL_OPEN 14 #define HCI_MON_CTRL_CLOSE 15 #define HCI_MON_CTRL_COMMAND 16 #define HCI_MON_CTRL_EVENT 17 #define HCI_MON_ISO_TX_PKT 18 #define HCI_MON_ISO_RX_PKT 19 #define HCI_MON_DRV_TX_PKT 20 #define HCI_MON_DRV_RX_PKT 21 +#define HCI_MON_VENDOR_TX_PKT 22 +#define HCI_MON_VENDOR_RX_PKT 23 =20 struct hci_mon_new_index { __u8 type; __u8 bus; bdaddr_t bdaddr; char name[8] __nonstring; } __packed; #define HCI_MON_NEW_INDEX_SIZE 16 diff --git a/net/bluetooth/hci_core.c b/net/bluetooth/hci_core.c index 66840df8c020..40a225d41cc3 100644 --- a/net/bluetooth/hci_core.c +++ b/net/bluetooth/hci_core.c @@ -2911,16 +2911,18 @@ int hci_recv_frame(struct hci_dev *hdev, struct sk_= buff *skb) type =3D=3D PA_LINK) hci_skb_pkt_type(skb) =3D HCI_ISODATA_PKT; } break; case HCI_SCODATA_PKT: break; case HCI_ISODATA_PKT: break; + case HCI_VENDOR_PKT: + break; case HCI_DRV_PKT: break; default: kfree_skb(skb); return -EINVAL; } =20 /* Incoming skb */ @@ -3047,16 +3049,51 @@ static int hci_send_frame(struct hci_dev *hdev, str= uct sk_buff *skb) =20 static int hci_send_conn_frame(struct hci_dev *hdev, struct hci_conn *conn, struct sk_buff *skb) { hci_conn_tx_queue(conn, skb); return hci_send_frame(hdev, skb); } =20 +/** + * hci_send_vendor_frame - Send an HCI_VENDOR_PKT frame to the HCI driver + * @hdev: The HCI device + * @iter: iov_iter carrying the frame + * + * Return: 0 on success, or a negative errno on failure. + */ +int hci_send_vendor_frame(struct hci_dev *hdev, struct iov_iter *iter) +{ + struct sk_buff *skb; + unsigned int len; + + if (WARN_ON(!iov_iter_is_kvec(iter))) + return -EINVAL; + + /* Vendor frames are opaque, the caller guarantees the size. */ + len =3D (unsigned int)iov_iter_count(iter); + if (!len) + return -EINVAL; + + skb =3D bt_skb_alloc(len, GFP_KERNEL); + if (!skb) + return -ENOMEM; + + if (!copy_from_iter_full(skb_put(skb, len), len, iter)) { + kfree_skb(skb); + return -EFAULT; + } + + hci_skb_pkt_type(skb) =3D HCI_VENDOR_PKT; + + return hci_send_frame(hdev, skb); +} +EXPORT_SYMBOL(hci_send_vendor_frame); + /* Send HCI command */ int hci_send_cmd(struct hci_dev *hdev, __u16 opcode, __u32 plen, const void *param) { struct sk_buff *skb; =20 BT_DBG("%s opcode 0x%4.4x plen %d", hdev->name, opcode, plen); =20 @@ -4051,16 +4088,24 @@ static void hci_rx_work(struct work_struct *work) hci_scodata_packet(hdev, skb); break; =20 case HCI_ISODATA_PKT: BT_DBG("%s ISO data packet", hdev->name); hci_isodata_packet(hdev, skb); break; =20 + case HCI_VENDOR_PKT: + BT_DBG("%s Vendor packet", hdev->name); + if (hdev->recv_vendor_pkt) + hdev->recv_vendor_pkt(hdev, skb); + else + kfree_skb(skb); + break; + default: kfree_skb(skb); break; } } } =20 static int hci_send_cmd_sync(struct hci_dev *hdev, struct sk_buff *skb) diff --git a/net/bluetooth/hci_sock.c b/net/bluetooth/hci_sock.c index 070ca388f9ac..406b70ecaf33 100644 --- a/net/bluetooth/hci_sock.c +++ b/net/bluetooth/hci_sock.c @@ -228,16 +228,17 @@ void hci_send_to_sock(struct hci_dev *hdev, struct sk= _buff *skb) continue; } else if (hci_pi(sk)->channel =3D=3D HCI_CHANNEL_USER) { if (!bt_cb(skb)->incoming) continue; if (hci_skb_pkt_type(skb) !=3D HCI_EVENT_PKT && hci_skb_pkt_type(skb) !=3D HCI_ACLDATA_PKT && hci_skb_pkt_type(skb) !=3D HCI_SCODATA_PKT && hci_skb_pkt_type(skb) !=3D HCI_ISODATA_PKT && + hci_skb_pkt_type(skb) !=3D HCI_VENDOR_PKT && hci_skb_pkt_type(skb) !=3D HCI_DRV_PKT) continue; } else { /* Don't send frame to other channel types */ continue; } =20 if (!skb_copy) { @@ -385,16 +386,22 @@ void hci_send_to_monitor(struct hci_dev *hdev, struct= sk_buff *skb) opcode =3D cpu_to_le16(HCI_MON_SCO_TX_PKT); break; case HCI_ISODATA_PKT: if (bt_cb(skb)->incoming) opcode =3D cpu_to_le16(HCI_MON_ISO_RX_PKT); else opcode =3D cpu_to_le16(HCI_MON_ISO_TX_PKT); break; + case HCI_VENDOR_PKT: + if (bt_cb(skb)->incoming) + opcode =3D cpu_to_le16(HCI_MON_VENDOR_RX_PKT); + else + opcode =3D cpu_to_le16(HCI_MON_VENDOR_TX_PKT); + break; case HCI_DRV_PKT: if (bt_cb(skb)->incoming) opcode =3D cpu_to_le16(HCI_MON_DRV_RX_PKT); else opcode =3D cpu_to_le16(HCI_MON_DRV_TX_PKT); break; case HCI_DIAG_PKT: opcode =3D cpu_to_le16(HCI_MON_VENDOR_DIAG); @@ -1863,16 +1870,17 @@ static int hci_sock_sendmsg(struct socket *sock, st= ruct msghdr *msg, * since that gets enforced when binding the socket. * * However check that the packet type is valid. */ if (hci_skb_pkt_type(skb) !=3D HCI_COMMAND_PKT && hci_skb_pkt_type(skb) !=3D HCI_ACLDATA_PKT && hci_skb_pkt_type(skb) !=3D HCI_SCODATA_PKT && hci_skb_pkt_type(skb) !=3D HCI_ISODATA_PKT && + hci_skb_pkt_type(skb) !=3D HCI_VENDOR_PKT && hci_skb_pkt_type(skb) !=3D HCI_DRV_PKT) { err =3D -EINVAL; goto drop; } =20 skb_queue_tail(&hdev->raw_q, skb); queue_work(hdev->workqueue, &hdev->tx_work); } else if (hci_skb_pkt_type(skb) =3D=3D HCI_COMMAND_PKT) { --=20 2.34.1 From nobody Fri Sep 25 23:54:10 2026 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 DF83F446C0C for ; Mon, 7 Sep 2026 08:45:59 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=205.220.168.131 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788770761; cv=none; b=t+EeLuh3WLZjd3qQkI/CPHm9biOcnbFWzHpcguzjZVV8bCJpL1U6+lo814k5r1vp0FAT9weD24tntPCFE5akTQueg3NwR/3HGDqxLK7Dj12a1gWIwmriskgsJZM3kJIQwgLn/3+jPvyHIsnmUXDO65vNmtuPchhw/4YUAdWm8+E= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788770761; c=relaxed/simple; bh=3QuQQakF3Zi22G3iYl7aYfWmFHhSS6FeB684lImThr8=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=UHzlv34/SC7JAxDhQ4ZRv0LkcN/aK1AmluaTvCHVYODFi1cVDnFc7m63GoijAoAR8YN5/OJgX6JUr6KQZtl5SJiEkw5VqUdKGfsGwLHiuT04AduNfYbxoWsrMA4OA+HLTjaHK0puSgLOIWJGEaTGUpNyF56THtCgXXc2HjOnFZE= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=oss.qualcomm.com; spf=pass smtp.mailfrom=oss.qualcomm.com; dkim=pass (2048-bit key) header.d=qualcomm.com header.i=@qualcomm.com header.b=FvMlEuna; dkim=pass (2048-bit key) header.d=oss.qualcomm.com header.i=@oss.qualcomm.com header.b=W7Avl9Ee; arc=none smtp.client-ip=205.220.168.131 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=oss.qualcomm.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=oss.qualcomm.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=qualcomm.com header.i=@qualcomm.com header.b="FvMlEuna"; dkim=pass (2048-bit key) header.d=oss.qualcomm.com header.i=@oss.qualcomm.com header.b="W7Avl9Ee" Received: from pps.filterd (m0279862.ppops.net [127.0.0.1]) by mx0a-0031df01.pphosted.com (8.18.1.11/8.18.1.11) with ESMTP id 68777CYj2838509 for ; Mon, 7 Sep 2026 08:45:59 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= +Brs4thZkb+ADUJ9KGOCW0Dja/RM97Wj6zvjTE185qk=; b=FvMlEunaU7Qa1/ad n8TQ150Cpe4L9m7E94E6cR0IW9BqaL2AvWiKfd8EiWoLzTMDP1FNLxmkCEhp6zRu 6sc23q35brr5mn5luYp9AezkVST9w60CfcfCK7mlrA4bxHuGYOwx8EhyaNNVjNEt DQ+IXsjtyjnI/9JFSL41OsofUdITzxWlS70eakjqiri9pzvEW2lUnd4yyCUtAPOT YFlXzmBEcqNzUUKq3U47GcKuNCvl0bm9OmPxMTRtXcV4XXgIyQ+cQpILVwdAU/Kv 3bFHC+Povb0uU4Bip7RB4qJDRA990i2UlmtFcWrbGdeV1cTRNpSRvSBlns8wJ/JO kxRpjA== Received: from mail-pj1-f69.google.com (mail-pj1-f69.google.com [209.85.216.69]) by mx0a-0031df01.pphosted.com (PPS) with ESMTPS id 4ggbuep1pt-1 (version=TLSv1.3 cipher=TLS_AES_128_GCM_SHA256 bits=128 verify=NOT) for ; Mon, 07 Sep 2026 08:45:58 +0000 (GMT) Received: by mail-pj1-f69.google.com with SMTP id 98e67ed59e1d1-39924189378so1094972a91.1 for ; Mon, 07 Sep 2026 01:45:58 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=oss.qualcomm.com; s=google; t=1788770758; x=1789375558; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :content-type:mime-version:subject:date:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=+Brs4thZkb+ADUJ9KGOCW0Dja/RM97Wj6zvjTE185qk=; b=W7Avl9EeiUE9t9CWvsdSRJ9haJJ4PS7QpTLNDuzEiuaMjUDv+aKXI9JmbxDiN4/pp9 hik6MgsGy4Bsp4LVHbx/1T+NQA1fYzfcFv2gZ5hIgB7Kvcn8fjQzPwWGxek7V3x66xJv ZMAe1l/RSsTPIJOXh1ukCX+isGaT2VcAzk+ZJOOEz4u8o2+tI+uia68cilVqDo49jI0m LfAKuL/yFuNDMFrj78aMgvJPzbzwJIZwGMjqRtROOnDck03Pclsef30oTJR28DanwErv iikq8KxUHgDrhJ1rqYYpHvFh2EBkVsZ4bQUCkQg9ANHEBK+pstTQ2r4PrFIavzf2/dxh pEAQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1788770758; x=1789375558; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :content-type:mime-version:subject:date:from:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to :content-type; bh=+Brs4thZkb+ADUJ9KGOCW0Dja/RM97Wj6zvjTE185qk=; b=i2ZhkTbZwckvr+r9nHlUlkqHL+68SZSPGoCvYe35kl1aLCd//m/V2wYzRJ9TyNZSZR KGAiVoyLufAiNUO/15tbGtcvaI7PVMK7eAIm+x1NRoY7vxaWtMyeKEcNAfVCIi88wYHq hU0kd/ydDO1oXaU6SmaYAY0l21Yl7XT/Pd/19FCj8APrQRuMJYZnaWqyyYbilznoUnb2 2AelU5JHsgRi7k5GiC22zStywbz7kBVEW7t1xScHuuEKq8hlZdKotP0WS8d1ZS3Ovg2W rmOKA2OdPJZOdhQfT8C0wUJ6SH+XgoUSk1EZf9LvJeHIZ6TpffvhKUdcYVuU+1hv1BWX PSRg== X-Forwarded-Encrypted: i=1; AKwUvBzKCVJqn/OmpnS2QA7SKGbWGqgp2Uk6dVmVB1MczHoxOeQ2PFSvr4WaugmqABDIp2iGuZpyKZDabLaKCu4=@vger.kernel.org X-Gm-Message-State: AFuF++ngeWe6qxnrAosFKSMry197j80V5fZctMif1IU4scHzQEjl6GZd FAd0WjJkQVOeqV+N+osJkOQnYDK4eR4VshF15rfL75jW0TCEepeSw8MyQDvwTUTWKYy6qtV/UqQ T33iLK7YdTvk+bw0JbNnyUrA4vL973DasyqVm5miDmasWYkJIxLO2yCyf04AiEpwOCes= X-Gm-Gg: AYBFou1TSRrndn1YeBvwX5+RWHO2vvfk6U+u7Nho9NVCEXxToPjqeAGKF8CM6p7m+Dt u5CgBg6ke6Ki1HocNcVZ4L5wTTaDdwLLLYC8LCbPfT59GG0Gogoa+vYNWwkpHLzc9eSTF+zzhQL ELok+rOK6bRDN3+n539JNoxV7HTqOLiDZ31dIK/CzjXM8YtfvL5FD0TA+rGyQUYR8ub0dyDsrd6 /udwnyR5ThYFNRsGxhxkF6QkD6ewZota0ac8ryGKqSFp9k3w67wtbFztxn0iKM9wMwBvQ9weVaf kin0oDuWEsyopDsUW1giG1fZ5+AHkbSB19g0mdFKVcnqzdkB9LZRBvxv6Qigiy6msaBl50VstdV CQa1ZuSwmZXLOfK2S/YDHgPzPWu1tHX6nlyA9gdjrUKnrWm8= X-Received: by 2002:a17:90a:2cc5:b0:39b:64e9:2510 with SMTP id 98e67ed59e1d1-39b64e9607amr3833185a91.4.1788770758045; Mon, 07 Sep 2026 01:45:58 -0700 (PDT) X-Received: by 2002:a17:90a:2cc5:b0:39b:64e9:2510 with SMTP id 98e67ed59e1d1-39b64e9607amr3833159a91.4.1788770757610; Mon, 07 Sep 2026 01:45:57 -0700 (PDT) Received: from hu-zijuhu-lv.qualcomm.com (Global_NAT1.qualcomm.com. [129.46.96.20]) by smtp.gmail.com with ESMTPSA id a92af1059eb24-143243f780csm23622117c88.13.2026.09.07.01.45.56 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 07 Sep 2026 01:45:56 -0700 (PDT) From: Zijun Hu Date: Mon, 07 Sep 2026 01:45:40 -0700 Subject: [PATCH v3 3/4] Bluetooth: btusb: Build the driver from multiple source files Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260907-btusb_qcc2072-v3-3-1f65350b03b8@oss.qualcomm.com> References: <20260907-btusb_qcc2072-v3-0-1f65350b03b8@oss.qualcomm.com> In-Reply-To: <20260907-btusb_qcc2072-v3-0-1f65350b03b8@oss.qualcomm.com> To: Marcel Holtmann , Luiz Augusto von Dentz Cc: Zijun Hu , linux-bluetooth@vger.kernel.org, linux-kernel@vger.kernel.org, Zijun Hu X-Mailer: b4 0.15.2 X-Proofpoint-Spam-Details-Enc: AW1haW4tMjYwOTA3MDA5NCBTYWx0ZWRfXxepZ1SigiRtw SUdXDmXajwPYMTo13aa3Oz96QrMto7WrD3f6bKVja7Q6t8iE6yHfpkTmREbbmp2QUkxgUzpC76v DxVSOgFUIHDqOi/In108JxF8ti8FH3zLFa8WTJgZOnXGK5juhKediYcBQMITN4kPGsghQXYIGh6 SiVcQJsK4ngt8C2hbl1XZSYID4uFzy73LOteiPIoIpP+BbtqHNd6HnV6L3QhQWs2RwUeyFxsgsj iqqzgu8AU51ip5G1+HQ4hCC3IylVwOTUOs/N+cu8Ruzy8iG7pQaIZgZwMMzX6w9WVu1xe4RlTVB E53eU1Dl7Th3SQYX4rzgn/vQbc14gHJr+gm+TVEPTH3teV+LdmGcPL8ODf9rU8Apy26iMRmq4+M ZTpDa9QLwhHZqpsb5x6TZ2dIHESHfJtx+hMC3IhmQda2peOeiorkNIHXzizMfeTpSNnZNC/lRyi Hear/sCtr5J81gXphDQ== X-Authority-Analysis: v=2.4 cv=P8AKQCAu c=1 sm=1 tr=0 ts=6a9e79c6 cx=c_pps a=vVfyC5vLCtgYJKYeQD43oA==:117 a=ouPCqIW2jiPt+lZRy3xVPw==:17 a=IkcTkHD0fZMA:10 a=VdqzKS8jKosA:10 a=s4-Qcg_JpJYA:10 a=VkNPw1HP01LnGYTKEx00:22 a=u7WPNUs3qKkmUXheDGA7:22 a=_K5XuSEh1TEqbUxoQ0s3:22 a=EUspDBNiAAAA:8 a=4J4qWk0Wo-fRNhQ-KTgA:9 a=QEXdDO2ut3YA:10 a=rl5im9kqc5Lf4LNbBjHf:22 X-Proofpoint-GUID: 0hUMIfWiBBBhxzRBWJ7cbl8VNLWrCKlj X-Proofpoint-ORIG-GUID: 0hUMIfWiBBBhxzRBWJ7cbl8VNLWrCKlj X-Proofpoint-Spam-Info: AW1haW4tMjYwOTA3MDA5NCBTYWx0ZWRfXyxzndj+A1BTt PInvrbJOjAVyMz9pcdGWP8FfLX7TXjqitKeEZjoYaN7Lsz4hBm1q32at2JwrMVopib9ZhmR62OA iFEHmafgwAKmmL27aqmtxZruacpZ0pE= X-Proofpoint-Virus-Version: vendor=baseguard engine=ICAP:2.0.293,Aquarius:18.0.1176,Hydra:6.1.134,FMLib:17.12.100.49 definitions=2026-09-07_02,2026-09-03_01,2025-10-01_01 X-Proofpoint-Spam-Details: rule=outbound_notspam policy=outbound score=0 phishscore=0 impostorscore=0 bulkscore=0 clxscore=1015 adultscore=0 spamscore=0 lowpriorityscore=0 suspectscore=0 priorityscore=1501 malwarescore=0 classifier=typeunknown authscore=0 authtc= authcc= route=outbound adjust=0 reason=mlx scancount=1 engine=8.22.0-2606150000 definitions=main-2609070094 Allow the driver to include vendor USB-transport-specific source files by: - renaming btusb.c to btusb_main.c - making btusb.o a composite object Used by Qualcomm QCC2072 support. Signed-off-by: Zijun Hu --- drivers/bluetooth/Makefile | 2 ++ drivers/bluetooth/{btusb.c =3D> btusb_main.c} | 0 2 files changed, 2 insertions(+) diff --git a/drivers/bluetooth/Makefile b/drivers/bluetooth/Makefile index e6b1c1180d1d..8b436c6de8b7 100644 --- a/drivers/bluetooth/Makefile +++ b/drivers/bluetooth/Makefile @@ -46,9 +46,11 @@ hci_uart-$(CONFIG_BT_HCIUART_3WIRE) +=3D hci_h5.o hci_uart-$(CONFIG_BT_HCIUART_INTEL) +=3D hci_intel.o hci_uart-$(CONFIG_BT_HCIUART_BCM) +=3D hci_bcm.o hci_uart-$(CONFIG_BT_HCIUART_QCA) +=3D hci_qca.o hci_uart-$(CONFIG_BT_HCIUART_AG6XX) +=3D hci_ag6xx.o hci_uart-$(CONFIG_BT_HCIUART_MRVL) +=3D hci_mrvl.o hci_uart-$(CONFIG_BT_HCIUART_AML) +=3D hci_aml.o hci_uart-objs :=3D $(hci_uart-y) =20 +btusb-y :=3D btusb_main.o + CONTEXT_ANALYSIS :=3D y diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb_main.c similarity index 100% rename from drivers/bluetooth/btusb.c rename to drivers/bluetooth/btusb_main.c --=20 2.34.1 From nobody Fri Sep 25 23:54:10 2026 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 6A3A4448BA0 for ; Mon, 7 Sep 2026 08:46:04 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=205.220.168.131 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788770770; cv=none; b=udzQb9Q8PvlgvxNYTjebFTgtjhPP9jYZ6wHEZxOMtvqAkNme+jx30lxRHieaVm49pQpC/WIlMeyTKH9gc38jfsfLRJz57hZf2P8aASfWYDnxM28y/nFOHd9nbUc7v9Q0QkbAy2e5bYdqS51/Y6+0/JRa/+SDsOJotGRsEK7/+y8= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788770770; c=relaxed/simple; bh=UgVbOEGei+ukguszjsLGV1QUBvbC8FuKHtvkIp1lhEE=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=DeN0//HWfJn9EJ0vNguvDNULwyMDwa3kVtvi67kxMVxRevlFS0Xt7X910gDj6Yvlp6BZaa8gOI1i8acAI7/RRY/w4tu92VaC4Cbj4C7Jv5CDpxh+wBdDQ47O5F2Bx6MMf7MyuTOtMQLWgHTM3vev9JRR2dnndrvs7cIklXuRdTk= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=oss.qualcomm.com; spf=pass smtp.mailfrom=oss.qualcomm.com; dkim=pass (2048-bit key) header.d=qualcomm.com header.i=@qualcomm.com header.b=BCJvldeV; dkim=pass (2048-bit key) header.d=oss.qualcomm.com header.i=@oss.qualcomm.com header.b=Y1u8zkuz; arc=none smtp.client-ip=205.220.168.131 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=oss.qualcomm.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=oss.qualcomm.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=qualcomm.com header.i=@qualcomm.com header.b="BCJvldeV"; dkim=pass (2048-bit key) header.d=oss.qualcomm.com header.i=@oss.qualcomm.com header.b="Y1u8zkuz" Received: from pps.filterd (m0279865.ppops.net [127.0.0.1]) by mx0a-0031df01.pphosted.com (8.18.1.11/8.18.1.11) with ESMTP id 68777JIU4161130 for ; Mon, 7 Sep 2026 08:46:03 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= pY81MCK2bQseGd+yVMdiI8vD3KdhcQlDJ+cOoNxQLFU=; b=BCJvldeV9t+XnsiM Q9HhKjfqlefzl+dDoPrnxCf3DXeuKqYCMgpeDhJ73NOVN2Q/tGeaZ/bZ/sWbot3c DcaG0ABCtgmxbO+3htZo5lwzTch9fGR6uVp3XS8UcGYy544Mmxo5+tg4TnxR1bg9 aVPpIXWrY/89YmevWWnxQcchOCDQ0nIAjQ1Qxtcm0/cvEcsdTqDs4CCxeUfgRbjN 5sWSQUcLPjOFg2v0xcSG+1EWOMRqJqiZg+qxPp7QdT+dRq7FeLYA02PcnFvYJd+s eBe9ptIg9x8rrJzZ2GWaB8WI6x06lrun/dY9lofAtN1hx6CQnsMoK1Lyo8xXYaiK AsrTtg== Received: from mail-pj1-f69.google.com (mail-pj1-f69.google.com [209.85.216.69]) by mx0a-0031df01.pphosted.com (PPS) with ESMTPS id 4ghfwjhswg-1 (version=TLSv1.3 cipher=TLS_AES_128_GCM_SHA256 bits=128 verify=NOT) for ; Mon, 07 Sep 2026 08:46:02 +0000 (GMT) Received: by mail-pj1-f69.google.com with SMTP id 98e67ed59e1d1-396e5cc1846so960884a91.0 for ; Mon, 07 Sep 2026 01:46:02 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=oss.qualcomm.com; s=google; t=1788770762; x=1789375562; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :content-type:mime-version:subject:date:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=pY81MCK2bQseGd+yVMdiI8vD3KdhcQlDJ+cOoNxQLFU=; b=Y1u8zkuzeKW6PBw6v1AlehYsF8ClmjeCK3wbtM1nSLf1MjaSkF9lMfXJ6x3jHKN/sT VnpULe48Scroj/zzhlgkTVArL0TdfrQKcZqCX//xVLKulgWN3T93zbSFLpM9f7wP2M/V kiOwAItk3hKsY2WWA/zb4YsmvGgwjhtnoyEmgMC08Ifcmq8TVCpUP4PAnrk6nzMisJaP w2isvLMliw7N/GV1LQmSVbU+pqMapPqVmKYjdACzT2Obx0lQeIT7ldHT2wgeuPXrb+7a F2uQpSEXbN26XOyVvHxpEGt1ki/+hSL/vHC8t/goFqhQ5LKF9lhJcWgEURpL4EGn0LiF eckQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1788770762; x=1789375562; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :content-type:mime-version:subject:date:from:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to :content-type; bh=pY81MCK2bQseGd+yVMdiI8vD3KdhcQlDJ+cOoNxQLFU=; b=Ap+v37ecfauEyjFK3TFlfTAzko7ITtiS8pVT1erDY6ZASUBGJo6v4Xkjro1QdXa+H5 vKFCnGrm5SZn0nKmIIlwjfQrI0ceAH7J7lBopWXs0RRASq3d3lTgXZbiC6SA9bydm80Y 4920BXko7T0y7/6tzXMOLu/RLhXqLfW+RQnT8hCy+kJWjdHV9CPyjZ2A+difYs6occ5G 3Ss6T84grRPo+e1TqBx7aLczRtR30ui20mRLWlVj5h8jZWTMHlF/Gr2e5nK7JIGx4lQX OHLykBM2Mcj8tE7DND003VoaBRl9rlGVTOIeoVA+sC5SDwp7nIzaD5VzpfGkmECRwXAq AJzA== X-Forwarded-Encrypted: i=1; AKwUvBzhNbwGj2u6vpgiUlxXzv+KJqIuPVNlWXjFMBvIHOe0hJdQGwyMSi5zDLkpcUCaIVeioBT/SPbCTsWzM88=@vger.kernel.org X-Gm-Message-State: AFuF++kVZzCljy30PhnwPPRl93XscW/ikmVjRtm6p1LojXznvx+iprVJ WnldFNnEO1jm9CtZBEBZG88LJdzO6UMyfX4zvmeuaudwykdtFAS8t8F1/y6YMcKBGG2pK1FhSzW ykhCJWWBn0hGWBkXR+KJja937oC2dA+pzVtUv9MjeoU0KF3Hm+UoRl2wdcIznnvhjz+8= X-Gm-Gg: AYBFou2e4j4MWyi2+Ml32FoN78+QceArntrRbyiizGVjBmyiS0gRIEjbLt0SXfQv8Aj TE+oqB1OquhSnCVOCKOYAgE1P7HhgQXSkHX3C3k9TH2g1QFYbcUFTGYu4L6aqm/vdDVhGXBRgbK dy/HzRLN9WsB0y4GXeSjscon6kT3FsN/DLsv2uA9kgrlw2ly/26wGgI7SCNfYaQktMdJ9fyblki +zUVg2RxwLDNqckWyVxM5Bl2qwdIfy8/M9vyDGuyEzxWoQIL+fkoudSS7aoL6GOy6yEfZlgZgAd s65c+JW+wMGYJixf5TUFxzZk8AxaFuUyY1ooZL2TPIACV1qnR2X7+/jRhELZ9/Y/akfXaM0oFOF Fgk9yjIX12PpoSmdzfNJBCHKIxXFdic4/8HirIoJy1OzgLng= X-Received: by 2002:a17:90b:3bcf:b0:396:d27c:8696 with SMTP id 98e67ed59e1d1-39b2693597emr21561201a91.4.1788770760322; Mon, 07 Sep 2026 01:46:00 -0700 (PDT) X-Received: by 2002:a17:90b:3bcf:b0:396:d27c:8696 with SMTP id 98e67ed59e1d1-39b2693597emr21561107a91.4.1788770758833; Mon, 07 Sep 2026 01:45:58 -0700 (PDT) Received: from hu-zijuhu-lv.qualcomm.com (Global_NAT1.qualcomm.com. [129.46.96.20]) by smtp.gmail.com with ESMTPSA id a92af1059eb24-143243f780csm23622117c88.13.2026.09.07.01.45.57 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 07 Sep 2026 01:45:58 -0700 (PDT) From: Zijun Hu Date: Mon, 07 Sep 2026 01:45:41 -0700 Subject: [PATCH v3 4/4] Bluetooth: btusb: Add support for Qualcomm multi-subsystem QCC2072 Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260907-btusb_qcc2072-v3-4-1f65350b03b8@oss.qualcomm.com> References: <20260907-btusb_qcc2072-v3-0-1f65350b03b8@oss.qualcomm.com> In-Reply-To: <20260907-btusb_qcc2072-v3-0-1f65350b03b8@oss.qualcomm.com> To: Marcel Holtmann , Luiz Augusto von Dentz Cc: Zijun Hu , linux-bluetooth@vger.kernel.org, linux-kernel@vger.kernel.org, Zijun Hu X-Mailer: b4 0.15.2 X-Proofpoint-Spam-Info: AW1haW4tMjYwOTA3MDA5NCBTYWx0ZWRfXxpicL/Ens/3B cGxAI4ygQ+FvUwhtNOkziYxUH3E4qq22Zn+FdicjY+zF40fnW0O8Q1dBr5/wQnc4+A6L+fFbd8t DT/wDaLB9OGoUrkRs7xm3NOlIG4KQIM= X-Authority-Analysis: v=2.4 cv=NanWEWD4 c=1 sm=1 tr=0 ts=6a9e79cb cx=c_pps a=vVfyC5vLCtgYJKYeQD43oA==:117 a=ouPCqIW2jiPt+lZRy3xVPw==:17 a=IkcTkHD0fZMA:10 a=VdqzKS8jKosA:10 a=s4-Qcg_JpJYA:10 a=VkNPw1HP01LnGYTKEx00:22 a=u7WPNUs3qKkmUXheDGA7:22 a=Um2Pa8k9VHT-vaBCBUpS:22 a=EUspDBNiAAAA:8 a=7qAHSk1smrznqxorK-sA:9 a=M6AJO_gN6mE_13cU:21 a=3ZKOabzyN94A:10 a=QEXdDO2ut3YA:10 a=rl5im9kqc5Lf4LNbBjHf:22 X-Proofpoint-GUID: _38qlhW8J1LzwLJAFoo6Tk2tl6wQ41GR X-Proofpoint-ORIG-GUID: _38qlhW8J1LzwLJAFoo6Tk2tl6wQ41GR X-Proofpoint-Spam-Details-Enc: AW1haW4tMjYwOTA3MDA5NCBTYWx0ZWRfX8L4wzpbQmTI2 4edVIZINqInku6WVWEqovZLV8T9oKq6pv4g+IAoE06KbSECn4Nsn127tB1v5spRFYijoZLl3XNG J+naSz3M7NLW4C/kuXTPPSbJrc6JgWd2b1HKzGt4af2QhTRO53xIP956oTVTaViV+XEHVa9QCf1 IXKWBjsQNerXparNI+6ERt0rFtoNhFuvjx47kCnU0HQ1cTU+TZJx0Qaf5V2aFL2mN/2KycfBmO5 bQHdt8xAvMxvEfSIxZ92uIKQgv8T66/X9ROZySTCvLG5TCjkj53tlWSsX/S7iRjjeN2+LR67zCe 1xM1UdQ7y38aDhwxtNfAa325/NA+TXdIBfflbsaZlXElxpHwtKLHcwkvPhWzayQ0ekh5EuHIIQZ TAr9DSCzwYLFNEJLmShMx+NXP/dOCwo2h/KWnkT1YzOx3TVM6JT2FwJuHhme49meQF3mL6qS9IW n/9Q0AevelubuxDpuRw== X-Proofpoint-Virus-Version: vendor=baseguard engine=ICAP:2.0.293,Aquarius:18.0.1176,Hydra:6.1.134,FMLib:17.12.100.49 definitions=2026-09-07_02,2026-09-03_01,2025-10-01_01 X-Proofpoint-Spam-Details: rule=outbound_notspam policy=outbound score=0 clxscore=1015 spamscore=0 impostorscore=0 suspectscore=0 adultscore=0 priorityscore=1501 lowpriorityscore=0 phishscore=0 bulkscore=0 malwarescore=0 classifier=typeunknown authscore=0 authtc= authcc= route=outbound adjust=0 reason=mlx scancount=1 engine=8.22.0-2606150000 definitions=main-2609070094 QCC2072 has a Peripheral (PERI) subsystem to support the BT function unit, the transport wire is owned by PERI and carries both BT-HCI and PERI-HCI frames, as shown below: _______________________________ USB I/F | internal link | BTHOST -------- |---- PERI -------------- BT | |_______________________________| (other on-chip subsystems omitted) PERI has its own command/event/ACL traffic, memdump, and firmware (PATCH and NVM), all different from BT's own, and BTHOST needs to interact with PERI directly. To support multi-subsystem controllers, add btusb_qcom.c/.h as a new transport-specific design: - Multi-subsystem memdump collection. - Generic PERI event handling, and sending a PERI frame then syncing an event sequence. - Downloading PERI firmware (rampatch and NVM). - Recovery on command timeout or hardware error event. BTW, it also supports BT-only chips. /sys/kernel/debug/usb/devices: T: Bus=3D03 Lev=3D02 Prnt=3D02 Port=3D02 Cnt=3D01 Dev#=3D 5 Spd=3D12 Mx= Ch=3D 0 D: Ver=3D 1.10 Cls=3De0(wlcon) Sub=3D01 Prot=3D01 MxPS=3D64 #Cfgs=3D 1 P: Vendor=3D0cf3 ProdID=3Dea00 Rev=3D 0.01 C:* #Ifs=3D 2 Cfg#=3D 1 Atr=3De0 MxPwr=3D100mA I:* If#=3D 0 Alt=3D 0 #EPs=3D 3 Cls=3De0(wlcon) Sub=3D01 Prot=3D01 Driver= =3Dbtusb E: Ad=3D81(I) Atr=3D03(Int.) MxPS=3D 64 Ivl=3D1ms E: Ad=3D82(I) Atr=3D02(Bulk) MxPS=3D 64 Ivl=3D0ms E: Ad=3D02(O) Atr=3D02(Bulk) MxPS=3D 64 Ivl=3D0ms I:* If#=3D 1 Alt=3D 0 #EPs=3D 2 Cls=3De0(wlcon) Sub=3D01 Prot=3D01 Driver= =3Dbtusb E: Ad=3D83(I) Atr=3D01(Isoc) MxPS=3D 0 Ivl=3D1ms E: Ad=3D03(O) Atr=3D01(Isoc) MxPS=3D 0 Ivl=3D1ms I: If#=3D 1 Alt=3D 1 #EPs=3D 2 Cls=3De0(wlcon) Sub=3D01 Prot=3D01 Driver= =3Dbtusb E: Ad=3D83(I) Atr=3D01(Isoc) MxPS=3D 9 Ivl=3D1ms E: Ad=3D03(O) Atr=3D01(Isoc) MxPS=3D 9 Ivl=3D1ms I: If#=3D 1 Alt=3D 2 #EPs=3D 2 Cls=3De0(wlcon) Sub=3D01 Prot=3D01 Driver= =3Dbtusb E: Ad=3D83(I) Atr=3D01(Isoc) MxPS=3D 17 Ivl=3D1ms E: Ad=3D03(O) Atr=3D01(Isoc) MxPS=3D 17 Ivl=3D1ms I: If#=3D 1 Alt=3D 3 #EPs=3D 2 Cls=3De0(wlcon) Sub=3D01 Prot=3D01 Driver= =3Dbtusb E: Ad=3D83(I) Atr=3D01(Isoc) MxPS=3D 25 Ivl=3D1ms E: Ad=3D03(O) Atr=3D01(Isoc) MxPS=3D 25 Ivl=3D1ms I: If#=3D 1 Alt=3D 4 #EPs=3D 2 Cls=3De0(wlcon) Sub=3D01 Prot=3D01 Driver= =3Dbtusb E: Ad=3D83(I) Atr=3D01(Isoc) MxPS=3D 33 Ivl=3D1ms E: Ad=3D03(O) Atr=3D01(Isoc) MxPS=3D 33 Ivl=3D1ms I: If#=3D 1 Alt=3D 5 #EPs=3D 2 Cls=3De0(wlcon) Sub=3D01 Prot=3D01 Driver= =3Dbtusb E: Ad=3D83(I) Atr=3D01(Isoc) MxPS=3D 49 Ivl=3D1ms E: Ad=3D03(O) Atr=3D01(Isoc) MxPS=3D 49 Ivl=3D1ms I: If#=3D 1 Alt=3D 6 #EPs=3D 2 Cls=3De0(wlcon) Sub=3D01 Prot=3D01 Driver= =3Dbtusb E: Ad=3D83(I) Atr=3D01(Isoc) MxPS=3D 63 Ivl=3D1ms E: Ad=3D03(O) Atr=3D01(Isoc) MxPS=3D 63 Ivl=3D1ms I: If#=3D 1 Alt=3D 7 #EPs=3D 2 Cls=3De0(wlcon) Sub=3D01 Prot=3D01 Driver= =3Dbtusb E: Ad=3D83(I) Atr=3D01(Isoc) MxPS=3D 65 Ivl=3D1ms E: Ad=3D03(O) Atr=3D01(Isoc) MxPS=3D 65 Ivl=3D1ms Signed-off-by: Zijun Hu --- drivers/bluetooth/Kconfig | 14 + drivers/bluetooth/Makefile | 1 + drivers/bluetooth/btusb_main.c | 120 ++ drivers/bluetooth/btusb_qcom.c | 4514 ++++++++++++++++++++++++++++++++++++= ++++ drivers/bluetooth/btusb_qcom.h | 99 + 5 files changed, 4748 insertions(+) diff --git a/drivers/bluetooth/Kconfig b/drivers/bluetooth/Kconfig index 4e8c24d757e9..5bd0f82bbe67 100644 --- a/drivers/bluetooth/Kconfig +++ b/drivers/bluetooth/Kconfig @@ -85,16 +85,30 @@ config BT_HCIBTUSB_RTL select BT_RTL default y help The Realtek protocol support enables firmware and configuration download support for Realtek Bluetooth controllers. =20 Say Y here to compile support for Realtek protocol. =20 +config BT_HCIBTUSB_QCOM + bool "Qualcomm protocol support" + depends on BT_HCIBTUSB + select FW_LOADER + select GPIOLIB + select WANT_DEV_COREDUMP + default y + help + Enables various required support, such as configuration, firmware + download, and error recovery, for Qualcomm Bluetooth controllers. + + Say Y here to compile support for Qualcomm protocol. + If unsure, say Y. + config BT_HCIBTSDIO tristate "HCI SDIO driver" depends on MMC help Bluetooth HCI SDIO driver. This driver is required if you want to use Bluetooth device with SDIO interface. =20 diff --git a/drivers/bluetooth/Makefile b/drivers/bluetooth/Makefile index 8b436c6de8b7..d5eb2568af2e 100644 --- a/drivers/bluetooth/Makefile +++ b/drivers/bluetooth/Makefile @@ -47,10 +47,11 @@ hci_uart-$(CONFIG_BT_HCIUART_INTEL) +=3D hci_intel.o hci_uart-$(CONFIG_BT_HCIUART_BCM) +=3D hci_bcm.o hci_uart-$(CONFIG_BT_HCIUART_QCA) +=3D hci_qca.o hci_uart-$(CONFIG_BT_HCIUART_AG6XX) +=3D hci_ag6xx.o hci_uart-$(CONFIG_BT_HCIUART_MRVL) +=3D hci_mrvl.o hci_uart-$(CONFIG_BT_HCIUART_AML) +=3D hci_aml.o hci_uart-objs :=3D $(hci_uart-y) =20 btusb-y :=3D btusb_main.o +btusb-$(CONFIG_BT_HCIBTUSB_QCOM) +=3D btusb_qcom.o =20 CONTEXT_ANALYSIS :=3D y diff --git a/drivers/bluetooth/btusb_main.c b/drivers/bluetooth/btusb_main.c index 39496336adf2..1004f4e7217b 100644 --- a/drivers/bluetooth/btusb_main.c +++ b/drivers/bluetooth/btusb_main.c @@ -24,16 +24,17 @@ #include #include =20 #include "btintel.h" #include "btbcm.h" #include "btrtl.h" #include "btmtk.h" #include "hci_uart.h" +#include "btusb_qcom.h" =20 #define VERSION "1.0" =20 static bool disable_scofix; static bool force_scofix; static bool enable_autosuspend =3D IS_ENABLED(CONFIG_BT_HCIBTUSB_AUTOSUSPE= ND); static bool enable_poll_sync =3D IS_ENABLED(CONFIG_BT_HCIBTUSB_POLL_SYNC); static bool reset =3D true; @@ -65,16 +66,17 @@ static struct usb_driver btusb_driver; #define BTUSB_INVALID_LE_STATES BIT(22) #define BTUSB_QCA_WCN6855 BIT(23) #define BTUSB_INTEL_BROKEN_SHUTDOWN_LED BIT(24) #define BTUSB_INTEL_BROKEN_INITIAL_NCMD BIT(25) #define BTUSB_INTEL_NO_WBS_SUPPORT BIT(26) #define BTUSB_ACTIONS_SEMI BIT(27) #define BTUSB_BARROT BIT(28) #define BTUSB_BROKEN_EXT_SCAN BIT(29) +#define BTUSB_QUALCOMM BIT(30) =20 static const struct usb_device_id btusb_table[] =3D { /* Generic Bluetooth USB device */ { USB_DEVICE_INFO(0xe0, 0x01, 0x01) }, =20 /* Generic Bluetooth AMP device */ { USB_DEVICE_INFO(0xe0, 0x01, 0x04), .driver_info =3D BTUSB_AMP }, =20 @@ -412,16 +414,20 @@ static const struct usb_device_id quirks_table[] =3D { BTUSB_WIDEBAND_SPEECH }, { USB_DEVICE(0x2c7c, 0x0130), .driver_info =3D BTUSB_QCA_WCN6855 | BTUSB_WIDEBAND_SPEECH }, { USB_DEVICE(0x2c7c, 0x0131), .driver_info =3D BTUSB_QCA_WCN6855 | BTUSB_WIDEBAND_SPEECH }, { USB_DEVICE(0x2c7c, 0x0132), .driver_info =3D BTUSB_QCA_WCN6855 | BTUSB_WIDEBAND_SPEECH }, =20 + /* Qualcomm multi-subsystem chipset QCC2072 */ + { USB_DEVICE(0x0cf3, 0xea00), .driver_info =3D BTUSB_QUALCOMM | + BTUSB_WIDEBAND_SPEECH }, + /* Broadcom BCM2035 */ { USB_DEVICE(0x0a5c, 0x2009), .driver_info =3D BTUSB_BCM92035 }, { USB_DEVICE(0x0a5c, 0x200a), .driver_info =3D BTUSB_WRONG_SCO_MTU }, { USB_DEVICE(0x0a5c, 0x2035), .driver_info =3D BTUSB_WRONG_SCO_MTU }, =20 /* Broadcom BCM2045 */ { USB_DEVICE(0x0a5c, 0x2039), .driver_info =3D BTUSB_WRONG_SCO_MTU }, { USB_DEVICE(0x0a5c, 0x2101), .driver_info =3D BTUSB_WRONG_SCO_MTU }, @@ -1370,16 +1376,41 @@ static int btusb_recv_acl(struct hci_dev *hdev, str= uct sk_buff *skb) return data->recv_acl(hdev, skb); =20 skb_queue_tail(&data->acl_q, skb); schedule_delayed_work(&data->rx_work, data->intr_interval); =20 return 0; } =20 +static int btusb_recv_frame(struct hci_dev *hdev, struct sk_buff *skb) +{ + u8 pkt_type =3D hci_skb_pkt_type(skb); + int ret; + + switch (pkt_type) { + case HCI_EVENT_PKT: + ret =3D btusb_recv_event(hdev, skb); + break; + case HCI_ACLDATA_PKT: + ret =3D btusb_recv_acl(hdev, skb); + break; + case HCI_SCODATA_PKT: + case HCI_ISODATA_PKT: + ret =3D hci_recv_frame(hdev, skb); + break; + default: + dev_kfree_skb_irq(skb); + ret =3D -EINVAL; + break; + } + + return ret; +} + /* Dispatch through the btusb_recv_* wrappers so that vendor specific * handling (data->recv_event, data->recv_acl) is preserved in H:4 mode. */ static const struct h4_recv_pkt btusb_recv_pkts[] =3D { { H4_RECV_ACL, .recv =3D btusb_recv_acl }, { H4_RECV_SCO, .recv =3D hci_recv_frame }, { H4_RECV_EVENT, .recv =3D btusb_recv_event }, { H4_RECV_ISO, .recv =3D hci_recv_frame }, @@ -4258,16 +4289,78 @@ static const struct hci_drv_handler btusb_hci_drv_s= pecific_handlers[] =3D { =20 static struct hci_drv btusb_hci_drv =3D { .common_handler_count =3D ARRAY_SIZE(btusb_hci_drv_common_handlers), .common_handlers =3D btusb_hci_drv_common_handlers, .specific_handler_count =3D ARRAY_SIZE(btusb_hci_drv_specific_handlers), .specific_handlers =3D btusb_hci_drv_specific_handlers, }; =20 +/* + * =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D + * Qualcomm support + * =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D + */ + +static int btusb_recv_intr_qcom(struct btusb_data *data, void *buffer, int= count) +{ + unsigned long flags; + int err =3D 0; + + spin_lock_irqsave(&data->rxlock, flags); + data->evt_skb =3D btusb_qcom_recv_intr(data->hdev, data->evt_skb, buffer,= count, + &err); + spin_unlock_irqrestore(&data->rxlock, flags); + + return err; +} + +static int btusb_recv_bulk_qcom(struct btusb_data *data, void *buffer, int= count) +{ + unsigned long flags; + int err =3D 0; + + spin_lock_irqsave(&data->rxlock, flags); + data->acl_skb =3D btusb_qcom_recv_bulk(data->hdev, data->acl_skb, buffer,= count, + &err); + spin_unlock_irqrestore(&data->rxlock, flags); + + return err; +} + +static int btusb_send_vendor_frame_qcom(struct hci_dev *hdev, struct sk_bu= ff *skb) +{ + u8 pkt_type =3D hci_skb_pkt_type(skb); + struct urb *urb; + + switch (pkt_type) { + case QPERI_COMMAND_PKT: + urb =3D alloc_ctrl_urb(hdev, skb); + break; + + case QPERI_ACLDATA_PKT: + urb =3D alloc_bulk_urb(hdev, skb); + break; + + default: + return -EILSEQ; + } + + if (IS_ERR(urb)) + return PTR_ERR(urb); + + return submit_or_queue_tx_urb(hdev, urb); +} + +/* + * =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D + * Qualcomm support end + * =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D + */ + static int btusb_probe(struct usb_interface *intf, const struct usb_device_id *id) { struct gpio_desc *reset_gpio; struct usb_host_interface *alt; struct usb_endpoint_descriptor *bulk_rx_ep, *bulk_tx_ep, *intr_ep; struct btusb_data *data; struct hci_dev *hdev; @@ -4388,16 +4481,19 @@ static int btusb_probe(struct usb_interface *intf, =20 data->recv_event =3D btusb_recv_event_realtek; } else if (id->driver_info & BTUSB_MEDIATEK) { /* Allocate extra space for Mediatek device */ priv_size +=3D sizeof(struct btmtk_data); } else if (id->driver_info & BTUSB_QCA_WCN6855) { /* Allocate extra space for QCA WCN6855 device */ priv_size +=3D sizeof(struct btqca_data); + } else if (id->driver_info & BTUSB_QUALCOMM) { + /* Allocate extra space for Qualcomm device */ + priv_size +=3D btusb_qcom_hdev_priv_size(); } =20 data->recv_acl =3D hci_recv_frame; =20 hdev =3D hci_alloc_dev_priv(priv_size); if (!hdev) { err =3D -ENOMEM; goto err_free_data; @@ -4546,16 +4642,40 @@ static int btusb_probe(struct usb_interface *intf, hdev->classify_pkt_type =3D btusb_classify_qca_pkt_type; hdev->shutdown =3D btusb_shutdown_qca; hdev->set_bdaddr =3D btusb_set_bdaddr_wcn6855; hdev->reset =3D btusb_qca_reset; hci_set_quirk(hdev, HCI_QUIRK_SIMULTANEOUS_DISCOVERY); hci_set_msft_opcode(hdev, 0xFD70); } =20 + if (id->driver_info & BTUSB_QUALCOMM) { + struct btusb_qcom *xport_data; + + if (!IS_ENABLED(CONFIG_BT_HCIBTUSB_QCOM)) { + err =3D -ENODEV; + bt_dev_err(hdev, "CONFIG_BT_HCIBTUSB_QCOM not enabled"); + goto err_kill_tx_urbs; + } + + xport_data =3D btusb_qcom_xport_data(hdev); + xport_data->reset_gpio =3D data->reset_gpio; + xport_data->prepare_reset =3D btusb_prepare_reset; + xport_data->recv_bt_frame =3D btusb_recv_frame; + xport_data->send_bt_frame =3D btusb_send_frame; + xport_data->send_vendor_frame =3D btusb_send_vendor_frame_qcom; + + data->recv_intr =3D btusb_recv_intr_qcom; + data->recv_bulk =3D btusb_recv_bulk_qcom; + data->disconnect =3D btusb_qcom_disconnect; + + hdev->send =3D btusb_qcom_send_frame; + hdev->setup =3D btusb_qcom_setup; + } + if (id->driver_info & BTUSB_AMP) { /* AMP controllers do not support SCO packets */ data->isoc =3D NULL; } else if (data->proto =3D=3D BTUSB_PROTO_H4) { /* In H:4 mode every packet, including SCO/ISO, is carried over * the bulk endpoints, so the isochronous interface must not be * claimed nor have its alternate settings switched. */ diff --git a/drivers/bluetooth/btusb_qcom.c b/drivers/bluetooth/btusb_qcom.c new file mode 100644 index 000000000000..7d45dffd1d9c --- /dev/null +++ b/drivers/bluetooth/btusb_qcom.c @@ -0,0 +1,4514 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Qualcomm Bluetooth USB transport-specific support + * + * Abbreviations: + * BTC - BT controller + * PERI - Peripheral subsystem of a multi-subsys BTC + * TME-L - Trust Management Engine Lite subsystem of a multi-subsys BTC + * DFU - Device Firmware Update + * EDL - Embedded Downloader + * TLV - Type-Length-Value, a firmware file format + * VSC - Vendor-Specific Command + * VSE - Vendor-Specific Event + * CCE - Command Complete Event + * CSE - Command Status Event + * + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "btusb_qcom.h" + +/* + * =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D + * Supported BTCs and their configuration + * =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D + */ + +/* + * enum qbtc_category - driver-perspective BTC category + * @QBTC_CAT_LEGACY: legacy + * @QBTC_CAT_UNIFIED: unified HCI, VSC gets CCE as response + * @QBTC_CAT_MSUBSYS: multi-subsys, always has PERI + * @QBTC_CAT_MAX: number of categories + */ +enum qbtc_category { + QBTC_CAT_LEGACY, + QBTC_CAT_UNIFIED, + QBTC_CAT_MSUBSYS, + QBTC_CAT_MAX, +}; + +static const char *qbtc_category_name(int category) +{ + const char *category_name =3D "unknown"; + + switch (category) { + case QBTC_CAT_LEGACY: + category_name =3D "legacy"; + break; + case QBTC_CAT_UNIFIED: + category_name =3D "unified"; + break; + case QBTC_CAT_MSUBSYS: + category_name =3D "multi-subsys"; + break; + default: + break; + } + + return category_name; +} + +/* flags for BTC info and default configuration */ + +/* BTC has PERI / TME-L subsystem */ +#define QBT_FLAG_HAS_PERI BIT(0) +#define QBT_FLAG_HAS_TMEL BIT(1) +/* BTC supports AOSP / MSFT vendor extension */ +#define QBT_FLAG_AOSP_EXT BIT(4) +#define QBT_FLAG_MSFT_EXT BIT(5) +/* BTC supports software reset */ +#define QBT_FLAG_SW_RESET BIT(6) +/* BTC supports memdump */ +#define QBT_FLAG_MEMDUMP BIT(7) +/* take foundry as a factor to select NVM */ +#define QBT_FLAG_FOUNDRY_NVM BIT(8) +/* select NVM based on board ID to download */ +#define QBT_FLAG_BID_NVM BIT(9) +/* fall back to the default NVM if no board-ID-specific NVM exists */ +#define QBT_FLAG_NVM_FALLBACK BIT(10) +/* reset PERI HCI by QHCI instead of QDFU */ +#define QBT_FLAG_RESET_PERI_HCI BIT(11) +/* trigger memdump on command timeout */ +#define QBT_FLAG_CMD_TIMEOUT_MEMDUMP BIT(12) +/* reserve bits [31:24] for board-level flags */ +#define QBT_FLAG_BTC_CFG_MASK GENMASK(23, 0) + +/* rare board ID to custom firmware directroy map */ +struct qbtc_bid_fwdir { + u16 board_id; + const char *fw_dir; +}; + +/* + * struct qbtc_info - BTC information and default configuration + * @category: driver-perspective BTC category (legacy/unified/multi-subsys) + * @flags: QBT_FLAG_* =E2=80=94 BTC attributes and default configuration + * @fw_dir: firmware folder, "qca" if NULL + * @custom_fw_table: {}-terminated array or NULL + */ +struct qbtc_info { + enum qbtc_category category; + unsigned long flags; + const char *fw_dir; + const struct qbtc_bid_fwdir *custom_fw_table; +}; + +/* + * struct qbtc_id - BTC ID entry in qbtc_id_table[] below + * @rom_version: ID to match against rom_version read from BTC + * @name: human-readable BTC name + * @btc_info: BTC information and default configuration, !=3D NULL + */ +struct qbtc_id { + u32 rom_version; + const char *name; + const struct qbtc_info *btc_info; +}; + +/* multi-subsys BTC here */ +#define QBTC_INFO_FLAGS_MSUBSYS (QBT_FLAG_HAS_PERI | QBT_FLAG_AOSP_EXT | \ + QBT_FLAG_MSFT_EXT | QBT_FLAG_SW_RESET | \ + QBT_FLAG_MEMDUMP | QBT_FLAG_BID_NVM | \ + QBT_FLAG_NVM_FALLBACK | QBT_FLAG_CMD_TIMEOUT_MEMDUMP) + +static const struct qbtc_info qbtc_msubsys_qcc2072 =3D { + .category =3D QBTC_CAT_MSUBSYS, + .flags =3D QBTC_INFO_FLAGS_MSUBSYS, + .fw_dir =3D "qca/QCC2072", +}; + +static const struct qbtc_id qbtc_id_table[] =3D { + { 0x00220100, "QCC2072 1.x", &qbtc_msubsys_qcc2072 }, + { } +}; + +/* + * =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D + * Qualcomm DFU for rampatch/NVM download + * =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D + */ + +/* QDFU USB vendor command codes (bRequest) */ +#define QDFU_BT_CMD_VSC_REQ_DOWNLOAD_LOCAL 0x01 +#define QDFU_BT_CMD_CHECK_TARGET_STATE 0x05 +#define QDFU_BT_CMD_GET_TARGET_VERSION 0x09 +#define QDFU_BT_CMD_VSC_REQ_DOWNLOAD_REMOTE 0x10 +#define QDFU_BT_CMD_RESET_PERI_HCI 0x11 +#define QDFU_BT_CMD_ACTIVATE_REMOTE_BTSS 0x12 +#define QDFU_BT_CMD_BT_ENABLE_RESET 0x13 + +/* QDFU target status bits (u8 bitmask from QDFU_BT_CMD_CHECK_TARGET_STATE= ) */ +#define QDFU_BT_STATE_LOADING_LOCAL BIT(3) +#define QDFU_BT_STATE_PATCHED_REMOTE BIT(4) +#define QDFU_BT_STATE_NVMED_REMOTE BIT(5) +#define QDFU_BT_STATE_NVMED_LOCAL BIT(6) +#define QDFU_BT_STATE_PATCHED_LOCAL BIT(7) + +/* QDFU_BT_CMD_GET_TARGET_VERSION response */ +struct qdfu_bt_version { + __le32 rom_version; + __le32 patch_version; + __le32 soc_ver; + __be16 board_id; + __le16 flag; + u8 reserved[4]; +} __packed; + +struct qdfu_bt_id { + u32 rom_version; + u32 patch_version; + u32 soc_id; + u16 board_id; +}; + +static inline int qdfu_recv_vendor_req(struct hci_dev *hdev, u8 request, + u16 value, void *buf, u16 size) +{ + struct btusb_qcom *xport_data =3D btusb_qcom_xport_data(hdev); + + return usb_control_msg_recv(xport_data->udev, 0, request, + USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_ENDPOINT, + value, 0, buf, size, + USB_CTRL_GET_TIMEOUT, GFP_KERNEL); +} + +static inline int qdfu_send_vendor_req(struct hci_dev *hdev, u8 request, + u16 value, const void *buf, u16 size) +{ + struct btusb_qcom *xport_data =3D btusb_qcom_xport_data(hdev); + + return usb_control_msg_send(xport_data->udev, 0, request, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_ENDPOINT, + value, 0, buf, size, + USB_CTRL_SET_TIMEOUT, GFP_KERNEL); +} + +static int qdfu_vsc_req_download(struct hci_dev *hdev, u8 request, + const void *buf, u16 size) +{ + int ret; + + if (request !=3D QDFU_BT_CMD_VSC_REQ_DOWNLOAD_LOCAL && + request !=3D QDFU_BT_CMD_VSC_REQ_DOWNLOAD_REMOTE) { + bt_dev_err(hdev, + "QDFU download invalid request code 0x%02x", request); + return -EINVAL; + } + + ret =3D qdfu_send_vendor_req(hdev, request, 0, buf, size); + if (ret) + bt_dev_err(hdev, + "QDFU download request 0x%02x failed: %pe", + request, ERR_PTR(ret)); + + return ret; +} + +static int qdfu_get_target_state(struct hci_dev *hdev, u8 *state_ptr) +{ + u8 state; + int ret; + + ret =3D qdfu_recv_vendor_req(hdev, QDFU_BT_CMD_CHECK_TARGET_STATE, 0, + &state, sizeof(state)); + if (!ret) + *state_ptr =3D state; + else + bt_dev_err(hdev, + "QDFU get target state failed: %pe", + ERR_PTR(ret)); + + return ret; +} + +static int qdfu_get_target_version(struct hci_dev *hdev, struct qdfu_bt_id= *id_info) +{ + struct qdfu_bt_version dfu_ver; + u16 board_id =3D 0; + int ret; + + ret =3D qdfu_recv_vendor_req(hdev, QDFU_BT_CMD_GET_TARGET_VERSION, 0, + &dfu_ver, sizeof(dfu_ver)); + if (ret) { + bt_dev_err(hdev, + "QDFU get target version failed: %pe", + ERR_PTR(ret)); + return ret; + } + + id_info->rom_version =3D le32_to_cpu(dfu_ver.rom_version); + id_info->patch_version =3D le32_to_cpu(dfu_ver.patch_version); + id_info->soc_id =3D le32_to_cpu(dfu_ver.soc_ver); + + if ((le16_to_cpu(dfu_ver.flag) >> 8) =3D=3D 0x80) + board_id =3D be16_to_cpu(dfu_ver.board_id); + + /* Take 0xffff as invalid board ID */ + if (board_id =3D=3D 0xffff) + board_id =3D 0; + + id_info->board_id =3D board_id; + + return 0; +} + +static int qdfu_activate_remote_btss(struct hci_dev *hdev, bool on, + unsigned int wait_us) +{ + u8 status =3D 0; + int ret; + + ret =3D qdfu_recv_vendor_req(hdev, QDFU_BT_CMD_ACTIVATE_REMOTE_BTSS, on, + &status, sizeof(status)); + if (ret) { + bt_dev_err(hdev, "QDFU turn %s remote BTSS failed: %pe", + str_on_off(on), ERR_PTR(ret)); + return ret; + } + + switch (status) { + case 0: + bt_dev_dbg(hdev, "QDFU Remote BTSS turned %s", str_on_off(on)); + fsleep(wait_us); + break; + case 0x17: + bt_dev_dbg(hdev, "QDFU Remote BTSS already %s", str_on_off(on)); + break; + default: + bt_dev_err(hdev, + "QDFU turn remote BTSS %s failed, unexpected status 0x%02x", + str_on_off(on), status); + ret =3D -ENODEV; + } + + return ret; +} + +static int qdfu_reset_peri_hci(struct hci_dev *hdev) +{ + int ret; + + ret =3D qdfu_send_vendor_req(hdev, QDFU_BT_CMD_RESET_PERI_HCI, 0, NULL, 0= ); + if (ret) { + bt_dev_err(hdev, "PERI HCI reset via QDFU failed: %pe", + ERR_PTR(ret)); + return ret; + } + bt_dev_info(hdev, "PERI HCI reset via QDFU succeeded"); + fsleep(20 * 1000); + + return 0; +} + +static int qdfu_sw_reset(struct hci_dev *hdev) +{ + int ret; + + ret =3D qdfu_send_vendor_req(hdev, QDFU_BT_CMD_BT_ENABLE_RESET, 0, NULL, = 0); + /* SW reset succeeds even if the request returns an error code */ + if (ret) + bt_dev_dbg(hdev, "QDFU SW reset failed: %pe", ERR_PTR(ret)); + + bt_dev_info(hdev, "QDFU SW reset succeeded"); + + return 0; +} + +/* + * qdfu_poll_state - wait for DFU target status flags to reach a wanted st= ate + * @hdev: the HCI device to poll + * @state_ptr: optional storage for the last status read; may be NULL + * @set: true to wait until @flags are all set, false until all cleared + * @flags: the status flag bits to wait on + * + * Repeatedly reads the DFU target status until @flags reach the requested + * state, a status read fails, or the overall timeout expires. + * + * Return: 0 on success, -ETIMEDOUT on timeout, or a negative errno on read + * failure. + */ +static int qdfu_poll_state(struct hci_dev *hdev, u8 *state_ptr, bool set, = u8 flags) +{ + int err, ret; + u8 dfu_state; + + if (!state_ptr) + state_ptr =3D &dfu_state; + + ret =3D read_poll_timeout(qdfu_get_target_state, err, + err || (set ? (*state_ptr & flags) =3D=3D flags + : !(*state_ptr & flags)), + 5 * 1000, + 3000 * 1000, true, + hdev, state_ptr); + if (ret) { + bt_dev_err(hdev, + "Timed out waiting for QDFU state flags 0x%02x to be %s: %pe", + flags, set ? "set" : "cleared", ERR_PTR(ret)); + return ret; + } + + return err; +} + +static inline int qdfu_reset_msubsys_bt(struct hci_dev *hdev) +{ + int ret; + + ret =3D qdfu_activate_remote_btss(hdev, false, 100 * 1000); + if (!ret) + ret =3D qdfu_activate_remote_btss(hdev, true, 100 * 1000); + if (!ret) + bt_dev_info(hdev, "QDFU reset msubsys BT succeeded"); + + return ret; +} + +/* + * =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D + * Common definitions, per-device struct btqcom_data, and PERI frame format + * =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D + */ + +/* BTC version, read out via an HCI command */ +struct qhci_btc_ver { + u32 product_id; + u32 soc_ver; + u16 rom_ver; + u16 patch_ver; + u8 sec_ver; +}; + +/* Subsystem field appears in a PERI command/response/event */ +enum qhci_subsys { + QHCI_SUBSYS_PERI, + QHCI_SUBSYS_BT, + QHCI_SUBSYS_UWB, + QHCI_SUBSYS_TMEL, + QHCI_SUBSYS_MAX, +}; + +/* + * The pseudo QHCI subsys: for BT-only BTC, or when the memdump comes + * from the BT channel (HCI_EVENT_PKT or HCI_ACLDATA_PKT) on a msubsys + * BTC, there is no real subsys =E2=80=94 it stands in for these cases. + */ +#define QHCI_SUBSYS_INVALID QHCI_SUBSYS_MAX + +static const char *qhci_subsys_name(int subsys) +{ + const char *subsys_name =3D "unknown"; + + switch (subsys) { + case QHCI_SUBSYS_PERI: + subsys_name =3D "PERI"; + break; + case QHCI_SUBSYS_BT: + subsys_name =3D "PERI_BT"; + break; + case QHCI_SUBSYS_UWB: + subsys_name =3D "UWB"; + break; + case QHCI_SUBSYS_TMEL: + subsys_name =3D "TME-L"; + break; + case QHCI_SUBSYS_INVALID: + subsys_name =3D "BT"; + break; + default: + break; + } + + return subsys_name; +} + +/* BTC subsystems we care about that support the BT function unit */ +enum qbtc_subsys { + QBTC_SUBSYS_PERI, + QBTC_SUBSYS_TMEL, + QBTC_SUBSYS_MAX, +}; + +/* + * The pseudo BTC subsys stands in for the BT function unit, not a + * real subsys, when building a firmware file path. + */ +#define QBTC_SUBSYS_INVALID QBTC_SUBSYS_MAX + +static const char *qbtc_subsys_name(int subsys) +{ + const char *subsys_name =3D "unknown"; + + switch (subsys) { + case QBTC_SUBSYS_PERI: + subsys_name =3D "PERI"; + break; + case QBTC_SUBSYS_TMEL: + subsys_name =3D "TME-L"; + break; + case QBTC_SUBSYS_INVALID: + subsys_name =3D "BT"; + break; + default: + break; + } + + return subsys_name; +} + +/* + * struct qbtc_subsys_data - per-subsys data + * @ver: subsys version + * @board_id: subsys board ID + * @build_info: the subsys firmware's build info string + */ +struct qbtc_subsys_data { + struct qhci_btc_ver ver; + u16 board_id; + char build_info[160]; +}; + +/* simple command payload */ +struct qhci_cp_simple { + u8 sub_opcode; +} __packed; + +/* simple CCE(response) payload */ +struct qhci_rp_simple { + u8 status; +} __packed; + +/* generic CCE(response) payload */ +struct qhci_rp_generic { + u8 status; + u8 sub_opcode; +} __packed; + +/* common VSE payload */ +struct qhci_vse_comm { + u8 ev_class; + u8 ev_type; +} __packed; +#define QHCI_VSE_COMM_SIZE (sizeof(struct qhci_vse_comm)) + +#define QHCI_MEMDUMP_SEQ_LAST 0xFFFF +#define QBT_EV_CLASS_DATALOG 0x01 +#define QBT_EV_TYPE_MEMDUMP 0x08 + +/* + * struct qhci_vse_memdump - one memdump segment + * @seqno: segment sequence number; 0 =3D first, QHCI_MEMDUMP_SEQ_LAST =3D= last + * @subsys: subsystem this segment belongs to (see enum qhci_subsys) + * @segdata: payload of a middle or last segment + * @dump_size: total dump size, valid only in the first segment (@seqno = =3D=3D 0) + * @first_segdata: payload of the first segment, following @dump_size + */ +struct qhci_vse_memdump { + __le16 seqno; + u8 subsys; + union { + u8 segdata[0]; + struct { + __le32 dump_size; + u8 first_segdata[]; + } __packed; + }; +} __packed; + +#define QHCI_MEMDUMP_SEGDATA_GAP \ + (offsetof(struct qhci_vse_memdump, first_segdata) - \ + offsetof(struct qhci_vse_memdump, segdata)) + +enum qhci_req_state { + QHCI_REQ_DONE, + QHCI_REQ_PEND, + QHCI_REQ_CANCELED, + /* unused for now */ + QHCI_REQ_STOP, +}; + +#define QHCI_OPCODE_INVALID HCI_OP_NOP +#define QHCI_SUB_OPCODE_INVALID 0xff +#define QHCI_REQ_EVENT_MAX 2 + +/* + * struct qhci_req_spec - request/response specification + * @opcode: command opcode, ignored if invalid + * @sub_opcode: command sub-opcode, ignored if invalid + * @event: a sequence of events, ending with an invalid marker + */ +struct qhci_req_spec { + u16 opcode; + u8 sub_opcode; + u8 event[QHCI_REQ_EVENT_MAX + 1]; +}; + +/* + * struct qhci_req - per-request data, often stack allocated by the reques= ter + * @pkt_type: the packet type to sync + * @spec: the request spec to sync + * @req_rsp: response skb on success, NULL on no data, or ERR_PTR() on fai= lure + * @req_result: >=3D 0 an HCI status code, or a negative errno on failure + * @event_idx: cursor into @spec->event + */ +struct qhci_req { + u8 pkt_type; + const struct qhci_req_spec *spec; + struct sk_buff *req_rsp; + int req_result; + u8 event_idx; +}; + +/* board has a BT_EN pin to reset BTC */ +#define QBT_FLAG_HW_RESET BIT(24) + +/* + * enum qbt_work_bit - bits in btqcom_data.work_flags + * @QBT_WORK_RESET_HDEV: request to reset hdev + */ +enum qbt_work_bit { + QBT_WORK_RESET_HDEV, +}; + +/* + * enum qbt_misc_bit - bits in btqcom_data.misc_flags + * @QBT_MISC_MEMDUMP_PERI: PERI memdump + * @QBT_MISC_MEMDUMP_BT: BT memdump + * @QBT_MISC_MEMDUMP_UWB: UWB memdump, never happens here + * @QBT_MISC_MEMDUMP_TMEL: TME-L memdump + * @QBT_MISC_MEMDUMP_INCOMING: memdump is arriving from the BTC + * @QBT_MISC_RESET_ACTIVE: a reset is in progress + * @QBT_MISC_HWERR_PERI: PERI hardware error event happens + * @QBT_MISC_HWERR_BT: BT hardware error event happens + * @QBT_MISC_CMD_TIMEOUT: command timeout happens + * + * The memdump bits are indexed by enum qhci_subsys, so a subsystem's bit = can + * be derived from its QHCI subsys value. + */ +enum qbt_misc_bit { + QBT_MISC_MEMDUMP_PERI =3D QHCI_SUBSYS_PERI, + QBT_MISC_MEMDUMP_BT =3D QHCI_SUBSYS_BT, + QBT_MISC_MEMDUMP_UWB =3D QHCI_SUBSYS_UWB, + QBT_MISC_MEMDUMP_TMEL =3D QHCI_SUBSYS_TMEL, + QBT_MISC_MEMDUMP_INCOMING, + QBT_MISC_RESET_ACTIVE, + QBT_MISC_HWERR_PERI, + QBT_MISC_HWERR_BT, + QBT_MISC_CMD_TIMEOUT, +}; + +/* each subsys's memdump pending flag in btqcom_data.md_pending_flags */ +#define QMD_FLAG_PENDING_PERI BIT(QBT_MISC_MEMDUMP_PERI) +#define QMD_FLAG_PENDING_BT BIT(QBT_MISC_MEMDUMP_BT) +#define QMD_FLAG_PENDING_TMEL BIT(QBT_MISC_MEMDUMP_TMEL) +#define QMD_FLAG_PENDING_MASK (QMD_FLAG_PENDING_PERI | \ + QMD_FLAG_PENDING_BT | \ + QMD_FLAG_PENDING_TMEL) + +static inline int qmd_subsys_to_bit(int qhci_subsys) +{ + return qhci_subsys =3D=3D QHCI_SUBSYS_INVALID ? + QBT_MISC_MEMDUMP_BT : qhci_subsys; +} + +static inline unsigned long qmd_subsys_to_flag(int qhci_subsys) +{ + return BIT(qmd_subsys_to_bit(qhci_subsys)); +} + +/* + * struct btqcom_memdump - collect memdump from a QHCI subsys + * @size: total memdump size to collect + * @subsys: QHCI subsys this memdump belongs to + * @seqno: next expected sequence number + * @from: which channel this memdump comes from, marked by hci_skb_pkt_typ= e() + * @submit_size: bytes submitted to HCI devcoredump for the subsys + */ +struct btqcom_memdump { + u32 size; + int subsys; + u16 seqno; + u8 from; + + u32 submit_size; +}; + +/* + * struct btqcom_data - transport-independent per-device common data, allo= cated as hci priv + * @drv_name: driver name + * @btc_name: human-readable BTC name + * @category: driver-perspective BTC category (legacy/unified/multi-subsys) + * @flags: QBT_FLAG_* flags for BTC/board and default config + * @hdev: the owning HCI device + * @xport_data: opaque transport-specific data (e.g. struct btusb_qcom for= USB) + * @inited: whether the hdev-lifetime fields are initialized + * @board_id: BT board ID + * @ver: BT version + * @build_info: BT firmware build info string + * @fw_dir: firmware directory configured by user via sysfs + * @fw_logging: firmware logging configured by user via sysfs + * @misc_flags: flags made from QBT_MISC_* bits defined above + * @work_flags: flags made from QBT_WORK_* bits defined above + * @dwork: delayed work to handle @work_flags + * @md_ready: memdump functionality is ready + * @md_state: devcoredump state as notified by the HCI devcoredump core + * @md_submit_err: first error submitting to HCI devcoredump, 0 if none + * @md_submit_size: total bytes submitted to HCI devcoredump, headers incl= uded + * @md_pending_flags: QMD_FLAG_PENDING_* flags tracking per-subsystem memd= ump + * @md: per-subsystem memdump collection track + * @req_mutex: serializes sync requesters + * @req_wait_q: wait queue for the requester + * @req_spinlock: protects @req_state, @req, and @*req + * @req_state: request state, see enum qhci_req_state + * @req: the in-flight request, valid while @req_state is QHCI_REQ_PEND + * @subsys: per-subsystem info for MSUBSYS chips + */ +struct btqcom_data { + const char *drv_name; + const char *btc_name; + enum qbtc_category category; + unsigned long flags; + + struct hci_dev *hdev; + void *xport_data; + + bool inited; + + u16 board_id; + struct qhci_btc_ver ver; + char build_info[160]; + + const char *fw_dir; + u8 fw_logging; + + unsigned long misc_flags; + unsigned long work_flags; + struct delayed_work dwork; + + bool md_ready; + enum devcoredump_state md_state; + int md_submit_err; + u32 md_submit_size; + unsigned long md_pending_flags; + struct btqcom_memdump md; + + struct mutex req_mutex; + wait_queue_head_t req_wait_q; + spinlock_t req_spinlock; + int req_state; + struct qhci_req *req; + + struct qbtc_subsys_data subsys[QBTC_SUBSYS_MAX]; +}; + +/* BT host ID in PERI command/event/ACL */ +#define QHCI_HOST_ID_BT 0 + +struct qperi_command_hdr { + u8 host_id; + __le16 opcode; + u8 plen; +} __packed; +#define QPERI_COMMAND_HDR_SIZE (sizeof(struct qperi_command_hdr)) + +struct qperi_acl_hdr { + u8 host_id; + __le16 handle; + __le16 dlen; +} __packed; +#define QPERI_ACL_HDR_SIZE (sizeof(struct qperi_acl_hdr)) +#define QPERI_MAX_FRAME_SIZE (HCI_MAX_FRAME_SIZE + 1) + +struct qperi_event_hdr { + u8 host_id; + u8 evt; + u8 plen; +} __packed; +#define QPERI_EVENT_HDR_SIZE (sizeof(struct qperi_event_hdr)) +#define QPERI_MAX_EVENT_SIZE (HCI_MAX_EVENT_SIZE + 1) + +static inline struct qperi_event_hdr *qperi_event_header(const struct sk_b= uff *skb) +{ + return (struct qperi_event_hdr *)skb->data; +} + +static inline struct qperi_acl_hdr *qperi_acl_header(const struct sk_buff = *skb) +{ + return (struct qperi_acl_hdr *)skb->data; +} + +static inline __u16 qperi_acl_handle(const struct sk_buff *skb) +{ + struct qperi_acl_hdr *hdr =3D qperi_acl_header(skb); + + return hci_handle(__le16_to_cpu(hdr->handle)); +} + +static inline __u16 qperi_acl_dlen(const struct sk_buff *skb) +{ + return __le16_to_cpu(qperi_acl_header(skb)->dlen); +} + +static const char *qhci_pkt_type_name(u8 pkt_type) +{ + const char *pkt_type_name =3D "Unknown"; + + switch (pkt_type) { + case HCI_EVENT_PKT: + pkt_type_name =3D "BT EVT"; + break; + case QPERI_EVENT_PKT: + pkt_type_name =3D "PERI EVT"; + break; + case HCI_ACLDATA_PKT: + pkt_type_name =3D "BT ACL"; + break; + case QPERI_ACLDATA_PKT: + pkt_type_name =3D "PERI ACL"; + break; + default: + break; + } + + return pkt_type_name; +} + +/* + * =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D + * Multi-subsys memdump design + * =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D + */ + +/* + * Design compatible with BT-only BTC as well: + * + * Abbreviations: SS - Subsystem, MD - Memdump, HDR - Header + * + * +--------------+-------------+---------+ +-------------+---------+ + * | file HDR | SS_A MD HDR | SS_A | ... | SS_N MD HDR | SS_N | + * | 512B | 512B | MD data | | 512B | MD data | + * | by dmp_hdr() | appended as | | | appended as | | + * | | MD data | | | MD data | | + * +--------------+-------------+---------+ +-------------+---------+ + * + * 1) All SS MDs are collected into a SINGLE file. + * 2) Each SS_X MD HDR records the size of its own MD data. + * 3) On the 1st segment of the 1st SS: call hci_devcd_init() with + * dump_size =E2=80=94 for a BT-only BTC, a 512B MD HDR plus the size c= arried + * in the segment itself; for a multi-subsys BTC, the sum of (a 512B + * MD HDR plus the max MD data size) over every SS. The file HDR is + * not counted, since the HCI devcoredump adds it on top of dump_size. + * 4) On the 1st segment of every SS: call hci_devcd_append() to append + * its 512B MD HDR as ordinary MD data. + * 5) On the last segment of a SS: call hci_devcd_complete() only if + * no other SS's MD is still pending. + * 6) On the hardware error event (either PERI or BT), which always + * comes after all MDs have been reported by the BTC: call + * hci_devcd_complete() there if it has not been called yet, since a + * SS's MD is optional. + */ + +static const u32 btqcom_memdump_maxsize[] =3D { + [QHCI_SUBSYS_PERI] =3D SZ_256K, + [QHCI_SUBSYS_BT] =3D SZ_1M, + [QHCI_SUBSYS_UWB] =3D 0, + [QHCI_SUBSYS_TMEL] =3D SZ_256K, + [QHCI_SUBSYS_INVALID] =3D SZ_1M, +}; + +/* + * btqcom_memdump_hdr_room - helper to fill a memdump header up to 512 byt= es + * @skb: the devcoredump header skb to fill + * + * Return: bytes safe to write into @skb. + */ +static int btqcom_memdump_hdr_room(struct sk_buff *skb) +{ + int end_marker_len =3D sizeof(HCI_DEVCD_HDR_END_MARKER) - 1; + /* + * don't use skb_tailroom(): observed to differ from requested + * alloc_size - skb->len + */ + int avail_room =3D HCI_DEVCD_HDR_SIZE_MAX - skb->len; + u8 *ptr; + + /* 1 char + '\n' at least */ + if (avail_room >=3D end_marker_len + 2) + return avail_room - end_marker_len; + + if (avail_room =3D=3D end_marker_len) + return 0; + + /* Avoid a blank line ('\n\n') in the header */ + skb_trim(skb, HCI_DEVCD_HDR_SIZE_MAX - end_marker_len - 1); + ptr =3D skb_tail_pointer(skb) - 1; + if (*ptr =3D=3D '\n') + *ptr =3D ' '; + skb_put_u8(skb, '\n'); + + return 0; +} + +/* + * btqcom_memdump_hdr - build the 512B memdump header + * @hdev: the HCI device + * @skb: the skb to build the header into + * + * Its 'Memdump Size' field is the size of the following payload. + */ +static void btqcom_memdump_hdr(struct hci_dev *hdev, struct sk_buff *skb) +{ + struct btqcom_data *qbt_data =3D hci_get_priv(hdev); + int avail_room =3D btqcom_memdump_hdr_room(skb); + struct btqcom_memdump *md =3D &qbt_data->md; + struct qbtc_subsys_data *subsys_data; + char buf[HCI_DEVCD_HDR_SIZE_MAX]; + struct qhci_btc_ver *btc_ver; + const char *build_info; + u16 board_id; + int len =3D 0; + + if (!avail_room) + return; + + len +=3D scnprintf(buf + len, sizeof(buf) - len, "Header Type: memdump\n"= ); + len +=3D scnprintf(buf + len, sizeof(buf) - len, "Header Size: %d\n", + HCI_DEVCD_HDR_SIZE_MAX); + len +=3D scnprintf(buf + len, sizeof(buf) - len, "Memdump Size: %u\n", + md->size); + len +=3D scnprintf(buf + len, sizeof(buf) - len, "Channel: %s\n", + qhci_pkt_type_name(md->from)); + len +=3D scnprintf(buf + len, sizeof(buf) - len, "Owner: %s\n", + qhci_subsys_name(md->subsys)); + + switch (md->subsys) { + case QHCI_SUBSYS_PERI: + subsys_data =3D &qbt_data->subsys[QBTC_SUBSYS_PERI]; + btc_ver =3D &subsys_data->ver; + board_id =3D subsys_data->board_id; + build_info =3D subsys_data->build_info; + break; + case QHCI_SUBSYS_TMEL: + subsys_data =3D &qbt_data->subsys[QBTC_SUBSYS_TMEL]; + btc_ver =3D &subsys_data->ver; + board_id =3D subsys_data->board_id; + build_info =3D subsys_data->build_info; + break; + default: + btc_ver =3D &qbt_data->ver; + board_id =3D qbt_data->board_id; + build_info =3D qbt_data->build_info; + break; + } + + len +=3D scnprintf(buf + len, sizeof(buf) - len, "SoC Version: 0x%08x\n", + btc_ver->soc_ver); + len +=3D scnprintf(buf + len, sizeof(buf) - len, "ROM Version: 0x%04x\n", + btc_ver->rom_ver); + len +=3D scnprintf(buf + len, sizeof(buf) - len, "Patch Version: 0x%04x\n= ", + btc_ver->patch_ver); + len +=3D scnprintf(buf + len, sizeof(buf) - len, "Board ID: 0x%04x\n", + board_id); + len +=3D scnprintf(buf + len, sizeof(buf) - len, "Firmware Version: %s\n", + build_info); + + if (len > avail_room) { + bt_dev_warn(hdev, "memdump header truncated (%d -> %d bytes)", + len, avail_room); + len =3D avail_room; + if (buf[len - 2] =3D=3D '\n') + buf[len - 2] =3D ' '; + buf[len - 1] =3D '\n'; + } + + skb_put_data(skb, buf, len); +} + +/* + * btusb_qcom_memdump_hdr - build the 512B file header + * @hdev: the HCI device + * @skb: the skb to build the header into + * + * Implements the dmp_hdr_t for hci_devcd_register(). + */ +static void btusb_qcom_memdump_hdr(struct hci_dev *hdev, struct sk_buff *s= kb) +{ + struct btusb_qcom *xport_data =3D btusb_qcom_xport_data(hdev); + struct btqcom_data *qbt_data =3D hci_get_priv(hdev); + char buf[HCI_DEVCD_HDR_SIZE_MAX]; + const char *vendor =3D "Qualcomm"; + int avail_room; + int len =3D 0; + + avail_room =3D btqcom_memdump_hdr_room(skb); + if (!avail_room) + return; + + len +=3D scnprintf(buf + len, sizeof(buf) - len, "Header Type: file\n"); + len +=3D scnprintf(buf + len, sizeof(buf) - len, "Header Size: %d\n", + HCI_DEVCD_HDR_SIZE_MAX); + len +=3D scnprintf(buf + len, sizeof(buf) - len, "Driver: %s\n", + qbt_data->drv_name); + len +=3D scnprintf(buf + len, sizeof(buf) - len, "Vendor: %s\n", vendor); + len +=3D scnprintf(buf + len, sizeof(buf) - len, "Controller Name: %s\n", + qbt_data->btc_name); + len +=3D scnprintf(buf + len, sizeof(buf) - len, "Controller Category: %s= \n", + qbtc_category_name(qbt_data->category)); + + if (qbt_data->category =3D=3D QBTC_CAT_MSUBSYS) { + if (qbt_data->flags & QBT_FLAG_HAS_TMEL) + len +=3D scnprintf(buf + len, sizeof(buf) - len, + "Controller Subsys: PERI, TME-L\n"); + else + len +=3D scnprintf(buf + len, sizeof(buf) - len, + "Controller Subsys: PERI\n"); + } + len +=3D scnprintf(buf + len, sizeof(buf) - len, "VID: 0x%04x\n", + xport_data->idVendor); + len +=3D scnprintf(buf + len, sizeof(buf) - len, "PID: 0x%04x\n", + xport_data->idProduct); + + if (len > avail_room) + bt_dev_warn(hdev, "dump header truncated (%d -> %d bytes)", + len, avail_room); + + memset(buf + len, 'P', sizeof(buf) - len); + + /* + * Deliberately overfill to skb's full 512 bytes, then leverage + * btqcom_memdump_hdr_room() to trim it back to the expected boundary. + */ + skb_put_data(skb, buf, HCI_DEVCD_HDR_SIZE_MAX - skb->len); + btqcom_memdump_hdr_room(skb); +} + +/* Used as the notify_change_t callback for hci_devcd_register(). */ +static void btusb_qcom_notify_memdump(struct hci_dev *hdev, int state) +{ + struct btusb_qcom *xport_data =3D btusb_qcom_xport_data(hdev); + struct btqcom_data *qbt_data =3D hci_get_priv(hdev); + enum devcoredump_state old_state; + + old_state =3D qbt_data->md_state; + + bt_dev_dbg(hdev, "memdump notify state: %s -> %s", + hci_devcd_state_name(old_state), + hci_devcd_state_name(state)); + bt_dev_dbg(hdev, "misc_flags: 0x%lx", READ_ONCE(qbt_data->misc_flags)); + bt_dev_dbg(hdev, "md_pending_flags: 0x%lx, md_submit_err: %d", + READ_ONCE(qbt_data->md_pending_flags), + READ_ONCE(qbt_data->md_submit_err)); + + switch (state) { + case HCI_DEVCOREDUMP_IDLE: + break; + case HCI_DEVCOREDUMP_ACTIVE: + usb_autopm_get_interface_no_resume(xport_data->intf); + bt_dev_dbg(hdev, "memdump notify: get autopm refcount"); + break; + case HCI_DEVCOREDUMP_TIMEOUT: + bt_dev_err(hdev, "memdump notify: %s", hci_devcd_state_name(state)); + qbt_data->md_submit_err =3D -ETIMEDOUT; + qbt_data->md_pending_flags =3D 0; + test_and_clear_bit(QBT_MISC_MEMDUMP_INCOMING, + &qbt_data->misc_flags); + fallthrough; + case HCI_DEVCOREDUMP_DONE: + case HCI_DEVCOREDUMP_ABORT: + usb_autopm_put_interface_no_suspend(xport_data->intf); + bt_dev_dbg(hdev, "memdump notify: put autopm refcount"); + break; + } + + qbt_data->md_state =3D state; +} + +/* handle received memdump frame here */ + +static inline void btqcom_reset_memdump(struct btqcom_memdump *md) +{ + memset(md, 0x00, sizeof(*md)); + md->subsys =3D QHCI_SUBSYS_INVALID; +} + +/* + * btqcom_submit_memdump - submit one memdump segment to HCI devcoredump + * @hdev: the HCI device the memdump comes from + * @skb: the memdump segment payload + * + * See the "Multi-subsys memdump design" block above for the overall scheme + * this implements. + * + * Return: 0 on success, + * 1 if the memdump ended normally, + * a negative errno on failure. + */ +static int btqcom_submit_memdump(struct hci_dev *hdev, struct sk_buff *skb) +{ + struct btqcom_data *qbt_data =3D hci_get_priv(hdev); + struct btqcom_memdump *md =3D &qbt_data->md; + unsigned int dump_size, seg_len; + bool is_first_subsys =3D false; + int ret =3D 0; + + dump_size =3D 0; + seg_len =3D skb->len; + + if (md->seqno =3D=3D 0) { + if (!test_and_set_bit(QBT_MISC_MEMDUMP_INCOMING, + &qbt_data->misc_flags)) { + /* wake the waiter in btusb_do_cmd_timeout_work() */ + wake_up_var(&qbt_data->misc_flags); + is_first_subsys =3D true; + qbt_data->md_submit_err =3D 0; + qbt_data->md_submit_size =3D 0; + if (qbt_data->category =3D=3D QBTC_CAT_MSUBSYS) { + qbt_data->md_pending_flags =3D QMD_FLAG_PENDING_BT | + QMD_FLAG_PENDING_PERI; + dump_size =3D HCI_DEVCD_HDR_SIZE_MAX + + btqcom_memdump_maxsize[QHCI_SUBSYS_BT] + + HCI_DEVCD_HDR_SIZE_MAX + + btqcom_memdump_maxsize[QHCI_SUBSYS_PERI]; + + if (qbt_data->flags & QBT_FLAG_HAS_TMEL) { + qbt_data->md_pending_flags |=3D QMD_FLAG_PENDING_TMEL; + dump_size +=3D HCI_DEVCD_HDR_SIZE_MAX + + btqcom_memdump_maxsize[QHCI_SUBSYS_TMEL]; + } + } else { + qbt_data->md_pending_flags =3D QMD_FLAG_PENDING_BT; + dump_size =3D HCI_DEVCD_HDR_SIZE_MAX + md->size; + } + } + + bt_dev_info(hdev, "%s memdump incoming: %u bytes", + qhci_subsys_name(md->subsys), md->size); + bt_dev_dbg(hdev, "md_pending_flags: 0x%lx", + qbt_data->md_pending_flags); + + if (is_first_subsys) { + ret =3D hci_devcd_init(hdev, dump_size); + if (ret) { + kfree_skb(skb); + bt_dev_err(hdev, "init memdump failed: %pe", + ERR_PTR(ret)); + return ret; + } + qbt_data->md_submit_size +=3D HCI_DEVCD_HDR_SIZE_MAX; + bt_dev_info(hdev, "file header: %u bytes", + HCI_DEVCD_HDR_SIZE_MAX); + } + + if (!qbt_data->md_submit_err) { + struct sk_buff *hdr_skb; + + hdr_skb =3D alloc_skb(HCI_DEVCD_HDR_SIZE_MAX, GFP_KERNEL); + if (hdr_skb) { + btqcom_memdump_hdr(hdev, hdr_skb); + if (hdr_skb->len < HCI_DEVCD_HDR_SIZE_MAX) + skb_put_zero(hdr_skb, + HCI_DEVCD_HDR_SIZE_MAX - hdr_skb->len); + ret =3D hci_devcd_append(hdev, hdr_skb); + } else { + ret =3D -ENOMEM; + } + if (ret) { + hci_devcd_abort(hdev); + kfree_skb(skb); + bt_dev_err(hdev, "append memdump header failed: %pe", + ERR_PTR(ret)); + return ret; + } + qbt_data->md_submit_size +=3D HCI_DEVCD_HDR_SIZE_MAX; + bt_dev_info(hdev, "%s memdump header appended: %u bytes", + qhci_subsys_name(md->subsys), HCI_DEVCD_HDR_SIZE_MAX); + } + } + + ret =3D qbt_data->md_submit_err; + if (ret) { + kfree_skb(skb); + } else { + ret =3D hci_devcd_append(hdev, skb); + if (unlikely(ret)) { + hci_devcd_abort(hdev); + bt_dev_err(hdev, "append memdump failed: %pe", ERR_PTR(ret)); + } else { + md->submit_size +=3D seg_len; + qbt_data->md_submit_size +=3D seg_len; + } + } + + if (md->seqno !=3D QHCI_MEMDUMP_SEQ_LAST) + return ret; + + if (md->submit_size =3D=3D md->size) + bt_dev_info(hdev, "%s memdump fully collected", + qhci_subsys_name(md->subsys)); + else + bt_dev_err(hdev, "%s memdump partially collected %u/%u bytes", + qhci_subsys_name(md->subsys), md->submit_size, md->size); + + qbt_data->md_pending_flags &=3D ~qmd_subsys_to_flag(md->subsys); + if (qbt_data->md_pending_flags) { + bt_dev_dbg(hdev, "md_pending_flags: 0x%lx", + qbt_data->md_pending_flags); + return ret; + } + + if (test_and_clear_bit(QBT_MISC_MEMDUMP_INCOMING, &qbt_data->misc_flags)) + bt_dev_dbg(hdev, "clear MEMDUMP_INCOMING on memdump completion"); + + if (ret) { + bt_dev_err(hdev, "memdump complete failed: %pe", ERR_PTR(ret)); + return ret; + } + + ret =3D hci_devcd_complete(hdev); + if (ret) { + hci_devcd_abort(hdev); + bt_dev_err(hdev, "memdump complete failed: %pe", ERR_PTR(ret)); + } else { + ret =3D 1; + bt_dev_info(hdev, "memdump completed: %u bytes", + qbt_data->md_submit_size); + } + return ret; +} + +/* + * btqcom_handle_memdump - handle one memdump segment + * @hdev: the HCI device the @skb comes from + * @skb: the memdump segment to handle + * + * Return: 0 on success, + * 1 if the memdump ended normally, + * a negative errno on failure. + */ +static int btqcom_handle_memdump(struct hci_dev *hdev, struct sk_buff *skb) +{ + struct btqcom_data *qbt_data =3D hci_get_priv(hdev); + struct btqcom_memdump *md =3D &qbt_data->md; + const struct qhci_vse_memdump *md_vse; + u8 pkt_type =3D hci_skb_pkt_type(skb); + int subsys =3D QHCI_SUBSYS_INVALID; + u16 seq_no =3D 0; + u32 dump_size; + int ret =3D 0; + + md_vse =3D skb_pull_data(skb, offsetof(struct qhci_vse_memdump, segdata)); + if (!md_vse) { + ret =3D -EMSGSIZE; + bt_dev_err(hdev, "bad memdump segment: too short (%u bytes)", skb->len); + goto out_abort_md; + } + + if (pkt_type =3D=3D QPERI_EVENT_PKT || pkt_type =3D=3D QPERI_ACLDATA_PKT)= { + subsys =3D md_vse->subsys; + if (subsys >=3D QHCI_SUBSYS_MAX || !btqcom_memdump_maxsize[subsys] || + (subsys =3D=3D QHCI_SUBSYS_TMEL && + !(qbt_data->flags & QBT_FLAG_HAS_TMEL))) { + kfree_skb(skb); + bt_dev_warn_ratelimited(hdev, + "drop memdump of unsupported subsys %d", + subsys); + return 0; + } + } + + seq_no =3D le16_to_cpu(md_vse->seqno); + if (seq_no =3D=3D 0) { + set_bit(qmd_subsys_to_bit(subsys), &qbt_data->misc_flags); + + if (!skb_pull(skb, QHCI_MEMDUMP_SEGDATA_GAP)) { + ret =3D -EMSGSIZE; + bt_dev_err(hdev, "bad first %s memdump segment: too short for dump_size= ", + qhci_subsys_name(subsys)); + goto out_abort_md; + } + + dump_size =3D le32_to_cpu(md_vse->dump_size); + if (!dump_size || dump_size > btqcom_memdump_maxsize[subsys]) { + ret =3D -EILSEQ; + bt_dev_err(hdev, "wrong %s memdump dump_size: %u", + qhci_subsys_name(subsys), dump_size); + goto out_abort_md; + } + + btqcom_reset_memdump(md); + md->from =3D pkt_type; + md->subsys =3D subsys; + md->size =3D dump_size; + } else { + if (md->subsys !=3D subsys) { + ret =3D -EILSEQ; + bt_dev_err_ratelimited(hdev, + "wrong memdump subsys: expected(%d), coming(%d)", + md->subsys, subsys); + goto out_abort_md; + } + + if (seq_no =3D=3D QHCI_MEMDUMP_SEQ_LAST) { + md->seqno =3D QHCI_MEMDUMP_SEQ_LAST; + } else if (seq_no !=3D md->seqno) { + ret =3D -EILSEQ; + bt_dev_err_ratelimited(hdev, + "wrong memdump seqno: expected(%u), coming(%u)", + md->seqno, seq_no); + goto out_abort_md; + } + } + + /* @skb is consumed here */ + ret =3D btqcom_submit_memdump(hdev, skb); + if (seq_no !=3D QHCI_MEMDUMP_SEQ_LAST) + md->seqno =3D seq_no + 1; + + goto out_reset_md; + +out_abort_md: + kfree_skb(skb); + if (qbt_data->md_pending_flags && !qbt_data->md_submit_err) { + hci_devcd_abort(hdev); + bt_dev_err(hdev, "abort memdump"); + } + +out_reset_md: + if (ret < 0 && !qbt_data->md_submit_err) + qbt_data->md_submit_err =3D ret; + + if (seq_no =3D=3D QHCI_MEMDUMP_SEQ_LAST) + btqcom_reset_memdump(md); + + return ret; +} + +/* + * =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D + * BT-HCI vendor-specific command/response + * =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D + */ + +/* reserved BT ACL handle for enhanced logging */ +#define QBT_HANDLE_ENHANCED_LOGGING 0xEDC +/* reserved BT ACL handle for memdump */ +#define QBT_HANDLE_MEMDUMP 0xEDD + +/* + * Unless otherwise noted, for the VSCs below: + * - command payload: struct qhci_cp_simple + * - response payload: struct qhci_rp_generic + * + * Grouped by { opcode, sub_opcode, cp, rp }. + */ + +/* BT EDL opcode and its sub-opcodes */ +#define QBT_OP_EDL 0xFC00 + +#define BEDL_PATCH_GETVER 0x19 +struct qbt_rp_patch_getver { + u8 status; + u8 sub_opcode; + u8 dlen; + __le32 product_id; + __le16 patch_ver; + __le16 rom_ver; + __le32 soc_ver; +} __packed; + +#define BEDL_GET_BUILD_INFO 0x20 +struct qbt_rp_get_build_info { + u8 status; + u8 sub_opcode; + u8 dlen; + u8 data[]; +} __packed; + +#define BEDL_GET_BOARD_ID 0x23 +struct qbt_rp_get_board_id { + u8 status; + u8 sub_opcode; + u8 dlen; + __be16 board_id; +} __packed; + +/* BT DEBUG opcode and its sub-opcodes */ +#define QBT_OP_DEBUG 0xFC0C + +#define BDBG_ERROR_FATAL_CMD 0x26 +/* no response */ + +/* BT write BD_ADDR opcode, no sub-opcode */ +#define QBT_OP_WRITE_BD_ADDR 0xFC14 +struct qbt_cp_write_bd_addr { + bdaddr_t bdaddr; +} __packed; + +/* BT firmware logging opcode and its sub-opcodes */ +#define QBT_OP_HOST_LOG 0xFC17 + +#define BHL_ENH_ENABLE_LOG 0x14 +struct qbt_cp_config_fw_logging { + u8 sub_opcode; + u8 flags; +} __packed; + +/* + * QBT_CHECK_RP_GENERIC - sanity check rp @_skb against type @_rp_type + * @_skb: the rp to check + * @_rp_type: the type to interpret @_skb as + * @_sub_opcode: sub_opcode to check @_skb against + * + * It is safe to evaluate @_skb and @_sub_opcode more than once for its + * usages. + * + * Returns a negative errno on failure, 0 on success, or error status code + * otherwise. + */ +#define QBT_CHECK_RP_GENERIC(_skb, _rp_type, _sub_opcode) \ +({ \ + _rp_type *_rp_ptr; \ + int _err =3D 0; \ + do { \ + if ((_skb)->len < sizeof(_rp_type)) { \ + _err =3D -EBADMSG; \ + break; \ + } \ + _rp_ptr =3D (void *)(_skb)->data; \ + if (_rp_ptr->sub_opcode !=3D (_sub_opcode)) { \ + _err =3D -EILSEQ; \ + break; \ + } \ + if (_rp_ptr->status) { \ + _err =3D _rp_ptr->status; \ + break; \ + } \ + } while (0); \ + _err; \ +}) + +/* check the @dlen field on top of QBT_CHECK_RP_GENERIC() */ +#define QBT_CHECK_RP_DLEN(_skb, _rp_type, _sub_opcode) \ +({ \ + _rp_type *_rp_ptr; \ + int _err =3D 0; \ + do { \ + _err =3D QBT_CHECK_RP_GENERIC(_skb, _rp_type, _sub_opcode);\ + if (_err) \ + break; \ + _rp_ptr =3D (void *)(_skb)->data; \ + if ((_skb)->len !=3D offsetofend(_rp_type, dlen) + \ + _rp_ptr->dlen) { \ + _err =3D -EMSGSIZE; \ + break; \ + } \ + } while (0); \ + _err; \ +}) + +/* + * qbt_edl_patch_getver - read BTC version info + * @hdev: the HCI device to query + * @ver: output version info, filled on success + * + * Return: 0 on success, or a negative errno on failure. + */ +static int qbt_edl_patch_getver(struct hci_dev *hdev, struct qhci_btc_ver = *ver) +{ + const struct qhci_cp_simple cp =3D { .sub_opcode =3D BEDL_PATCH_GETVER }; + struct qbt_rp_patch_getver *rp; + struct sk_buff *skb; + int err; + + skb =3D __hci_cmd_sync_ev(hdev, QBT_OP_EDL, sizeof(cp), + &cp, 0, HCI_INIT_TIMEOUT); + if (IS_ERR(skb)) { + err =3D PTR_ERR(skb); + goto out; + } + + err =3D QBT_CHECK_RP_DLEN(skb, struct qbt_rp_patch_getver, cp.sub_opcode); + if (err) + goto out_free_skb; + + rp =3D (void *)skb->data; + ver->product_id =3D le32_to_cpu(rp->product_id); + ver->soc_ver =3D le32_to_cpu(rp->soc_ver); + ver->rom_ver =3D le16_to_cpu(rp->rom_ver); + ver->patch_ver =3D le16_to_cpu(rp->patch_ver); + + bt_dev_dbg(hdev, "Product ID :0x%08x", ver->product_id); + bt_dev_dbg(hdev, "SOC Version :0x%08x", ver->soc_ver); + bt_dev_dbg(hdev, "ROM Version :0x%04x", ver->rom_ver); + bt_dev_dbg(hdev, "Patch Version:0x%04x", ver->patch_ver); + + if (ver->soc_ver =3D=3D 0 || ver->rom_ver =3D=3D 0) + err =3D -EILSEQ; + +out_free_skb: + kfree_skb(skb); + + if (err > 0) { + bt_dev_dbg(hdev, "get BT version status error: 0x%02x", + err); + err =3D -bt_to_errno(err); + } +out: + if (err) + bt_dev_err(hdev, "get BT version failed: %pe", ERR_PTR(err)); + + return err; +} + +/* + * qbt_edl_get_build_info - get BTC build info string + * @hdev: the HCI device to query + * @build_info: output build info string, allocated on success; caller fre= es + * + * Return: 0 on success, or a negative errno on failure. + */ +static int qbt_edl_get_build_info(struct hci_dev *hdev, char **build_info) +{ + const struct qhci_cp_simple cp =3D { .sub_opcode =3D BEDL_GET_BUILD_INFO = }; + struct qbt_rp_get_build_info *rp; + struct sk_buff *skb; + int err; + + skb =3D __hci_cmd_sync_ev(hdev, QBT_OP_EDL, sizeof(cp), + &cp, 0, HCI_INIT_TIMEOUT); + if (IS_ERR(skb)) { + err =3D PTR_ERR(skb); + goto out; + } + + err =3D QBT_CHECK_RP_DLEN(skb, struct qbt_rp_get_build_info, cp.sub_opcod= e); + if (err) + goto out_free_skb; + + rp =3D (void *)skb->data; + *build_info =3D kmemdup_nul(rp->data, rp->dlen, GFP_KERNEL); + if (!*build_info) { + err =3D -ENOMEM; + goto out_free_skb; + } + + bt_dev_dbg(hdev, "BT build info: %s", *build_info); + +out_free_skb: + kfree_skb(skb); + + if (err > 0) { + bt_dev_err(hdev, "get BT build info status error: 0x%02x", + err); + err =3D -bt_to_errno(err); + } +out: + if (err) + bt_dev_err(hdev, "get BT build info failed: %pe", ERR_PTR(err)); + + return err; +} + +/* + * qbt_edl_get_board_id - get BTC board ID + * @hdev: the HCI device to query + * @board_id: output board ID, filled on success + * + * Return: 0 on success, or a negative errno on failure. + */ +static int qbt_edl_get_board_id(struct hci_dev *hdev, u16 *board_id) +{ + const struct qhci_cp_simple cp =3D { .sub_opcode =3D BEDL_GET_BOARD_ID }; + struct qbt_rp_get_board_id *rp; + struct sk_buff *skb; + int err; + + skb =3D __hci_cmd_sync_ev(hdev, QBT_OP_EDL, sizeof(cp), + &cp, 0, HCI_INIT_TIMEOUT); + if (IS_ERR(skb)) { + err =3D PTR_ERR(skb); + goto out; + } + + err =3D QBT_CHECK_RP_DLEN(skb, struct qbt_rp_get_board_id, cp.sub_opcode); + if (err) + goto out_free_skb; + + rp =3D (void *)skb->data; + *board_id =3D be16_to_cpu(rp->board_id); + /* Take 0xffff as invalid board ID */ + if (*board_id =3D=3D 0xffff) + *board_id =3D 0; + + bt_dev_dbg(hdev, "BT Board ID: 0x%04x", *board_id); + +out_free_skb: + kfree_skb(skb); + + if (err > 0) { + bt_dev_dbg(hdev, "get BT board ID status error: 0x%02x", + err); + err =3D -bt_to_errno(err); + } +out: + if (err) + bt_dev_err(hdev, "get BT board ID failed: %pe", ERR_PTR(err)); + + return err; +} + +/* + * qbt_error_fatal_cmd - trigger a controller-side fatal error for debuggi= ng + * @hdev: the HCI device to command + * + * Return: 0 on success, or a negative errno on failure. + */ +static int qbt_error_fatal_cmd(struct hci_dev *hdev) +{ + const struct qhci_cp_simple cp =3D { .sub_opcode =3D BDBG_ERROR_FATAL_CMD= }; + int err; + + err =3D __hci_cmd_send(hdev, QBT_OP_DEBUG, sizeof(cp), &cp); + if (err < 0) + bt_dev_err(hdev, "send error fatal cmd failed: %d", err); + else + bt_dev_info(hdev, "send error fatal cmd succeeded"); + + return err; +} + +/* + * qbt_write_bda - set the controller's BD address + * @hdev: the HCI device to configure + * @bdaddr: the address to set + * + * Return: 0 on success, or a negative errno on failure. + */ +static int qbt_write_bda(struct hci_dev *hdev, const bdaddr_t *bdaddr) +{ + struct qbt_cp_write_bd_addr cp; + int err; + + /* The controller expects the address in reversed byte order. */ + baswap(&cp.bdaddr, bdaddr); + + err =3D __hci_cmd_sync_status(hdev, QBT_OP_WRITE_BD_ADDR, + sizeof(cp), &cp, HCI_INIT_TIMEOUT); + if (err < 0) { + bt_dev_err(hdev, "set BT address failed: %pe", + ERR_PTR(err)); + return err; + } + if (err > 0) { + bt_dev_err(hdev, "set BT address status error: 0x%02x", + err); + return -bt_to_errno(err); + } + + bt_dev_info(hdev, "BT address set to %pMR", bdaddr); + + return 0; +} + +/* + * qbt_config_fw_logging - configure enhanced firmware logging + * @hdev: the HCI device to configure + * @flags: logging configuration flags, normally 1 (enable) or 0 (disable) + * + * Return: 0 on success, or a negative errno on failure. + */ +static int qbt_config_fw_logging(struct hci_dev *hdev, u8 flags) +{ + const struct qbt_cp_config_fw_logging cp =3D { + .sub_opcode =3D BHL_ENH_ENABLE_LOG, + .flags =3D flags, + }; + struct sk_buff *skb; + int err; + + skb =3D __hci_cmd_sync_ev(hdev, QBT_OP_HOST_LOG, sizeof(cp), + &cp, 0, HCI_INIT_TIMEOUT); + if (IS_ERR(skb)) { + err =3D PTR_ERR(skb); + goto out; + } + + err =3D QBT_CHECK_RP_GENERIC(skb, struct qhci_rp_generic, cp.sub_opcode); + if (err) + goto out_free_skb; + + bt_dev_dbg(hdev, "firmware logging configured (flags=3D0x%02x)", flags); + +out_free_skb: + kfree_skb(skb); + + if (err > 0) { + bt_dev_dbg(hdev, "firmware logging config status error: 0x%02x", err); + err =3D -bt_to_errno(err); + } +out: + if (err) + bt_dev_err(hdev, "firmware logging config failed: %pe", ERR_PTR(err)); + + return err; +} + +/* + * =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D + * qperi_handle_evt(): per-event handler and TX sync event sequence + * =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D + */ + +#define QPERI_EV_CLASS_PERI 0xF0 +#define QPERI_EV_TYPE_INVALID 0xff + +/* Pass QPERI_EV_TYPE_INVALID as event if no events to sync */ +#define DEFINE_QPERI_REQ_SPEC(specname, _opcode, _sub_opcode, _events...) \ + const struct qhci_req_spec specname =3D { \ + .opcode =3D (_opcode), \ + .sub_opcode =3D (_sub_opcode), \ + .event =3D { _events, QPERI_EV_TYPE_INVALID }, \ + } + +/* + * struct qperi_evt - per-event helper for qperi_handle_evt() + * + * @handler: event handler, executed before the TX sync event check. + * Return: true if @skb was consumed, false otherwise. + * + * @verify_wakeup: extra verify if @skb belongs to the event sequence bein= g synced. + * + * Pull @skb empty if all its own checks pass and nothing is left for the + * requester to verify, and the requester will get NULL as response, e.g. a + * CSE, or a CCE whose payload has only status, or only status and + * sub_opcode. + * + * Return: 0 if @skb doesn't belong, 1 if it does, or a negative errno if + * @skb is malformed. + */ +struct qperi_evt { + bool (*handler)(struct hci_dev *hdev, struct sk_buff *skb); + int (*verify_wakeup)(struct hci_dev *hdev, struct qhci_req *req, + struct sk_buff *skb); +}; + +#define PERI_EV_CMD_STATUS 0x00 +struct peri_ev_cmd_status { + u8 status; + u8 ncmd; + __le16 opcode; +} __packed; + +#define PERI_EV_CMD_COMPLETE 0x01 +struct peri_ev_cmd_complete { + u8 ncmd; + __le16 opcode; +} __packed; + +#define PERI_EV_SUBSYS_ACTIVATE_COMPLETE 0x02 +struct peri_ev_subsys_activate_complete { + u8 subsys; + u8 action; +} __packed; + +#define PERI_EV_SUBSYS_PATCH_NOTIFICATION 0x03 +struct peri_ev_subsys_patch_notification { + u8 status; + u8 subsys; +} __packed; + +#define PERI_EV_CRASH_DUMP_MEMDUMP 0x04 +/* struct qhci_vse_memdump */ + +#define PERI_EV_HARDWARE_ERROR 0x06 +struct peri_ev_hardware_error { + u8 code; +} __packed; + +/* As qperi_evt::verify_wakeup() for PERI_EV_CMD_STATUS */ +static int peri_verify_cmd_status(struct hci_dev *hdev __maybe_unused, + struct qhci_req *req, struct sk_buff *skb) +{ + const struct peri_ev_cmd_status *ev; + + if (skb->len !=3D sizeof(*ev)) + return -EMSGSIZE; + + ev =3D (const void *)skb->data; + + if (le16_to_cpu(ev->opcode) !=3D req->spec->opcode) + return 0; + + req->req_result =3D ev->status; + if (!req->req_result) + skb_pull(skb, sizeof(*ev)); + + return 1; +} + +/* CCE rp payload: [0] status, [1] sub_opcode if present, then rp-specific= data if any */ +static int peri_verify_rp_payload(struct hci_dev *hdev __maybe_unused, + struct qhci_req *req, struct sk_buff *skb) +{ + if (!skb->len) + return -EMSGSIZE; + + if (req->spec->sub_opcode !=3D QHCI_SUB_OPCODE_INVALID) { + if (skb->len < 2) + return -EMSGSIZE; + + if (skb->data[1] !=3D req->spec->sub_opcode) + return 0; + } + + req->req_result =3D skb->data[0]; + if (!req->req_result && skb->len <=3D 2) + skb_pull(skb, skb->len); + + return 1; +} + +/* As qperi_evt::verify_wakeup() for PERI_EV_CMD_COMPLETE */ +static int peri_verify_cmd_complete(struct hci_dev *hdev, struct qhci_req = *req, + struct sk_buff *skb) +{ + const struct peri_ev_cmd_complete *ev; + u16 opcode; + + ev =3D skb_pull_data(skb, sizeof(*ev)); + if (!ev) + return -EMSGSIZE; + + opcode =3D le16_to_cpu(ev->opcode); + if (opcode !=3D req->spec->opcode) + return 0; + + hci_skb_opcode(skb) =3D opcode; + + return peri_verify_rp_payload(hdev, req, skb); +} + +/* As qperi_evt::verify_wakeup() for PERI_EV_SUBSYS_ACTIVATE_COMPLETE */ +static int peri_verify_subsys_activate_complete(struct hci_dev *hdev __may= be_unused, + struct qhci_req *req __maybe_unused, + struct sk_buff *skb) +{ + if (skb->len !=3D sizeof(struct peri_ev_subsys_activate_complete)) + return -EMSGSIZE; + + return 1; +} + +/* As qperi_evt::verify_wakeup() for PERI_EV_SUBSYS_PATCH_NOTIFICATION */ +static int peri_verify_subsys_patch_notification(struct hci_dev *hdev __ma= ybe_unused, + struct qhci_req *req, + struct sk_buff *skb) +{ + const struct peri_ev_subsys_patch_notification *ev; + + if (skb->len !=3D sizeof(*ev)) + return -EMSGSIZE; + + ev =3D (const void *)skb->data; + req->req_result =3D ev->status; + + return 1; +} + +/* As qperi_evt::handler() for PERI_EV_CRASH_DUMP_MEMDUMP */ +static bool peri_crash_dump_memdump(struct hci_dev *hdev, struct sk_buff *= skb) +{ + btqcom_handle_memdump(hdev, skb); + + return true; +} + +/* As qperi_evt::handler() for PERI_EV_HARDWARE_ERROR */ +static bool peri_hardware_error(struct hci_dev *hdev, struct sk_buff *skb) +{ + struct btqcom_data *qbt_data =3D hci_get_priv(hdev); + const struct peri_ev_hardware_error *ev; + u8 code =3D HCI_ERROR_UNSPECIFIED; + + ev =3D skb_pull_data(skb, sizeof(*ev)); + if (ev) + code =3D ev->code; + else + bt_dev_err(hdev, "malformed PERI hardware error event"); + + set_bit(QBT_MISC_HWERR_PERI, &qbt_data->misc_flags); + __hci_reset_dev(hdev, code); + + return false; +} + +static const struct qperi_evt qperi_evt_table[] =3D { + /* [0x00 =3D PERI_EV_CMD_STATUS] */ + [PERI_EV_CMD_STATUS] =3D { + .verify_wakeup =3D peri_verify_cmd_status, + }, + /* [0x01 =3D PERI_EV_CMD_COMPLETE] */ + [PERI_EV_CMD_COMPLETE] =3D { + .verify_wakeup =3D peri_verify_cmd_complete, + }, + /* [0x02 =3D PERI_EV_SUBSYS_ACTIVATE_COMPLETE] */ + [PERI_EV_SUBSYS_ACTIVATE_COMPLETE] =3D { + .verify_wakeup =3D peri_verify_subsys_activate_complete, + }, + /* [0x03 =3D PERI_EV_SUBSYS_PATCH_NOTIFICATION] */ + [PERI_EV_SUBSYS_PATCH_NOTIFICATION] =3D { + .verify_wakeup =3D peri_verify_subsys_patch_notification, + }, + /* [0x04 =3D PERI_EV_CRASH_DUMP_MEMDUMP] */ + [PERI_EV_CRASH_DUMP_MEMDUMP] =3D { + .handler =3D peri_crash_dump_memdump, + }, + /* [0x06 =3D PERI_EV_HARDWARE_ERROR] */ + [PERI_EV_HARDWARE_ERROR] =3D { + .handler =3D peri_hardware_error, + }, +}; + +/* + * qperi_try_wakeup - check if event @skb with type @ev_type can wake up t= he requester + * @hdev: the HCI device the event comes from + * @ev_type: the event type + * @skb: the event payload, or NULL on skb clone failure + * + * Note: the caller only cares about the return value rather than the wake= up result. + * + * Return: true if @skb was consumed, false otherwise. + */ +static bool qperi_try_wakeup(struct hci_dev *hdev, u8 ev_type, + struct sk_buff *skb) +{ + const struct qperi_evt *evt =3D &qperi_evt_table[ev_type]; + struct btqcom_data *qbt_data =3D hci_get_priv(hdev); + struct sk_buff *req_rsp =3D skb; + struct qhci_req *req; + int res; + + /* fast path */ + if (READ_ONCE(qbt_data->req_state) !=3D QHCI_REQ_PEND) + return false; + + guard(spinlock)(&qbt_data->req_spinlock); + + if (qbt_data->req_state !=3D QHCI_REQ_PEND) + return false; + + req =3D qbt_data->req; + if (WARN_ON_ONCE(!req) || WARN_ON_ONCE(!req->spec)) + return false; + + if (req->spec->event[req->event_idx] !=3D ev_type) + return false; + + /* skb clone failure */ + if (!skb) { + req->req_result =3D -ENOMEM; + goto out_wakeup; + } + + /* malformed frame */ + if (!skb->len) { + req->req_result =3D -EMSGSIZE; + goto out_wakeup; + } + + req->req_result =3D 0; + if (evt->verify_wakeup) { + res =3D evt->verify_wakeup(hdev, req, skb); + if (!res) + return false; + + if (res < 0) { + req->req_result =3D res; + goto out_wakeup; + } + + /* HCI status error */ + if (req->req_result) + goto out_wakeup; + + /* nothing left for the requester to verify */ + if (!req_rsp->len) + req_rsp =3D NULL; + } + + if (req->spec->event[req->event_idx + 1] !=3D QPERI_EV_TYPE_INVALID) { + req->event_idx++; + return false; + } + +out_wakeup: + if (req->req_result < 0) + req_rsp =3D NULL; + + req->req_rsp =3D req_rsp; + if (req->req_result) + bt_dev_err(hdev, "PERI cmd opcode(0x%04x) sub_opcode(0x%02x) failed on e= v_type(0x%02x): %d", + req->spec->opcode, req->spec->sub_opcode, ev_type, req->req_result); + qbt_data->req_state =3D QHCI_REQ_DONE; + wake_up_interruptible(&qbt_data->req_wait_q); + /* test if the requester now owns @skb */ + return req_rsp =3D=3D skb; +} + +/* + * __qperi_tx_sync_evt - send a frame and sync its event sequence + * @hdev: the HCI device + * @iter: the frame to send, NULL to sync only + * @spec: the request spec to sync, NULL to send only + * @timeout: timeout in jiffies for the sync + * @evt_idx: index of the waking event + * @status: HCI status code if it has one, 0 otherwise + * + * Requires the caller holds qbt_data->req_mutex. + * + * Return: ERR_PTR() on failure, + * NULL on success for send-only or nothing left for the caller to verify, + * the waking event otherwise. + */ +static struct sk_buff *__qperi_tx_sync_evt(struct hci_dev *hdev, + struct iov_iter *iter, + const struct qhci_req_spec *spec, + unsigned int timeout, + u8 *evt_idx, u8 *status) +{ + struct qhci_req req =3D { .pkt_type =3D QPERI_EVENT_PKT, .spec =3D spec }; + struct btqcom_data *qbt_data =3D hci_get_priv(hdev); + struct sk_buff *ret =3D NULL; + int res =3D 0, wait_res =3D 0; + int req_state; + + if (!iter && !spec) + return ERR_PTR(-EINVAL); + + /* send only, no event to sync */ + if (!spec) { + res =3D hci_send_vendor_frame(hdev, iter); + if (res < 0) { + bt_dev_err(hdev, "send-only PERI frame failed: %pe", ERR_PTR(res)); + return ERR_PTR(res); + } + return NULL; + } + + scoped_guard(spinlock, &qbt_data->req_spinlock) { + qbt_data->req =3D &req; + qbt_data->req_state =3D QHCI_REQ_PEND; + } + + if (iter) + res =3D hci_send_vendor_frame(hdev, iter); + + if (!res) + wait_res =3D wait_event_interruptible_timeout(qbt_data->req_wait_q, + READ_ONCE(qbt_data->req_state) !=3D + QHCI_REQ_PEND, + timeout); + + scoped_guard(spinlock, &qbt_data->req_spinlock) { + req_state =3D qbt_data->req_state; + qbt_data->req =3D NULL; + qbt_data->req_state =3D QHCI_REQ_DONE; + } + + if (res >=3D 0) { + switch (req_state) { + case QHCI_REQ_DONE: + res =3D req.req_result; + break; + case QHCI_REQ_CANCELED: + if (req.req_result < 0) + bt_dev_info(hdev, "PERI cmd opcode(0x%04x) sub_opcode(0x%02x) canceled= : %pe", + spec->opcode, spec->sub_opcode, + ERR_PTR(req.req_result)); + else + bt_dev_info(hdev, "PERI cmd opcode(0x%04x) sub_opcode(0x%02x) canceled= ", + spec->opcode, spec->sub_opcode); + res =3D req.req_result < 0 ? req.req_result : -ECANCELED; + break; + case QHCI_REQ_PEND: + default: + res =3D wait_res < 0 ? -EINTR : -ETIMEDOUT; + break; + } + } + + ret =3D req.req_rsp; + if (res < 0) { + /* log unless qperi_try_wakeup() already did */ + if (res !=3D req.req_result) + bt_dev_err(hdev, "PERI cmd opcode(0x%04x) sub_opcode(0x%02x) failed: %p= e", + spec->opcode, spec->sub_opcode, ERR_PTR(res)); + + if (WARN_ON_ONCE(req.req_rsp)) + kfree_skb(req.req_rsp); + + ret =3D ERR_PTR(res); + } + + if (evt_idx) + *evt_idx =3D req.event_idx; + if (status) + *status =3D (u8)req.req_result; + return ret; +} + +/* synchronously cancel pending request with error code @err */ +static void qperi_tx_sync_cancel_sync(struct hci_dev *hdev, int err) +{ + struct btqcom_data *qbt_data =3D hci_get_priv(hdev); + struct qhci_req *req; + + guard(spinlock)(&qbt_data->req_spinlock); + + if (qbt_data->req_state !=3D QHCI_REQ_PEND) + return; + + req =3D qbt_data->req; + if (WARN_ON_ONCE(!req)) + return; + + req->req_result =3D err; + qbt_data->req =3D NULL; + qbt_data->req_state =3D QHCI_REQ_CANCELED; + + wake_up_interruptible(&qbt_data->req_wait_q); +} + +/* + * __qperi_cmd_sync_evt - send a command and sync its event sequence + * @hdev: the HCI device + * @opcode: command opcode, QHCI_OPCODE_INVALID to sync only + * @plen: length of @cp + * @cp: the command payload + * @spec: the request spec to sync, NULL to send only + * @evt_idx: index of the waking event + * @status: HCI status code if it has one, 0 otherwise + * + * Requires the caller holds qbt_data->req_mutex. + * + * Return: ERR_PTR() on failure, + * NULL on success for send-only or nothing left for the caller to verify, + * the waking event otherwise. + */ +static struct sk_buff *__qperi_cmd_sync_evt(struct hci_dev *hdev, + u16 opcode, u32 plen, const void *cp, + const struct qhci_req_spec *spec, + u8 *evt_idx, u8 *status) +{ + u8 pkt_type =3D QPERI_COMMAND_PKT; + struct qperi_command_hdr cmd_hdr =3D { + .host_id =3D QHCI_HOST_ID_BT, + .opcode =3D cpu_to_le16(opcode), + .plen =3D (u8)plen, + }; + struct kvec cmd_kv[] =3D { + { .iov_base =3D &pkt_type, .iov_len =3D 1 }, + { .iov_base =3D &cmd_hdr, .iov_len =3D sizeof(cmd_hdr) }, + { .iov_base =3D (void *)cp, .iov_len =3D plen }, + }; + struct iov_iter iter; + + /* no command to send, sync only */ + if (opcode =3D=3D QHCI_OPCODE_INVALID) + return __qperi_tx_sync_evt(hdev, NULL, spec, HCI_INIT_TIMEOUT, evt_idx, = status); + + iov_iter_kvec(&iter, ITER_SOURCE, cmd_kv, plen ? 3 : 2, + 1 + sizeof(cmd_hdr) + plen); + + return __qperi_tx_sync_evt(hdev, &iter, spec, HCI_INIT_TIMEOUT, evt_idx, = status); +} + +static inline struct sk_buff *qperi_cmd_sync_evt(struct hci_dev *hdev, + u16 opcode, u32 plen, const void *cp, + const struct qhci_req_spec *spec, + u8 *evt_idx, u8 *status) +{ + struct btqcom_data *qbt_data =3D hci_get_priv(hdev); + + guard(mutex)(&qbt_data->req_mutex); + + return __qperi_cmd_sync_evt(hdev, opcode, plen, cp, spec, evt_idx, status= ); +} + +/* + * send a command and sync an event with type @ev_type; + * @cp's first byte is sub_opcode for all commands so far + * + * Requires the caller holds qbt_data->req_mutex. + */ +static struct sk_buff *__qperi_cmd_sync_one_evt(struct hci_dev *hdev, + u16 opcode, u32 plen, const void *cp, + u8 ev_type, u8 *status) +{ + u8 sub_opcode =3D plen ? *(const u8 *)cp : QHCI_SUB_OPCODE_INVALID; + DEFINE_QPERI_REQ_SPEC(spec, opcode, sub_opcode, ev_type); + + return __qperi_cmd_sync_evt(hdev, opcode, plen, cp, &spec, NULL, status); +} + +static inline struct sk_buff *qperi_cmd_sync_one_evt(struct hci_dev *hdev, + u16 opcode, u32 plen, const void *cp, + u8 ev_type, u8 *status) +{ + struct btqcom_data *qbt_data =3D hci_get_priv(hdev); + + guard(mutex)(&qbt_data->req_mutex); + + return __qperi_cmd_sync_one_evt(hdev, opcode, plen, cp, ev_type, status); +} + +/* + * only wait for an unsolicited event with type @ev_type + * + * Requires the caller holds qbt_data->req_mutex. + */ +static struct sk_buff __maybe_unused *__qperi_wait_one_evt(struct hci_dev = *hdev, + u8 ev_type, u8 *status) +{ + DEFINE_QPERI_REQ_SPEC(spec, QHCI_OPCODE_INVALID, QHCI_SUB_OPCODE_INVALID,= ev_type); + + return __qperi_tx_sync_evt(hdev, NULL, &spec, HCI_INIT_TIMEOUT, NULL, sta= tus); +} + +/* + * handle a received PERI event @skb, called from hdev->recv_vendor_pkt(); + * generic processing first, then the TX sync event, without interfering + */ +static void qperi_handle_evt(struct hci_dev *hdev, struct sk_buff *skb) +{ + struct btqcom_data *qbt_data =3D hci_get_priv(hdev); + const struct qhci_vse_comm *vse_comm; + const struct qperi_event_hdr *hdr; + const struct qperi_evt *evt_entry; + /* for qperi_try_wakeup() call below */ + struct sk_buff *orig_skb =3D NULL; + u8 ev_type; + int res; + + ev_type =3D QPERI_EV_TYPE_INVALID; + res =3D -EBADMSG; + hdr =3D skb_pull_data(skb, QPERI_EVENT_HDR_SIZE); + if (!hdr || hdr->evt !=3D HCI_EV_VENDOR) + goto out_free_skb; + + res =3D -EILSEQ; + vse_comm =3D skb_pull_data(skb, QHCI_VSE_COMM_SIZE); + if (!vse_comm || vse_comm->ev_class !=3D QPERI_EV_CLASS_PERI) + goto out_free_skb; + + ev_type =3D vse_comm->ev_type; + res =3D -ENOENT; + if (ev_type >=3D ARRAY_SIZE(qperi_evt_table)) + goto out_free_skb; + + res =3D 0; + hci_skb_event(skb) =3D ev_type; + evt_entry =3D &qperi_evt_table[ev_type]; + + if (evt_entry->handler) { + const struct qhci_req *req; + bool may_wakeup; + + spin_lock(&qbt_data->req_spinlock); + req =3D qbt_data->req; + may_wakeup =3D qbt_data->req_state =3D=3D QHCI_REQ_PEND && + req && req->spec && + req->spec->event[req->event_idx] =3D=3D ev_type; + spin_unlock(&qbt_data->req_spinlock); + + if (may_wakeup) { + orig_skb =3D skb_clone(skb, GFP_KERNEL); + if (!orig_skb) + res =3D -ENOMEM; + } + + if (evt_entry->handler(hdev, skb)) + skb =3D NULL; + } else { + orig_skb =3D skb; + skb =3D NULL; + } + + if (qperi_try_wakeup(hdev, ev_type, orig_skb)) + orig_skb =3D NULL; + +out_free_skb: + kfree_skb(orig_skb); + kfree_skb(skb); + if (res < 0) + bt_dev_err(hdev, "fails to handle PERI event with type 0x%02x: %pe", + ev_type, ERR_PTR(res)); +} + +/* + * =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D + * PERI-HCI vendor-specific command/response + * =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D + */ + +/* reserved PERI ACL handle for enhanced logging */ +#define QPERI_HANDLE_ENHANCED_LOGGING 0xEC0 +/* reserved PERI ACL handle for memdump */ +#define QPERI_HANDLE_MEMDUMP 0xEC1 + +/* + * Unless otherwise noted, for the VSCs below: + * - command payload: struct qhci_cp_simple + * - response payload: struct qhci_rp_generic + * + * Grouped by { opcode, sub_opcode, cp, rp }. + */ + +/* PERI EDL opcode and its sub-opcodes */ +#define QPERI_OP_EDL 0xFFF0 + +#define PEDL_GET_BUILD_INFO 0x09 +struct qperi_cp_generic { + u8 sub_opcode; + u8 subsys; +} __packed; +struct qperi_rp_get_build_info { + u8 status; + u8 sub_opcode; + u8 subsys; + u8 dlen; + u8 data[]; +} __packed; + +/* PERI Generic opcode and its sub-opcodes */ +#define QPERI_OP_GENERIC 0xFFF1 + +#define PGEN_PERI_RESET 0x03 +/* struct qhci_rp_generic */ +/* struct peri_ev_subsys_patch_notification */ + +#define PGEN_INITIATE_BT_CRASH 0x05 +/* struct peri_ev_cmd_status */ + +#define __QPERI_CP_INIT_SUBSYS_0(...) +#define __QPERI_CP_INIT_SUBSYS_1(_subsys, ...) .subsys =3D (_subsys), + +#define QPERI_CP_INITIALIZER(_sub_opcode, _subsys...) { \ + .sub_opcode =3D (_sub_opcode), \ + CONCATENATE(__QPERI_CP_INIT_SUBSYS_, COUNT_ARGS(_subsys))(_subsys) \ +} + +/* + * QPERI_CHECK_RP_LEN - check rp @_skb's length and its @dlen + * @_skb: the rp to check + * @_rp_type: the type to interpret @_skb as + * + * It is safe to evaluate @_skb more than once for its usages. + * + * Returns a negative errno on failure, or 0 on success. + */ +#define QPERI_CHECK_RP_LEN(_skb, _rp_type) \ +({ \ + _rp_type *_rp_ptr; \ + int _err =3D 0; \ + do { \ + if (!(_skb) || (_skb)->len < sizeof(_rp_type)) { \ + _err =3D -EBADMSG; \ + break; \ + } \ + _rp_ptr =3D (void *)(_skb)->data; \ + if ((_skb)->len !=3D offsetofend(_rp_type, dlen) + \ + _rp_ptr->dlen) { \ + _err =3D -EMSGSIZE; \ + break; \ + } \ + } while (0); \ + _err; \ +}) + +/* + * qperi_get_subsys_build_info - get subsystem build info string + * @hdev: the HCI device to query + * @subsys: the subsystem to query + * @build_info: output build info string, allocated on success; caller fre= es + * + * Return: 0 on success, or a negative errno on failure. + */ +static int qperi_get_subsys_build_info(struct hci_dev *hdev, u8 subsys, + char **build_info) +{ + struct qperi_cp_generic cp =3D QPERI_CP_INITIALIZER(PEDL_GET_BUILD_INFO, = subsys); + struct qperi_rp_get_build_info *rp; + struct sk_buff *skb; + u8 status =3D 0; + int ret; + + skb =3D qperi_cmd_sync_one_evt(hdev, QPERI_OP_EDL, sizeof(cp), &cp, + PERI_EV_CMD_COMPLETE, &status); + if (IS_ERR(skb)) + return PTR_ERR(skb); + + ret =3D QPERI_CHECK_RP_LEN(skb, struct qperi_rp_get_build_info); + if (ret) + goto out_free_skb; + rp =3D (void *)skb->data; + + if (status) { + ret =3D -bt_to_errno(status); + goto out_free_skb; + } + + if (rp->subsys !=3D subsys) { + ret =3D -EILSEQ; + goto out_free_skb; + } + + *build_info =3D kmemdup_nul(rp->data, rp->dlen, GFP_KERNEL); + ret =3D *build_info ? 0 : -ENOMEM; + +out_free_skb: + kfree_skb(skb); + + if (ret) + bt_dev_err(hdev, "get %s build info failed: %pe", + qhci_subsys_name(subsys), ERR_PTR(ret)); + else + bt_dev_dbg(hdev, "%s build info: %s", + qhci_subsys_name(subsys), *build_info); + + return ret; +} + +/* + * qperi_hci_reset - reset PERI HCI + * @hdev: the HCI device to reset + * + * Return: 0 on success, or a negative errno on failure. + */ +static int qperi_hci_reset(struct hci_dev *hdev) +{ + DEFINE_QPERI_REQ_SPEC(spec, QPERI_OP_GENERIC, PGEN_PERI_RESET, + PERI_EV_CMD_COMPLETE, PERI_EV_SUBSYS_PATCH_NOTIFICATION); + struct qhci_cp_simple cp =3D QPERI_CP_INITIALIZER(PGEN_PERI_RESET); + struct sk_buff *skb; + u8 evt_idx =3D 0; + u8 status =3D 0; + int ret =3D 0; + + skb =3D qperi_cmd_sync_evt(hdev, QPERI_OP_GENERIC, sizeof(cp), &cp, + &spec, &evt_idx, &status); + if (IS_ERR(skb)) + return PTR_ERR(skb); + + if (status) { + ret =3D -bt_to_errno(status); + } else if (evt_idx =3D=3D 1) { + const struct peri_ev_subsys_patch_notification *ev; + + ev =3D skb_pull_data(skb, sizeof(*ev)); + if (ev->subsys !=3D QHCI_SUBSYS_PERI) { + ret =3D -EILSEQ; + bt_dev_err(hdev, "PERI HCI reset via QHCI failed: wrong subsys %d", + ev->subsys); + } + } + + kfree_skb(skb); + + if (!ret) + bt_dev_info(hdev, "PERI HCI reset via QHCI succeeded"); + + return ret; +} + +/* + * qperi_initiate_bt_crash - initiate a BT subsystem crash + * @hdev: the HCI device + * + * Return: 0 on success, or a negative errno on failure. + */ +static int qperi_initiate_bt_crash(struct hci_dev *hdev) +{ + struct qhci_cp_simple cp =3D QPERI_CP_INITIALIZER(PGEN_INITIATE_BT_CRASH); + struct sk_buff *skb; + u8 status =3D 0; + int ret =3D 0; + + skb =3D qperi_cmd_sync_one_evt(hdev, QPERI_OP_GENERIC, sizeof(cp), &cp, + PERI_EV_CMD_STATUS, &status); + if (IS_ERR(skb)) + return PTR_ERR(skb); + + if (skb) { + ret =3D -bt_to_errno(status); + kfree_skb(skb); + } + + if (!ret) + bt_dev_info(hdev, "PERI initiate BT crash succeeded"); + + return ret; +} + +/* + * =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D + * hdev callbacks + * =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D + */ + +/* test if a HW or SW reset is available */ +static inline bool btqcom_has_btc_reset(struct hci_dev *hdev) +{ + struct btqcom_data *qbt_data =3D hci_get_priv(hdev); + + return qbt_data->flags & (QBT_FLAG_HW_RESET | QBT_FLAG_SW_RESET); +} + +/* + * btqcom_set_bdaddr - set BD_ADDR + * @hdev: the HCI device + * @bdaddr: the BD_ADDR to set + * + * Implements hdev->set_bdaddr(). + * + * Return: 0 on success, or a negative errno on failure. + */ +static int btqcom_set_bdaddr(struct hci_dev *hdev, const bdaddr_t *bdaddr) +{ + int ret; + + ret =3D __hci_reset_sync(hdev); + if (ret) { + bt_dev_err(hdev, "HCI reset before setting BD_ADDR failed: %pe", + ERR_PTR(ret)); + return ret; + } + + ret =3D qbt_write_bda(hdev, bdaddr); + + return ret; +} + +/* + * btqcom_handle_ev_vendor - handle @skb if interested + * @hdev: the HCI device @skb comes from + * @skb: the VSE payload + * + * Implements hdev->handle_ev_vendor(). + * + * Return: true if @skb was handled, false otherwise. + */ +static bool btqcom_handle_ev_vendor(struct hci_dev *hdev, struct sk_buff *= skb) +{ + const struct qhci_vse_comm *vse_comm; + u16 vse_id; + + if (skb->len < QHCI_VSE_COMM_SIZE) + return false; + + vse_comm =3D (const void *)skb->data; + vse_id =3D vse_comm->ev_class << 8 | vse_comm->ev_type; + + switch (vse_id) { + case (QBT_EV_CLASS_DATALOG << 8) | QBT_EV_TYPE_MEMDUMP: + skb_pull_data(skb, QHCI_VSE_COMM_SIZE); + btqcom_handle_memdump(hdev, skb_get(skb)); + return true; + default: + return false; + } +} + +/* handle a received BT vendor ACL frame */ +static void handle_acl_vendor(struct hci_dev *hdev, struct sk_buff *skb) +{ + const struct qhci_vse_comm *vse_comm; + const struct hci_event_hdr *evt_hdr; + u16 vse_id; + + if (hci_acl_handle(skb) !=3D QBT_HANDLE_MEMDUMP) + goto out_free_skb; + + skb_pull(skb, HCI_ACL_HDR_SIZE); + + evt_hdr =3D skb_pull_data(skb, HCI_EVENT_HDR_SIZE); + if (!evt_hdr || evt_hdr->evt !=3D HCI_EV_VENDOR) + goto out_free_skb; + + vse_comm =3D skb_pull_data(skb, QHCI_VSE_COMM_SIZE); + if (!vse_comm) + goto out_free_skb; + + vse_id =3D vse_comm->ev_class << 8 | vse_comm->ev_type; + + switch (vse_id) { + case (QBT_EV_CLASS_DATALOG << 8) | QBT_EV_TYPE_MEMDUMP: + btqcom_handle_memdump(hdev, skb); + return; + default: + break; + } + +out_free_skb: + kfree_skb(skb); +} + +/* handle a received PERI ACL frame */ +static void qperi_handle_acl(struct hci_dev *hdev, struct sk_buff *skb) +{ + const struct hci_event_hdr *bt_evt_hdr; + const struct qhci_vse_comm *vse_comm; + u8 ev_type; + + if (qperi_acl_handle(skb) !=3D QPERI_HANDLE_MEMDUMP) + goto out_free_skb; + + skb_pull(skb, QPERI_ACL_HDR_SIZE); + + bt_evt_hdr =3D skb_pull_data(skb, HCI_EVENT_HDR_SIZE); + if (!bt_evt_hdr || bt_evt_hdr->evt !=3D HCI_EV_VENDOR) + goto out_free_skb; + + vse_comm =3D skb_pull_data(skb, sizeof(*vse_comm)); + if (!vse_comm || vse_comm->ev_class !=3D QPERI_EV_CLASS_PERI) + goto out_free_skb; + + ev_type =3D vse_comm->ev_type; + switch (ev_type) { + case PERI_EV_CRASH_DUMP_MEMDUMP: + btqcom_handle_memdump(hdev, skb); + return; + default: + break; + } + +out_free_skb: + kfree_skb(skb); +} + +/* Implements hdev->recv_vendor_pkt(). */ +static void btqcom_recv_vendor_pkt(struct hci_dev *hdev, struct sk_buff *s= kb) +{ + hci_skb_pkt_type(skb) =3D *(const u8 *)skb_pull_data(skb, 1); + + switch (hci_skb_pkt_type(skb)) { + case QPERI_EVENT_PKT: + qperi_handle_evt(hdev, skb); + break; + case QPERI_ACLDATA_PKT: + qperi_handle_acl(hdev, skb); + break; + case HCI_ACLDATA_PKT: + handle_acl_vendor(hdev, skb); + break; + default: + bt_dev_err(hdev, "unexpected vendor HCI frame with pkt_type 0x%02x", + hci_skb_pkt_type(skb)); + kfree_skb(skb); + break; + } +} + +/* Implements hdev->dump.coredump() for hci_devcd_register(). */ +static void btusb_qcom_trigger_memdump(struct hci_dev *hdev) +{ + struct btqcom_data *qbt_data =3D hci_get_priv(hdev); + + if (qbt_data->category =3D=3D QBTC_CAT_MSUBSYS) + qperi_initiate_bt_crash(hdev); + else + qbt_error_fatal_cmd(hdev); +} + +/* return 0 on success, or a negative errno on failure */ +static int btusb_qcom_deactivate_msubsys_bt(struct hci_dev *hdev) +{ + struct btusb_qcom *xport_data =3D btusb_qcom_xport_data(hdev); + int ret; + + ret =3D usb_autopm_get_interface(xport_data->intf); + if (ret) { + bt_dev_err(hdev, "get autopm for deactivate BT failed: %pe", ERR_PTR(ret= )); + return ret; + } + + ret =3D qdfu_activate_remote_btss(hdev, false, 100 * 1000); + + usb_autopm_put_interface(xport_data->intf); + + if (!ret) + bt_dev_info(hdev, "deactivate BT succeeded"); + + return ret; +} + +/* return 0 on success, or a negative errno on failure */ +static int btusb_qcom_sw_reset(struct hci_dev *hdev) +{ + struct btusb_qcom *xport_data =3D btusb_qcom_xport_data(hdev); + struct btqcom_data *qbt_data =3D hci_get_priv(hdev); + int ret =3D -EOPNOTSUPP; + + if (!(qbt_data->flags & QBT_FLAG_SW_RESET)) + return ret; + + ret =3D usb_autopm_get_interface(xport_data->intf); + if (ret) { + bt_dev_err(hdev, "get autopm for SW reset failed: %pe", ERR_PTR(ret)); + return ret; + } + + ret =3D qdfu_sw_reset(hdev); + + usb_autopm_put_interface(xport_data->intf); + + return ret; +} + +#define QBT_RESET_TYPE_SYNC "sync" +#define QBT_RESET_TYPE_ASYNC "async" +#define QBT_RESET_TYPE_DIRECT "direct" + +/* + * btusb_do_reset_work - reset the BTC + * @hdev: the HCI device + * @type: QBT_RESET_TYPE_* above, for logging + * + * Return: 0 on success, or a negative errno on failure. + */ +static int btusb_do_reset_work(struct hci_dev *hdev, void *type) +{ + struct btusb_qcom *xport_data =3D btusb_qcom_xport_data(hdev); + struct gpio_desc *reset_gpio =3D xport_data->reset_gpio; + struct btqcom_data *qbt_data =3D hci_get_priv(hdev); + int ret; + + bt_dev_dbg(hdev, "%s reset: misc_flags(0x%lx)", (char *)type, + READ_ONCE(qbt_data->misc_flags)); + bt_dev_dbg(hdev, "md_state: %s, md_submit_err: %d", + hci_devcd_state_name(READ_ONCE(qbt_data->md_state)), + READ_ONCE(qbt_data->md_submit_err)); + + if (test_and_set_bit(QBT_MISC_RESET_ACTIVE, &qbt_data->misc_flags)) { + bt_dev_info(hdev, "reset already in progress"); + return 0; + } + + if (xport_data->prepare_reset) + xport_data->prepare_reset(hdev); + + if (reset_gpio) { + bt_dev_info(hdev, "hardware reset"); + gpiod_set_value_cansleep(reset_gpio, 0); + fsleep(200 * 1000); + gpiod_set_value_cansleep(reset_gpio, 1); + return 0; + } + + ret =3D btusb_qcom_sw_reset(hdev); + if (!ret) + return 0; + + ret =3D usb_autopm_get_interface(xport_data->intf); + if (ret) { + bt_dev_err(hdev, "reset: autopm get failed: %pe", ERR_PTR(ret)); + clear_bit(QBT_MISC_RESET_ACTIVE, &qbt_data->misc_flags); + return ret; + } + usb_autopm_put_interface_no_suspend(xport_data->intf); + + bt_dev_info(hdev, "usb reset"); + /* Clear it here since usb reset isn't guaranteed to succeed. */ + clear_bit(QBT_MISC_RESET_ACTIVE, &qbt_data->misc_flags); + usb_queue_reset_device(xport_data->intf); + + return 0; +} + +static void btusb_qcom_reset_sync(struct hci_dev *hdev) +{ + int res; + + res =3D hci_cmd_sync_queue_once(hdev, btusb_do_reset_work, QBT_RESET_TYPE= _SYNC, NULL); + if (res) + bt_dev_dbg(hdev, "queue reset work failed: %pe", ERR_PTR(res)); + if (!res || res =3D=3D -EEXIST || res =3D=3D -ENODEV) + return; + + btusb_do_reset_work(hdev, QBT_RESET_TYPE_DIRECT); +} + +static void btqcom_reset_async(struct hci_dev *hdev, unsigned int delay_ms) +{ + struct btqcom_data *qbt_data =3D hci_get_priv(hdev); + + set_bit(QBT_WORK_RESET_HDEV, &qbt_data->work_flags); + schedule_delayed_work(&qbt_data->dwork, msecs_to_jiffies(delay_ms)); +} + +/* + * btusb_do_cmd_timeout_work - HCI cmd sync work function to handle comman= d timeout + * @hdev: the HCI device + * @data: unused + * + * Return: 0 on success, or a negative errno on failure. + */ +static int btusb_do_cmd_timeout_work(struct hci_dev *hdev, + void *data __maybe_unused) +{ + struct btqcom_data *qbt_data =3D hci_get_priv(hdev); + unsigned int wait_ms =3D 0; + int ret =3D 0; + + if (!(qbt_data->flags & QBT_FLAG_MEMDUMP) || + !(qbt_data->flags & QBT_FLAG_CMD_TIMEOUT_MEMDUMP)) + goto out_reset; + + switch (qbt_data->category) { + case QBTC_CAT_LEGACY: + return -EOPNOTSUPP; + case QBTC_CAT_UNIFIED: + wait_ms =3D 800; + ret =3D qbt_error_fatal_cmd(hdev); + break; + case QBTC_CAT_MSUBSYS: + wait_ms =3D 1200; + ret =3D qperi_initiate_bt_crash(hdev); + break; + default: + return -EINVAL; + } + + if (ret) + goto out_reset; + + ret =3D wait_var_event_timeout(&qbt_data->misc_flags, + test_bit(QBT_MISC_MEMDUMP_INCOMING, + &qbt_data->misc_flags) || + !test_bit(QBT_MISC_CMD_TIMEOUT, + &qbt_data->misc_flags), + msecs_to_jiffies(wait_ms)); + if (!test_bit(QBT_MISC_CMD_TIMEOUT, &qbt_data->misc_flags)) { + bt_dev_dbg(hdev, "Flag CMD_TIMEOUT cleared"); + return 0; + } + if (ret) { + bt_dev_dbg(hdev, "trigger memdump on command timeout succeeded"); + return 0; + } + bt_dev_err(hdev, "trigger memdump on command timeout failed"); + +out_reset: + btusb_do_reset_work(hdev, QBT_RESET_TYPE_SYNC); + + return 0; +} + +/* Implements hdev->reset(). */ +static void btusb_qcom_reset(struct hci_dev *hdev) +{ + struct btqcom_data *qbt_data =3D hci_get_priv(hdev); + bool has_cmd_timeout; + int res; + + has_cmd_timeout =3D current_work() =3D=3D &hdev->cmd_timer.work; + bt_dev_info(hdev, "reset triggered by %s", + has_cmd_timeout ? "command timeout" : "user"); + + if (test_bit(QBT_MISC_MEMDUMP_PERI, &qbt_data->misc_flags) || + test_bit(QBT_MISC_MEMDUMP_BT, &qbt_data->misc_flags) || + test_bit(QBT_MISC_MEMDUMP_TMEL, &qbt_data->misc_flags)) { + bt_dev_info(hdev, "reset will happen after memdump"); + return; + } + + if (!has_cmd_timeout) { + btusb_qcom_reset_sync(hdev); + return; + } + + if (test_and_set_bit(QBT_MISC_CMD_TIMEOUT, &qbt_data->misc_flags)) { + bt_dev_info(hdev, "handling command timeout is in progress"); + return; + } + + res =3D hci_cmd_sync_queue_once(hdev, btusb_do_cmd_timeout_work, NULL, NU= LL); + if (res) + bt_dev_dbg(hdev, "queue command timeout work failed: %pe", ERR_PTR(res)); + if (!res || res =3D=3D -EEXIST || res =3D=3D -ENODEV) + return; + + btqcom_reset_async(hdev, 0); +} + +/* Implements hdev->hw_error(). */ +static void btqcom_hw_error(struct hci_dev *hdev, u8 code) +{ + const char *subsys_name =3D qhci_subsys_name(QHCI_SUBSYS_PERI); + struct btqcom_data *qbt_data =3D hci_get_priv(hdev); + int res; + + if (!test_bit(QBT_MISC_HWERR_PERI, &qbt_data->misc_flags)) { + set_bit(QBT_MISC_HWERR_BT, &qbt_data->misc_flags); + subsys_name =3D qhci_subsys_name(QHCI_SUBSYS_INVALID); + } + + bt_dev_info(hdev, "%s hardware error (0x%02x)", subsys_name, code); + bt_dev_dbg(hdev, "md_pending_flags: 0x%lx", qbt_data->md_pending_flags); + bt_dev_dbg(hdev, "md_submit_err: %d", qbt_data->md_submit_err); + + if (qbt_data->md_pending_flags && !qbt_data->md_submit_err) { + res =3D hci_devcd_complete(hdev); + if (res) + bt_dev_err(hdev, "memdump complete on %s hardware error failed: %pe", + subsys_name, ERR_PTR(res)); + else + bt_dev_info(hdev, "memdump completed on %s hardware error: %u bytes", + subsys_name, qbt_data->md_submit_size); + } + + qbt_data->md_pending_flags =3D 0; + + if (test_and_clear_bit(QBT_MISC_MEMDUMP_INCOMING, &qbt_data->misc_flags)) + bt_dev_dbg(hdev, "clear MEMDUMP_INCOMING on %s hardware error", subsys_n= ame); +} + +static int btusb_qcom_shutdown_unified(struct hci_dev *hdev) +{ + struct btqcom_data *qbt_data =3D hci_get_priv(hdev); + bool had_hwerr, had_memdump; + unsigned int delay_ms =3D 10; + int ret; + + had_memdump =3D test_bit(QBT_MISC_MEMDUMP_BT, &qbt_data->misc_flags); + had_hwerr =3D test_bit(QBT_MISC_HWERR_BT, &qbt_data->misc_flags); + + bt_dev_dbg(hdev, "shutdown: had_memdump %d, had_hwerr %d", + had_memdump, had_hwerr); + + if (!had_hwerr && !had_memdump) { + ret =3D __hci_reset_sync(hdev); + if (ret) + bt_dev_err(hdev, "shutdown: HCI reset failed: %pe", + ERR_PTR(ret)); + else + bt_dev_info(hdev, "shutdown: HCI reset succeeded"); + goto out; + } + + if (!had_memdump) { + ret =3D __hci_cmd_sync_status(hdev, HCI_OP_RESET, 0, NULL, + HCI_CMD_TIMEOUT); + if (!ret) { + clear_bit(QBT_MISC_HWERR_BT, &qbt_data->misc_flags); + bt_dev_info(hdev, "shutdown: recovered from hardware error via HCI rese= t"); + goto out; + } + + bt_dev_warn(hdev, "shutdown: HCI reset failed to recover from hardware e= rror: %pe", + ERR_PTR(ret)); + } + + ret =3D -EIO; + if (had_memdump && !btqcom_has_btc_reset(hdev)) { + bt_dev_info(hdev, "shutdown: BTC will self-recover via re-enumeration"); + return ret; + } + + bt_dev_info(hdev, "shutdown: reset BTC after %u msec", delay_ms); + btqcom_reset_async(hdev, delay_ms); + + return ret; +out: + if (test_and_clear_bit(QBT_MISC_CMD_TIMEOUT, &qbt_data->misc_flags)) + bt_dev_dbg(hdev, "clear CMD_TIMEOUT on shutdown"); + + return ret; +} + +static int btusb_qcom_shutdown_msubsys(struct hci_dev *hdev) +{ + struct btqcom_data *qbt_data =3D hci_get_priv(hdev); + bool had_peri_hwerr, had_peri_memdump; + bool had_bt_hwerr, had_bt_memdump; + bool had_tmel_memdump; + unsigned int delay_ms; + int ret; + + delay_ms =3D 10; + had_peri_memdump =3D test_bit(QBT_MISC_MEMDUMP_PERI, &qbt_data->misc_flag= s); + had_peri_hwerr =3D test_bit(QBT_MISC_HWERR_PERI, &qbt_data->misc_flags); + had_bt_memdump =3D test_bit(QBT_MISC_MEMDUMP_BT, &qbt_data->misc_flags); + had_bt_hwerr =3D test_bit(QBT_MISC_HWERR_BT, &qbt_data->misc_flags); + had_tmel_memdump =3D test_bit(QBT_MISC_MEMDUMP_TMEL, &qbt_data->misc_flag= s); + + bt_dev_dbg(hdev, "shutdown: had_peri_memdump %d, had_peri_hwerr %d", + had_peri_memdump, had_peri_hwerr); + bt_dev_dbg(hdev, "shutdown: had_bt_memdump %d, had_bt_hwerr %d", + had_bt_memdump, had_bt_hwerr); + bt_dev_dbg(hdev, "shutdown: had_tmel_memdump %d", had_tmel_memdump); + + ret =3D -EIO; + if (had_peri_memdump || had_peri_hwerr || had_tmel_memdump) + goto out_reset; + + ret =3D btusb_qcom_deactivate_msubsys_bt(hdev); + if (ret && (had_bt_memdump || had_bt_hwerr)) + goto out_reset; + + clear_bit(QBT_MISC_HWERR_BT, &qbt_data->misc_flags); + clear_bit(QBT_MISC_MEMDUMP_BT, &qbt_data->misc_flags); + if (test_and_clear_bit(QBT_MISC_CMD_TIMEOUT, &qbt_data->misc_flags)) + bt_dev_dbg(hdev, "clear CMD_TIMEOUT on shutdown"); + + return ret; + +out_reset: + bt_dev_info(hdev, "shutdown: reset BTC after %u msec", delay_ms); + btqcom_reset_async(hdev, delay_ms); + return ret; +} + +/* Implements hdev->shutdown(). */ +static int btusb_qcom_shutdown(struct hci_dev *hdev) +{ + struct btqcom_data *qbt_data =3D hci_get_priv(hdev); + int ret; + + switch (qbt_data->category) { + case QBTC_CAT_LEGACY: + ret =3D -EOPNOTSUPP; + break; + case QBTC_CAT_UNIFIED: + ret =3D btusb_qcom_shutdown_unified(hdev); + break; + case QBTC_CAT_MSUBSYS: + ret =3D btusb_qcom_shutdown_msubsys(hdev); + break; + default: + ret =3D -EINVAL; + break; + } + + return ret; +} + +/* + * =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D + * QDFU firmware downloading + * =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D + */ + +#define QBT_FW_PATH_MAX 96 + +enum qbt_fw_type { + QBT_FW_TYPE_PATCH, + QBT_FW_TYPE_NVM, + QBT_FW_TYPE_MAX, +}; + +#define QBT_PATCH_TYPE_TLV 0x01 +#define QBT_NVM_TYPE_TLV 0x02 +struct qbt_tlv_file_hdr { + u8 type; + u8 length[3]; +} __packed; + +struct qbt_patch_tlv_hdr { + struct qbt_tlv_file_hdr tlv_hdr; + __le32 total_len; + __le32 patch_data_len; + u8 sign_ver; + u8 sign_algo; + u8 download_cfg; + u8 image_type; + __le16 product_id; + __le16 rom_ver; + __le16 patch_ver; + u8 reserved1[2]; + __le32 anti_rollback_ver; + __le32 serial_low; + __le16 serial_high; + u8 debug_option; + u8 reserved2; + __le32 entry_addr; +} __packed; + +struct qbt_nvm_tlv_hdr { + struct qbt_tlv_file_hdr tlv_hdr; +} __packed; + +/* + * struct qdfu_fw_cfg - QDFU firmware download configuration + * @desc: short description for logging (e.g. "PERI patch", "PERI NVM") + * @format: QBT_PATCH_TYPE_TLV or QBT_NVM_TYPE_TLV + * @hdr_size: TLV header size for @format + * @request: QDFU_BT_CMD_VSC_REQ_DOWNLOAD_(LOCAL|REMOTE) + * @loaded_flag: QDFU_BT_STATE_* flag that marks fw downloaded or not + * @settle_us: default time to wait after download for the BTC to settle + * @get_path: build firmware file path, return number of paths or error + * @check: check firmware, return 0 on success or a negative errno + * @download: download firmware, return 0 on success or a negative errno + * @get_settle_us: get the time to wait for BTC to settle + * @post_download: run after download, return 0 on success or a negative e= rrno + */ +struct qdfu_fw_cfg { + const char *desc; + u8 format; + u8 hdr_size; + u8 request; + u8 loaded_flag; + u32 settle_us; + + int (*get_path)(struct hci_dev *hdev, const struct qdfu_fw_cfg *fw_cfg, + const struct qbtc_info *info, const struct qdfu_bt_id *ctrl_id, + char path[], size_t size); + + int (*check)(struct hci_dev *hdev, const struct qdfu_fw_cfg *fw_cfg, + const struct qbtc_info *info, const struct qdfu_bt_id *ctrl_id, + const struct firmware *fw); + + int (*download)(struct hci_dev *hdev, const struct qdfu_fw_cfg *fw_cfg, + const struct qbtc_info *info, const struct qdfu_bt_id *ctrl_id, + const void *data, size_t size); + + u32 (*get_settle_us)(struct hci_dev *hdev, const struct qdfu_fw_cfg *fw_c= fg, + const struct qbtc_info *info, const struct qdfu_bt_id *ctrl_id); + + int (*post_download)(struct hci_dev *hdev, const struct qdfu_fw_cfg *fw_c= fg, + const struct qbtc_info *info, const struct qdfu_bt_id *ctrl_id); +}; + +static int get_qbtc_subsys(const struct qdfu_fw_cfg *fw_cfg); + +static void qhci_to_qdfu_id(struct qdfu_bt_id *dfu_id, + const struct qhci_btc_ver *hci_ver, u16 board_id) +{ + dfu_id->rom_version =3D (u32)hci_ver->product_id << 16 | hci_ver->rom_v= er; + dfu_id->patch_version =3D hci_ver->patch_ver; + dfu_id->soc_id =3D hci_ver->soc_ver; + dfu_id->board_id =3D board_id; +} + +static void qdfu_to_qhci_id(struct qhci_btc_ver *hci_ver, u16 *board_id, + const struct qdfu_bt_id *dfu_id) +{ + hci_ver->product_id =3D upper_16_bits(dfu_id->rom_version); + hci_ver->rom_ver =3D lower_16_bits(dfu_id->rom_version); + hci_ver->patch_ver =3D (u16)dfu_id->patch_version; + hci_ver->soc_ver =3D dfu_id->soc_id; + *board_id =3D dfu_id->board_id; +} + +static int get_qdfu_id_via_hci(struct hci_dev *hdev, + struct qdfu_bt_id *dfu_id) +{ + struct qhci_btc_ver hci_ver; + u16 board_id; + int ret; + + ret =3D qbt_edl_patch_getver(hdev, &hci_ver); + if (ret) + return ret; + + ret =3D qbt_edl_get_board_id(hdev, &board_id); + if (ret) + return ret; + + qhci_to_qdfu_id(dfu_id, &hci_ver, board_id); + + return 0; +} + +static const char *get_fw_directory(struct hci_dev *hdev, const struct qbt= c_info *info, + const struct qdfu_bt_id *ctrl_id) +{ + struct btqcom_data *qbt_data =3D hci_get_priv(hdev); + const struct qbtc_bid_fwdir *custom_fwdir; + + if (qbt_data->fw_dir) + return qbt_data->fw_dir; + + if (!info->custom_fw_table) + goto info_fw_dir; + + for (custom_fwdir =3D info->custom_fw_table; custom_fwdir->board_id; cust= om_fwdir++) { + if (custom_fwdir->board_id =3D=3D ctrl_id->board_id) + return custom_fwdir->fw_dir; + } + +info_fw_dir: + if (info->fw_dir) + return info->fw_dir; + + return "qca"; +} + +/* + * btusb_get_patch_path - build the patch firmware file path + * @hdev: the HCI device to build the path for + * @fw_cfg: the firmware download config + * @info: BTC info for this hdev + * @ctrl_id: BTC QDFU ID + * @path: output buffer for the built path + * @size: size of @path + * + * Used as the qdfu_fw_cfg ->get_path() callback. + * + * Return: 1 path written into @path, or a negative errno on failure. + */ +static int btusb_get_patch_path(struct hci_dev *hdev, + const struct qdfu_fw_cfg *fw_cfg, + const struct qbtc_info *info, + const struct qdfu_bt_id *ctrl_id, + char path[], size_t size) +{ + const char *fw_dir =3D get_fw_directory(hdev, info, ctrl_id); + int qbtc_subsys; + int len; + + qbtc_subsys =3D get_qbtc_subsys(fw_cfg); + + if (qbtc_subsys =3D=3D QBTC_SUBSYS_INVALID) { + /* BT function unit. */ + len =3D snprintf(path, size, "%s/rampatch_usb_%08x.bin", + fw_dir, ctrl_id->rom_version); + goto out; + } + + switch (qbtc_subsys) { + case QBTC_SUBSYS_PERI: + len =3D snprintf(path, size, "%s/peripatch_usb_%08x.bin", + fw_dir, ctrl_id->rom_version); + break; + case QBTC_SUBSYS_TMEL: + return -EOPNOTSUPP; + default: + return -EINVAL; + } + +out: + if (len >=3D size) + return -ENAMETOOLONG; + + return 1; +} + +/* + * btusb_get_nvm_path - build the NVM firmware file path(s) + * @hdev: the HCI device to build the path for + * @fw_cfg: the firmware download config + * @info: BTC info for this hdev + * @ctrl_id: BTC QDFU ID + * @path: output buffer for the built path(s) + * @size: size of @path + * + * @path may hold two NUL-terminated paths back-to-back: a board-ID path + * followed by a fallback path, when NVM fallback is enabled. + * + * Used as the qdfu_fw_cfg ->get_path() callback. + * + * Return: number of paths (1 or 2) written into @path, or a negative + * errno on failure. + */ +static int btusb_get_nvm_path(struct hci_dev *hdev, + const struct qdfu_fw_cfg *fw_cfg, + const struct qbtc_info *info, + const struct qdfu_bt_id *ctrl_id, + char path[], size_t size) +{ + const char *fw_dir =3D get_fw_directory(hdev, info, ctrl_id); + const char *prefix =3D NULL, *foundry_str =3D NULL; + bool has_bid =3D false, need_fb =3D false; + char fw_fb[QBT_FW_PATH_MAX] =3D { 0 }; + int qbtc_subsys; + int len =3D 0; + + qbtc_subsys =3D get_qbtc_subsys(fw_cfg); + if (qbtc_subsys =3D=3D QBTC_SUBSYS_INVALID) { + /* BT function unit. */ + prefix =3D "nvm_usb"; + } else { + switch (qbtc_subsys) { + case QBTC_SUBSYS_PERI: + prefix =3D "perinvm_usb"; + break; + case QBTC_SUBSYS_TMEL: + return -EOPNOTSUPP; + default: + return -EINVAL; + } + } + + if (info->flags & QBT_FLAG_FOUNDRY_NVM) { + u8 foundry =3D FIELD_GET(GENMASK(15, 12), ctrl_id->soc_id); + + switch (foundry) { + /* GlobalFoundries */ + case 0x01: + foundry_str =3D "_gf"; + break; + default: + break; + } + } + + if ((info->flags & QBT_FLAG_BID_NVM) && ctrl_id->board_id) { + has_bid =3D true; + if (info->flags & QBT_FLAG_NVM_FALLBACK) + need_fb =3D true; + } + + len =3D snprintf(path, size, "%s/%s_%08x", fw_dir, prefix, ctrl_id->rom_v= ersion); + if (len >=3D size) + return -ENAMETOOLONG; + + if (foundry_str) { + len +=3D snprintf(path + len, size - len, "%s", foundry_str); + if (len >=3D size) + return -ENAMETOOLONG; + } + + if (!has_bid) { + len +=3D snprintf(path + len, size - len, ".bin"); + if (len >=3D size) + return -ENAMETOOLONG; + return 1; + } + + if (need_fb) { + int fb_len =3D snprintf(fw_fb, sizeof(fw_fb), "%s.bin", path); + + if (fb_len >=3D sizeof(fw_fb)) + return -ENAMETOOLONG; + + len +=3D snprintf(path + len, size - len, "_%04x.bin", + ctrl_id->board_id); + if (len >=3D size) + return -ENAMETOOLONG; + + /* path holds two NUL-terminated strings back-to-back: + * [board-id path '\0'][fallback path '\0'] + * Verify the buffer fits both before writing. + */ + if (len + fb_len + 2 > size) + return -ENAMETOOLONG; + + len +=3D 1; + snprintf(path + len, size - len, "%s", fw_fb); + return 2; + } + + len +=3D snprintf(path + len, size - len, "_%04x.bin", ctrl_id->board_id); + if (len >=3D size) + return -ENAMETOOLONG; + + return 1; +} + +/* check the TLV file header; return 0 on success, or a negative errno */ +static int qbt_check_tlv_file(struct hci_dev *hdev, const struct firmware = *fw, u8 format) +{ + const struct qbt_tlv_file_hdr *tlv_hdr =3D (const struct qbt_tlv_file_hdr= *)fw->data; + size_t file_len; + + if (fw->size < sizeof(*tlv_hdr)) + return -ENODATA; + + if (tlv_hdr->type !=3D format) + return -EINVAL; + + file_len =3D get_unaligned_le24(tlv_hdr->length) + sizeof(*tlv_hdr); + if (file_len !=3D fw->size) + return -EBADF; + + return 0; +} + +/* + * btusb_check_patch_tlv - validate a patch TLV file against the BTC + * before download + * @hdev: the HCI device to validate the file for + * @fw_cfg: the firmware download config (unused) + * @info: BTC info for this hdev (unused) + * @ctrl_id: BTC QDFU ID + * @fw: the firmware to validate + * + * Used as the qdfu_fw_cfg ->check() callback. + * + * Return: 0 on success, or a negative errno on failure. + */ +static int btusb_check_patch_tlv(struct hci_dev *hdev, + const struct qdfu_fw_cfg *fw_cfg, + const struct qbtc_info *info __maybe_unused, + const struct qdfu_bt_id *ctrl_id, + const struct firmware *fw) +{ + const struct qbt_patch_tlv_hdr *patch_hdr =3D (const void *)fw->data; + u32 fw_rom_version, fw_patch_version; + int ret; + + ret =3D qbt_check_tlv_file(hdev, fw, QBT_PATCH_TYPE_TLV); + if (ret) { + bt_dev_err(hdev, "Check %s TLV file header failed: %pe", + fw_cfg->desc, ERR_PTR(ret)); + return ret; + } + + if (fw->size < sizeof(*patch_hdr)) { + bt_dev_err(hdev, "%s header truncated (%zu bytes, header needs %zu)", + fw_cfg->desc, fw->size, sizeof(*patch_hdr)); + return -ENODATA; + } + + if (likely(upper_16_bits(ctrl_id->rom_version))) { + fw_rom_version =3D (le16_to_cpu(patch_hdr->product_id) << 16) | + le16_to_cpu(patch_hdr->rom_ver); + } else { + bt_dev_warn(hdev, "check %s: invalid rom_version 0x%08x", + fw_cfg->desc, ctrl_id->rom_version); + fw_rom_version =3D le16_to_cpu(patch_hdr->rom_ver); + } + + fw_patch_version =3D le16_to_cpu(patch_hdr->patch_ver); + + if (fw_rom_version !=3D ctrl_id->rom_version) { + bt_dev_err(hdev, + "%s ROM version 0x%08x does not match controller 0x%08x", + fw_cfg->desc, fw_rom_version, ctrl_id->rom_version); + return -EINVAL; + } + + if (fw_patch_version < ctrl_id->patch_version) { + bt_dev_err(hdev, + "%s version 0x%x older than controller 0x%x, anti-rollback", + fw_cfg->desc, fw_patch_version, ctrl_id->patch_version); + return -EPERM; + } + + return 0; +} + +/* + * validate a NVM TLV file against the BTC + * Used as the qdfu_fw_cfg ->check() callback. + */ +static int btusb_check_nvm_tlv(struct hci_dev *hdev, + const struct qdfu_fw_cfg *fw_cfg, + const struct qbtc_info *info __maybe_unused, + const struct qdfu_bt_id *ctrl_id __maybe_unused, + const struct firmware *fw) +{ + int ret; + + ret =3D qbt_check_tlv_file(hdev, fw, QBT_NVM_TYPE_TLV); + if (ret) { + bt_dev_err(hdev, "Check %s TLV file header failed: %pe", + fw_cfg->desc, ERR_PTR(ret)); + return ret; + } + + return 0; +} + +/* + * btusb_download_via_qdfu - download a firmware image via QDFU to BTC + * @hdev: the HCI device to send the firmware to + * @fw_cfg: the firmware download config + * @info: BTC info for this hdev (unused) + * @ctrl_id: BTC QDFU ID (unused) + * @fw_data: the firmware image to send, header included + * @fw_size: size of @fw_data + * + * Used as the qdfu_fw_cfg ->download() callback. + * + * Return: 0 on success, or a negative errno on failure. + */ +static int btusb_download_via_qdfu(struct hci_dev *hdev, + const struct qdfu_fw_cfg *fw_cfg, + const struct qbtc_info *info __maybe_unused, + const struct qdfu_bt_id *ctrl_id __maybe_unused, + const void *fw_data, size_t fw_size) +{ + struct btusb_qcom *xport_data =3D btusb_qcom_xport_data(hdev); + unsigned int pipe =3D usb_sndbulkpipe(xport_data->udev, 0x02); + const unsigned int xfer_timeout_ms =3D 3000; + const size_t xfer_size =3D SZ_4K; + const u8 *base =3D fw_data; + const u8 *ptr =3D fw_data; + size_t seg_size, size; + int snd_len, ret; + u8 *buf; + + ret =3D qdfu_vsc_req_download(hdev, fw_cfg->request, ptr, fw_cfg->hdr_siz= e); + if (ret) + return ret; + + ptr +=3D fw_cfg->hdr_size; + size =3D fw_size - fw_cfg->hdr_size; + + /* ep2 needs time to switch from ACL to DFU function mode. */ + fsleep(20 * 1000); + + buf =3D kmalloc(xfer_size, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + while (size) { + seg_size =3D min_t(size_t, size, xfer_size); + memcpy(buf, ptr, seg_size); + + snd_len =3D 0; + ret =3D usb_bulk_msg(xport_data->udev, pipe, buf, seg_size, &snd_len, + xfer_timeout_ms); + if (ret < 0) { + bt_dev_err(hdev, + "QDFU bulk send failed at offset %zu: %pe", + ptr - base, ERR_PTR(ret)); + break; + } + + if (seg_size !=3D snd_len) { + bt_dev_err(hdev, + "QDFU bulk short write (%d of %zu bytes) at offset %zu", + snd_len, seg_size, ptr - base); + ret =3D -EIO; + break; + } + + ptr +=3D seg_size; + size -=3D seg_size; + } + + kfree(buf); + return ret; +} + +/* + * Used as the qdfu_fw_cfg ->get_settle_us() callback. + * Kept here for future per-BTC settle time use. + */ +static u32 btusb_get_settle_us(struct hci_dev *hdev __maybe_unused, + const struct qdfu_fw_cfg *fw_cfg, + const struct qbtc_info *info __maybe_unused, + const struct qdfu_bt_id *ctrl_id __maybe_unused) +{ + return fw_cfg->settle_us; +} + +/* Used as the qdfu_fw_cfg ->post_download() callback. */ +static int btusb_post_download_unified_bt(struct hci_dev *hdev, + const struct qdfu_fw_cfg *fw_cfg __maybe_unused, + const struct qbtc_info *info __maybe_unused, + const struct qdfu_bt_id *ctrl_id __maybe_unused) +{ + struct btqcom_data *qbt_data =3D hci_get_priv(hdev); + char *build_info =3D NULL; + int ret; + + ret =3D qbt_edl_get_build_info(hdev, &build_info); + if (!ret) { + strscpy(qbt_data->build_info, build_info, sizeof(qbt_data->build_info)); + kfree(build_info); + } + + ret =3D __hci_reset_sync(hdev); + if (ret) + bt_dev_err(hdev, "HCI reset after BT NVM download failed: %pe", ERR_PTR(= ret)); + else + bt_dev_info(hdev, "HCI reset after BT NVM download succeeded"); + + return ret; +} + +/* Used as the qdfu_fw_cfg ->post_download() callback. */ +static int btusb_post_download_msubsys_peri(struct hci_dev *hdev, + const struct qdfu_fw_cfg *fw_cfg __maybe_unused, + const struct qbtc_info *info __maybe_unused, + const struct qdfu_bt_id *ctrl_id __maybe_unused) +{ + struct btqcom_data *qbt_data =3D hci_get_priv(hdev); + struct qbtc_subsys_data *subsys_data; + char *build_info =3D NULL; + int ret; + + subsys_data =3D &qbt_data->subsys[QBTC_SUBSYS_PERI]; + + ret =3D qperi_get_subsys_build_info(hdev, QHCI_SUBSYS_PERI, &build_info); + if (!ret) { + strscpy(subsys_data->build_info, build_info, sizeof(subsys_data->build_i= nfo)); + kfree(build_info); + } + + if (qbt_data->flags & QBT_FLAG_RESET_PERI_HCI) + ret =3D qperi_hci_reset(hdev); + else + ret =3D qdfu_reset_peri_hci(hdev); + + return ret; +} + +static const struct qdfu_fw_cfg qdfu_fw_cfgs_bt[QBTC_CAT_MAX][QBT_FW_TYPE_= MAX] =3D { + [QBTC_CAT_UNIFIED][QBT_FW_TYPE_PATCH] =3D { + .desc =3D "patch", + .format =3D QBT_PATCH_TYPE_TLV, + .hdr_size =3D sizeof(struct qbt_patch_tlv_hdr), + .request =3D QDFU_BT_CMD_VSC_REQ_DOWNLOAD_LOCAL, + .loaded_flag =3D QDFU_BT_STATE_PATCHED_LOCAL, + .settle_us =3D 10 * 1000, + .get_path =3D btusb_get_patch_path, + .check =3D btusb_check_patch_tlv, + .download =3D btusb_download_via_qdfu, + .get_settle_us =3D btusb_get_settle_us, + .post_download =3D NULL, + }, + [QBTC_CAT_UNIFIED][QBT_FW_TYPE_NVM] =3D { + .desc =3D "NVM", + .format =3D QBT_NVM_TYPE_TLV, + .hdr_size =3D sizeof(struct qbt_nvm_tlv_hdr), + .request =3D QDFU_BT_CMD_VSC_REQ_DOWNLOAD_LOCAL, + .loaded_flag =3D QDFU_BT_STATE_NVMED_LOCAL, + .settle_us =3D 40 * 1000, + .get_path =3D btusb_get_nvm_path, + .check =3D btusb_check_nvm_tlv, + .download =3D btusb_download_via_qdfu, + .get_settle_us =3D btusb_get_settle_us, + .post_download =3D btusb_post_download_unified_bt, + }, + [QBTC_CAT_MSUBSYS][QBT_FW_TYPE_PATCH] =3D { + .desc =3D "BT patch", + .format =3D QBT_PATCH_TYPE_TLV, + .hdr_size =3D sizeof(struct qbt_patch_tlv_hdr), + .request =3D QDFU_BT_CMD_VSC_REQ_DOWNLOAD_REMOTE, + .loaded_flag =3D QDFU_BT_STATE_PATCHED_REMOTE, + .settle_us =3D 30 * 1000, + .get_path =3D btusb_get_patch_path, + .check =3D btusb_check_patch_tlv, + .download =3D btusb_download_via_qdfu, + .get_settle_us =3D btusb_get_settle_us, + .post_download =3D NULL, + }, + [QBTC_CAT_MSUBSYS][QBT_FW_TYPE_NVM] =3D { + .desc =3D "BT NVM", + .format =3D QBT_NVM_TYPE_TLV, + .hdr_size =3D sizeof(struct qbt_nvm_tlv_hdr), + .request =3D QDFU_BT_CMD_VSC_REQ_DOWNLOAD_REMOTE, + .loaded_flag =3D QDFU_BT_STATE_NVMED_REMOTE, + .settle_us =3D 60 * 1000, + .get_path =3D btusb_get_nvm_path, + .check =3D btusb_check_nvm_tlv, + .download =3D btusb_download_via_qdfu, + .get_settle_us =3D btusb_get_settle_us, + .post_download =3D btusb_post_download_unified_bt, + }, +}; + +static const struct qdfu_fw_cfg qdfu_fw_cfgs_subsys[QBTC_SUBSYS_MAX][QBT_F= W_TYPE_MAX] =3D { + [QBTC_SUBSYS_PERI][QBT_FW_TYPE_PATCH] =3D { + .desc =3D "PERI patch", + .format =3D QBT_PATCH_TYPE_TLV, + .hdr_size =3D sizeof(struct qbt_patch_tlv_hdr), + .request =3D QDFU_BT_CMD_VSC_REQ_DOWNLOAD_LOCAL, + .loaded_flag =3D QDFU_BT_STATE_PATCHED_LOCAL, + .settle_us =3D 10 * 1000, + .get_path =3D btusb_get_patch_path, + .check =3D btusb_check_patch_tlv, + .download =3D btusb_download_via_qdfu, + .get_settle_us =3D btusb_get_settle_us, + .post_download =3D NULL, + }, + [QBTC_SUBSYS_PERI][QBT_FW_TYPE_NVM] =3D { + .desc =3D "PERI NVM", + .format =3D QBT_NVM_TYPE_TLV, + .hdr_size =3D sizeof(struct qbt_nvm_tlv_hdr), + .request =3D QDFU_BT_CMD_VSC_REQ_DOWNLOAD_LOCAL, + .loaded_flag =3D QDFU_BT_STATE_NVMED_LOCAL, + .settle_us =3D 20 * 1000, + .get_path =3D btusb_get_nvm_path, + .check =3D btusb_check_nvm_tlv, + .download =3D btusb_download_via_qdfu, + .get_settle_us =3D btusb_get_settle_us, + .post_download =3D btusb_post_download_msubsys_peri, + }, +}; + +static int get_qbtc_subsys(const struct qdfu_fw_cfg *fw_cfg) +{ + int qbtc_subsys; + int fw_type; + + for (qbtc_subsys =3D 0; qbtc_subsys < QBTC_SUBSYS_MAX; qbtc_subsys++) { + for (fw_type =3D 0; fw_type < QBT_FW_TYPE_MAX; fw_type++) { + if (&qdfu_fw_cfgs_subsys[qbtc_subsys][fw_type] =3D=3D fw_cfg) + return qbtc_subsys; + } + } + + return QBTC_SUBSYS_INVALID; +} + +/* + * btusb_download_fw - download a firmware image + * @hdev: the HCI device to download the firmware to + * @fw_cfg: the firmware download config + * @info: BTC info for this hdev + * @ctrl_id: BTC ID + * + * Return: 1 if downloaded, 0 if already downloaded, or a negative errno + * on failure. + */ +static int btusb_download_fw(struct hci_dev *hdev, + const struct qdfu_fw_cfg *fw_cfg, + const struct qbtc_info *info, + const struct qdfu_bt_id *ctrl_id) +{ + const struct firmware *fw =3D NULL; + char paths[QBT_FW_PATH_MAX * 2]; + const char *loaded_name; + u8 state, state_new; + u32 settle_us; + int n_paths; + int ret; + + bt_dev_dbg(hdev, "download %s", fw_cfg->desc); + ret =3D qdfu_get_target_state(hdev, &state); + if (ret) + goto out; + + if (state & fw_cfg->loaded_flag) { + bt_dev_info(hdev, "%s already downloaded", fw_cfg->desc); + goto out; + } + + n_paths =3D fw_cfg->get_path(hdev, fw_cfg, info, ctrl_id, paths, sizeof(p= aths)); + if (n_paths < 0) { + bt_dev_err(hdev, "Failed to get %s path: %pe", + fw_cfg->desc, ERR_PTR(n_paths)); + ret =3D n_paths; + goto out; + } + bt_dev_dbg(hdev, "%s path: %s", fw_cfg->desc, paths); + if (n_paths =3D=3D 2) + bt_dev_dbg(hdev, "%s fallback path: %s", fw_cfg->desc, + paths + strlen(paths) + 1); + + loaded_name =3D paths; + ret =3D request_firmware(&fw, paths, &hdev->dev); + if (ret =3D=3D -ENOENT && n_paths =3D=3D 2) { + bt_dev_err(hdev, "Failed to request %s %s: %pe", + fw_cfg->desc, loaded_name, ERR_PTR(ret)); + loaded_name =3D paths + strlen(paths) + 1; + ret =3D request_firmware(&fw, loaded_name, &hdev->dev); + } + if (ret) { + bt_dev_err(hdev, "Failed to request %s %s: %pe", + fw_cfg->desc, loaded_name, ERR_PTR(ret)); + goto out; + } + bt_dev_dbg(hdev, "%s request succeeded: %s", fw_cfg->desc, loaded_name); + + ret =3D fw_cfg->check(hdev, fw_cfg, info, ctrl_id, fw); + if (ret) + goto out_free_fw; + + ret =3D fw_cfg->download(hdev, fw_cfg, info, ctrl_id, fw->data, fw->size); + if (ret) + goto out_free_fw; + bt_dev_info(hdev, "%s download succeeded: %s", fw_cfg->desc, loaded_name); + + ret =3D qdfu_poll_state(hdev, &state_new, true, fw_cfg->loaded_flag); + if (ret) + goto out_free_fw; + if (state_new !=3D state) + bt_dev_dbg(hdev, "%s state: 0x%02x -> 0x%02x", + fw_cfg->desc, state, state_new); + + if (fw_cfg->get_settle_us) + settle_us =3D fw_cfg->get_settle_us(hdev, fw_cfg, info, ctrl_id); + else + settle_us =3D fw_cfg->settle_us; + fsleep(settle_us); + + if (fw_cfg->post_download) { + ret =3D fw_cfg->post_download(hdev, fw_cfg, info, ctrl_id); + if (ret) + bt_dev_err(hdev, "%s post-download failed: %pe", + fw_cfg->desc, ERR_PTR(ret)); + else + bt_dev_dbg(hdev, "%s post-download succeeded", fw_cfg->desc); + } + + if (!ret) { + bt_dev_dbg(hdev, "download %s succeeded", fw_cfg->desc); + ret =3D 1; + } + +out_free_fw: + release_firmware(fw); +out: + if (ret < 0) + bt_dev_err(hdev, "download %s failed: %pe", fw_cfg->desc, ERR_PTR(ret)); + return ret; +} + +/* + * =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D + * btusb_qcom.h APIs + * =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D + */ + +/* size of the hci priv area */ +int btusb_qcom_hdev_priv_size(void) +{ + return sizeof(struct btqcom_data) + sizeof(struct btusb_qcom); +} + +/* get @hdev's transport-specific data */ +struct btusb_qcom *btusb_qcom_xport_data(struct hci_dev *hdev) +{ + struct btqcom_data *qbt_data =3D hci_get_priv(hdev); + + return (struct btusb_qcom *)(qbt_data + 1); +} + +/* work function for qbt_data->dwork */ +static void btqcom_work(struct work_struct *work) +{ + struct btqcom_data *qbt_data; + struct hci_dev *hdev; + + qbt_data =3D container_of(to_delayed_work(work), struct btqcom_data, dwor= k); + hdev =3D qbt_data->hdev; + + if (test_and_clear_bit(QBT_WORK_RESET_HDEV, &qbt_data->work_flags)) { + hci_dev_hold(hdev); + hci_req_sync_lock(hdev); + + btusb_do_reset_work(hdev, QBT_RESET_TYPE_ASYNC); + + hci_req_sync_unlock(hdev); + hci_dev_put(hdev); + } +} + +static void devm_btusb_qcom_deinit(void *data) +{ + struct btqcom_data *qbt_data =3D data; + struct btusb_qcom *xport_data; + + if (!qbt_data->inited) + return; + + xport_data =3D qbt_data->xport_data; + mutex_destroy(&qbt_data->req_mutex); + mutex_destroy(&xport_data->tx_mutex); + + qbt_data->inited =3D false; + bt_dev_dbg(qbt_data->hdev, "btusb qcom deinited"); +} + +static void __maybe_unused btusb_qcom_deinit(struct btqcom_data *qbt_data) +{ + devm_release_action(&qbt_data->hdev->dev, devm_btusb_qcom_deinit, + qbt_data); +} + +/* + * btusb_qcom_init - initialize hdev and per-device btqcom_data + * @qbt_data: the btqcom_data to initialize + * + * Doesn't touch btqcom_data fields that live for the hdev's lifetime, + * e.g. @fw_dir and @fw_logging. + * + * Return: 0 on success, or a negative errno on failure. + */ +static int btusb_qcom_init(struct btqcom_data *qbt_data) +{ + struct btusb_qcom *xport_data =3D qbt_data->xport_data; + struct hci_dev *hdev =3D qbt_data->hdev; + int res; + + WRITE_ONCE(qbt_data->misc_flags, 0); + WRITE_ONCE(qbt_data->work_flags, 0); + + qbt_data->md_state =3D HCI_DEVCOREDUMP_IDLE; + qbt_data->md_submit_err =3D 0; + qbt_data->md_submit_size =3D 0; + qbt_data->md_pending_flags =3D 0; + btqcom_reset_memdump(&qbt_data->md); + + WRITE_ONCE(qbt_data->req, NULL); + WRITE_ONCE(qbt_data->req_state, QHCI_REQ_DONE); + + if (qbt_data->inited) + return 0; + + hdev->manufacturer =3D 0x1d; + hci_set_quirk(hdev, HCI_QUIRK_NON_PERSISTENT_SETUP); + hci_set_quirk(hdev, HCI_QUIRK_SIMULTANEOUS_DISCOVERY); + hci_set_quirk(hdev, HCI_QUIRK_WIDEBAND_SPEECH_SUPPORTED); + + if (qbt_data->flags & QBT_FLAG_AOSP_EXT) + hci_set_aosp_capable(hdev); + if (qbt_data->flags & QBT_FLAG_MSFT_EXT) + hci_set_msft_opcode(hdev, 0xFD70); + + res =3D devm_add_action(&hdev->dev, devm_btusb_qcom_deinit, qbt_data); + if (res) { + bt_dev_err(hdev, "Add btusb qcom deinit action failed: %pe", + ERR_PTR(res)); + return res; + } + + INIT_DELAYED_WORK(&qbt_data->dwork, btqcom_work); + mutex_init(&xport_data->tx_mutex); + mutex_init(&qbt_data->req_mutex); + init_waitqueue_head(&qbt_data->req_wait_q); + spin_lock_init(&qbt_data->req_spinlock); + + hdev->set_bdaddr =3D btqcom_set_bdaddr; + hdev->reset =3D btusb_qcom_reset; + hdev->shutdown =3D btusb_qcom_shutdown; + hdev->hw_error =3D btqcom_hw_error; + hdev->handle_ev_vendor =3D btqcom_handle_ev_vendor; + hdev->recv_vendor_pkt =3D btqcom_recv_vendor_pkt; + + qbt_data->md_ready =3D false; + if (qbt_data->flags & QBT_FLAG_MEMDUMP) { + res =3D hci_devcd_register(hdev, btusb_qcom_trigger_memdump, + btusb_qcom_memdump_hdr, + btusb_qcom_notify_memdump); + if (!res) + qbt_data->md_ready =3D true; + else if (res =3D=3D -EOPNOTSUPP) + bt_dev_warn(hdev, "CONFIG_DEV_COREDUMP not enabled"); + else + bt_dev_err(hdev, "register devcoredump failed: %pe", + ERR_PTR(res)); + } + + /* pairs with smp_load_acquire() in btusb_qcom_disconnect() */ + smp_store_release(&qbt_data->inited, true); + + bt_dev_dbg(hdev, "btusb qcom inited"); + return 0; +} + +/* + * btusb_qcom_setup_unified - setup for a unified BTC + * @hdev: the HCI device to set up + * @info: BTC info for this hdev + * @ctrl_id: BTC QDFU ID + * + * Return: 0 on success, or a negative errno on failure. + */ +static int btusb_qcom_setup_unified(struct hci_dev *hdev, + const struct qbtc_info *info, + const struct qdfu_bt_id *ctrl_id) +{ + struct btqcom_data *qbt_data =3D hci_get_priv(hdev); + enum qbtc_category btc_cat =3D info->category; + const struct qdfu_fw_cfg *cfgs_bt; + struct qdfu_bt_id bt_id; + int ret; + + cfgs_bt =3D qdfu_fw_cfgs_bt[btc_cat]; + + ret =3D btusb_download_fw(hdev, &cfgs_bt[QBT_FW_TYPE_PATCH], info, ctrl_i= d); + if (ret < 0) + return ret; + + ret =3D qdfu_get_target_version(hdev, &bt_id); + if (ret) + return ret; + qdfu_to_qhci_id(&qbt_data->ver, &qbt_data->board_id, &bt_id); + + ret =3D btusb_download_fw(hdev, &cfgs_bt[QBT_FW_TYPE_NVM], info, &bt_id); + if (ret) + return ret < 0 ? ret : 0; + + ret =3D __hci_reset_sync(hdev); + if (ret) + bt_dev_err(hdev, "HCI reset failed: %pe", ERR_PTR(ret)); + else + bt_dev_dbg(hdev, "HCI reset succeeded"); + + return ret; +} + +/* + * btusb_qcom_setup_msubsys - setup for a multi-subsystem BTC + * @hdev: the HCI device to set up + * @info: BTC info for this hdev + * @ctrl_id: BTC QDFU ID + * + * Return: 0 on success, or a negative errno on failure. + */ +static int btusb_qcom_setup_msubsys(struct hci_dev *hdev, + const struct qbtc_info *info, + const struct qdfu_bt_id *ctrl_id) +{ + const struct qdfu_fw_cfg *cfgs_peri =3D qdfu_fw_cfgs_subsys[QBTC_SUBSYS_P= ERI]; + const struct qdfu_fw_cfg *cfgs_bt =3D qdfu_fw_cfgs_bt[QBTC_CAT_MSUBSYS]; + struct btqcom_data *qbt_data =3D hci_get_priv(hdev); + struct qbtc_subsys_data *subsys_peri; + struct qdfu_bt_id peri_id, bt_id; + int ret; + + subsys_peri =3D &qbt_data->subsys[QBTC_SUBSYS_PERI]; + + ret =3D btusb_download_fw(hdev, &cfgs_peri[QBT_FW_TYPE_PATCH], info, ctrl= _id); + if (ret < 0) + return ret; + + ret =3D qdfu_get_target_version(hdev, &peri_id); + if (ret) + return ret; + qdfu_to_qhci_id(&subsys_peri->ver, &subsys_peri->board_id, &peri_id); + + ret =3D btusb_download_fw(hdev, &cfgs_peri[QBT_FW_TYPE_NVM], info, &peri_= id); + if (ret < 0) + return ret; + + ret =3D qdfu_reset_msubsys_bt(hdev); + if (ret) + return ret; + + ret =3D get_qdfu_id_via_hci(hdev, &bt_id); + if (ret) + return ret; + + if (bt_id.rom_version !=3D peri_id.rom_version || + bt_id.soc_id !=3D peri_id.soc_id || + bt_id.board_id !=3D peri_id.board_id) { + bt_dev_info(hdev, "BT ROM Version: 0x%08x", + bt_id.rom_version); + bt_dev_info(hdev, "BT Patch Version: 0x%08x", + bt_id.patch_version); + bt_dev_info(hdev, "BT SoC Version: 0x%08x", + bt_id.soc_id); + bt_dev_info(hdev, "BT Board ID: 0x%04x", + bt_id.board_id); + } + + ret =3D btusb_download_fw(hdev, &cfgs_bt[QBT_FW_TYPE_PATCH], info, &bt_id= ); + if (ret < 0) + return ret; + + ret =3D get_qdfu_id_via_hci(hdev, &bt_id); + if (ret) + return ret; + qdfu_to_qhci_id(&qbt_data->ver, &qbt_data->board_id, &bt_id); + + ret =3D btusb_download_fw(hdev, &cfgs_bt[QBT_FW_TYPE_NVM], info, &bt_id); + if (ret < 0) + return ret; + + return 0; +} + +static int btqcom_post_setup(struct hci_dev *hdev) +{ + struct btqcom_data *qbt_data =3D hci_get_priv(hdev); + struct qbtc_subsys_data *subsys_data; + char buf[512]; + int len =3D 0; + + bt_dev_dbg(hdev, "flags: 0x%lx", qbt_data->flags); + bt_dev_info(hdev, "memdump ready? %s", str_yes_no(qbt_data->md_ready)); + + qbt_config_fw_logging(hdev, qbt_data->fw_logging); + + hci_set_hw_info(hdev, "%s", qbt_data->btc_name); + if (qbt_data->category =3D=3D QBTC_CAT_MSUBSYS) { + subsys_data =3D &qbt_data->subsys[QBTC_SUBSYS_PERI]; + + bt_dev_info(hdev, "%s build info: %s", + qbtc_subsys_name(QBTC_SUBSYS_PERI), subsys_data->build_info); + len =3D scnprintf(buf, sizeof(buf), "%s: %s\n", + qbtc_subsys_name(QBTC_SUBSYS_PERI), subsys_data->build_info); + } + bt_dev_info(hdev, "BT build info: %s", qbt_data->build_info); + len +=3D snprintf(buf + len, sizeof(buf) - len, "BT: %s", qbt_data->build= _info); + hci_set_fw_info(hdev, "%s", buf); + + return 0; +} + +/* + * btusb_qcom_setup - setup a BTC + * @hdev: the HCI device + * + * Identifies the BTC, then dispatches setup based on its category. + * Implements hdev->setup(). + * + * Return: 0 on success, or a negative errno on failure. + */ +int btusb_qcom_setup(struct hci_dev *hdev) +{ + struct btusb_qcom *xport_data =3D btusb_qcom_xport_data(hdev); + bool is_first_setup =3D hci_dev_test_flag(hdev, HCI_SETUP); + struct btqcom_data *qbt_data =3D hci_get_priv(hdev); + const struct qbtc_id *id_entry =3D qbtc_id_table; + struct qdfu_bt_id ctrl_id; + int ret; + + qbt_data->hdev =3D hdev; + xport_data->intf =3D to_usb_interface(hdev->dev.parent); + xport_data->udev =3D interface_to_usbdev(xport_data->intf); + xport_data->idVendor =3D le16_to_cpu(xport_data->udev->descriptor.idVend= or); + xport_data->idProduct =3D le16_to_cpu(xport_data->udev->descriptor.idProd= uct); + qbt_data->xport_data =3D xport_data; + + ret =3D usb_autopm_get_interface(xport_data->intf); + if (ret) { + bt_dev_err(hdev, "get autopm for QCOM BTUSB setup failed: %pe", + ERR_PTR(ret)); + return ret; + } + + ret =3D qdfu_get_target_version(hdev, &ctrl_id); + if (ret) + goto out; + + while (id_entry->rom_version) { + if (id_entry->rom_version =3D=3D ctrl_id.rom_version) + break; + id_entry++; + } + + if (!id_entry->rom_version) { + ret =3D -ENODEV; + bt_dev_warn(hdev, "Detected unsupported BT controller:"); + } else { + bt_dev_info(hdev, "%s QCOM %s BT controller: %s", + is_first_setup ? "Detected" : "Setup", + qbtc_category_name(id_entry->btc_info->category), + id_entry->name); + } + + bt_dev_info(hdev, "ROM Version : 0x%08x", ctrl_id.rom_version); + bt_dev_info(hdev, "Patch Version: 0x%08x", ctrl_id.patch_version); + bt_dev_info(hdev, "SoC Version : 0x%08x", ctrl_id.soc_id); + bt_dev_info(hdev, "Board ID : 0x%04x", ctrl_id.board_id); + + if (ret) + goto out; + + if (!qbt_data->drv_name) + qbt_data->drv_name =3D dev_driver_string(&xport_data->intf->dev); + qbt_data->btc_name =3D id_entry->name; + qbt_data->category =3D id_entry->btc_info->category; + qbt_data->flags =3D id_entry->btc_info->flags & QBT_FLAG_BTC_CFG_MASK; + if (xport_data->reset_gpio) + qbt_data->flags |=3D QBT_FLAG_HW_RESET; + ret =3D btusb_qcom_init(qbt_data); + if (ret) + goto out; + + switch (qbt_data->category) { + case QBTC_CAT_LEGACY: + ret =3D -EOPNOTSUPP; + break; + case QBTC_CAT_UNIFIED: + hci_set_quirk(hdev, HCI_QUIRK_BROKEN_ENHANCED_SETUP_SYNC_CONN); + ret =3D btusb_qcom_setup_unified(hdev, id_entry->btc_info, &ctrl_id); + break; + case QBTC_CAT_MSUBSYS: + ret =3D btusb_qcom_setup_msubsys(hdev, id_entry->btc_info, &ctrl_id); + break; + default: + ret =3D -EINVAL; + break; + } + + if (!ret) + ret =3D btqcom_post_setup(hdev); + +out: + usb_autopm_put_interface(xport_data->intf); + if (!ret) + bt_dev_info(hdev, "QCOM BTUSB setup succeeded (^_^)"); + else + bt_dev_err(hdev, "QCOM BTUSB setup failed: %pe", ERR_PTR(ret)); + return ret; +} + +/* + * btusb_qcom_disconnect - clean up on USB disconnect + * @hdev: the HCI device being disconnected + * + * Return: 0 on success, or a negative errno on failure. + */ +int btusb_qcom_disconnect(struct hci_dev *hdev) +{ + struct btqcom_data *qbt_data =3D hci_get_priv(hdev); + + /* pairs with smp_store_release() in btusb_qcom_init() */ + if (!smp_load_acquire(&qbt_data->inited)) + return 0; + + bt_dev_dbg(hdev, "disconnection: misc_flags(0x%lx)", + READ_ONCE(qbt_data->misc_flags)); + + if (test_bit(QBT_MISC_MEMDUMP_INCOMING, &qbt_data->misc_flags) && + !qbt_data->md_submit_err) + bt_dev_warn(hdev, "memdump collection interrupted by disconnection"); + + qperi_tx_sync_cancel_sync(hdev, -ENODEV); + if (test_and_clear_bit(QBT_MISC_CMD_TIMEOUT, &qbt_data->misc_flags)) { + wake_up_var(&qbt_data->misc_flags); + bt_dev_dbg(hdev, "wake command-timeout waiter on disconnection"); + } + disable_delayed_work_sync(&qbt_data->dwork); + + return 0; +} + +/* + * btusb_qcom_send_frame - send a frame for BT or PERI + * @hdev: the HCI device + * @skb: the frame to send + * + * Implements hdev->send(). + * + * Return: 0 on success, or a negative errno on failure. + */ +int btusb_qcom_send_frame(struct hci_dev *hdev, struct sk_buff *skb) +{ + struct btusb_qcom *xport_data =3D btusb_qcom_xport_data(hdev); + struct btqcom_data *qbt_data =3D hci_get_priv(hdev); + bool is_vendor; + bool need_lock; + u8 pkt_type; + int ret; + + if (qbt_data->category !=3D QBTC_CAT_MSUBSYS) + return xport_data->send_bt_frame(hdev, skb); + + pkt_type =3D hci_skb_pkt_type(skb); + is_vendor =3D pkt_type =3D=3D HCI_VENDOR_PKT; + if (is_vendor) + hci_skb_pkt_type(skb) =3D *(const u8 *)skb_pull_data(skb, 1); + + pkt_type =3D hci_skb_pkt_type(skb); + need_lock =3D pkt_type =3D=3D HCI_COMMAND_PKT || + pkt_type =3D=3D QPERI_COMMAND_PKT; + + if (need_lock) + mutex_lock(&xport_data->tx_mutex); + + if (is_vendor) + ret =3D xport_data->send_vendor_frame(hdev, skb); + else + ret =3D xport_data->send_bt_frame(hdev, skb); + + if (need_lock) + mutex_unlock(&xport_data->tx_mutex); + + return ret; +} + +/* + * RX flow: + * + * USB intr/bulk endpoint (byte stream) + * -> btusb_qcom_recv_{intr,bulk}() (reassemble to frame) + * -> btqcom_recv_frame() + * -> btusb_upward_frame() (dispatch frame upwards) + * -> hci_recv_frame() (PERI frame as HCI_VENDOR_PKT) + * -> xport_data->recv_bt_frame() (BT, back to btusb_main.c) + */ + +/* + * btusb_upward_frame - dispatch a received frame upwards + * @hdev: the HCI device the @skb comes from + * @skb: the frame to dispatch upwards + * + * Return: 0 on success, or a negative errno on failure. + */ +static int btusb_upward_frame(struct hci_dev *hdev, struct sk_buff *skb) +{ + struct btusb_qcom *xport_data =3D btusb_qcom_xport_data(hdev); + u8 pkt_type =3D hci_skb_pkt_type(skb); + u16 handle; + int ret; + + switch (pkt_type) { + case QPERI_EVENT_PKT: + case QPERI_ACLDATA_PKT: + *(u8 *)skb_push(skb, 1) =3D pkt_type; + hci_skb_pkt_type(skb) =3D HCI_VENDOR_PKT; + break; + case HCI_EVENT_PKT: + break; + case HCI_ACLDATA_PKT: + handle =3D hci_acl_handle(skb); + /* reroute vendor ACLs as HCI_VENDOR_PKT */ + if (handle =3D=3D QBT_HANDLE_ENHANCED_LOGGING || + handle =3D=3D QBT_HANDLE_MEMDUMP) { + *(u8 *)skb_push(skb, 1) =3D pkt_type; + hci_skb_pkt_type(skb) =3D HCI_VENDOR_PKT; + } + break; + default: + dev_kfree_skb_irq(skb); + ret =3D -EINVAL; + goto out; + } + + if (hci_skb_pkt_type(skb) =3D=3D HCI_VENDOR_PKT) + ret =3D hci_recv_frame(hdev, skb); + else + ret =3D xport_data->recv_bt_frame(hdev, skb); + +out: + if (ret) + bt_dev_err(hdev, "upward frame with type (0x%02x) failed: %pe", + pkt_type, ERR_PTR(ret)); + + return ret; +} + +/* Every frame lands here first =E2=80=94 the ideal spot for pre-processin= g. */ +static int btqcom_recv_frame(struct hci_dev *hdev, struct sk_buff *skb) +{ + u8 pkt_type =3D hci_skb_pkt_type(skb); + + switch (pkt_type) { + case HCI_EVENT_PKT: + case QPERI_EVENT_PKT: + case HCI_ACLDATA_PKT: + case QPERI_ACLDATA_PKT: + break; + default: + dev_kfree_skb_irq(skb); + bt_dev_err_ratelimited(hdev, "unexpected pkt type 0x%02x", pkt_type); + return -EINVAL; + } + + return btusb_upward_frame(hdev, skb); +} + +/* + * RX reassembly state, stashed in hci_skb_pkt_seqnum(skb) between calls: + * QRX_STATE_INDICATOR - determining the packet type + * QRX_STATE_HEADER - receiving the packet header + * QRX_STATE_PAYLOAD - receiving the payload + */ +enum { + QRX_STATE_INDICATOR, + QRX_STATE_HEADER, + QRX_STATE_PAYLOAD, +}; + +/* + * btusb_qcom_recv_intr - reassemble byte stream from the intr endpoint to= frame + * @hdev: the HCI device + * @skb: the in-progress frame, or NULL to start a new one + * @buffer: bytes received + * @count: bytes count + * @err: output error code + * + * See the RX flow diagram above for further dispatch. + * + * Return: the in-progress frame to resume on the next call, or NULL + * otherwise. + */ +struct sk_buff *btusb_qcom_recv_intr(struct hci_dev *hdev, struct sk_buff = *skb, + void *buffer, int count, int *err) +{ + struct btqcom_data *qbt_data =3D hci_get_priv(hdev); + enum qbtc_category btc_cat =3D qbt_data->category; + int rx_state; + u8 pkt_type; + int len; + int res; + + *err =3D 0; + while (count) { + if (!skb) { + u8 host_id =3D *(u8 *)buffer; + + skb =3D bt_skb_alloc(QPERI_MAX_EVENT_SIZE, GFP_ATOMIC); + if (!skb) { + *err =3D -ENOMEM; + break; + } + + /* see struct qperi_event_hdr for PERI event format */ + if (host_id =3D=3D QHCI_HOST_ID_BT) { + hci_skb_pkt_type(skb) =3D QPERI_EVENT_PKT; + hci_skb_pkt_seqnum(skb) =3D QRX_STATE_HEADER; + hci_skb_expect(skb) =3D QPERI_EVENT_HDR_SIZE; + } else { + hci_skb_pkt_type(skb) =3D HCI_EVENT_PKT; + hci_skb_pkt_seqnum(skb) =3D QRX_STATE_HEADER; + hci_skb_expect(skb) =3D HCI_EVENT_HDR_SIZE; + } + } + + len =3D min_t(uint, hci_skb_expect(skb), count); + skb_put_data(skb, buffer, len); + + count -=3D len; + buffer +=3D len; + hci_skb_expect(skb) -=3D len; + + if (hci_skb_expect(skb)) + continue; + + rx_state =3D hci_skb_pkt_seqnum(skb); + if (rx_state =3D=3D QRX_STATE_PAYLOAD) + goto frame_done; + + hci_skb_pkt_seqnum(skb) =3D QRX_STATE_PAYLOAD; + pkt_type =3D hci_skb_pkt_type(skb); + if (pkt_type =3D=3D QPERI_EVENT_PKT) + hci_skb_expect(skb) =3D qperi_event_header(skb)->plen; + else if (pkt_type =3D=3D HCI_EVENT_PKT) + hci_skb_expect(skb) =3D hci_event_hdr(skb)->plen; + + if (hci_skb_expect(skb)) + continue; + +frame_done: + if (count && count < HCI_EVENT_HDR_SIZE) { + bt_dev_warn(hdev, + "Unexpected continuation: %d bytes", + count); + if (btc_cat !=3D QBTC_CAT_MSUBSYS) + count =3D 0; + } + + hci_skb_pkt_seqnum(skb) =3D 0; + res =3D btqcom_recv_frame(hdev, skb); + if (res) + bt_dev_err_ratelimited(hdev, "recv intr frame failed: %pe", + ERR_PTR(res)); + skb =3D NULL; + } + + return skb; +} + +/* + * btusb_qcom_recv_bulk - reassemble byte stream from the bulk endpoint to= frame + * @hdev: the HCI device + * @skb: the in-progress frame, or NULL to start a new one + * @buffer: bytes received + * @count: bytes count + * @err: output error code + * + * See the RX flow diagram above for further dispatch. + * + * Return: the in-progress frame to resume on the next call, or NULL + * otherwise. + */ +struct sk_buff *btusb_qcom_recv_bulk(struct hci_dev *hdev, struct sk_buff = *skb, + void *buffer, int count, int *err) +{ + struct qperi_acl_hdr *peri_hdr; + u16 peri_handle; + int rx_state; + u8 pkt_type; + int res; + int len; + + *err =3D 0; + while (count) { + if (!skb) { + skb =3D bt_skb_alloc(QPERI_MAX_FRAME_SIZE, GFP_ATOMIC); + if (!skb) { + *err =3D -ENOMEM; + break; + } + hci_skb_pkt_seqnum(skb) =3D QRX_STATE_INDICATOR; + hci_skb_expect(skb) =3D HCI_ACL_HDR_SIZE; + } + + len =3D min_t(uint, hci_skb_expect(skb), count); + skb_put_data(skb, buffer, len); + + count -=3D len; + buffer +=3D len; + hci_skb_expect(skb) -=3D len; + + if (hci_skb_expect(skb)) + continue; + + rx_state =3D hci_skb_pkt_seqnum(skb); + switch (rx_state) { + case QRX_STATE_INDICATOR: + hci_skb_pkt_seqnum(skb) =3D QRX_STATE_HEADER; + peri_hdr =3D qperi_acl_header(skb); + peri_handle =3D qperi_acl_handle(skb); + if (peri_hdr->host_id =3D=3D QHCI_HOST_ID_BT && + peri_handle >=3D 0xEC0 && peri_handle <=3D 0xECF) { + hci_skb_pkt_type(skb) =3D QPERI_ACLDATA_PKT; + hci_skb_expect(skb) =3D QPERI_ACL_HDR_SIZE - HCI_ACL_HDR_SIZE; + continue; + } + hci_skb_pkt_type(skb) =3D HCI_ACLDATA_PKT; + fallthrough; + + case QRX_STATE_HEADER: + pkt_type =3D hci_skb_pkt_type(skb); + if (pkt_type =3D=3D QPERI_ACLDATA_PKT) + hci_skb_expect(skb) =3D qperi_acl_dlen(skb); + else if (pkt_type =3D=3D HCI_ACLDATA_PKT) + hci_skb_expect(skb) =3D hci_acl_dlen(skb); + + if (hci_skb_expect(skb) > QPERI_MAX_FRAME_SIZE - skb->len) { + dev_kfree_skb_irq(skb); + skb =3D NULL; + *err =3D -EILSEQ; + return NULL; + } + hci_skb_pkt_seqnum(skb) =3D QRX_STATE_PAYLOAD; + if (hci_skb_expect(skb)) + continue; + fallthrough; + + case QRX_STATE_PAYLOAD: + break; + } + + hci_skb_pkt_seqnum(skb) =3D 0; + res =3D btqcom_recv_frame(hdev, skb); + if (res) + bt_dev_err_ratelimited(hdev, "recv bulk frame failed: %pe", + ERR_PTR(res)); + skb =3D NULL; + } + + return skb; +} + +MODULE_AUTHOR("Zijun Hu "); diff --git a/drivers/bluetooth/btusb_qcom.h b/drivers/bluetooth/btusb_qcom.h new file mode 100644 index 000000000000..8fbc105f4eaf --- /dev/null +++ b/drivers/bluetooth/btusb_qcom.h @@ -0,0 +1,99 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Qualcomm Bluetooth USB transport-specific support + * + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#ifndef __BTUSB_QCOM_H +#define __BTUSB_QCOM_H + +#include +#include +#include +#include + +#include + +#define QPERI_COMMAND_PKT 0x31 +#define QPERI_ACLDATA_PKT 0x32 +#define QPERI_EVENT_PKT 0x34 + +struct btusb_qcom { + __u16 idVendor; + __u16 idProduct; + struct usb_device *udev; + struct usb_interface *intf; + struct gpio_desc *reset_gpio; + + /* serializes sending BT and vendor frames to the transport */ + struct mutex tx_mutex; + + void (*prepare_reset)(struct hci_dev *hdev); + int (*recv_bt_frame)(struct hci_dev *hdev, struct sk_buff *skb); + int (*send_bt_frame)(struct hci_dev *hdev, struct sk_buff *skb); + int (*send_vendor_frame)(struct hci_dev *hdev, struct sk_buff *skb); +}; + +#if IS_ENABLED(CONFIG_BT_HCIBTUSB_QCOM) + +int btusb_qcom_hdev_priv_size(void); +struct btusb_qcom *btusb_qcom_xport_data(struct hci_dev *hdev); + +int btusb_qcom_setup(struct hci_dev *hdev); +int btusb_qcom_disconnect(struct hci_dev *hdev); +int btusb_qcom_send_frame(struct hci_dev *hdev, struct sk_buff *skb); + +struct sk_buff *btusb_qcom_recv_intr(struct hci_dev *hdev, struct sk_buff = *skb, + void *buffer, int count, int *err); + +struct sk_buff *btusb_qcom_recv_bulk(struct hci_dev *hdev, struct sk_buff = *skb, + void *buffer, int count, int *err); + +#else + +static inline int btusb_qcom_hdev_priv_size(void) +{ + return 0; +} + +static inline struct btusb_qcom *btusb_qcom_xport_data(struct hci_dev *hde= v) +{ + return NULL; +} + +static inline int btusb_qcom_setup(struct hci_dev *hdev) +{ + return -EOPNOTSUPP; +} + +static inline int btusb_qcom_disconnect(struct hci_dev *hdev) +{ + return -EOPNOTSUPP; +} + +static inline int btusb_qcom_send_frame(struct hci_dev *hdev, struct sk_bu= ff *skb) +{ + return -EOPNOTSUPP; +} + +static inline struct sk_buff *btusb_qcom_recv_intr(struct hci_dev *hdev, + struct sk_buff *skb, + void *buffer, int count, + int *err) +{ + *err =3D -EOPNOTSUPP; + return NULL; +} + +static inline struct sk_buff *btusb_qcom_recv_bulk(struct hci_dev *hdev, + struct sk_buff *skb, + void *buffer, int count, + int *err) +{ + *err =3D -EOPNOTSUPP; + return NULL; +} + +#endif +#endif --=20 2.34.1