From nobody Sat Feb 7 07:11:42 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; arc=pass (i=1 dmarc=pass fromdomain=aspeedtech.com); dmarc=pass(p=quarantine dis=none) header.from=aspeedtech.com ARC-Seal: i=2; a=rsa-sha256; t=1770348035; cv=pass; d=zohomail.com; s=zohoarc; b=PUtCkw5bHfy9zYhZcH4xpBCI5eABIRoFSErZRlBLnNeDjSjL26E0bHh3cIcTA5KSuOfSYaMnrqMkhgOWIMlsa4NjdRmnppWewYUKXZ5NoAULwhDmnKLI/02HUNtHzwDiEVWa6UC9jE6LhqIMzs+TiVEdWEdPCQ59ZE2fKFcsLxE= ARC-Message-Signature: i=2; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1770348035; h=Content-Type:Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:Subject:To:To:Message-Id:Reply-To; bh=cq6lqDdsUDKT6H2KDYLpPMs8pjOFzpZprKW99LvIS9Q=; b=NX298Sk7F2YJZnMe4tNuJ5PNxKAc7GBXQqZE3xh1EjpGxWZ0NBsYW1QChUOCATvE5wT5mxfyBq9EiOpQma7xYx7D9jhxTZijdmoWvO8rbjk/juDn6mqR/ousVvJtKrBUnS5Mxpl2rY/5wzBm7CGhNJnEfqw/QMmR/0X0J+P7g7E= ARC-Authentication-Results: i=2; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; arc=pass (i=1 dmarc=pass fromdomain=aspeedtech.com); dmarc=pass header.from= (p=quarantine dis=none) Return-Path: Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1770348035369236.344659124636; Thu, 5 Feb 2026 19:20:35 -0800 (PST) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1voCNy-0000SM-Ni; Thu, 05 Feb 2026 22:20:00 -0500 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1voCNm-0000Pu-Ew; Thu, 05 Feb 2026 22:19:49 -0500 Received: from mail-koreacentralazlp170130006.outbound.protection.outlook.com ([2a01:111:f403:c40f::6] helo=SEYPR02CU001.outbound.protection.outlook.com) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1voCNi-0008F6-L1; Thu, 05 Feb 2026 22:19:45 -0500 Received: from TYPPR06MB8206.apcprd06.prod.outlook.com (2603:1096:405:383::19) by SEZPR06MB5762.apcprd06.prod.outlook.com (2603:1096:101:ac::5) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.20.9587.15; Fri, 6 Feb 2026 03:19:30 +0000 Received: from TYPPR06MB8206.apcprd06.prod.outlook.com ([fe80::e659:1ead:77cb:f6d3]) by TYPPR06MB8206.apcprd06.prod.outlook.com ([fe80::e659:1ead:77cb:f6d3%3]) with mapi id 15.20.9587.013; Fri, 6 Feb 2026 03:19:30 +0000 ARC-Seal: i=1; a=rsa-sha256; s=arcselector10001; d=microsoft.com; cv=none; b=E1/n9+SGBAi29lyWNSTisqLRzU0W/+ZBlugBClpp+9SVk2WjdRHrDNxWU9LlxA6bJuXwK5u0OoFZ3pwqB0NdLON0RtlMJkIFoiegvTAQXuql48B3l2JwQf7ajGf6sGcNPOMP2Z/ivQs1CLiTyNUTop+sEYqarhvxYgb0FeobgTP7LtHaMMx2wVGUHgh3Fhj5U9WTWEfwQGbxd/vvXrMVsEMmEQmxSwiyQ2EIDBO3wfPRpCykdvetCwkVM9XZolaLdqEXcTPx6s0zvpdZ8Bi5oWQBEoFfrZa2ZeSXkJoKrKE6KnWDV+Y/XCcZwQxJK3q5GjbtMqoEtfeFuRdtRtXwYA== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=microsoft.com; s=arcselector10001; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-AntiSpam-MessageData-ChunkCount:X-MS-Exchange-AntiSpam-MessageData-0:X-MS-Exchange-AntiSpam-MessageData-1; bh=cq6lqDdsUDKT6H2KDYLpPMs8pjOFzpZprKW99LvIS9Q=; b=kmb4ZoojJG3bkzqHBHnjfWG2dArUqm3pTe+NnBZWWkNygOFUrpxr4DrhQEVFAOtoedlCp2bEoibQZ8F3Hsc204HQ8wuXC70O9u62Tbcr8d5ZSFh6/yoNuoxrCsJTUgHnM9YgIAVwDS03z11DioFeuQMBYlj7a6gQ/SzDck2ki5MSrV7Nq11dwSX8GYRHH4i7BK6N1/nVYZO8oMIfoZHfTpHaciJeMEHEledmK9Qs5ViySjA8i6QUHlPWYUWm86crO4PYnfCqmolB9koXxBjLqkGNJ/Zey7ZTzB1x/oHHPyxj31rSwYEZyNI6bZIooJpPeHj0LGf4iPmnNEF/N9cSyg== ARC-Authentication-Results: i=1; mx.microsoft.com 1; spf=pass smtp.mailfrom=aspeedtech.com; dmarc=pass action=none header.from=aspeedtech.com; dkim=pass header.d=aspeedtech.com; arc=none DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=aspeedtech.com; s=selector1; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-SenderADCheck; bh=cq6lqDdsUDKT6H2KDYLpPMs8pjOFzpZprKW99LvIS9Q=; b=PYgl8NJmoPMnM2gT6YkOAG2lsnJJm1pc02719yAMjvztfS0wq0ei5BGkYOmDwnsdXdplz7PuZl8AS0c45Eq0srl2m2XTs7SZV9HfpBi+dI7X/mnimkiP9PCbOR4LkpZ+yjobM6OLA4VrQQ17cqi1LcWZCVRXGnLtYSc5GT8IsnV3kAwH6giSGTIUT/WY/khN4WaVfTu0uOYcZu4Yx1C39BSyzkrMvAyrkTmx1oFHLuvX5Kr1hOfXFZPKtJ0tewJ5hOdO+CeDxeLp70MU2Mj3XMB21A1gkWX+fG+8VeByuo9ijeIeNJfPX194W3aYygi4dn8aa0QneD6GMPX17NduIA== From: Jamin Lin To: Paolo Bonzini , Peter Maydell , =?iso-8859-1?Q?C=E9dric_Le_Goater?= , Steven Lee , Troy Lee , Andrew Jeffery , Joel Stanley , =?iso-8859-1?Q?Marc-Andr=E9_Lureau?= , =?iso-8859-1?Q?Daniel_P=2E_Berrang=E9?= , =?iso-8859-1?Q?Philippe_Mathieu-Daud=E9?= , "open list:All patches CC here" , "open list:ARM TCG CPUs" CC: Jamin Lin , Troy Lee , Kane Chen , Joe Komlodi , Patrick Venture , Titus Rwantare Subject: [PATCH v3 02/20] hw/i3c: Add bus support Thread-Topic: [PATCH v3 02/20] hw/i3c: Add bus support Thread-Index: AQHclxdoOb0K7ZnheE+yUjY8p7Dykw== Date: Fri, 6 Feb 2026 03:19:30 +0000 Message-ID: <20260206031926.3227848-3-jamin_lin@aspeedtech.com> References: <20260206031926.3227848-1-jamin_lin@aspeedtech.com> In-Reply-To: <20260206031926.3227848-1-jamin_lin@aspeedtech.com> Accept-Language: zh-TW, en-US Content-Language: en-US X-MS-Has-Attach: X-MS-TNEF-Correlator: authentication-results: dkim=none (message not signed) header.d=none;dmarc=none action=none header.from=aspeedtech.com; x-ms-publictraffictype: Email x-ms-traffictypediagnostic: TYPPR06MB8206:EE_|SEZPR06MB5762:EE_ x-ms-office365-filtering-correlation-id: b47b0e0b-edd7-4066-6b7c-08de652e8aea x-ms-exchange-senderadcheck: 1 x-ms-exchange-antispam-relay: 0 x-microsoft-antispam: BCL:0; ARA:13230040|1800799024|366016|376014|7416014|921020|38070700021; x-microsoft-antispam-message-info: =?iso-8859-1?Q?ho4cJkrX6Ebm+XUp9qvhTWWLLPSRcnK3yVtzUuBiNIoBJr3cn1SgNTg/tt?= =?iso-8859-1?Q?HdaNLWGg8sgt1w5IzlOMcEpn6pn56gSpwu4fbhAxnFfKmpKpKe+7yoS+4n?= =?iso-8859-1?Q?BrYvYBI7fPGQJri2hZBZoYA/itLKnENGnPGebZeTnatCrKHt6+FkVvg3sF?= =?iso-8859-1?Q?yBKuZ7wwqHEu6mpa/ZLHAvYOLadWLCJt533cslwuwKE5hSP/OdnX9AN5cq?= =?iso-8859-1?Q?Br7MQhRsHt4oSGCUBM3dggWmbho2ZcIN081LddCl8vrwDHdV/anlVG/7+h?= =?iso-8859-1?Q?kpU0WKAbWRHDZKQ053mPhgJjAbqUvYEy7yb+E80amQYYDxkEipLLHJVcx3?= =?iso-8859-1?Q?uR/+ekGlhNEDsZVdUwomYn2ZDGPi/vLf7eoph4MBrD8C9abBRziLvJOOkC?= =?iso-8859-1?Q?QgLpbfhTIOJ9FyuXxKlN0Uz9WQse3+5M/E9QAqf9YXhHSfHBnzcbN/kLw6?= =?iso-8859-1?Q?fqxQLOAHXz8SVcRuihjWfb6XxGGZHMXHyHDjIXFPIzrHsGnNExZv7Eonve?= =?iso-8859-1?Q?dicdN6zPjWMNlVXtwL6utETI6wj9CHtyg4QDZwscyxViestVFSPn7xCcHz?= =?iso-8859-1?Q?u3+IG1WOzSCV+VzR+c98hKRfhLhoaDhZQ5PWjDCa5QY8XqX5Ko462nyV2q?= =?iso-8859-1?Q?okBkncvg8M6BFfWtcUcZ8ZogarYMvUFtOwfedxITmT2fVKE4M3ri9d0C/V?= =?iso-8859-1?Q?xJFACVpQ04xZ8w775M/kuGZG9iO939eUCxFCkkYRnTPvUhblme1/WO2hN9?= =?iso-8859-1?Q?OkFs65JkUhrJSiU6sZz95yGAOZZ6+SjpJr4zzv6YjEU2IyN3N0N9CyKVcB?= =?iso-8859-1?Q?rYPM6hYwGSFp9N04YjzngQxBYK+n8/vClahOzJaaa935C1LkqdEN341lsR?= =?iso-8859-1?Q?60+KlF4U7ETmEhBNZ/U8ZBO2xYpO16Y9vwiPeqQEmNy8c1nZ8z8LJ9Z1uk?= =?iso-8859-1?Q?bBsKHghkafbyUBiOK18ETBVaOrbcObYRojtQrXnRCjXFocUgvZ0R9K+Pqk?= =?iso-8859-1?Q?e84LRscvj/+z7ajWnYQ6BNau9WwX6IgJk/42c+h8sp09uI3xdhoJES5QHs?= =?iso-8859-1?Q?dg2VO9UlimbteP2ImWoCN7mwdrRcTONkfYjyFNJQv0UrQgq2+RntHA5m6N?= =?iso-8859-1?Q?e5Rc5WKyRSkSqkzpa3wmZLxjmehTdG9KqgQNVKl/+eIHXrSL8mkjQXzSDP?= =?iso-8859-1?Q?ktZ6BYufMRiW7EiO20MPiyklvKPyem+BhApSh2vp9aXyZ6Jyaz5BiZ5V7+?= =?iso-8859-1?Q?2HvbB6APSE8054VKrL/1tAPengHeQOHaYTlwnMqJBwh36Q6pAZCftksc6w?= =?iso-8859-1?Q?H6t6/LZMrzBv1Lg7VpLfj5HAGh2V3VZQ/ybuerMcbq/8dCfM911Uz/FC9N?= =?iso-8859-1?Q?ar70D4tXHnITD0GU4DgpBfCpgLfvpNc02g88a/IaiObUUBpAY/YM5+f2J1?= =?iso-8859-1?Q?67CQyB0i6Qsw+KDvoZtfyRh+Yt2CnpJ7arcWnPtKqZpyTENvR4hiAAfM1m?= =?iso-8859-1?Q?139dltlBaABHO8pkKdLRPoZaiMO6Y8pjmvGQx0H3uVSIagtkTUsLSpZLZg?= =?iso-8859-1?Q?2LtU8Rmee09fkhhRqEJMXs8m7gfC8jHtwMdVmLDUi1wlhITyyfGxHUBuuH?= =?iso-8859-1?Q?dT/e4adOY/Q2L9KO/UrqB/q5GJ4qE18y+F3wAyqQ50TxUR59FQBRN7pu1K?= =?iso-8859-1?Q?qXWR5uIPowuMAzOgb3/0vE/f6VWL7TBJRnChbEnW?= x-forefront-antispam-report: CIP:255.255.255.255; CTRY:; LANG:en; SCL:1; SRV:; IPV:NLI; SFV:NSPM; H:TYPPR06MB8206.apcprd06.prod.outlook.com; PTR:; CAT:NONE; SFS:(13230040)(1800799024)(366016)(376014)(7416014)(921020)(38070700021); DIR:OUT; SFP:1102; x-ms-exchange-antispam-messagedata-chunkcount: 1 x-ms-exchange-antispam-messagedata-0: =?iso-8859-1?Q?/V7CtAAqU/QHkLX7in8f55fj8VP9jD16pLMk4PrxlyPQbcEwFbTr+lg1zZ?= =?iso-8859-1?Q?Y65JlloiY+9OK5FCmZfhPa5ulQ5Qfd2xgZR9QTLVsNTqgrRkCr0S3URC2G?= =?iso-8859-1?Q?nZVFJGID18au23IDaexkh9ASfuhYG+6LuS3IWIWnO966jkhhQtLGNbA8NU?= =?iso-8859-1?Q?NIYacqm0j12g8okzF+IEseNwouvSbzv9mLyzE8U1kfL0jbLWWLfykaolqT?= =?iso-8859-1?Q?ixrMAPuYoby/2cNm3sW7svKjmXHGT1cfcU4M+K3QaSZqo5wJlVRtmjrX0v?= =?iso-8859-1?Q?lu5BSrRT6juUHP74Cc+sjqzpGx4iQBYSwfSCyKdPJu82NvU1tPemXiFhjp?= =?iso-8859-1?Q?n7WLqBaA4zXSYV8xFhTB216ggV5NOPuhvUR6g/65YQP9dcIFAsvH2rpGds?= =?iso-8859-1?Q?+EO8bnc9iGDLp5W2tCNlGXqY2BTCfJ4ixsRJJLhW1s47kwwB/QRfu5cACa?= =?iso-8859-1?Q?JT/CDWbQlL9UddNSzJX51ByhlgMK3LoSKlJfGhB/38Us8aPeCIWtotzSYt?= =?iso-8859-1?Q?g3CY14ZMrUwHpToSeKSU6Pg6TxTx0Aeer9FgZLiMtW7/uDMZpxFA95+pEM?= =?iso-8859-1?Q?K2LSZqPQobwNfO61+6vxlO9i7txys+LrrPFsc743i3R03sltvineZdEuiE?= =?iso-8859-1?Q?KMn0cc0GHISRg26zZvzRqHP/v4+laGJrVadH1bV8hbkXrVP2gY8ABqVIvC?= =?iso-8859-1?Q?RhrRSKyuYYjOLMK5bHXQ7MH1YSIT7Wxk2HB9Zq/o8RGZ/7gVLrh8DY16K8?= =?iso-8859-1?Q?DxNp22jKk2tv9RzH9O1BwJlASZo8/9/70cPo5N9GkxSXyFH0jSw0QbmVcd?= =?iso-8859-1?Q?NqSsTACGIcZLDOlzpHesQn4u09D60yI94xiJjfam5n2N0GhStVNiZC+lnN?= =?iso-8859-1?Q?YXIhJx+zlAzAzCpoM6UEQZiCwtIC2OiEgzQRKDrKJvFzEDNrtsDt8n7R+C?= =?iso-8859-1?Q?4Zetei1dTv20dUlA6nNca/GRUlvapLPgpQdBp6pBZKKmf927In0sCSTfko?= =?iso-8859-1?Q?uU383rQxfjZKeO9KP/qidBU4nTtbtPLsvNUOcUV499cXQJbAqQUz8EGZpY?= =?iso-8859-1?Q?89nLARDaHzLnDAkEib5X2hHcPYP6LTI+uvsrG+hD/gGnnJXvncz+zPK4bc?= =?iso-8859-1?Q?jR6DrEjb3b8qUVHmSXa8C90e0fIQ0AJV0cExqTlh1aSTdp/o5pVFsBgrDo?= =?iso-8859-1?Q?euKh6FxozN8uweUWx43HrIv3X99pj03g3hQLDU1y0Bghlj9bZPhXwdBr3u?= =?iso-8859-1?Q?FD9Pgm4ySN8DELkLV9NmnqL47tUhEGcUYAQkK8O00zG0YznbYac0P4wPU7?= =?iso-8859-1?Q?5DmlzdtwAmL+2Jyn1IjoCqHkJKZDeva0hNZFez42U8EC9C4yeybNTmr8YF?= =?iso-8859-1?Q?bYH6zZ82kkdtWtbIq97QWLOi1PbK7LJcFrEiLbXkyZ3eEShsqZfCRVrOgj?= =?iso-8859-1?Q?KIcb7OtZBOfd0/gPsa69aT5skFnrXMYmG+MJtc3ZfbHwxMZZnEp/HVoDkR?= =?iso-8859-1?Q?wAM/cmx6/FecjonP9UpV2NGclZIWn2Sp/hx0+JyMIRJp+7gppYYRGNK1Wj?= =?iso-8859-1?Q?i35vs+f5xugY4Ef9SNZnG9g7u6JExP0PhKggz2babXNziz+7ohWC2EPoK/?= =?iso-8859-1?Q?f+VTaX0xrmNWhlIkFVPFts2aVww4r8f3Pi+rNs2GnaEzdT0ALzB9UoZNjN?= =?iso-8859-1?Q?qZ6Lp3Yil5pfmB7Z5MIOGuOiEMhqQU3KKdFqvtZ24P6J8kx/dkotUJq4Hp?= =?iso-8859-1?Q?SqXtzomkrJJTp3gIOQkfLz4/8nz6V/gER50SejO4yzhg00Wajm9ax9i3o+?= =?iso-8859-1?Q?w/lTYRMNWQ=3D=3D?= Content-Transfer-Encoding: quoted-printable MIME-Version: 1.0 X-OriginatorOrg: aspeedtech.com X-MS-Exchange-CrossTenant-AuthAs: Internal X-MS-Exchange-CrossTenant-AuthSource: TYPPR06MB8206.apcprd06.prod.outlook.com X-MS-Exchange-CrossTenant-Network-Message-Id: b47b0e0b-edd7-4066-6b7c-08de652e8aea X-MS-Exchange-CrossTenant-originalarrivaltime: 06 Feb 2026 03:19:30.6691 (UTC) X-MS-Exchange-CrossTenant-fromentityheader: Hosted X-MS-Exchange-CrossTenant-id: 43d4aa98-e35b-4575-8939-080e90d5a249 X-MS-Exchange-CrossTenant-mailboxtype: HOSTED X-MS-Exchange-CrossTenant-userprincipalname: TAgdw95l6VFYiMTHCxRMM8hQH2BJmPD0LyOhXGK8Vb6jPfmT82Arf6StpFndTwkKJHJi5qfsL0K4pDwiB6+44tcPh7Q6rtLA7XncW7oo2V0= X-MS-Exchange-Transport-CrossTenantHeadersStamped: SEZPR06MB5762 Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists.gnu.org; Received-SPF: pass client-ip=2a01:111:f403:c40f::6; envelope-from=jamin_lin@aspeedtech.com; helo=SEYPR02CU001.outbound.protection.outlook.com X-Spam_score_int: -20 X-Spam_score: -2.1 X-Spam_bar: -- X-Spam_report: (-2.1 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, SPF_HELO_PASS=-0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: qemu development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @aspeedtech.com) X-ZM-MESSAGEID: 1770348037883158500 Content-Type: text/plain; charset="utf-8" Adds an I3C bus and a target class. The bus supports: - I3C data transmission and reception - CCCs (including ENTDAA) - IBIs - legacy I2C transactions General usage of the bus is similar to I2C. Users are expected to initialize a bus via i3c_init_bus, and use the bus returned from the init function to do transactions on the bus. In order to handle IBIs, the controller provides callbacks to handle receiving an IBI from a target, receiving (optional) additional IBI bytes from a target, and handling when a target is done with its IBI. Similarly, target creation is done via i3c_target_create_simple and users use the provided I3CTarget to handle transactions. The target has functions provided that it can use to invoke an IBI and send additional bytes. Along with the send, recv, and event callbacks that are expected of an I3C target, which are similar to I2C, there is a separate callback for CCC handling. This is to help encapsulate CCC handling and keep it separate from target-specific read/write functionality. To avoid repition for required CCCs among I3C targets, there is some class-level CCC handling added. The CCC is then passed to the target in case it needs to handle it in some way. Signed-off-by: Joe Komlodi Signed-off-by: Jamin Lin Reviewed-by: Patrick Venture Reviewed-by: Titus Rwantare Reviewed-by: Jamin Lin --- include/hw/i3c/i3c.h | 277 ++++++++++++++++++ hw/i3c/core.c | 652 +++++++++++++++++++++++++++++++++++++++++++ hw/i3c/meson.build | 1 + hw/i3c/trace-events | 16 ++ 4 files changed, 946 insertions(+) create mode 100644 include/hw/i3c/i3c.h create mode 100644 hw/i3c/core.c diff --git a/include/hw/i3c/i3c.h b/include/hw/i3c/i3c.h new file mode 100644 index 0000000000..455c2a4a4f --- /dev/null +++ b/include/hw/i3c/i3c.h @@ -0,0 +1,277 @@ +/* + * QEMU I3C bus interface. + * + * Copyright 2025 Google LLC + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef QEMU_INCLUDE_HW_I3C_I3C_H_ +#define QEMU_INCLUDE_HW_I3C_I3C_H_ + +#include "hw/core/qdev.h" +#include "qom/object.h" +#include "hw/i2c/i2c.h" + +#define TYPE_I3C_TARGET "i3c-target" +OBJECT_DECLARE_TYPE(I3CTarget, I3CTargetClass, I3C_TARGET) + +typedef enum I3CEvent { + I3C_START_RECV, + I3C_START_SEND, + I3C_STOP, + I3C_NACK, +} I3CEvent; + +typedef enum I3CCCC { + /* Broadcast CCCs */ + I3C_CCC_ENEC =3D 0x00, + I3C_CCC_DISEC =3D 0x01, + I3C_CCC_ENTAS0 =3D 0x02, + I3C_CCC_ENTAS1 =3D 0x03, + I3C_CCC_ENTAS2 =3D 0x04, + I3C_CCC_ENTAS3 =3D 0x05, + I3C_CCC_RSTDAA =3D 0x06, + I3C_CCC_ENTDAA =3D 0x07, + I3C_CCC_DEFTGTS =3D 0x08, + I3C_CCC_SETMWL =3D 0x09, + I3C_CCC_SETMRL =3D 0x0a, + I3C_CCC_ENTTM =3D 0x0b, + I3C_CCC_SETBUSCON =3D 0x0c, + I3C_CCC_ENDXFER =3D 0x12, + I3C_CCC_ENTHDR0 =3D 0x20, + I3C_CCC_ENTHDR1 =3D 0x21, + I3C_CCC_ENTHDR2 =3D 0x22, + I3C_CCC_ENTHDR3 =3D 0x23, + I3C_CCC_ENTHDR4 =3D 0x24, + I3C_CCC_ENTHDR5 =3D 0x25, + I3C_CCC_ENTHDR6 =3D 0x26, + I3C_CCC_ENTHDR7 =3D 0x27, + I3C_CCC_SETXTIME =3D 0x28, + I3C_CCC_SETAASA =3D 0x29, + I3C_CCC_RSTACT =3D 0x2a, + I3C_CCC_DEFGRPA =3D 0x2b, + I3C_CCC_RSTGRPA =3D 0x2c, + I3C_CCC_MLANE =3D 0x2d, + /* Direct CCCs */ + I3C_CCCD_ENEC =3D 0x80, + I3C_CCCD_DISEC =3D 0x81, + I3C_CCCD_ENTAS0 =3D 0x82, + I3C_CCCD_ENTAS1 =3D 0x83, + I3C_CCCD_ENTAS2 =3D 0x84, + I3C_CCCD_ENTAS3 =3D 0x85, + I3C_CCCD_SETDASA =3D 0x87, + I3C_CCCD_SETNEWDA =3D 0x88, + I3C_CCCD_SETMWL =3D 0x89, + I3C_CCCD_SETMRL =3D 0x8a, + I3C_CCCD_GETMWL =3D 0x8b, + I3C_CCCD_GETMRL =3D 0x8c, + I3C_CCCD_GETPID =3D 0x8d, + I3C_CCCD_GETBCR =3D 0x8e, + I3C_CCCD_GETDCR =3D 0x8f, + I3C_CCCD_GETSTATUS =3D 0x90, + I3C_CCCD_GETACCCR =3D 0x91, + I3C_CCCD_ENDXFER =3D 0x92, + I3C_CCCD_SETBRGTGT =3D 0x93, + I3C_CCCD_GETMXDS =3D 0x94, + I3C_CCCD_GETCAPS =3D 0x95, + I3C_CCCD_SETROUTE =3D 0x96, + I3C_CCCD_SETXTIME =3D 0x98, + I3C_CCCD_GETXTIME =3D 0x99, + I3C_CCCD_RSTACT =3D 0x9a, + I3C_CCCD_SETGRPA =3D 0x9b, + I3C_CCCD_RSTGRPA =3D 0x9c, + I3C_CCCD_MLANE =3D 0x9d, +} I3CCCC; + +#define CCC_IS_DIRECT(_ccc) (_ccc & 0x80) + +#define I3C_BROADCAST 0x7e +#define I3C_HJ_ADDR 0x02 +#define I3C_ENTDAA_SIZE 8 + +struct I3CTargetClass { + DeviceClass parent; + + /* + * Controller to target. Returns 0 for success, non-zero for NAK or ot= her + * error. + */ + int (*send)(I3CTarget *s, const uint8_t *data, uint32_t num_to_send, + uint32_t *num_sent); + /* + * Target to controller. I3C targets are able to terminate reads early= , so + * this returns the number of bytes read from the target. + */ + uint32_t (*recv)(I3CTarget *s, uint8_t *data, uint32_t num_to_read); + /* Notify the target of a bus state change. */ + int (*event)(I3CTarget *s, enum I3CEvent event); + /* + * Handle a read CCC transmitted from a controller. + * CCCs are I3C commands that I3C targets support. + * The target can NACK the CCC if it does not support it. + */ + int (*handle_ccc_read)(I3CTarget *s, uint8_t *data, uint32_t num_to_re= ad, + uint32_t *num_read); + /* + * Handle a write CCC transmitted from a controller. + * CCCs are I3C commands that I3C targets support. + * The target can NACK the CCC if it does not support it. + */ + int (*handle_ccc_write)(I3CTarget *s, const uint8_t *data, + uint32_t num_to_send, uint32_t *num_sent); + + /* + * Matches and adds the candidate if the address matches the candidate= 's + * address. + * Returns true if the address matched, or if this was a broadcast, and + * updates the device list. Otherwise returns false. + */ + bool (*target_match)(I3CTarget *candidate, uint8_t address, bool is_re= ad, + bool broadcast, bool in_entdaa); +}; + +struct I3CTarget { + DeviceState qdev; + + uint8_t address; + uint8_t static_address; + uint8_t dcr; + uint8_t bcr; + uint64_t pid; + + /* CCC State tracking. */ + I3CCCC curr_ccc; + uint8_t ccc_byte_offset; + bool in_ccc; + bool in_test_mode; +}; + +struct I3CNode { + I3CTarget *target; + QLIST_ENTRY(I3CNode) next; +}; + +typedef struct I3CNode I3CNode; + +typedef QLIST_HEAD(I3CNodeList, I3CNode) I3CNodeList; + +#define TYPE_I3C_BUS "i3c-bus" +OBJECT_DECLARE_TYPE(I3CBus, I3CBusClass, I3C_BUS) + +struct I3CBus { + BusState qbus; + + /* Legacy I2C. */ + I2CBus *i2c_bus; + + I3CNodeList current_devs; + bool broadcast; + uint8_t ccc; + bool in_ccc; + bool in_entdaa; + uint8_t saved_address; +}; + +struct I3CBusClass { + DeviceClass parent; + + /* Handle an incoming IBI request from a target */ + int (*ibi_handle) (I3CBus *bus, uint8_t addr, bool is_recv); + /* Receive data from an IBI request */ + int (*ibi_recv) (I3CBus *bus, uint8_t data); + /* Do anything that needs to be done, since the IBI is finished. */ + int (*ibi_finish) (I3CBus *bus); +}; + +I3CBus *i3c_init_bus(DeviceState *parent, const char *name); +I3CBus *i3c_init_bus_type(const char *type, DeviceState *parent, + const char *name); +void i3c_set_target_address(I3CTarget *dev, uint8_t address); +bool i3c_bus_busy(I3CBus *bus); + +/* + * Start a transfer on an I3C bus. + * If is_recv is known at compile-time (i.e. a device will always be sendi= ng or + * will always be receiving at a certain point), prefer to use i3c_start_r= ecv or + * i3c_start_send instead. + * + * Returns 0 on success, non-zero on an error. + */ +int i3c_start_transfer(I3CBus *bus, uint8_t address, bool is_recv); + +/* + * Start a receive transfer on an I3C bus. + * + * Returns 0 on success, non-zero on an error + */ +int i3c_start_recv(I3CBus *bus, uint8_t address); + +/* + * Start a send transfer on an I3C bus. + * + * Returns 0 on success, non-zero on an error + */ +int i3c_start_send(I3CBus *bus, uint8_t address); + +void i3c_end_transfer(I3CBus *bus); +void i3c_nack(I3CBus *bus); +int i3c_send_byte(I3CBus *bus, uint8_t data); +int i3c_send(I3CBus *bus, const uint8_t *data, uint32_t num_to_send, + uint32_t *num_sent); +/* + * I3C receives can only NACK on a CCC. The target should NACK a CCC it do= es not + * support. + */ +int i3c_recv_byte(I3CBus *bus, uint8_t *data); +int i3c_recv(I3CBus *bus, uint8_t *data, uint32_t num_to_read, + uint32_t *num_read); +bool i3c_scan_bus(I3CBus *bus, uint8_t address, enum I3CEvent event); +int i3c_do_entdaa(I3CBus *bus, uint8_t address, uint64_t *pid, uint8_t *bc= r, + uint8_t *dcr); +int i3c_start_device_transfer(I3CTarget *dev, int send_length); +bool i3c_target_match_and_add(I3CBus *bus, I3CTarget *target, uint8_t addr= ess, + enum I3CEvent event); +int i3c_target_send_ibi(I3CTarget *t, uint8_t addr, bool is_recv); +int i3c_target_send_ibi_bytes(I3CTarget *t, uint8_t data); +int i3c_target_ibi_finish(I3CTarget *t, uint8_t data); + +/* + * Legacy I2C functions. + * + * These are wrapper for I2C functions that take in an I3C bus instead of = an I2C + * bus. Internally they use the I2C bus (and devices attached to it) that'= s a + * part of the I3C bus + */ +void legacy_i2c_nack(I3CBus *bus); +uint8_t legacy_i2c_recv(I3CBus *bus); +int legacy_i2c_send(I3CBus *bus, uint8_t data); +int legacy_i2c_start_transfer(I3CBus *bus, uint8_t address, bool is_recv); +int legacy_i2c_start_recv(I3CBus *bus, uint8_t address); +int legacy_i2c_start_send(I3CBus *bus, uint8_t address); +void legacy_i2c_end_transfer(I3CBus *bus); +I2CSlave *legacy_i2c_device_create_simple(I3CBus *bus, const char *name, + uint8_t addr); + +/** + * Create an I3C Target. + * + * The target returned from this function still needs to be realized. + */ +I3CTarget *i3c_target_new(const char *name, uint8_t addr, uint8_t dcr, + uint8_t bcr, uint64_t pid); + +/** + * Create and realize an I3C target. + * + * Create the target, initialize it, put it on the specified I3C bus, and + * realize it. + */ +I3CTarget *i3c_target_create_simple(I3CBus *bus, const char *name, + uint8_t addr, uint8_t dcr, uint8_t bcr, + uint64_t pid); + +/* Realize and drop the reference count on an I3C target. */ +bool i3c_target_realize_and_unref(I3CTarget *dev, I3CBus *bus, Error **err= p); + +#endif /* QEMU_INCLUDE_HW_I3C_I3C_H_ */ diff --git a/hw/i3c/core.c b/hw/i3c/core.c new file mode 100644 index 0000000000..fb4bfe5aec --- /dev/null +++ b/hw/i3c/core.c @@ -0,0 +1,652 @@ +/* + * QEMU I3C bus interface. + * + * Copyright 2025 Google LLC + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qemu/log.h" +#include "qapi/error.h" +#include "trace.h" +#include "hw/i3c/i3c.h" +#include "hw/core/qdev-properties.h" + +/* + * In test mode (enabled by ENTTM CCC) we're supposed to send a random PID + * during ENTDAA, so we'll just send "QEMU". + */ +#define TEST_MODE_PROVISIONED_ID 0x0000554d4551ULL + +static const Property i3c_props[] =3D { + DEFINE_PROP_UINT8("static-address", struct I3CTarget, static_address, = 0), + DEFINE_PROP_UINT8("dcr", struct I3CTarget, dcr, 0), + DEFINE_PROP_UINT8("bcr", struct I3CTarget, bcr, 0), + DEFINE_PROP_UINT64("pid", struct I3CTarget, pid, 0), +}; + +static const TypeInfo i3c_bus_info =3D { + .name =3D TYPE_I3C_BUS, + .parent =3D TYPE_BUS, + .instance_size =3D sizeof(I3CBus), + .class_size =3D sizeof(I3CBusClass), +}; + +I3CBus *i3c_init_bus(DeviceState *parent, const char *name) +{ + return i3c_init_bus_type(TYPE_I3C_BUS, parent, name); +} + +I3CBus *i3c_init_bus_type(const char *type, DeviceState *parent, + const char *name) +{ + I3CBus *bus; + + bus =3D I3C_BUS(qbus_new(type, parent, name)); + QLIST_INIT(&bus->current_devs); + bus->broadcast =3D false; + bus->in_entdaa =3D false; + bus->in_ccc =3D false; + + /* I2C init. */ + g_autofree gchar *i2c_bus_name =3D g_strdup_printf("%s-legacy-i2c", na= me); + bus->i2c_bus =3D i2c_init_bus(parent, i2c_bus_name); + + return bus; +} + +bool i3c_bus_busy(I3CBus *bus) +{ + return !QLIST_EMPTY(&bus->current_devs); +} + +static bool i3c_target_match(I3CTarget *candidate, uint8_t address, + bool is_recv, bool broadcast, bool in_entdaa) +{ + /* Once a target has a dynamic address, it only responds to that. */ + uint8_t targ_addr =3D candidate->address ? candidate->address : + candidate->static_address; + + if (in_entdaa) { + if (address !=3D I3C_BROADCAST) { + g_autofree char *path =3D + object_get_canonical_path(OBJECT(candidate)); + qemu_log_mask(LOG_GUEST_ERROR, "%s: I3C Address 0x%.2x sent du= ring " + "ENTDAA instead of a broadcast address\n", + path, address); + return false; + } + + /* + * Targets should only ACK ENTDAA broadcasts if they have no dynam= ic + * address. + */ + return candidate->address =3D=3D 0; + } + + /* Return if our addresses match, or if it's a broadcast. */ + return targ_addr =3D=3D address || broadcast; +} + +bool i3c_target_match_and_add(I3CBus *bus, I3CTarget *target, uint8_t addr= ess, + enum I3CEvent event) +{ + I3CTargetClass *tc =3D I3C_TARGET_GET_CLASS(target); + bool matched =3D tc->target_match(target, address, event =3D=3D I3C_ST= ART_RECV, + bus->broadcast, bus->in_entdaa); + + if (matched) { + I3CNode *node =3D g_new(struct I3CNode, 1); + node->target =3D target; + QLIST_INSERT_HEAD(&bus->current_devs, node, next); + } + return matched; +} + +bool i3c_scan_bus(I3CBus *bus, uint8_t address, enum I3CEvent event) +{ + BusChild *child; + I3CNode *node, *next; + + /* Clear out any devices from a previous (re-)START. */ + QLIST_FOREACH_SAFE(node, &bus->current_devs, next, next) { + QLIST_REMOVE(node, next); + g_free(node); + } + + QTAILQ_FOREACH(child, &bus->qbus.children, sibling) { + DeviceState *qdev =3D child->child; + I3CTarget *target =3D I3C_TARGET(qdev); + + if (i3c_target_match_and_add(bus, target, address, event)) { + return true; + } + } + + /* No one on the bus could respond. */ + return false; +} + +/* Class-level event handling, since we do some CCCs at the class level. */ +static int i3c_target_event(I3CTarget *t, enum I3CEvent event) +{ + I3CTargetClass *tc =3D I3C_TARGET_GET_CLASS(t); + trace_i3c_target_event(t->address, event); + + if (event =3D=3D I3C_STOP) { + t->curr_ccc =3D 0; + t->ccc_byte_offset =3D 0; + t->in_ccc =3D false; + } + return tc->event(t, event); +} + +/* + * Sends a START or repeated START and the address for an I3C transaction. + * + * This function returns 0 if a device on the bus was able to respond to t= he + * address, and non-zero otherwise. + * A non-zero return represents a NACK. + */ +static int i3c_do_start_transfer(I3CBus *bus, uint8_t address, + enum I3CEvent event) +{ + I3CTargetClass *tc; + I3CNode *node; + + if (address =3D=3D I3C_BROADCAST) { + bus->broadcast =3D true; + /* If we're not in ENTDAA, a broadcast is the start of a new CCC. = */ + if (!bus->in_entdaa) { + bus->in_ccc =3D false; + } + } else { + bus->broadcast =3D false; + } + + /* No one responded to the address, NACK it. */ + if (!i3c_scan_bus(bus, address, event)) { + return -1; + } + + QLIST_FOREACH(node, &bus->current_devs, next) { + I3CTarget *t =3D node->target; + + tc =3D I3C_TARGET_GET_CLASS(t); + if (tc->event) { + int rv =3D i3c_target_event(t, event); + if (rv && !bus->broadcast) { + return rv; + } + } + } + + return 0; +} + +int i3c_start_transfer(I3CBus *bus, uint8_t address, bool is_recv) +{ + trace_i3c_start_transfer(address, is_recv); + return i3c_do_start_transfer(bus, address, is_recv + ? I3C_START_RECV + : I3C_START_SEND); +} + +int i3c_start_recv(I3CBus *bus, uint8_t address) +{ + trace_i3c_start_transfer(address, true); + return i3c_do_start_transfer(bus, address, I3C_START_RECV); +} + +int i3c_start_send(I3CBus *bus, uint8_t address) +{ + trace_i3c_start_transfer(address, false); + return i3c_do_start_transfer(bus, address, I3C_START_SEND); +} + +void i3c_end_transfer(I3CBus *bus) +{ + I3CTargetClass *tc; + I3CNode *node, *next; + + trace_i3c_end_transfer(); + + /* + * If we're in ENTDAA, we need to notify all devices when ENTDAA is do= ne. + * This is because everyone initially participates due to the broadcas= t, + * but gradually drops out as they get assigned addresses. + * Since the current_devs list only stores who's currently participati= ng, + * and not everyone who previously participated, we send the STOP to a= ll + * children. + */ + if (bus->in_entdaa) { + BusChild *child; + + QTAILQ_FOREACH(child, &bus->qbus.children, sibling) { + DeviceState *qdev =3D child->child; + I3CTarget *t =3D I3C_TARGET(qdev); + tc =3D I3C_TARGET_GET_CLASS(t); + if (tc->event) { + i3c_target_event(t, I3C_STOP); + } + } + } else { + QLIST_FOREACH_SAFE(node, &bus->current_devs, next, next) { + I3CTarget *t =3D node->target; + tc =3D I3C_TARGET_GET_CLASS(t); + if (tc->event) { + i3c_target_event(t, I3C_STOP); + } + QLIST_REMOVE(node, next); + g_free(node); + } + } + bus->broadcast =3D false; + bus->in_entdaa =3D false; + bus->in_ccc =3D false; +} + +/* + * Any CCCs that are universal across all I3C devices should be handled he= re. + * Once they're handled, we pass the CCC up to the I3C target to do anythi= ng + * else it may want with the bytes. + */ +static int i3c_target_handle_ccc_write(I3CTarget *t, const uint8_t *data, + uint32_t num_to_send, uint32_t *num= _sent) +{ + I3CTargetClass *tc =3D I3C_TARGET_GET_CLASS(t); + *num_sent =3D 0; + + /* Is this the start of a new CCC? */ + if (!t->in_ccc) { + t->curr_ccc =3D *data; + t->in_ccc =3D true; + *num_sent =3D 1; + trace_i3c_target_handle_ccc(t->address, t->curr_ccc); + } + + switch (t->curr_ccc) { + case I3C_CCC_ENTDAA: + /* + * This is the last byte of ENTDAA, the controller is assigning us= an + * address. + */ + if (t->ccc_byte_offset =3D=3D 8) { + t->address =3D *data; + t->in_ccc =3D false; + t->curr_ccc =3D 0; + t->ccc_byte_offset =3D 0; + *num_sent =3D 1; + } + break; + case I3C_CCCD_SETDASA: + t->address =3D t->static_address; + break; + case I3C_CCC_SETAASA: + t->address =3D t->static_address; + break; + case I3C_CCC_RSTDAA: + t->address =3D 0; + break; + case I3C_CCCD_SETNEWDA: + /* If this isn't the CCC byte, it's our new address. */ + if (*num_sent =3D=3D 0) { + t->address =3D *data; + *num_sent =3D 1; + } + break; + case I3C_CCC_ENTTM: + /* + * If there are still more to look at, the next byte is the test m= ode + * byte. + */ + if (*num_sent !=3D num_to_send) { + /* Enter test mode if the byte is non-zero. Otherwise exit. */ + t->in_test_mode =3D !!data[*num_sent]; + ++*num_sent; + } + break; + /* Ignore other CCCs it's better to handle on a device-by-device basis= . */ + default: + break; + } + return tc->handle_ccc_write(t, data, num_to_send, num_sent); +} + +int i3c_send_byte(I3CBus *bus, uint8_t data) +{ + /* + * Ignored, the caller can determine how many were sent based on if th= is was + * ACKed/NACKed. + */ + uint32_t num_sent; + return i3c_send(bus, &data, 1, &num_sent); +} + +int i3c_send(I3CBus *bus, const uint8_t *data, uint32_t num_to_send, + uint32_t *num_sent) +{ + I3CTargetClass *tc; + I3CTarget *t; + I3CNode *node; + int ret =3D 0; + + /* If this message is a broadcast and no CCC has been found, grab it. = */ + if (bus->broadcast && !bus->in_ccc) { + bus->ccc =3D *data; + bus->in_ccc =3D true; + /* + * We need to keep track if we're currently in ENTDAA. + * On any other CCC, the CCC is over on a RESTART or STOP, but ENT= DAA + * is only over on a STOP. + */ + if (bus->ccc =3D=3D I3C_CCC_ENTDAA) { + bus->in_entdaa =3D true; + } + } + + QLIST_FOREACH(node, &bus->current_devs, next) { + t =3D node->target; + tc =3D I3C_TARGET_GET_CLASS(t); + if (bus->in_ccc) { + if (!tc->handle_ccc_write) { + ret =3D -1; + continue; + } + ret =3D i3c_target_handle_ccc_write(t, data, num_to_send, num_= sent); + /* Targets should only NACK on a direct CCC. */ + if (ret && !CCC_IS_DIRECT(bus->ccc)) { + ret =3D 0; + } + } else { + if (tc->send) { + ret =3D ret || tc->send(t, data, num_to_send, num_sent); + } else { + ret =3D -1; + } + } + } + + trace_i3c_send(*num_sent, num_to_send, ret =3D=3D 0); + + return ret ? -1 : 0; +} + +static int i3c_target_handle_ccc_read(I3CTarget *t, uint8_t *data, + uint32_t num_to_read, uint32_t *num_= read) +{ + I3CTargetClass *tc =3D I3C_TARGET_GET_CLASS(t); + uint8_t read_count =3D 0; + uint64_t pid; + + switch (t->curr_ccc) { + case I3C_CCC_ENTDAA: + if (t->in_test_mode) { + pid =3D TEST_MODE_PROVISIONED_ID; + } else { + pid =3D t->pid; + } + /* Return the 6-byte PID, followed by BCR then DCR. */ + while (t->ccc_byte_offset < 6) { + if (read_count >=3D num_to_read) { + break; + } + data[read_count] =3D (pid >> (t->ccc_byte_offset * 8)) & 0xff; + t->ccc_byte_offset++; + read_count++; + } + if (read_count < num_to_read) { + data[read_count] =3D t->bcr; + t->ccc_byte_offset++; + read_count++; + } + if (read_count < num_to_read) { + data[read_count] =3D t->dcr; + t->ccc_byte_offset++; + read_count++; + } + *num_read =3D read_count; + break; + case I3C_CCCD_GETPID: + while (t->ccc_byte_offset < 6) { + if (read_count >=3D num_to_read) { + break; + } + data[read_count] =3D (t->pid >> (t->ccc_byte_offset * 8)) & 0x= ff; + t->ccc_byte_offset++; + read_count++; + } + *num_read =3D read_count; + break; + case I3C_CCCD_GETBCR: + *data =3D t->bcr; + *num_read =3D 1; + break; + case I3C_CCCD_GETDCR: + *data =3D t->dcr; + *num_read =3D 1; + break; + default: + /* Unhandled on the I3CTarget class level. */ + break; + } + + return tc->handle_ccc_read(t, data, num_to_read, num_read); +} + +int i3c_recv_byte(I3CBus *bus, uint8_t *data) +{ + /* + * Ignored, the caller can determine how many bytes were read based o= n if + * this is ACKed/NACKed. + */ + uint32_t num_read; + return i3c_recv(bus, data, 1, &num_read); +} + +int i3c_recv(I3CBus *bus, uint8_t *data, uint32_t num_to_read, + uint32_t *num_read) +{ + int ret =3D 0; + I3CTargetClass *tc; + I3CTarget *t; + + *data =3D 0xff; + if (!QLIST_EMPTY(&bus->current_devs)) { + tc =3D I3C_TARGET_GET_CLASS(QLIST_FIRST(&bus->current_devs)->targe= t); + t =3D QLIST_FIRST(&bus->current_devs)->target; + if (bus->in_ccc) { + if (!tc->handle_ccc_read) { + return -1; + } + ret =3D i3c_target_handle_ccc_read(t, data, num_to_read, num_r= ead); + } else { + if (tc->recv) { + /* + * Targets cannot NACK on a direct transfer, so the data + * is returned directly. + */ + *num_read =3D tc->recv(t, data, num_to_read); + } + } + } + + trace_i3c_recv(*num_read, num_to_read, ret =3D=3D 0); + + return ret; +} + +void i3c_nack(I3CBus *bus) +{ + I3CTargetClass *tc; + I3CNode *node; + + if (QLIST_EMPTY(&bus->current_devs)) { + return; + } + + QLIST_FOREACH(node, &bus->current_devs, next) { + tc =3D I3C_TARGET_GET_CLASS(node->target); + if (tc->event) { + i3c_target_event(node->target, I3C_NACK); + } + } +} + +int i3c_target_send_ibi(I3CTarget *t, uint8_t addr, bool is_recv) +{ + I3CBus *bus =3D I3C_BUS(t->qdev.parent_bus); + I3CBusClass *bc =3D I3C_BUS_GET_CLASS(bus); + trace_i3c_target_send_ibi(addr, is_recv); + return bc->ibi_handle(bus, addr, is_recv); +} + +int i3c_target_send_ibi_bytes(I3CTarget *t, uint8_t data) +{ + I3CBus *bus =3D I3C_BUS(t->qdev.parent_bus); + I3CBusClass *bc =3D I3C_BUS_GET_CLASS(bus); + trace_i3c_target_send_ibi_bytes(data); + return bc->ibi_recv(bus, data); +} + +int i3c_target_ibi_finish(I3CTarget *t, uint8_t data) +{ + I3CBus *bus =3D I3C_BUS(t->qdev.parent_bus); + I3CBusClass *bc =3D I3C_BUS_GET_CLASS(bus); + trace_i3c_target_ibi_finish(); + return bc->ibi_finish(bus); +} + +static bool i3c_addr_is_rsvd(uint8_t addr) +{ + const bool is_rsvd[255] =3D { + [0x00] =3D true, + [0x01] =3D true, + [0x02] =3D true, + [0x3e] =3D true, + [0x5e] =3D true, + [0x6e] =3D true, + [0x76] =3D true, + [0x7a] =3D true, + [0x7c] =3D true, + [0x7e] =3D true, + [0x7f] =3D true, + }; + + return is_rsvd[addr]; +} + +I3CTarget *i3c_target_new(const char *name, uint8_t addr, uint8_t dcr, + uint8_t bcr, uint64_t pid) +{ + DeviceState *dev; + + dev =3D qdev_new(name); + qdev_prop_set_uint8(dev, "static-address", addr); + qdev_prop_set_uint8(dev, "dcr", dcr); + qdev_prop_set_uint8(dev, "bcr", bcr); + qdev_prop_set_uint64(dev, "pid", pid); + + if (i3c_addr_is_rsvd(addr)) { + g_autofree char *path =3D object_get_canonical_path(OBJECT(dev)); + qemu_log_mask(LOG_GUEST_ERROR, "%s: I3C target created with reserv= ed " + "address 0x%.2x\n", path, addr); + } + return I3C_TARGET(dev); +} + +bool i3c_target_realize_and_unref(I3CTarget *dev, I3CBus *bus, Error **err= p) +{ + return qdev_realize_and_unref(&dev->qdev, &bus->qbus, errp); +} + +I3CTarget *i3c_target_create_simple(I3CBus *bus, const char *name, uint8_t= addr, + uint8_t dcr, uint8_t bcr, uint64_t pid) +{ + I3CTarget *dev =3D i3c_target_new(name, addr, dcr, bcr, pid); + dev->address =3D 0; + i3c_target_realize_and_unref(dev, bus, &error_abort); + + return dev; +} + +/* Legacy I2C functions. */ +void legacy_i2c_nack(I3CBus *bus) +{ + trace_legacy_i2c_nack(); + i2c_nack(bus->i2c_bus); +} + +uint8_t legacy_i2c_recv(I3CBus *bus) +{ + uint8_t byte =3D i2c_recv(bus->i2c_bus); + trace_legacy_i2c_recv(byte); + return byte; +} + +int legacy_i2c_send(I3CBus *bus, uint8_t data) +{ + trace_legacy_i2c_send(data); + return i2c_send(bus->i2c_bus, data); +} + +int legacy_i2c_start_transfer(I3CBus *bus, uint8_t address, bool is_recv) +{ + trace_legacy_i2c_start_transfer(address, is_recv); + return i2c_start_transfer(bus->i2c_bus, address, is_recv); +} + +int legacy_i2c_start_recv(I3CBus *bus, uint8_t address) +{ + trace_legacy_i2c_start_transfer(address, true); + return i2c_start_transfer(bus->i2c_bus, address, /*is_recv=3D*/true); +} + +int legacy_i2c_start_send(I3CBus *bus, uint8_t address) +{ + trace_legacy_i2c_start_transfer(address, false); + return i2c_start_transfer(bus->i2c_bus, address, /*is_recv=3D*/false); +} + +void legacy_i2c_end_transfer(I3CBus *bus) +{ + trace_legacy_i2c_end_transfer(); + i2c_end_transfer(bus->i2c_bus); +} + +I2CSlave *legacy_i2c_device_create_simple(I3CBus *bus, const char *name, + uint8_t addr) +{ + I2CSlave *dev =3D i2c_slave_new(name, addr); + + i2c_slave_realize_and_unref(dev, bus->i2c_bus, &error_abort); + return dev; +} + +static void i3c_target_class_init(ObjectClass *klass, const void *data) +{ + DeviceClass *k =3D DEVICE_CLASS(klass); + I3CTargetClass *sc =3D I3C_TARGET_CLASS(klass); + set_bit(DEVICE_CATEGORY_MISC, k->categories); + k->bus_type =3D TYPE_I3C_BUS; + device_class_set_props(k, i3c_props); + sc->target_match =3D i3c_target_match; +} + +static const TypeInfo i3c_target_type_info =3D { + .name =3D TYPE_I3C_TARGET, + .parent =3D TYPE_DEVICE, + .instance_size =3D sizeof(I3CTarget), + .abstract =3D true, + .class_size =3D sizeof(I3CTargetClass), + .class_init =3D i3c_target_class_init, +}; + +static void i3c_register_types(void) +{ + type_register_static(&i3c_bus_info); + type_register_static(&i3c_target_type_info); +} + +type_init(i3c_register_types) diff --git a/hw/i3c/meson.build b/hw/i3c/meson.build index ebf20325cb..fb127613fe 100644 --- a/hw/i3c/meson.build +++ b/hw/i3c/meson.build @@ -1,3 +1,4 @@ i3c_ss =3D ss.source_set() +i3c_ss.add(when: 'CONFIG_I3C', if_true: files('core.c')) i3c_ss.add(when: 'CONFIG_ASPEED_SOC', if_true: files('aspeed_i3c.c')) system_ss.add_all(when: 'CONFIG_I3C', if_true: i3c_ss) diff --git a/hw/i3c/trace-events b/hw/i3c/trace-events index 3ead84eb45..cdf7cb07f6 100644 --- a/hw/i3c/trace-events +++ b/hw/i3c/trace-events @@ -5,3 +5,19 @@ aspeed_i3c_read(uint64_t offset, uint64_t data) "I3C read:= offset 0x%" PRIx64 " aspeed_i3c_write(uint64_t offset, uint64_t data) "I3C write: offset 0x%" P= RIx64 " data 0x%" PRIx64 aspeed_i3c_device_read(uint32_t deviceid, uint64_t offset, uint64_t data) = "I3C Dev[%u] read: offset 0x%" PRIx64 " data 0x%" PRIx64 aspeed_i3c_device_write(uint32_t deviceid, uint64_t offset, uint64_t data)= "I3C Dev[%u] write: offset 0x%" PRIx64 " data 0x%" PRIx64 + +# core.c +i3c_target_event(uint8_t address, uint8_t event) "I3C target 0x%" PRIx8 " = event 0x%" PRIx8 +i3c_target_handle_ccc(uint8_t address, uint8_t ccc) "I3C target 0x%" PRIx8= " handling CCC 0x%" PRIx8 +i3c_target_send_ibi(uint8_t address, bool is_recv) "I3C target IBI address= 0x%" PRIx8 " RnW=3D%d" +i3c_target_send_ibi_bytes(uint8_t byte) "I3C target IBI byte 0x%" PRIx8 +i3c_target_ibi_finish(void) "I3C target IBI finish" +i3c_start_transfer(uint8_t address, bool is_recv) "I3C START with address = 0x%" PRIx8 " is_recv=3D%d" +i3c_end_transfer(void) "I3C transfer done" +i3c_send(uint32_t num_sent, uint32_t num_to_send, bool ack) "I3C send %" P= RId32 "/%" PRId32 " bytes, ack=3D%d" +i3c_recv(uint32_t num_read, uint32_t num_to_read, bool ack) "I3C recv %" P= RId32 "/%" PRId32 " bytes, ack=3D%d" +legacy_i2c_nack(void) "Legacy I2C NACK" +legacy_i2c_recv(uint8_t byte) "Legacy I2C recv 0x%" PRIx8 +legacy_i2c_send(uint8_t byte) "Legacy I2C send 0x%" PRIx8 +legacy_i2c_start_transfer(uint8_t address, bool is_recv) "Legacy I2C START= with address 0x%" PRIx8 " is_recv=3D%d" +legacy_i2c_end_transfer(void) "Legacy I2C STOP" --=20 2.43.0