From nobody Fri Sep 25 18:24:50 2026 Received: from mail-oo1-f45.google.com (mail-oo1-f45.google.com [209.85.161.45]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 596A957980E for ; Wed, 9 Sep 2026 15:04:35 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=pass smtp.client-ip=209.85.161.45 ARC-Seal: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788966277; cv=pass; b=ceDoa1GTynUmU02AHgYd411IlyZL6mWnQK5UCqN+WjHZQU/qDpiAikzJjPullDTnDtmnR05c0O5yoQ9ZvaJ6XDaMyI3JVtQTvrJ8CU68Anju3WQepJ1b7QOMOPqbW05ImGRVkbKq9T9QRWto5uuMLfQalnunrrECNqYGdDLtqnI= ARC-Message-Signature: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788966277; c=relaxed/simple; bh=U9knPo6p/2xLEVyt8CjUdjDIRZX4yEwqE/qKt1LDz20=; h=MIME-Version:From:In-Reply-To:References:Date:Message-ID:Subject: To:Cc:Content-Type; b=FAtARqGyn+EUciCOfWRo+1g7y7789njEiwRNUHdjEGVOyt+OplSOjp4UVrxukitHh62BtuWlnZZNDZeXbP1M5QnmMZSpduZvaEPW/qrunQGdQ7oiJ8oyNLcxcKTYLt4Q945vVV470hgV1OaDKRfbJXiUI5VQ8xOTp1XNX/ii/qE= ARC-Authentication-Results: i=2; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=ZLPlQfnA; arc=pass smtp.client-ip=209.85.161.45 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="ZLPlQfnA" Received: by mail-oo1-f45.google.com with SMTP id 006d021491bc7-6b1c75f0049so4463827eaf.3 for ; Wed, 09 Sep 2026 08:04:35 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1788966274; cv=none; d=google.com; s=arc-20260327; b=i82cli0edDV+yxgtAI1HMusBBF9gRa7PHdZQasm0qKNeioI2zlV7jZQ4BLI9ibwRvV 7mXMFMEGDxq7ELRHMwj9LNbXWV3ClMe1Khggix4K4cMSwBplP7P7boYFyiyjE2BEXRij EOGP6m7I/Za3HQv+QF7nC05eNtRXJpkhqxx8i6umG+IQj/DWo3VZy2B73Znu69dx4nFd 0KjKKRUhQtrMnh4xfXd2SOzv3EC1jJ+56cGlRfm6q9ef9twN1ec0fn2xjXEYe+wEAMWf gaxe1YO1EG9xTqFI+eHekmrZNMmu2jiiKTN33sZ4U7oakrnhiEBhvbz7IKvU/YFfyznP dElA== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20260327; h=cc:to:subject:message-id:date:references:in-reply-to:from :mime-version:dkim-signature; bh=c49khVQxrcrrH9RLPrVBMjgMScn97uDmz2gB0YQQwyU=; fh=dBBDWIvLq6/gSV1MzK3jkhYWamDqqA/5gg8Djcwybac=; b=TsWrrA8dD9gH4wK9Y/vTReTOwzHqa35ztTISAzu8RP3n0TNibn7e29Cv3/6L0jKDwn 7VI2nPCe+Kwm5QJfbfN2NGALYpnio3GP7kR/7dCCLAVhSY0Kt+3uBU4Dw2PpxK9lTunA G5dJ4pcBKLUypdoIsLmFQ4+ci7rQ+3SbqDkBVlS6aOlUSxTvhEP3/qBv+H2hssSln7kj 8J1qAIfEGfdOsTZVAnp0p21RlGIXTxqCVVqEHVaurir/6DiQURtkgsCkoTANoXdH2bYc uvP0XLMR0yJEICzRE4+aE2ik2MQ9Ja9cURwJkD3ImxT93dyCHW9KQY0FlfZB6xlZls+D Z0IQ==; darn=vger.kernel.org ARC-Authentication-Results: i=1; mx.google.com; arc=none DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1788966274; x=1789571074; darn=vger.kernel.org; h=content-type:cc:to:subject:message-id:date:references:in-reply-to :from:mime-version:from:to:cc:subject:date:message-id:reply-to :content-type; bh=c49khVQxrcrrH9RLPrVBMjgMScn97uDmz2gB0YQQwyU=; b=ZLPlQfnAlt0lm9ztZf7OG7v/t4qbjajhcQK7x77emyaBtqaoS2qH5ltqhTOtv8FbX8 f9W3DEJgytR2KYbTf9mH758VTJ8PkfN2Wy11z67h+UMrzOeMWsUSlsgBOKVcUXDucfWh HWbWikRBqKCGFwspjjujlBHkP2EZLNxQ25YZs/nBw2KwaiudPQ24Lbb3Pys1tYwJTv4w dbz6aYD9RcxQnmSKsao7sVPpftC+EisgmBqfiLdJS3UwT5qEpi3ga3gOUSy6I1dw/0zA celuHT8xE3BIfY8CEE2g7DrL9vUPpv6oBkD1TaIqfHl1Opdx8Gn8j1H3y8642lIbQMe9 fFmQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1788966274; x=1789571074; h=content-type:cc:to:subject:message-id:date:references:in-reply-to :from:mime-version:x-gm-gg:x-gm-message-state:from:to:cc:subject :date:message-id:reply-to:content-type; bh=c49khVQxrcrrH9RLPrVBMjgMScn97uDmz2gB0YQQwyU=; b=cW8xKqntH44rFPfjUY5+Er8W68e0XSJQEUVp00TK72MOS6F4TmGTJ9BE1RtWB8mvGn YSzEZ2HoUx6ZmDbpcdO61rENggjFcelGVUBX5y04lRLo6omcg9uj4sotvhkbU7nR3GL3 vut/ujyGdMN//DBrNJ5d5GW1N0/Cd8E8Re3MgkOmxbcj6J3N1cPUlP3A1kiaYUpnEAB6 FE/j9UR4DClxIU22xxdMmu45cLjLiJhbE3oNZXD4zgGTggcb7zXkMxI3uqs+dHt7TWiL 0nxiVPIDKx7ZvUgb2fcckg1AdMPtM6/mq16IORFnT6aEPXD5tE6Amo1pznbfcxGlMqaF YO6w== X-Forwarded-Encrypted: i=1; AKwUvBwGQAb3hkYlEtt3lrH/y6TY22Y7v6TC+gIMrY7dWBrtwSvVEAik/9cR/jlRxS2ow80lIuBsliHsngASAtY=@vger.kernel.org X-Gm-Message-State: AFuF++n4V7hOkwU38otUm/aPiBHYOrvSV4q3bw0jwtR+JOIIwGrNx0mv EU5SlCWIaqrYfgAsyAstx+hsUfVmJqCQ5Gej0d6Fy/AjYCCZOUrAn6rYToTjE64XzUI8jybT5ly 8qXufhVlnGPu8juUQDMY0s4lmpvlzgfg= X-Gm-Gg: AYBFou3Fy4fKN2GzH1pcMnwRt/UjxpebvpugYr9eRYoINy2ssMaceupAkuJtKo3Db31 ayhgNgSGaPHiqVuZ26nPPKvjl54SbxIGXXksxGMQon1g7Gi7+y0Dyev5C020aS7P6lICjQXIgBs /tdwYZ5O0WeBfZj7SHqfcRw/52vyrGeZELNy+3kf7y0V2Fkr59olixVuFhTnUBMr55BwoH2uLdy qNiKcgHS1I5lw1dsTWfOxR1YXvxebV97vb0tYHtlOGbBef45w5e06PF6kBHPl1guDL97RK6bTf7 kz3W/uCIq1UVv/YB+1ocWXtr5nS11PtzttjJ2lNIc9d1obQkDveTNFyxmIlLsmpPYccP2zpyivT wOV/NYQnrmvVtwXOI/nhwXMbKHfk3+jz2sL5atA== X-Received: by 2002:a05:6820:4ccb:b0:6be:31b7:e684 with SMTP id 006d021491bc7-6be31b7f404mr2736324eaf.34.1788966274062; Wed, 09 Sep 2026 08:04:34 -0700 (PDT) Received: from 77377267392 named unknown by gmailapi.google.com with HTTPREST; Wed, 9 Sep 2026 08:04:30 -0700 Received: from 77377267392 named unknown by gmailapi.google.com with HTTPREST; Wed, 9 Sep 2026 08:04:30 -0700 Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 From: Jinseob Kim In-Reply-To: References: Date: Wed, 9 Sep 2026 08:04:30 -0700 X-Gm-Features: AcwNN1XYs9GTk3zlIcbnA3lnOyqdq9oaXAcxw599RIPrGm1qpMGnH9zSuQWNF6Y Message-ID: Subject: [PATCH v9 1/5] dt-bindings: iio: add Open Sensor Fusion device To: jic23@kernel.org, linux-iio@vger.kernel.org Cc: dlechner@baylibre.com, nuno.sa@analog.com, andy@kernel.org, linux-kernel@vger.kernel.org, rdunlap@infradead.org, joshua.crofts1@gmail.com, u.kleine-koenig@baylibre.com, julianbraha@gmail.com, robh@kernel.org, krzk+dt@kernel.org, conor+dt@kernel.org, devicetree@vger.kernel.org Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Add a binding for the generic Open Sensor Fusion host interface. Open Sensor Fusion devices report capabilities and samples over an OSF protocol stream. Sensor channels are discovered at runtime from capability reports instead of being described individually in Device Tree. The protocol version is discovered at runtime from the OSF frame header. OSF GREEN is a product identity, and OSF0 is a wire-format magic value, so neither is used as the Linux compatible string. Reviewed-by: Conor Dooley Signed-off-by: Jinseob Kim --- .../bindings/iio/opensensorfusion,osf.yaml | 52 +++++++++++++++++++ .../devicetree/bindings/vendor-prefixes.yaml | 2 + MAINTAINERS | 6 +++ 3 files changed, 60 insertions(+) create mode 100644 Documentation/devicetree/bindings/iio/opensensorfusion,osf.yaml diff --git a/Documentation/devicetree/bindings/iio/opensensorfusion,osf.yaml b/Documentation/devicetree/bindings/iio/opensensorfusion,osf.yaml new file mode 100644 index 000000000000..3998390828cc --- /dev/null +++ b/Documentation/devicetree/bindings/iio/opensensorfusion,osf.yaml @@ -0,0 +1,52 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/iio/opensensorfusion,osf.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Open Sensor Fusion Sensor Aggregation Hub + +maintainers: + - Jinseob Kim + +description: | + This binding documents the generic Open Sensor Fusion host interface. Op= en + Sensor Fusion is a sensor aggregation hub. The hub exposes an OSF protoc= ol + data stream over its host interface and reports capabilities and samples= for + multiple sensor classes. The actual sensor channels are discovered at ru= ntime + from OSF capability reports instead of describing them in Device Tree. T= he + protocol version is discovered at runtime. + + Public project documentation is available at: + + https://github.com/opensensorfusion + + OSF0, protocol_major, and protocol_minor are wire-protocol details + exchanged in OSF frames. + +allOf: + - $ref: /schemas/serial/serial-peripheral-props.yaml# + +properties: + compatible: + const: opensensorfusion,osf + + vcc-supply: + description: + Regulator supplying power to the Open Sensor Fusion device. + +required: + - compatible + - vcc-supply + +unevaluatedProperties: false + +examples: + - | + serial { + sensor { + compatible =3D "opensensorfusion,osf"; + vcc-supply =3D <&vcc_sensor>; + }; + }; +... diff --git a/Documentation/devicetree/bindings/vendor-prefixes.yaml b/Documentation/devicetree/bindings/vendor-prefixes.yaml index ba2002969373..0ec4e481c64b 100644 --- a/Documentation/devicetree/bindings/vendor-prefixes.yaml +++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml @@ -1267,6 +1267,8 @@ patternProperties: description: OpenPandora GmbH "^openrisc,.*": description: OpenRISC.io + "^opensensorfusion,.*": + description: Open Sensor Fusion "^openwrt,.*": description: OpenWrt "^option,.*": diff --git a/MAINTAINERS b/MAINTAINERS index e033f2d564d6..ff09899bd645 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -20511,6 +20511,12 @@ F: Documentation/process/maintainer-devicetree.rst F: arch/*/boot/dts/ F: include/dt-bindings/ +OPEN SENSOR FUSION +M: Jinseob Kim +S: Maintained +F: Documentation/devicetree/bindings/iio/opensensorfusion,osf.yaml +K: opensensorfusion + OPENCOMPUTE PTP CLOCK DRIVER M: Vadim Fedorenko L: netdev@vger.kernel.org --=20 2.43.0 From nobody Fri Sep 25 18:24:50 2026 Received: from mail-ot1-f42.google.com (mail-ot1-f42.google.com [209.85.210.42]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id DC9B44F55A5 for ; Wed, 9 Sep 2026 15:05:39 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=pass smtp.client-ip=209.85.210.42 ARC-Seal: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788966341; cv=pass; b=ifphSdtDI9wIB7dcjOWPu0NQbAeZInhEJ49noosMDPDnHUl+jXUdX/TqWODSV4hSH7NX1hrOI92l2x7BRP2xlE7Ww1TQJv2qd6bN/oxDa874AdxyLLb2bA5jBg2V2KpGoT5ZhutrLlRymcHG1S2ys0/4sLy6i2vafYejxCT4MYs= ARC-Message-Signature: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788966341; c=relaxed/simple; bh=CdSWv/WiURR6Acy7P/4aVQQtslC45I4dSTkhDw1BvgI=; h=MIME-Version:From:In-Reply-To:References:Date:Message-ID:Subject: To:Cc:Content-Type; b=LDbXOOrNlus2SSb0FY9yoYk2UrMlDVtlYzerV2FbuyHXRaAAATUU4oH7RLT19KsDu8jVJmFlewBTNWn9jyUs0B5PuM/xooXydmL+CLswU8U4e2PiPi/B9NIloXoYv5JZzXM1RXtnGN8LhuGHrajklq1SKX6tVKJ0+QOtLWAfiJI= ARC-Authentication-Results: i=2; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=MffD7ozd; arc=pass smtp.client-ip=209.85.210.42 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="MffD7ozd" Received: by mail-ot1-f42.google.com with SMTP id 46e09a7af769-7f18c0e03e3so3172531a34.2 for ; Wed, 09 Sep 2026 08:05:39 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1788966339; cv=none; d=google.com; s=arc-20260327; b=DSMWHea3LyrcLSjwMbydeJmnE00hmqyyM93AS+KdKIcvT+Gupl2aPFcRvyR1wGOZlv Ka2Kvcg/ISZ5B8ecMo9sTAZREDYUvwQOo9iU5WX1sdZFNhivnrXQej3A0FKx7v6jYHIw PmSe3/VeT+Myq5D3WpExsqdG3yHcNsnfgHfSNHTr6HlYI88qvmGaeOQl+3+LijxpmHUU VSa8eYQw9WTe9mvtQ3L7ojha66jo81ep3nJZvB5HKCc3PT0Ai/1cwGsMjux74plmqtgn 4vt7UO8uvngpWQ7EyyIj0HWQBBNQ5Z1/sFyaS2X0wNBUCyOKpjWFlZhwNOhUZ4b0w1Kn c2Bw== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20260327; h=cc:to:subject:message-id:date:references:in-reply-to:from :mime-version:dkim-signature; bh=bPifW9Smu+tKBLdTNDc9b+YeXLJbZl3BP3bUtreOS1w=; fh=0jZOKk/msoxmxVuBHqaghtNzp5J2TELUZSHLStqeye8=; b=qKxljRsrd1JuhbfwqlIsSS5qOnkbzMoRcV1Lg9MkhFTZhzDxE4gkJCbJJloxdRDwn6 fohluVKZHBGOuTF+MvqmZQ/rfqs9ujToU1gjNmf2/wEaV/zoLF4fWYRNALnQxlHPZNqN b+eYTWSGKHc44RAftNFcn/duGb9PnXkl63lrSP98tcl2b3xqYFXfmhUIJND44lvd5BzV zglhhIvYlL82ks/yoE0ZGRaR22FXQFG+rjatSFAPA3XWi0H9M1UnARyhTZTYprLhqGzY C6gEtsQxKa8eu/43phjjXgzbxtb2SiUjYJTQRq6mPe+1ewd0i0I1Jsj4SKxu86IfY/u+ kVag==; darn=vger.kernel.org ARC-Authentication-Results: i=1; mx.google.com; arc=none DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1788966339; x=1789571139; darn=vger.kernel.org; h=content-type:cc:to:subject:message-id:date:references:in-reply-to :from:mime-version:from:to:cc:subject:date:message-id:reply-to :content-type; bh=bPifW9Smu+tKBLdTNDc9b+YeXLJbZl3BP3bUtreOS1w=; b=MffD7ozdMsfGj8F4DamnIZ+7kNFrlvGiu1toNtJyONjoc3+nDY9xgXfFQRt/+ou3Cn T0LQE4bSPGK/mW3OcN7SL/NXMEHBcXLahoxXFiGsjUICJvxz9jv1Wf0VQAa1Ar6trJa0 HO5uNBADKgUgIVu8Gz3x6lrFfENkOW0r1ZSNFdZKZ77hE/QDK2OaqMWwHhdaiKbLV2kW yjCTNnfsCBBvuyQOflwe7r2YaTruWLxs4CkaawZErJLiJFnd2zeUn62SrAL1hkOzcKBx D7W39vBhTkjAF7SFL6vyMIbUsNJry+bILMutzBH0omCg4WibewZ0bb5XzPUeMu1Z5kHD 8YGg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1788966339; x=1789571139; h=content-type:cc:to:subject:message-id:date:references:in-reply-to :from:mime-version:x-gm-gg:x-gm-message-state:from:to:cc:subject :date:message-id:reply-to:content-type; bh=bPifW9Smu+tKBLdTNDc9b+YeXLJbZl3BP3bUtreOS1w=; b=PD2Qy4jqiGQuGKvbFgmc0oz+tJ/Q34olwSetqO5rpRX3EyQySB4aLUF4q9P11QpGyn lb8dJC1WoBvUBKc6ROJAjkz1gqv8s5KmTwBswGRDGzNPZofG+rbEWFlWpd1mymDTdQHr I9UylNGgJ6Ro/J2/spmMd9v/zAgS47RdmNM+6Z4lVFip2YMu0s3aNoM+i8BRSUYXsnZI bUSM80/PUqY6lbR3F8ymlAx8zk7Jpy77swGSWGq6mfBqqncaqdQzRaNI2eWWQUQBocI+ Ffv8i0U8T+cvU802PqcAtjc7DbUvLSRY0qtfrrlTtIybIPfEjHniUNOymWDiJbOoQGfN vouQ== X-Forwarded-Encrypted: i=1; AKwUvBypCO5ZTn2Fb8+b/Iulzid0SVXsmSqxxXNAdfsht8/N44P5UJmbhnfCYh0+z6mwyFta70X52WbY/zJYnFg=@vger.kernel.org X-Gm-Message-State: AFuF++msnQtG7B9q3CViEg2XDGmOnGN3R0zWeBDwQOjhqkpYT9ShDeaF EjiL+449dXr96G6vgdF9raCd1xl8ll5pEhT0vj8Xl6yxYnLa4Ph6wBsfJIZZIbMY+MLXtRgdZlw g/g3CKP9W1OQhC6gP/sKm/D2zODathHs= X-Gm-Gg: AYBFou2aWk6qgZqUvCnmn9R3jF73ssyp3C+Q7S3vldwft4rfxoNqEHIOp7Vm9qYIfNb EfNjA2YjN+odfRsqAtQmc6K8/vECzP1D/lm9sInbuMQ/G4BtdXNRo5CkP+QDO0BnVsLGkFefqd9 Xg1FyBX28Zyoj4pfYkeZV78bCcOjj83BIAPbObvdi7eaZgNm6AYAMLfLvQ2wCgu7oFgEGL9Js3E mX+SWtxCa99z08W8ezD5awrT8Y6Izv6Z6PROX1vcQlLZQBH8LI2334y/k9Ay53xSTNyUheOtcBP //mtNkXD+6IkRN4xvsIQ4bmfRSyCb2Soz6r7dWrFUH8xA7XB4WR73WFbAe3rmK42IFK7RjJVeir XSexCqoFP5qPAEcXR3+AvLzLnPiOBdMFmpDsAew== X-Received: by 2002:a4a:e847:0:b0:6b7:46e9:9700 with SMTP id 006d021491bc7-6b746e9a360mr19044134eaf.48.1788966338598; Wed, 09 Sep 2026 08:05:38 -0700 (PDT) Received: from 77377267392 named unknown by gmailapi.google.com with HTTPREST; Wed, 9 Sep 2026 08:05:36 -0700 Received: from 77377267392 named unknown by gmailapi.google.com with HTTPREST; Wed, 9 Sep 2026 08:05:36 -0700 Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 From: Jinseob Kim In-Reply-To: References: Date: Wed, 9 Sep 2026 08:05:36 -0700 X-Gm-Features: AcwNN1XXvx0UutbQz3C8pC-IVMf-p4wSoPFQaanIGZK3T3vTnj6LaeC92XUpPTI Message-ID: Subject: [PATCH v9 2/5] Documentation: iio: add Open Sensor Fusion driver overview To: jic23@kernel.org, linux-iio@vger.kernel.org Cc: dlechner@baylibre.com, nuno.sa@analog.com, andy@kernel.org, linux-kernel@vger.kernel.org, rdunlap@infradead.org, joshua.crofts1@gmail.com, u.kleine-koenig@baylibre.com, julianbraha@gmail.com, corbet@lwn.net, skhan@linuxfoundation.org, linux-doc@vger.kernel.org Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Document the Linux IIO mapping for Open Sensor Fusion devices. The overview explains that sensor channels are discovered at runtime from mandatory capability reports. It also documents that OSF0 is a wire-format detail and that protocol_major and protocol_minor carry protocol compatibility information. Assisted-by: LLM Signed-off-by: Jinseob Kim --- Documentation/iio/index.rst | 1 + Documentation/iio/open-sensor-fusion.rst | 84 ++++++++++++++++++++++++ MAINTAINERS | 1 + 3 files changed, 86 insertions(+) create mode 100644 Documentation/iio/open-sensor-fusion.rst diff --git a/Documentation/iio/index.rst b/Documentation/iio/index.rst index b02b879b053a..c2b7963348fd 100644 --- a/Documentation/iio/index.rst +++ b/Documentation/iio/index.rst @@ -40,4 +40,5 @@ Industrial I/O Kernel Drivers adxl345 bno055 ep93xx_adc + open-sensor-fusion opt4060 diff --git a/Documentation/iio/open-sensor-fusion.rst b/Documentation/iio/open-sensor-fusion.rst new file mode 100644 index 000000000000..15d41202eb66 --- /dev/null +++ b/Documentation/iio/open-sensor-fusion.rst @@ -0,0 +1,84 @@ +.. SPDX-License-Identifier: GPL-2.0-only + +Open Sensor Fusion +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +Open Sensor Fusion is a sensor aggregation hub interface. The Linux IIO dr= iver +receives OSF protocol frames from an attached device and registers matchin= g IIO +devices for the sensor classes supported by the driver. The actual sensor +channels are discovered at runtime from mandatory OSF capability reports. + +This document is a driver-facing overview for the Linux IIO mapping. The f= ull +wire protocol, firmware behavior, and hardware model details belong in the= Open +Sensor Fusion project documentation. + +Device Model +------------ + +An OSF device sends binary frames from the device to the host. Devices using the +``opensensorfusion,osf`` compatible are expected to provide +``CAPABILITY_REPORT`` messages so the host can discover which sensor streams are +available. Device Tree describes the attached OSF sensor aggregation hub; it does +not enumerate the individual sensors discovered at runtime. + +The currently supported Linux subset exposes: + +* accelerometer samples as ``IIO_ACCEL`` X/Y/Z channels, +* gyroscope samples as ``IIO_ANGL_VEL`` X/Y/Z channels, +* magnetometer samples as ``IIO_MAGN`` X/Y/Z channels, and +* temperature samples as ``IIO_TEMP``. + +Protocol Scope +--------------- + +The driver supports OSF protocol major version 0 for the IIO receive path. +The current wire magic is ``OSF0``; that string is a wire-format detail an= d is +not the Linux driver identity. Device Tree keeps the generic +``opensensorfusion,osf`` compatible rather than naming a product such as O= SF +GREEN or a wire magic value. + +Protocol versioning is carried by the ``protocol_major`` and ``protocol_mi= nor`` +fields at fixed offsets in the OSF frame header. The driver currently +supports ``protocol_major`` 0. ``protocol_minor`` changes within major ver= sion +0 are intended to remain backward-compatible within the fixed header layou= t. +Incompatible wire-format changes require a new ``protocol_major``. A future +device that cannot expose compatible version discovery through that fixed +header layout would need a different Device Tree compatible. + +The Linux driver handles device-to-host frames for: + +* ``SENSOR_SAMPLE`` buffered and direct-mode sample data, +* ``CAPABILITY_REPORT`` based IIO device registration, and +* ``DEVICE_STATUS`` cache updates. + +Vendor-private message types are ignored. Command transport, calibration +control ABI, fusion output ABI, and runtime capability removal are outside= the +Linux IIO receive path. + +Timestamps +---------- + +OSF frames include a device-side ``timestamp_us`` field. Buffered IIO samples use +an IIO timestamp captured on the host when samples are pushed to IIO buffe= rs. +The driver does not correlate the device timestamp with the host IIO +clock. + +Compatibility Notes +------------------- + +This overview describes the implemented receive path, not a normative wire +specification. A publicly versioned specification covering reserved fields, +extensions and physical scale units remains needed for interoperability re= view. + +The decoder requires the fixed header size and the exact payload lengths of +known messages. It ignores unsupported protocol majors, unknown message ty= pes +and frames with nonzero header reserved fields. Unsupported capability ent= ries +are skipped individually; malformed known payloads are rejected. Appending= data +to an existing known message is therefore not a compatible extension for t= his +receiver. + +IIO scale is taken from the initial capability report and is not updated by +sample frames. Devices must supply scales in the units required by each IIO +channel type, including gauss for magnetometers. Historical OSF draft docu= ments +differ on reserved-field handling, trailing extensions and magnetometer un= its; +those drafts do not establish a canonical specification for this driver. diff --git a/MAINTAINERS b/MAINTAINERS index ff09899bd645..0c9c322162b2 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -20515,6 +20515,7 @@ OPEN SENSOR FUSION M: Jinseob Kim S: Maintained F: Documentation/devicetree/bindings/iio/opensensorfusion,osf.yaml +F: Documentation/iio/open-sensor-fusion.rst K: opensensorfusion OPENCOMPUTE PTP CLOCK DRIVER --=20 2.43.0 From nobody Fri Sep 25 18:24:50 2026 Received: from mail-ot1-f49.google.com (mail-ot1-f49.google.com [209.85.210.49]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 5DECB5540A0 for ; Wed, 9 Sep 2026 15:05:52 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=pass smtp.client-ip=209.85.210.49 ARC-Seal: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788966354; cv=pass; b=I8J3I6OYGH2ic2ijuKaLUYw5DRBkv+JSaJ01F0HpbIFg8Anw5fwRzfvMFxS+aUIQpmoSGvl6+vHFTGLUC5ZI40avNSPIMNB5Sh4eCs2JXqGJmfPir6QDAnQZv4jLdsboqP/Z2c4nNRzhU4hKMa3EHBnUV5n7Gh5dzqYDAe7sEQM= ARC-Message-Signature: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788966354; c=relaxed/simple; bh=Pg0OPE12vmwuWYVjsDIrUEAMoARJ9Iewm2dvzaLg3Oo=; h=MIME-Version:From:In-Reply-To:References:Date:Message-ID:Subject: To:Cc:Content-Type; b=CooIQfwjq0gUdelLD6TsKd2mbBFb2NGO8H/bw5ZcjrkBRnEm6nn/wIStxYVjZanq/q6I+Wu1rpLXV6qr1uGfS9SvOu7UekZDCRrjf0kRgZZSQFYzYMGtnsUbjt4VruLohgWQXLqY9RtMFZIPzfczuGyBxV+aNLmtTYDWgWxI1z4= ARC-Authentication-Results: i=2; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=oADgO/Nd; arc=pass smtp.client-ip=209.85.210.49 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="oADgO/Nd" Received: by mail-ot1-f49.google.com with SMTP id 46e09a7af769-7e9ecd7216cso5132678a34.3 for ; Wed, 09 Sep 2026 08:05:52 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1788966351; cv=none; d=google.com; s=arc-20260327; b=RFz1y0H2MHYqoGWka6eT0LP7rN+w9NRtZWoVejGL+DWoqDVo6Fw6HSunD8g8GkGh/u xomI5o6FrVHJx29aG+iQpj9mZ3CuEfjslYVubn1KaSChM5g85ZNIALguJrY5JNKux2NJ LQoYuEtRNnygzjlaG1sr2hvdRUmp5pGmcdLmGWwlAEZhW1PvljYomP41oJtVS5eaNeMv NjfOD57GlpDuXTOazbgXngOaNjmCua5ncirG0EdcyFzPqfuwb3nTdUNy9L91eY5TH9bM u9IKzMgHT0asWxvkp/WEFqRKi2Q9WgyXPPCq8C4onjdD/v4Re/Qv3iRtweiKMYfk4GF9 XMoQ== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20260327; h=cc:to:subject:message-id:date:references:in-reply-to:from :mime-version:dkim-signature; bh=AyUR7S+hdqUxAtfODrKh6/6SxqzP+uIq+xymgWT62Gg=; fh=xBFE4+CmMlnL01JZBoGNmnwV5cKv/8BGl71gA6yomLY=; b=eq3NqSNJwuf8C7PjVx3EZSrzPrJ3Icf4MpgNQzFlRhsIs1YDNjyHxuYtByJDw38szF B1eUcYhPLsQQPMlxEhsICA/BSlfkeRiz9EPbjFcsY7oPgbg+pHkDrTpF1T2DH3bUHkF4 En7rVX7ja8IBZP9ixAMWOGD2J1dkJ28M5sh1yujdBZewZatePbf7hE627B4N1lVvSz2X 7Ao/D39GeWAtqX9pqvlIN71Ji6X8ePXI5LpIY9WbIuAdSix58dylgPI9S/aDcNnCT+4I qWSa3sxiUf2VDPVPv4gSvv/sw1VIbODrXoEg827Ml1+sNegEeSYFHbHVnzJky+y0xePY nXyQ==; darn=vger.kernel.org ARC-Authentication-Results: i=1; mx.google.com; arc=none DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1788966351; x=1789571151; darn=vger.kernel.org; h=content-type:cc:to:subject:message-id:date:references:in-reply-to :from:mime-version:from:to:cc:subject:date:message-id:reply-to :content-type; bh=AyUR7S+hdqUxAtfODrKh6/6SxqzP+uIq+xymgWT62Gg=; b=oADgO/NdcS2pejRPW5QAWVZgewxMo1rNeTbDso+Oj66BCFKG9vW8ld+nRunvyqXu2h TLNXPybtJBOO8aNd86PIfmIA5b9qV0Ecf57bwi+GE7wqhhxlyPcBVhOUyaYw+E95eKvk AUMBdPHAy5wR9rNWG7t7qRMFoLOe5N7RXouAi/u40bBBHZ+c2joLVE8ojv+zGTt5gPPW 4T/JndVCnzvx18bTmqM5OfMsciWG69cAbfAU6WIiFU1/o1tOdTOP4XClvcze+YTMpTI1 HbuEO2JOxZ1pQhvSkoc233wjJkeUNKYxnnxee7LksDVLPEnW8/sEQO6Bz26monqR/oNQ xXTA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1788966351; x=1789571151; h=content-type:cc:to:subject:message-id:date:references:in-reply-to :from:mime-version:x-gm-gg:x-gm-message-state:from:to:cc:subject :date:message-id:reply-to:content-type; bh=AyUR7S+hdqUxAtfODrKh6/6SxqzP+uIq+xymgWT62Gg=; b=M/geTP/Fkuj6pyUNvt6k1cRckILe8sEXSBkkvnz1VvmMbFWRJ5gfmxphQ8TQK7dSVz eiBabwXMzYVm7nQwi4kWBh1fgcbVJE8sM8IOj7vkijwAsnzCt49AHumBING7vS+u+1d1 eHMBZocx+jbTo1YqpFmDSN0sAzagU+MMw5sXlVlaIYFuCoELsaKKxnfhK/kYzx0uzGcC 279s3cDv2iiJggxmsfLt4th1NMbBfgGVuNJDt0EoSYLO67S4ycNFxs5Z/Wr9V1qSWxsW vKeMEbG9z10tDs5dSgtC3fCHicZonl85LI7LjAXRXVDwEVlALqLZ3cCNkFy9G5odBnQ8 pIDw== X-Forwarded-Encrypted: i=1; AKwUvBzmQKs8eV+mE0aMRk+M17k5Rjc1gpfBzkRHHL73aOVwuSFid7iaGQK8Q/CxSDrzePAfmqLS+qbAV6fQN/k=@vger.kernel.org X-Gm-Message-State: AFuF++ke+xPxiGYItOkOhthB7Ch8wTsinkJiHWb/mCc/XjkcuNazbj88 bLBf+aiG/Wzzax5SrJTzC3AYMGrT/Q2XzKE7mn7smbJIbKgsy5qAGt25NdyEkKuFkXc6Y73RIjY hFxbd4MRTs+hiDG8Pq5qnP7TcyTmSeuw= X-Gm-Gg: AYBFou1bJZgpv894wQ8os4DZdemB1ZXuzx2OO0l+m0eKEvxeT4SLLBpV6c5ADs/STyO BzKuqsg8Tcdx7reHFhOhZwTUzJoH2anrY5ghjmmzZu8sW6BROiYzZKCmQpU0U4qlp2StzcI2g16 izDzLjYvDRrFjGOL8Jkc0DxdFK58Z9711G8d/3jHi4pH2IPafVw0P5TLyiCVNFRNbZ/lFJi0phK yOoJxD5x/TATvq31pO8y1LWKts1I1bg5E5WRb0Ezp9XE/SuqBtvfXPcVjmFIaXTXn1mxB3OtbVs /ICIZLfXFcl/TGRlGe9NI/jsn3+mKiNcUY0ZuVs9My04i/y5WZP2K7SSp3fugRRgFCEJN8FiCGl AvHZQkJlqpVKtUwEoCJ/yGJwV0KX2A7j5lAt5cIV/2W5aO+JC X-Received: by 2002:a05:6820:810a:b0:6ac:b6c0:2bf6 with SMTP id 006d021491bc7-6b6fabd2f22mr20142537eaf.5.1788966351014; Wed, 09 Sep 2026 08:05:51 -0700 (PDT) Received: from 77377267392 named unknown by gmailapi.google.com with HTTPREST; Wed, 9 Sep 2026 08:05:49 -0700 Received: from 77377267392 named unknown by gmailapi.google.com with HTTPREST; Wed, 9 Sep 2026 08:05:49 -0700 Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 From: Jinseob Kim In-Reply-To: References: Date: Wed, 9 Sep 2026 08:05:49 -0700 X-Gm-Features: AcwNN1UJyoZ5NxE2b9rqL4o8eMYp7QtQzYieEg-YsNXYdTJfmBhIY59oc__BguQ Message-ID: Subject: [PATCH v9 3/5] iio: osf: add protocol decoding To: jic23@kernel.org, linux-iio@vger.kernel.org Cc: dlechner@baylibre.com, nuno.sa@analog.com, andy@kernel.org, linux-kernel@vger.kernel.org, rdunlap@infradead.org, joshua.crofts1@gmail.com, u.kleine-koenig@baylibre.com, julianbraha@gmail.com Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Add helpers for decoding Open Sensor Fusion frame headers and supported message payloads. The decoder validates framing, payload bounds and CRC before exposing decoded frame contents to the rest of the driver. Framing validation includes the OSF0 wire magic and fixed header length. Protocol version and reserved header fields are exposed to the core so authenticated-frame policy is handled after CRC validation. Capability entries are decoded structurally, including their flags and reserved fields. The core can then apply support policy without rejecting an entire report solely because it contains future entry values. Use explicit little-endian wire storage sizes and designated initializers for decoded output structures. Signed-off-by: Jinseob Kim --- MAINTAINERS | 1 + drivers/iio/opensensorfusion/osf_protocol.c | 224 ++++++++++++++++++++ drivers/iio/opensensorfusion/osf_protocol.h | 101 +++++++++ 3 files changed, 326 insertions(+) create mode 100644 drivers/iio/opensensorfusion/osf_protocol.c create mode 100644 drivers/iio/opensensorfusion/osf_protocol.h diff --git a/MAINTAINERS b/MAINTAINERS index 0c9c322162b2..f7ead365f946 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -20516,6 +20516,7 @@ M: Jinseob Kim S: Maintained F: Documentation/devicetree/bindings/iio/opensensorfusion,osf.yaml F: Documentation/iio/open-sensor-fusion.rst +F: drivers/iio/opensensorfusion/osf_protocol.* K: opensensorfusion OPENCOMPUTE PTP CLOCK DRIVER diff --git a/drivers/iio/opensensorfusion/osf_protocol.c b/drivers/iio/opensensorfusion/osf_protocol.c new file mode 100644 index 000000000000..cd7b8587f737 --- /dev/null +++ b/drivers/iio/opensensorfusion/osf_protocol.c @@ -0,0 +1,224 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include +#include +#include +#include +#include + +#include "osf_protocol.h" + +#define OSF_CRC32_INIT GENMASK(31, 0) +#define OSF_CRC32_XOROUT GENMASK(31, 0) + +static bool osf_sensor_type_valid(u16 sensor_type) +{ + return sensor_type >=3D OSF_SENSOR_ACCELEROMETER && + sensor_type <=3D OSF_SENSOR_PROXIMITY; +} + +static u32 osf_crc32_ieee(const u8 *buf, size_t len) +{ + return crc32_le(OSF_CRC32_INIT, buf, len) ^ OSF_CRC32_XOROUT; +} + +int osf_protocol_decode_frame(const u8 *buf, size_t len, + struct osf_frame *frame, size_t *frame_len) +{ + u32 expected_crc; + u32 actual_crc; + u32 payload_len; + size_t total_len; + + if (!buf || !frame || !frame_len) + return -EINVAL; + + if (len < OSF_FRAME_MIN_LEN) + return -EMSGSIZE; + + if (get_unaligned_le32(buf) !=3D OSF_FRAME_MAGIC) + return -EPROTO; + + if (get_unaligned_le16(buf + 6) !=3D OSF_FRAME_HEADER_LEN) + return -EPROTO; + + payload_len =3D get_unaligned_le32(buf + 10); + if (payload_len > len - OSF_FRAME_MIN_LEN) + return -EMSGSIZE; + + total_len =3D OSF_FRAME_HEADER_LEN + payload_len + OSF_FRAME_CRC_LEN; + expected_crc =3D osf_crc32_ieee(buf, OSF_FRAME_HEADER_LEN + payload_len); + actual_crc =3D get_unaligned_le32(buf + OSF_FRAME_HEADER_LEN + payload_le= n); + + if (actual_crc !=3D expected_crc) + return -EBADMSG; + + frame->protocol_major =3D buf[4]; + frame->protocol_minor =3D buf[5]; + frame->message_type =3D get_unaligned_le16(buf + 8); + frame->payload_len =3D payload_len; + frame->sequence =3D get_unaligned_le64(buf + 14); + frame->timestamp_us =3D get_unaligned_le64(buf + 22); + frame->flags =3D get_unaligned_le32(buf + 30); + frame->reserved =3D get_unaligned_le32(buf + 34); + frame->payload =3D buf + OSF_FRAME_HEADER_LEN; + frame->crc =3D actual_crc; + *frame_len =3D total_len; + + return 0; +} + +int osf_protocol_decode_sensor_sample(const struct osf_frame *frame, + struct osf_sensor_sample *sample) +{ + u16 channel_count; + u16 sample_format; + u16 sensor_type; + size_t expected_len; + const u8 *payload; + + if (!frame || !sample || !frame->payload) + return -EINVAL; + + if (frame->message_type !=3D OSF_MSG_SENSOR_SAMPLE) + return -EPROTO; + + if (frame->payload_len < OSF_SENSOR_SAMPLE_BASE_LEN) + return -EMSGSIZE; + + payload =3D frame->payload; + sensor_type =3D get_unaligned_le16(payload); + channel_count =3D get_unaligned_le16(payload + 4); + sample_format =3D get_unaligned_le16(payload + 6); + + if (!osf_sensor_type_valid(sensor_type)) + return -EPROTO; + + if (!channel_count) + return -EPROTO; + + if (sample_format !=3D OSF_SAMPLE_FORMAT_S32) + return -EPROTO; + + if (get_unaligned_le32(payload + 12)) + return -EPROTO; + + expected_len =3D OSF_SENSOR_SAMPLE_BASE_LEN + channel_count * sizeof(__le= 32); + if (frame->payload_len !=3D expected_len) + return -EMSGSIZE; + + *sample =3D (struct osf_sensor_sample) { + .sensor_type =3D sensor_type, + .sensor_index =3D get_unaligned_le16(payload + 2), + .channel_count =3D channel_count, + .sample_format =3D sample_format, + .scale_nano =3D get_unaligned_le32(payload + 8), + .samples =3D payload + OSF_SENSOR_SAMPLE_BASE_LEN, + }; + + return 0; +} + +int osf_protocol_sensor_sample_value(const struct osf_sensor_sample *sampl= e, + u16 index, s32 *value) +{ + if (!sample || !sample->samples || !value) + return -EINVAL; + + if (index >=3D sample->channel_count) + return -ERANGE; + + /* Samples are little-endian two's-complement signed values. */ + *value =3D get_unaligned_le32(sample->samples + index * sizeof(__le32)); + + return 0; +} + +int osf_protocol_decode_device_status(const struct osf_frame *frame, + struct osf_device_status *status) +{ + const u8 *payload; + + if (!frame || !status || !frame->payload) + return -EINVAL; + + if (frame->message_type !=3D OSF_MSG_DEVICE_STATUS) + return -EPROTO; + + if (frame->payload_len !=3D OSF_DEVICE_STATUS_LEN) + return -EMSGSIZE; + + payload =3D frame->payload; + if (get_unaligned_le32(payload + 16)) + return -EPROTO; + + *status =3D (struct osf_device_status) { + .uptime_s =3D get_unaligned_le32(payload), + .status_flags =3D get_unaligned_le32(payload + 4), + .error_flags =3D get_unaligned_le32(payload + 8), + .dropped_frames =3D get_unaligned_le32(payload + 12), + }; + + return 0; +} + +int osf_protocol_decode_capability_report(const struct osf_frame *frame, + struct osf_capability_report *report) +{ + u16 capability_count; + size_t expected_len; + const u8 *payload; + + if (!frame || !report || !frame->payload) + return -EINVAL; + + if (frame->message_type !=3D OSF_MSG_CAPABILITY_REPORT) + return -EPROTO; + + if (frame->payload_len < OSF_CAP_REPORT_BASE_LEN) + return -EMSGSIZE; + + payload =3D frame->payload; + capability_count =3D get_unaligned_le16(payload); + + if (get_unaligned_le16(payload + 2)) + return -EPROTO; + + expected_len =3D OSF_CAP_REPORT_BASE_LEN + + capability_count * OSF_CAP_SENSOR_ENTRY_LEN; + if (frame->payload_len !=3D expected_len) + return -EMSGSIZE; + + *report =3D (struct osf_capability_report) { + .capability_count =3D capability_count, + .entries =3D payload + OSF_CAP_REPORT_BASE_LEN, + }; + + return 0; +} + +int osf_protocol_decode_capability_entry(const struct osf_capability_report + *report, u16 index, + struct osf_capability_entry *entry) +{ + const u8 *payload; + + if (!report || !report->entries || !entry) + return -EINVAL; + + if (index >=3D report->capability_count) + return -ERANGE; + + payload =3D report->entries + index * OSF_CAP_SENSOR_ENTRY_LEN; + *entry =3D (struct osf_capability_entry) { + .sensor_type =3D get_unaligned_le16(payload), + .sensor_index =3D get_unaligned_le16(payload + 2), + .channel_count =3D get_unaligned_le16(payload + 4), + .sample_format =3D get_unaligned_le16(payload + 6), + .scale_nano =3D get_unaligned_le32(payload + 8), + .flags =3D get_unaligned_le32(payload + 12), + .reserved =3D get_unaligned_le32(payload + 16), + }; + + return 0; +} diff --git a/drivers/iio/opensensorfusion/osf_protocol.h b/drivers/iio/opensensorfusion/osf_protocol.h new file mode 100644 index 000000000000..2b616f2d5197 --- /dev/null +++ b/drivers/iio/opensensorfusion/osf_protocol.h @@ -0,0 +1,101 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +#ifndef _OSF_PROTOCOL_H +#define _OSF_PROTOCOL_H + +#include +#include + +#define OSF_PROTOCOL_MAJOR 0 +#define OSF_PROTOCOL_MINOR 0 +#define OSF_FRAME_HEADER_LEN 38 +#define OSF_FRAME_CRC_LEN 4 +#define OSF_FRAME_MIN_LEN (OSF_FRAME_HEADER_LEN + OSF_FRAME_CRC_LEN) +#define OSF_FRAME_MAGIC 0x3046534f /* "OSF0", little-endian */ + +#define OSF_SENSOR_SAMPLE_BASE_LEN 16 +#define OSF_DEVICE_STATUS_LEN 20 +#define OSF_CAP_REPORT_BASE_LEN 4 +#define OSF_CAP_SENSOR_ENTRY_LEN 20 +#define OSF_CAPABILITY_FLAGS_MASK GENMASK(1, 0) + +enum osf_message_type { + OSF_MSG_SENSOR_SAMPLE =3D 0x0001, + OSF_MSG_DEVICE_STATUS =3D 0x0002, + OSF_MSG_CAPABILITY_REPORT =3D 0x0003, +}; + +enum osf_sensor_type { + OSF_SENSOR_ACCELEROMETER =3D 0x0001, + OSF_SENSOR_GYROSCOPE =3D 0x0002, + OSF_SENSOR_MAGNETOMETER =3D 0x0003, + OSF_SENSOR_BAROMETER =3D 0x0004, + OSF_SENSOR_TEMPERATURE =3D 0x0005, + OSF_SENSOR_HUMIDITY =3D 0x0006, + OSF_SENSOR_AMBIENT_LIGHT =3D 0x0007, + OSF_SENSOR_PROXIMITY =3D 0x0008, +}; + +enum osf_sample_format { + OSF_SAMPLE_FORMAT_S32 =3D 0x0001, +}; + +struct osf_frame { + u8 protocol_major; + u8 protocol_minor; + u16 message_type; + u32 payload_len; + u64 sequence; + u64 timestamp_us; + u32 flags; + u32 reserved; + /* payload points into the caller-owned frame buffer. */ + const u8 *payload; + u32 crc; +}; + +struct osf_sensor_sample { + u16 sensor_type; + u16 sensor_index; + u16 channel_count; + u16 sample_format; + u32 scale_nano; + const u8 *samples; +}; + +struct osf_device_status { + u32 uptime_s; + u32 status_flags; + u32 error_flags; + u32 dropped_frames; +}; + +struct osf_capability_report { + u16 capability_count; + const u8 *entries; +}; + +struct osf_capability_entry { + u16 sensor_type; + u16 sensor_index; + u16 channel_count; + u16 sample_format; + u32 scale_nano; + u32 flags; + u32 reserved; +}; + +int osf_protocol_decode_frame(const u8 *buf, size_t len, + struct osf_frame *frame, size_t *frame_len); +int osf_protocol_decode_sensor_sample(const struct osf_frame *frame, + struct osf_sensor_sample *sample); +int osf_protocol_decode_device_status(const struct osf_frame *frame, + struct osf_device_status *status); +int osf_protocol_decode_capability_report(const struct osf_frame *frame, + struct osf_capability_report *report); +int osf_protocol_decode_capability_entry(const struct osf_capability_report + *report, u16 index, + struct osf_capability_entry *entry); +int osf_protocol_sensor_sample_value(const struct osf_sensor_sample *sampl= e, + u16 index, s32 *value); + +#endif --=20 2.43.0 From nobody Fri Sep 25 18:24:50 2026 Received: from mail-oa1-f46.google.com (mail-oa1-f46.google.com [209.85.160.46]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 8DCF1569F26 for ; Wed, 9 Sep 2026 15:06:50 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=pass smtp.client-ip=209.85.160.46 ARC-Seal: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788966412; cv=pass; b=iYMPmA8OQlDrep2lVzgY2crfvkbhrk7yR2dh5eigdSTXaWR40fvRJg8fEouuJJXfj7p8etGBBSmgjd8rKB6wtKz5bzjEpXXWIkTFZkIC5xOdhYLO5J1OsJ9AdIw1XKvwx1ClwVhAu9TpomMIfSJ+v2/xpQsxyw0PgS/gmgJwlrM= ARC-Message-Signature: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788966412; c=relaxed/simple; bh=k2qfzy4hErs+vQGwIONiuwFv8cd9e9/OCLbxweTDTGU=; h=MIME-Version:From:In-Reply-To:References:Date:Message-ID:Subject: To:Cc:Content-Type; b=s+pLyvD+Vdu3+Y2G5rjyCPgMMbkM49vrNlGrD1p3x9RXPLeZ9Bpu4bc3ejufmUjC1Ak77dPApqddg6wTL3s4NIA4R8FuPBWgnTnHGB/3RKorgf1CU6NWWPgIJDmQJ5wcKTs2txWqUfJvbvM2er/c/h8DDqPBuJ3VQHf4HKnUteY= ARC-Authentication-Results: i=2; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=VZKxfi67; arc=pass smtp.client-ip=209.85.160.46 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="VZKxfi67" Received: by mail-oa1-f46.google.com with SMTP id 586e51a60fabf-46adfc80ff6so3637322fac.2 for ; Wed, 09 Sep 2026 08:06:50 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1788966409; cv=none; d=google.com; s=arc-20260327; b=qaK0EutWgZetuGuBGHVyJ+EqrJO4aKHXr6RODlK13rEeRtsylRwurCcuEtpB9oq6a9 WMXKIuQFhaQqoTbKpFwjy8wIvGNmh85AIKOqmTC6FgSS4nNDWru8pX0g2FWQfDq9+Doa FKQNDP/PnLbMYgbgZI9XVQzgmEByrJV01igjL+JD0C8/qeJV79JeUIF1K8Tkw5QhQevc qBPowRr1SzpjWU1BmFwAeuxSemhvQz1kNkHyvQCYmrn2hw48ASqbxpwKxPdhp2U2Aa6A XFR0T80CHjftnAfZ+uwNa3Jv6C7dToqBvfdDBvvjBrJJlC4YStiMYghybYgrxcJDt5FP vAGQ== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20260327; h=cc:to:subject:message-id:date:references:in-reply-to:from :mime-version:dkim-signature; bh=c31/Tnh+sbAxB7gCPzYWt77jlxu/AsDNJ4xDcUYAnY8=; fh=o9xuKx3JoV3tO8v03yUpAmgb+sRPpbLG9QTNQpMOElg=; b=bDPYXcykfjkGPHb580Y+BmvgvatGkBySYn7UU+qRyMTnA08MKB2woYBkyA/+4mqoEN 2dk8lLXa1BpHGy8lYEtN/Ey8L4sNHs2yDSDgGZX/DcwGV1rrqdvR5LuXY9h3KUaP6tiD Lou6icBQoF5Z99TCjbj1k1TbCZVvTE9R/NBhPDK5vyu7F5bB5TU8K7koLzyZhNHPCbaF LeIvJ8Y6r0x/OVU0NPrMMXS+LL6DXcIAwwaiukQ8Tbz5YZb7+bA175fxPmfB+libmh+Q tAto6UMAysOSh0NHP+YgT0VX8liNUtnRsn6Zvh+gj3/MCErDN5+729xOl4ZugqxQ5N3s Gj5A==; darn=vger.kernel.org ARC-Authentication-Results: i=1; mx.google.com; arc=none DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1788966409; x=1789571209; darn=vger.kernel.org; h=content-type:cc:to:subject:message-id:date:references:in-reply-to :from:mime-version:from:to:cc:subject:date:message-id:reply-to :content-type; bh=c31/Tnh+sbAxB7gCPzYWt77jlxu/AsDNJ4xDcUYAnY8=; b=VZKxfi676YfOO3TyjzLTgME+SuoKbHABuVPPQsxdNhFI0lS8l7WrLxn4L+0SZQV7jh xuiZWqWPP+/zCXYb/mDmMnMXX6zJ46v8UocDfAKhOoepEB7wF8CflioFQxQkJYwYxKi5 En2wQ0tS/sZJoRHY2MViUdkIdyeSpS5x+ZVPkWPm0pEJaYiIMbSqOegcQgiY+QEFNk5/ IVjS9ILt85TTDqJJNsKJFeIEqO0iNBT+IowxwcMrogsDfFx2uYMZ2NtyuZbmCPsk2RdL jOkojlkZe+tRm4+FXedabyHsaKzccRer7chRNWppmZrscu0lz0AhhT+JqkjFs3fLH6fi ywLg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1788966409; x=1789571209; h=content-type:cc:to:subject:message-id:date:references:in-reply-to :from:mime-version:x-gm-gg:x-gm-message-state:from:to:cc:subject :date:message-id:reply-to:content-type; bh=c31/Tnh+sbAxB7gCPzYWt77jlxu/AsDNJ4xDcUYAnY8=; b=oR94LMi9MKhLGW+Cl7G/RjOInXHv+YO65njWI+eOUlDuv1SE5SmTtcBSyG6eDLEJUR f1vlUYo4tncZQJ+Ddt0BUv+PzBGubdbNONiH2RW6AV/0RAygo39eTxDhVMWQ+NLwvStH Pl3Dy5PUqQ33lszDGF9R6YVMz9s7cZ795Z2YeUvnrdH3Od1S+pEaMTHE81DlZU7JRbRF YK7Lf8/3VzvAxAS85gmJyX+hKb9dvt3bORIiFvF/fQTfPGFJSDrfZSutxWdO7IMqe8zY nEQvgYDqA3mfSDPiizpYLGlmvVcPxoiHtecd7loKSxYMMhXzgqBzQe1M90T3glixkZVj ZUzA== X-Forwarded-Encrypted: i=1; AKwUvBwpuz1AUzHIZ44Ylt5/V7JDeKi6izENvsWGWkoK62JXz0YFKzpHY/eB96ebKR8dWfE/9f45usLdDiWOCeY=@vger.kernel.org X-Gm-Message-State: AFuF++lwImB2fWKSYL3/dhevdpNm5BJjpU7WXzNk8jx44TFhVuYKy4tB qHTcAdDC71vsVDtyog8slFk8526vyS61McLnLezoCkIpSt0nHG2ZLC2nPBD1sX9skEnq3/vRKUp meXRAy9PkhSpnEHPNRKnBhFcWDIPWwNw= X-Gm-Gg: AYBFou3a070MkcI927XkIhUPQrEFGZFIt/MoGVWJueQax6I61FBftUG88z3/dMdxZ2H h3tKiXHskmvSUEKYzI2VFvPjSQHmd2Ujih/YGwxY6VIupDgcYQ+suz4rPxOLLfuVL7g58WGOGVe vIS8VvWsppg+Dl8wepTnn+nr5pX22/6o+SJm9aaX06pzug4bh8pxZCqOw3ucdR3zPqrSAwC506e Ohn6bSd1z7s4FWrG4DEsWLgOQ5udm/Yq/9mYaLwcYL2cdtYQrL53i6D8MqpzWehwvQXVnzwZZq1 6OuhEI4MT3JfDtOKckW2U2hvM8IiavvhRjfRwYOjnKQzkvCEeImjlniC67ytuBM0r4O1BAPaZb9 ab/D05PI7oa5jaClk7LxdKtfkeXS90Ehrl1QfaA== X-Received: by 2002:a05:6820:4ccb:b0:6be:31b7:e684 with SMTP id 006d021491bc7-6be31b7f404mr2750410eaf.34.1788966408409; Wed, 09 Sep 2026 08:06:48 -0700 (PDT) Received: from 77377267392 named unknown by gmailapi.google.com with HTTPREST; Wed, 9 Sep 2026 08:06:47 -0700 Received: from 77377267392 named unknown by gmailapi.google.com with HTTPREST; Wed, 9 Sep 2026 08:06:47 -0700 Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 From: Jinseob Kim In-Reply-To: References: Date: Wed, 9 Sep 2026 08:06:47 -0700 X-Gm-Features: AcwNN1XFlQtgrH-fU4DEoQF7whI63Qa144Fp304SoCenIf7iGcNQ5my1HL_QW-w Message-ID: Subject: [PATCH v9 4/5] iio: osf: add authenticated stream parser To: jic23@kernel.org, linux-iio@vger.kernel.org Cc: dlechner@baylibre.com, nuno.sa@analog.com, andy@kernel.org, linux-kernel@vger.kernel.org, rdunlap@infradead.org, joshua.crofts1@gmail.com, u.kleine-koenig@baylibre.com, julianbraha@gmail.com Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Add a UART byte-stream parser for Open Sensor Fusion frames. The parser searches for the OSF0 wire magic, keeps partial frames buffered, checks header length and payload bounds, and passes complete candidate frames to a registered frame callback. Candidates rejected before authentication drop only the current head byte before resynchronizing, so a corrupted unauthenticated payload length cannot make the parser skip later valid frames. CRC-valid authenticated frames are consumed in full and classified as handled, ignored, or rejected. Use a direct callback member with an opaque context and keep explicit statistics for authenticated outcomes and framing failures. Signed-off-by: Jinseob Kim --- MAINTAINERS | 1 + drivers/iio/opensensorfusion/osf_stream.c | 231 ++++++++++++++++++++++ drivers/iio/opensensorfusion/osf_stream.h | 53 +++++ 3 files changed, 285 insertions(+) create mode 100644 drivers/iio/opensensorfusion/osf_stream.c create mode 100644 drivers/iio/opensensorfusion/osf_stream.h diff --git a/MAINTAINERS b/MAINTAINERS index f7ead365f946..a1117459ebd9 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -20517,6 +20517,7 @@ S: Maintained F: Documentation/devicetree/bindings/iio/opensensorfusion,osf.yaml F: Documentation/iio/open-sensor-fusion.rst F: drivers/iio/opensensorfusion/osf_protocol.* +F: drivers/iio/opensensorfusion/osf_stream.* K: opensensorfusion OPENCOMPUTE PTP CLOCK DRIVER diff --git a/drivers/iio/opensensorfusion/osf_stream.c b/drivers/iio/opensensorfusion/osf_stream.c new file mode 100644 index 000000000000..4e2a1deffbaa --- /dev/null +++ b/drivers/iio/opensensorfusion/osf_stream.c @@ -0,0 +1,231 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include +#include +#include +#include +#include + +#include "osf_protocol.h" +#include "osf_stream.h" + +#define OSF_STREAM_MAGIC_LEN sizeof(__le32) +#define OSF_STREAM_MAX_PAYLOAD_LEN \ + (OSF_STREAM_MAX_FRAME_LEN - OSF_FRAME_HEADER_LEN - OSF_FRAME_CRC_LEN) + +static void osf_stream_discard(struct osf_stream *stream, size_t count) +{ + if (count >=3D stream->len) { + stream->len =3D 0; + return; + } + + memmove(stream->buf, stream->buf + count, stream->len - count); + stream->len -=3D count; +} + +static void osf_stream_drop_invalid_head(struct osf_stream *stream) +{ + osf_stream_discard(stream, 1); +} + +static bool osf_stream_magic_prefix_match(const u8 *buf, size_t len) +{ + for (size_t i =3D 0; i < len; i++) { + if (buf[i] !=3D (u8)(OSF_FRAME_MAGIC >> (i * 8))) + return false; + } + + return true; +} + +static size_t osf_stream_discard_to_magic(struct osf_stream *stream) +{ + size_t old_len =3D stream->len; + size_t keep_len; + + for (size_t i =3D 0; i + OSF_STREAM_MAGIC_LEN <=3D stream->len; i++) { + if (get_unaligned_le32(stream->buf + i) =3D=3D OSF_FRAME_MAGIC) { + if (i) + osf_stream_discard(stream, i); + return i; + } + } + + /* + * Keep a final 1-3 byte OSF_FRAME_MAGIC prefix so a magic split + * across receive_buf() calls can be completed by the next chunk. + */ + keep_len =3D min(stream->len, OSF_STREAM_MAGIC_LEN - 1); + while (keep_len) { + size_t offset =3D stream->len - keep_len; + + if (osf_stream_magic_prefix_match(stream->buf + offset, keep_len)) { + if (offset) + osf_stream_discard(stream, offset); + return offset; + } + keep_len--; + } + + stream->len =3D 0; + return old_len; +} + +static int osf_stream_process(struct osf_stream *stream) +{ + size_t discarded; + size_t frame_len; + u32 payload_len; + int frame_result; + int first_err =3D 0; + + while (stream->len) { + discarded =3D osf_stream_discard_to_magic(stream); + if (discarded) { + stream->stats.bad_magic_resyncs++; + stream->stats.dropped_bytes +=3D discarded; + if (!first_err) + first_err =3D -EPROTO; + } + + if (!stream->len) + break; + + if (stream->len < OSF_FRAME_HEADER_LEN) + break; + + if (get_unaligned_le16(stream->buf + 6) !=3D OSF_FRAME_HEADER_LEN) { + stream->stats.dropped_bytes++; + osf_stream_drop_invalid_head(stream); + if (!first_err) + first_err =3D -EPROTO; + continue; + } + + payload_len =3D get_unaligned_le32(stream->buf + 10); + if (payload_len > OSF_STREAM_MAX_PAYLOAD_LEN) { + stream->stats.dropped_bytes++; + osf_stream_drop_invalid_head(stream); + if (!first_err) + first_err =3D -EMSGSIZE; + continue; + } + + frame_len =3D OSF_FRAME_HEADER_LEN + payload_len + OSF_FRAME_CRC_LEN; + if (stream->len < frame_len) + break; + + frame_result =3D stream->receive_frame(stream->frame_context, + stream->buf, frame_len); + if (frame_result < 0) { + if (frame_result =3D=3D -EBADMSG) + stream->stats.bad_crc_frames++; + + /* + * Decoding failed before the frame was authenticated; + * payload_len is still untrusted. Drop only the current + * head and resynchronize. + */ + stream->stats.dropped_bytes++; + osf_stream_drop_invalid_head(stream); + if (!first_err) + first_err =3D frame_result; + continue; + } + + /* Count exactly one outcome for every authenticated frame. */ + stream->stats.authenticated_frames++; + switch (frame_result) { + case OSF_STREAM_FRAME_HANDLED: + stream->stats.handled_frames++; + break; + case OSF_STREAM_FRAME_IGNORED: + stream->stats.ignored_frames++; + break; + case OSF_STREAM_FRAME_REJECTED: + stream->stats.rejected_frames++; + break; + default: + /* + * Preserve the authenticated boundary without scanning the + * payload for another magic value. + */ + stream->stats.rejected_frames++; + if (!first_err) + first_err =3D -EPROTO; + break; + } + osf_stream_discard(stream, frame_len); + } + + return first_err; +} + +void osf_stream_init(struct osf_stream *stream, + int (*receive_frame)(void *context, const u8 *buf, + size_t len), + void *frame_context) +{ + if (!stream) + return; + + stream->receive_frame =3D receive_frame; + stream->frame_context =3D frame_context; + stream->len =3D 0; + memset(&stream->stats, 0, sizeof(stream->stats)); +} + +void osf_stream_reset(struct osf_stream *stream) +{ + if (!stream) + return; + + stream->len =3D 0; + memset(&stream->stats, 0, sizeof(stream->stats)); +} + +int osf_stream_receive_bytes(struct osf_stream *stream, + const u8 *buf, size_t len) +{ + size_t copy_len; + size_t space; + int first_err =3D 0; + int ret; + + if (!stream || !stream->receive_frame || (!buf && len)) + return -EINVAL; + + if (!len) + return osf_stream_process(stream); + + /* + * Continue processing this receive_buf() chunk after recoverable + * framing errors so later valid frames do not wait for another callback. + * first_err retains the first diagnostic return, while the serdev + * callback reports the full byte count consumed. Every authenticated + * callback result is consumed in full by osf_stream_process(). + */ + while (len) { + space =3D OSF_STREAM_MAX_FRAME_LEN - stream->len; + if (!space) { + stream->stats.dropped_bytes++; + osf_stream_discard(stream, 1); + if (!first_err) + first_err =3D -EMSGSIZE; + continue; + } + + copy_len =3D min(len, space); + memcpy(stream->buf + stream->len, buf, copy_len); + stream->len +=3D copy_len; + buf +=3D copy_len; + len -=3D copy_len; + + ret =3D osf_stream_process(stream); + if (ret && !first_err) + first_err =3D ret; + } + + return first_err; +} diff --git a/drivers/iio/opensensorfusion/osf_stream.h b/drivers/iio/opensensorfusion/osf_stream.h new file mode 100644 index 000000000000..c13f4e3af18f --- /dev/null +++ b/drivers/iio/opensensorfusion/osf_stream.h @@ -0,0 +1,53 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +#ifndef _OSF_STREAM_H +#define _OSF_STREAM_H + +#include + +#define OSF_STREAM_MAX_FRAME_LEN 4096 + +/** + * enum osf_stream_frame_result - authenticated frame callback result + * @OSF_STREAM_FRAME_HANDLED: frame was processed successfully + * @OSF_STREAM_FRAME_IGNORED: frame was valid but unsupported or ignored + * @OSF_STREAM_FRAME_REJECTED: authenticated application processing failed + * + * A frame callback returns a negative errno only when a candidate could n= ot + * be authenticated and the parser may perform one-byte resynchronization. + * Every nonnegative result must be one of these values. Such a result mea= ns + * the CRC-valid frame boundary is trusted, so the parser must consume the + * full frame. + */ +enum osf_stream_frame_result { + OSF_STREAM_FRAME_HANDLED, + OSF_STREAM_FRAME_IGNORED, + OSF_STREAM_FRAME_REJECTED, +}; + +struct osf_stream_stats { + u64 authenticated_frames; + u64 handled_frames; + u64 ignored_frames; + u64 rejected_frames; + u64 bad_magic_resyncs; + u64 bad_crc_frames; + u64 dropped_bytes; +}; + +struct osf_stream { + int (*receive_frame)(void *context, const u8 *buf, size_t len); + void *frame_context; + u8 buf[OSF_STREAM_MAX_FRAME_LEN]; + size_t len; + struct osf_stream_stats stats; +}; + +void osf_stream_init(struct osf_stream *stream, + int (*receive_frame)(void *context, const u8 *buf, + size_t len), + void *frame_context); +void osf_stream_reset(struct osf_stream *stream); +int osf_stream_receive_bytes(struct osf_stream *stream, + const u8 *buf, size_t len); + +#endif --=20 2.43.0 From nobody Fri Sep 25 18:24:50 2026 Received: from mail-ot1-f54.google.com (mail-ot1-f54.google.com [209.85.210.54]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 222273AEB2C for ; Wed, 9 Sep 2026 15:07:01 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=pass smtp.client-ip=209.85.210.54 ARC-Seal: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788966426; cv=pass; b=WaKSEGP2eW1gj3VmN2ahBrwaKBxmcWQgYDNn9kBYLW5LeuFztQnhRElGBzRZG4KpWqxnYkirzS8cUJW8Kfijc3q2DklcEKKcHkYGIRjXuiTfmO3sjDWEbIhPzTOTONzWh1dkm4vHYjWblPpuSOIzLvrAFPcl+UMkJAYgzn2EYMM= ARC-Message-Signature: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788966426; c=relaxed/simple; bh=dc4HN6Jp8H2wsCYNj0MVVyevdvFTBFpnW8gJ3mIbmQs=; h=MIME-Version:From:In-Reply-To:References:Date:Message-ID:Subject: To:Cc:Content-Type; b=l5YkzEBN7Z0xk+znm7hoM+wBK0eiepr/9roapvCBa8S4dBG3Z7ki8OOn2RjIkTpoZVMwhvpDjw8/x5AVNABAYlr/JaW+SRIhTl7xnfy3w2eLuW2gNWWomUsOBfrCPxmTjg28Ons3fngiufyQo+P2SQupb6mxDSX1IKD9nLjVqsA= ARC-Authentication-Results: i=2; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=TJZd5wpv; arc=pass smtp.client-ip=209.85.210.54 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="TJZd5wpv" Received: by mail-ot1-f54.google.com with SMTP id 46e09a7af769-7f3ece23165so4824098a34.0 for ; Wed, 09 Sep 2026 08:07:01 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1788966421; cv=none; d=google.com; s=arc-20260327; b=RIKr3UUTvr3/cGIq3XYlNKdd2pdeNxQTteCA3ysXWbESlZDuT4QZfQAf+LH94I74Jw ZQUHsjaYysAaRMJ0VBR3IcPXttNv/xT53ujvmFdCQmC344fil1ouNvkLxGEs9u95OSd2 ii501KUscMETiuTzZojzVSsZ5MMLQ3Vk2Hewt8wV70MFwWokB0AUwdlukJJ3qI2AGd5I /G36zsGp4SIju7auHitRu4ANbbXWREQbG45N2Umj2tXoLj3+bjHTHua9unqBWibcIrxx 88CwAO7GfzZRF/tuT933PmHr68l/rvLm+BTFbaAT+P9VvNkxLYBrf4PZoTqjx4iFwAJH 6Sfw== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20260327; h=cc:to:subject:message-id:date:references:in-reply-to:from :mime-version:dkim-signature; bh=NeLf6D1Riu1bxe9num4y0vk2Apay5Ac8RiKpR8O8mCM=; fh=phANnxXhGM9oVArTlsVcoCcDW6eU5q4QUZVRMYtfcic=; b=ktUUyy9IHXq2hCsYV4DQbz4/HpcZY/zCHlhzMCYchV9D/YR8o11eqjoLuwmOJGm/Qc g7t/rVHsojcpgy3gut+fWV1x8SdWD2DZwAt3UBA79ewtIbWs9qAgsUkcQReaMVfGNPXW Q9MDtOGscEfOqIJp+CQFlSUd/EhmN1GbEpNbvYETNPZpbYhZSxRFTXYjtyRxUrRaeMh9 BpObC4y1KrNk6cYj20Vv0N+Mhj9m+prg2zWpYR5zYsxXrGvSHUI8pMyH+8pl4cES7KpC 6tmao+qfyy/YMRDRio/SCWnXPR41lwSbOjEKs0FVU1rh0PEzIrLVLs2Pmk+WLSlgYyyA qJsg==; darn=vger.kernel.org ARC-Authentication-Results: i=1; mx.google.com; arc=none DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1788966421; x=1789571221; darn=vger.kernel.org; h=content-type:cc:to:subject:message-id:date:references:in-reply-to :from:mime-version:from:to:cc:subject:date:message-id:reply-to :content-type; bh=NeLf6D1Riu1bxe9num4y0vk2Apay5Ac8RiKpR8O8mCM=; b=TJZd5wpv677pi/YXOBIyljONexJHTiUB2LC9nQgzlksb9+4jlm8z8tqZOt1oP53QcQ NYMOUXvcwfYG8WezNq30sDG3B6oaB+LZ8zlkdCxY5VdUP5+R9TX3zWUUHBoOexUB6c7M YpIAOwN6Xr6dJzj99ADOjsVz9qBNKl0C7ebZF5oQVTi768ObKVLFYkvmcVOBWy2H2zMi w508h0DRIdFDVLtVgvzkmO+s+aRPDv2lS6pgGpL0UGlFEG+FbuNYQUaJ/2ygU+3QFh5W Jya4dCWLMwr8mtuokMHnONehJpTqgPQgv/4NWdjE+BjlpSX015F91BMwrhRxBG7/FCYI Rchg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1788966421; x=1789571221; h=content-type:cc:to:subject:message-id:date:references:in-reply-to :from:mime-version:x-gm-gg:x-gm-message-state:from:to:cc:subject :date:message-id:reply-to:content-type; bh=NeLf6D1Riu1bxe9num4y0vk2Apay5Ac8RiKpR8O8mCM=; b=f5KpNT92DVqqmW2zdJ5KJzxKLbNW/ExPMiOR0Qac/xjsQKXqb60C9O8DNaIlwXGzyl 00JGJB0TGx8ETc19cEF87UHSyNU3gTy+tFjV96BvdPvv7NJNmnJldFuP5HDfBh5ID6HF JzNkypLUtJAbcoOmknnM5/lYdEN9o740YytNlKJjXn10KyDdyMyt+q4n0KjVGgezqkG7 c/PqdYgXbWms8TeC6CPJHtGQ94xGC06oIDxBX7kS4Pb0yBsuYJ+jxi1REVUojAVdugGQ xQQZfC988rFYb22AHrd7a7T1CyXRGu8T7mfRbpYkpo0jt2qQI1JcWpGhq5A+11i2y95R cv0A== X-Forwarded-Encrypted: i=1; AKwUvBxyfYuSQibbl6tYH4MlMH+5ITCWRim6Q762AtFGJ6cSGYMchhsTC9m7bWkzggWDEgM7NYOvG5u1vISv7bI=@vger.kernel.org X-Gm-Message-State: AFuF++mWlBoPt7KlDF+8QarCTau7DzEhDVBTKbFIn6qLQEEH9axk4QL/ CGsYDgwikQ5H+u8SPJqO+zzenj0Gzkw6yxDgtFM2NC21Mr6wA2FtG6dR0axRWJTB4MzweKUb4RE mdokGBWKYgutyEFo2TFdn/V5lWDBY/oU= X-Gm-Gg: AYBFou1D7612FdL4Oq2p361z70FLTVsgajh7ZCvwgh4+M3eklFlkYTW6T+vKpofXtm1 3ygvYRLxXLHLW0OLGK1XYKPW5ah2KfOcDSaQ5o5zCbBPHr0pLIej6rq/bRx75La7plDcKItj9U6 hxgsRreWYxUqt7gKHeq4F5wEaNCWorvc5S5CO+zgd4hV3T7j7ytCA0hsAeTbfPYTl7boe8atAQx klhZNlb84l1P/AIC0/kj4l8vpQGSK5PkObTxWZbNOtCzHEMtwBkkj1aNvsoniwKG7WcOe7WjL9f jyae5CCFicQLWHsH3q57wfkWxc74ikL1l7pemYYgZOQ0dp9mwg4GOuKdlqpO8Wsv1eqReU1uxSG MiArLQmUCxs7Zxu5Eg2+05xCrIoSVV6eX+SZ3/Q== X-Received: by 2002:a05:6820:c85:b0:6b3:619f:25ce with SMTP id 006d021491bc7-6b6fdebe0cfmr20708741eaf.30.1788966420330; Wed, 09 Sep 2026 08:07:00 -0700 (PDT) Received: from 77377267392 named unknown by gmailapi.google.com with HTTPREST; Wed, 9 Sep 2026 10:06:58 -0500 Received: from 77377267392 named unknown by gmailapi.google.com with HTTPREST; Wed, 9 Sep 2026 10:06:58 -0500 Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 From: Jinseob Kim In-Reply-To: References: Date: Wed, 9 Sep 2026 10:06:58 -0500 X-Gm-Features: AcwNN1V0BZwK1v2LCrfHkJMBobBPVuxVWfURBEpd-mGdoKHsszS2xpFmmEu-sWw Message-ID: Subject: [PATCH v9 5/5] iio: osf: add UART IIO driver To: jic23@kernel.org, linux-iio@vger.kernel.org Cc: dlechner@baylibre.com, nuno.sa@analog.com, andy@kernel.org, linux-kernel@vger.kernel.org, rdunlap@infradead.org, joshua.crofts1@gmail.com, u.kleine-koenig@baylibre.com, julianbraha@gmail.com Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Add the Open Sensor Fusion serdev transport and runtime IIO registration from capability reports. Configure UART before enabling vcc, and retain early capabilities until the remaining probe setup succeeds. Use initialized aligned scan storage, pack active channels, and quiesce pushes before buffer reconfiguration. Only accepted samples update the latest-sample cache used by direct reads. Include KUnit coverage for cache integrity, scan bytes and timestamps, buffer lifetime, and early capability ownership. Assisted-by: LLM Signed-off-by: Jinseob Kim --- MAINTAINERS | 3 +- drivers/iio/Kconfig | 1 + drivers/iio/Makefile | 1 + drivers/iio/opensensorfusion/Kconfig | 27 + drivers/iio/opensensorfusion/Makefile | 7 + drivers/iio/opensensorfusion/osf_core.c | 414 +++++++++++++ drivers/iio/opensensorfusion/osf_core.h | 73 +++ drivers/iio/opensensorfusion/osf_core_test.c | 577 +++++++++++++++++++ drivers/iio/opensensorfusion/osf_iio.c | 336 +++++++++++ drivers/iio/opensensorfusion/osf_iio.h | 22 + drivers/iio/opensensorfusion/osf_iio_test.c | 325 +++++++++++ drivers/iio/opensensorfusion/osf_serdev.c | 162 ++++++ 12 files changed, 1946 insertions(+), 2 deletions(-) create mode 100644 drivers/iio/opensensorfusion/Kconfig create mode 100644 drivers/iio/opensensorfusion/Makefile create mode 100644 drivers/iio/opensensorfusion/osf_core.c create mode 100644 drivers/iio/opensensorfusion/osf_core.h create mode 100644 drivers/iio/opensensorfusion/osf_core_test.c create mode 100644 drivers/iio/opensensorfusion/osf_iio.c create mode 100644 drivers/iio/opensensorfusion/osf_iio.h create mode 100644 drivers/iio/opensensorfusion/osf_iio_test.c create mode 100644 drivers/iio/opensensorfusion/osf_serdev.c diff --git a/MAINTAINERS b/MAINTAINERS index a1117459ebd9..41609e2f2929 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -20516,8 +20516,7 @@ M: Jinseob Kim S: Maintained F: Documentation/devicetree/bindings/iio/opensensorfusion,osf.yaml F: Documentation/iio/open-sensor-fusion.rst -F: drivers/iio/opensensorfusion/osf_protocol.* -F: drivers/iio/opensensorfusion/osf_stream.* +F: drivers/iio/opensensorfusion/ K: opensensorfusion OPENCOMPUTE PTP CLOCK DRIVER diff --git a/drivers/iio/Kconfig b/drivers/iio/Kconfig index 652557a5b851..89bb5b65c761 100644 --- a/drivers/iio/Kconfig +++ b/drivers/iio/Kconfig @@ -102,6 +102,7 @@ source "drivers/iio/light/Kconfig" source "drivers/iio/magnetometer/Kconfig" source "drivers/iio/multiplexer/Kconfig" source "drivers/iio/orientation/Kconfig" +source "drivers/iio/opensensorfusion/Kconfig" source "drivers/iio/test/Kconfig" if IIO_TRIGGER source "drivers/iio/trigger/Kconfig" diff --git a/drivers/iio/Makefile b/drivers/iio/Makefile index f03a4100c800..a51c4446ee09 100644 --- a/drivers/iio/Makefile +++ b/drivers/iio/Makefile @@ -38,6 +38,7 @@ obj-y +=3D light/ obj-y +=3D magnetometer/ obj-y +=3D multiplexer/ obj-y +=3D orientation/ +obj-y +=3D opensensorfusion/ obj-y +=3D position/ obj-y +=3D potentiometer/ obj-y +=3D potentiostat/ diff --git a/drivers/iio/opensensorfusion/Kconfig b/drivers/iio/opensensorfusion/Kconfig new file mode 100644 index 000000000000..b4a2cde9645e --- /dev/null +++ b/drivers/iio/opensensorfusion/Kconfig @@ -0,0 +1,27 @@ +# SPDX-License-Identifier: GPL-2.0-only + +config OPEN_SENSOR_FUSION + tristate "Open Sensor Fusion UART IIO driver" + depends on SERIAL_DEV_BUS + select CRC32 + select IIO_BUFFER + select IIO_KFIFO_BUF + help + Build the Open Sensor Fusion UART IIO driver. + + The driver receives OSF protocol frames over a serdev UART and + registers IIO devices for supported capability entries. It exposes + accelerometer, gyroscope, magnetometer, and temperature samples + through IIO direct reads and software buffers. + +config OPEN_SENSOR_FUSION_KUNIT_TEST + bool "KUnit tests for Open Sensor Fusion" if !KUNIT_ALL_TESTS + depends on OPEN_SENSOR_FUSION=3Dy && KUNIT=3Dy + default KUNIT_ALL_TESTS + help + Build focused unit tests for the Open Sensor Fusion core sample + acceptance, direct-read cache, and IIO buffer paths. The tests + exercise accepted, ignored, and rejected sample handling without + exposing additional production interfaces. + + If unsure, say N. diff --git a/drivers/iio/opensensorfusion/Makefile b/drivers/iio/opensensorfusion/Makefile new file mode 100644 index 000000000000..6a28b14cae1f --- /dev/null +++ b/drivers/iio/opensensorfusion/Makefile @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0-only + +obj-$(CONFIG_OPEN_SENSOR_FUSION) +=3D open-sensor-fusion.o + +open-sensor-fusion-y :=3D osf_core.o osf_iio.o osf_protocol.o osf_serdev.o= \ + osf_stream.o +open-sensor-fusion-$(CONFIG_OPEN_SENSOR_FUSION_KUNIT_TEST) +=3D osf_core_test.o osf_iio_test.o diff --git a/drivers/iio/opensensorfusion/osf_core.c b/drivers/iio/opensensorfusion/osf_core.c new file mode 100644 index 000000000000..b4d359706279 --- /dev/null +++ b/drivers/iio/opensensorfusion/osf_core.c @@ -0,0 +1,414 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include +#include +#include +#include +#include + +#include "osf_core.h" +#include "osf_iio.h" +#include "osf_stream.h" + +#define OSF_RESERVED_MSG_FIRST 0x7f00 +#define OSF_RESERVED_MSG_LAST 0x7fff +#define OSF_VENDOR_PRIVATE_FIRST 0x8000 + +void osf_core_init(struct osf_device *osf, struct device *dev) +{ + *osf =3D (struct osf_device) { + .dev =3D dev, + }; + mutex_init(&osf->latest_lock); +} + +void osf_core_unregister_iio(struct osf_device *osf) +{ + for (unsigned int i =3D 0; i < osf->iio_dev_count; i++) + osf_iio_unregister_sensor(osf->iio_devs[i].indio_dev); + + osf->iio_dev_count =3D 0; +} + +static struct iio_dev *osf_core_find_iio_dev(struct osf_device *osf, + u16 sensor_type, u16 sensor_index) +{ + const struct osf_iio_binding *binding; + + for (unsigned int i =3D 0; i < osf->iio_dev_count; i++) { + binding =3D &osf->iio_devs[i]; + if (binding->sensor_type =3D=3D sensor_type && + binding->sensor_index =3D=3D sensor_index) + return binding->indio_dev; + } + + return NULL; +} + +static struct osf_latest_sample * +osf_core_find_latest_sample(struct osf_device *osf, u16 sensor_type, + u16 sensor_index) +{ + struct osf_latest_sample *latest; + + for (unsigned int i =3D 0; i < osf->latest_sample_count; i++) { + latest =3D &osf->latest_samples[i]; + if (latest->sensor_type =3D=3D sensor_type && + latest->sensor_index =3D=3D sensor_index) + return latest; + } + + if (osf->latest_sample_count >=3D OSF_MAX_CAPABILITIES) + return NULL; + + return &osf->latest_samples[osf->latest_sample_count++]; +} + +static bool +osf_core_capability_supported(const struct osf_capability_entry *entry) +{ + return osf_iio_sensor_supported(entry->sensor_type, + entry->channel_count) && + entry->sample_format =3D=3D OSF_SAMPLE_FORMAT_S32 && + !(entry->flags & ~OSF_CAPABILITY_FLAGS_MASK) && + !entry->reserved; +} + +static bool osf_core_capability_is_duplicate(const struct osf_capability_cache *cache, + u16 index) +{ + const struct osf_capability_entry *entry =3D &cache->entries[index]; + + for (u16 i =3D 0; i < index; i++) { + if (cache->entries[i].sensor_type =3D=3D entry->sensor_type && + cache->entries[i].sensor_index =3D=3D entry->sensor_index) + return true; + } + + return false; +} + +static int osf_core_register_capabilities(struct osf_device *osf, + const struct osf_capability_cache *cache) +{ + struct iio_dev *indio_dev; + int ret; + + for (u16 i =3D 0; i < cache->capability_count; i++) { + ret =3D osf_iio_register_sensor(osf->dev, &cache->entries[i], + osf, &indio_dev); + if (ret) + goto err_unregister; + + osf->iio_devs[osf->iio_dev_count++] =3D (struct osf_iio_binding) { + .sensor_type =3D cache->entries[i].sensor_type, + .sensor_index =3D cache->entries[i].sensor_index, + .indio_dev =3D indio_dev, + }; + } + + return 0; + +err_unregister: + osf_core_unregister_iio(osf); + + return ret; +} + +void osf_core_start(struct osf_device *osf) +{ + int ret; + + if (osf->iio_ready) + return; + + osf->iio_ready =3D true; + if (!osf->capability_cache.valid || + !osf->capability_cache.capability_count) + return; + + ret =3D osf_core_register_capabilities(osf, &osf->capability_cache); + if (ret) { + /* As with RX discovery failure, a later report may retry. */ + osf->capability_cache.valid =3D false; + dev_err_ratelimited(osf->dev, + "failed to register pending capabilities: %d\n", + ret); + } +} + +static int osf_core_handle_sensor_sample(struct osf_device *osf, + const struct osf_frame *frame) +{ + struct osf_latest_sample *latest; + struct osf_sensor_sample sample; + struct iio_dev *indio_dev; + s32 values[OSF_MAX_SAMPLE_CHANNELS] =3D { }; + int ret; + + ret =3D osf_protocol_decode_sensor_sample(frame, &sample); + if (ret) { + dev_warn_ratelimited(osf->dev, + "rejecting malformed sensor sample: %d\n", + ret); + return ret; + } + + indio_dev =3D osf_core_find_iio_dev(osf, sample.sensor_type, + sample.sensor_index); + if (!indio_dev) { + dev_dbg_ratelimited(osf->dev, + "ignoring sample for unregistered sensor %#x:%u\n", + sample.sensor_type, sample.sensor_index); + return OSF_STREAM_FRAME_IGNORED; + } + + if (sample.channel_count > OSF_MAX_SAMPLE_CHANNELS) { + dev_warn_ratelimited(osf->dev, + "rejecting sensor sample with %u channels\n", + sample.channel_count); + return -E2BIG; + } + + for (u16 i =3D 0; i < sample.channel_count; i++) { + ret =3D osf_protocol_sensor_sample_value(&sample, i, &values[i]); + if (ret) { + dev_warn_ratelimited(osf->dev, + "rejecting malformed sample value: %d\n", + ret); + return ret; + } + } + + ret =3D osf_iio_push_sample(indio_dev, values, sample.channel_count); + if (ret) { + dev_err_ratelimited(osf->dev, + "failed to push sensor %#x:%u sample: %d\n", + sample.sensor_type, sample.sensor_index, ret); + return ret; + } + + scoped_guard(mutex, &osf->latest_lock) { + latest =3D osf_core_find_latest_sample(osf, sample.sensor_type, + sample.sensor_index); + if (!latest) { + dev_err_ratelimited(osf->dev, + "latest sample cache full for sensor %#x:%u\n", + sample.sensor_type, + sample.sensor_index); + return -ENOSPC; + } + + memcpy(latest->values, values, sizeof(values)); + latest->sensor_type =3D sample.sensor_type; + latest->sensor_index =3D sample.sensor_index; + latest->channel_count =3D sample.channel_count; + latest->sample_format =3D sample.sample_format; + latest->scale_nano =3D sample.scale_nano; + latest->sequence =3D frame->sequence; + latest->timestamp_us =3D frame->timestamp_us; + latest->valid =3D true; + osf->last_sequence =3D frame->sequence; + } + + return OSF_STREAM_FRAME_HANDLED; +} + +static int osf_core_handle_device_status(struct osf_device *osf, + const struct osf_frame *frame) +{ + struct osf_device_status status; + int ret; + + ret =3D osf_protocol_decode_device_status(frame, &status); + if (ret) { + dev_warn_ratelimited(osf->dev, + "rejecting malformed device status: %d\n", + ret); + return ret; + } + + osf->status_cache =3D (struct osf_status_cache) { + .uptime_s =3D status.uptime_s, + .status_flags =3D status.status_flags, + .error_flags =3D status.error_flags, + .dropped_frames =3D status.dropped_frames, + .sequence =3D frame->sequence, + .valid =3D true, + }; + osf->last_sequence =3D frame->sequence; + + return OSF_STREAM_FRAME_HANDLED; +} + +static int osf_core_handle_capability_report(struct osf_device *osf, + const struct osf_frame *frame) +{ + struct osf_capability_cache cache =3D { }; + struct osf_capability_report report; + int frame_result; + int ret; + + ret =3D osf_protocol_decode_capability_report(frame, &report); + if (ret) { + dev_warn_ratelimited(osf->dev, + "rejecting malformed capability report: %d\n", + ret); + return ret; + } + + if (osf->capability_cache.valid) { + dev_dbg_ratelimited(osf->dev, + "ignoring repeated capability report\n"); + osf->last_sequence =3D frame->sequence; + return OSF_STREAM_FRAME_IGNORED; + } + + for (u16 i =3D 0; i < report.capability_count; i++) { + struct osf_capability_entry entry; + + ret =3D osf_protocol_decode_capability_entry(&report, i, &entry); + if (ret) { + dev_warn_ratelimited(osf->dev, + "rejecting malformed capability entry: %d\n", + ret); + return ret; + } + + if (!osf_core_capability_supported(&entry)) + continue; + + if (cache.capability_count >=3D OSF_MAX_CAPABILITIES) { + dev_warn_ratelimited(osf->dev, + "too many supported capabilities\n"); + return -E2BIG; + } + + cache.entries[cache.capability_count] =3D entry; + if (osf_core_capability_is_duplicate(&cache, cache.capability_count)) { + dev_warn_ratelimited(osf->dev, + "rejecting duplicate capability\n"); + return -EEXIST; + } + cache.capability_count++; + } + + cache.sequence =3D frame->sequence; + cache.valid =3D true; + + frame_result =3D OSF_STREAM_FRAME_IGNORED; + if (cache.capability_count) { + if (osf->iio_ready) { + ret =3D osf_core_register_capabilities(osf, &cache); + if (ret) { + dev_err_ratelimited(osf->dev, + "failed to register capabilities: %d\n", + ret); + return ret; + } + } + frame_result =3D OSF_STREAM_FRAME_HANDLED; + } else { + dev_dbg_ratelimited(osf->dev, + "ignoring report without supported capabilities\n"); + } + + osf->capability_cache =3D cache; + osf->last_sequence =3D frame->sequence; + + return frame_result; +} + +int osf_core_receive_frame(struct osf_device *osf, const u8 *buf, size_t l= en) +{ + struct osf_frame frame; + size_t frame_len; + int ret; + + ret =3D osf_protocol_decode_frame(buf, len, &frame, &frame_len); + if (ret) + return ret; + + if (frame_len !=3D len) + return -EMSGSIZE; + + if (frame.protocol_major !=3D OSF_PROTOCOL_MAJOR) { + dev_dbg_ratelimited(osf->dev, + "ignoring unsupported protocol major %u\n", + frame.protocol_major); + return OSF_STREAM_FRAME_IGNORED; + } + + if (frame.reserved) { + dev_dbg_ratelimited(osf->dev, + "ignoring frame with reserved field %#x\n", + frame.reserved); + return OSF_STREAM_FRAME_IGNORED; + } + + switch (frame.message_type) { + case OSF_MSG_SENSOR_SAMPLE: + ret =3D osf_core_handle_sensor_sample(osf, &frame); + break; + case OSF_MSG_DEVICE_STATUS: + ret =3D osf_core_handle_device_status(osf, &frame); + break; + case OSF_MSG_CAPABILITY_REPORT: + ret =3D osf_core_handle_capability_report(osf, &frame); + break; + default: + if (frame.message_type >=3D OSF_RESERVED_MSG_FIRST && + frame.message_type <=3D OSF_RESERVED_MSG_LAST) { + dev_dbg_ratelimited(osf->dev, + "ignoring reserved message type %#x\n", + frame.message_type); + return OSF_STREAM_FRAME_IGNORED; + } + if (frame.message_type >=3D OSF_VENDOR_PRIVATE_FIRST) { + dev_dbg_ratelimited(osf->dev, + "ignoring vendor message type %#x\n", + frame.message_type); + return OSF_STREAM_FRAME_IGNORED; + } + + dev_dbg_ratelimited(osf->dev, + "ignoring unsupported message type %#x\n", + frame.message_type); + return OSF_STREAM_FRAME_IGNORED; + } + + /* + * Handler failures are authenticated application rejections. Keep the + * trusted frame boundary and let the stream consume the complete frame. + */ + if (ret < 0) + return OSF_STREAM_FRAME_REJECTED; + + return ret; +} + +int osf_core_read_latest_sample(struct osf_device *osf, u16 sensor_type, + u16 sensor_index, u16 channel, + s32 *value) +{ + const struct osf_latest_sample *latest; + + if (!osf || !value) + return -EINVAL; + + guard(mutex)(&osf->latest_lock); + for (unsigned int i =3D 0; i < osf->latest_sample_count; i++) { + latest =3D &osf->latest_samples[i]; + if (latest->sensor_type !=3D sensor_type || + latest->sensor_index !=3D sensor_index) + continue; + + if (!latest->valid || channel >=3D latest->channel_count) + break; + + *value =3D latest->values[channel]; + return 0; + } + + return -ENODATA; +} diff --git a/drivers/iio/opensensorfusion/osf_core.h b/drivers/iio/opensensorfusion/osf_core.h new file mode 100644 index 000000000000..21f37dcd0781 --- /dev/null +++ b/drivers/iio/opensensorfusion/osf_core.h @@ -0,0 +1,73 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +#ifndef _OSF_CORE_H +#define _OSF_CORE_H + +#include +#include + +#include "osf_protocol.h" + +#define OSF_MAX_SAMPLE_CHANNELS 3 +#define OSF_MAX_CAPABILITIES 16 + +struct device; +struct iio_dev; + +struct osf_latest_sample { + u16 sensor_type; + u16 sensor_index; + u16 channel_count; + u16 sample_format; + u32 scale_nano; + s32 values[OSF_MAX_SAMPLE_CHANNELS]; + u64 sequence; + u64 timestamp_us; + bool valid; +}; + +struct osf_capability_cache { + u16 capability_count; + struct osf_capability_entry entries[OSF_MAX_CAPABILITIES]; + u64 sequence; + bool valid; +}; + +struct osf_status_cache { + u32 uptime_s; + u32 status_flags; + u32 error_flags; + u32 dropped_frames; + u64 sequence; + bool valid; +}; + +struct osf_iio_binding { + u16 sensor_type; + u16 sensor_index; + struct iio_dev *indio_dev; +}; + +struct osf_device { + struct device *dev; + bool iio_ready; + /* Protects latest_samples and latest_sample_count. */ + struct mutex latest_lock; + struct osf_latest_sample latest_samples[OSF_MAX_CAPABILITIES]; + unsigned int latest_sample_count; + struct osf_capability_cache capability_cache; + struct osf_status_cache status_cache; + struct osf_iio_binding iio_devs[OSF_MAX_CAPABILITIES]; + unsigned int iio_dev_count; + u64 last_sequence; +}; + +void osf_core_init(struct osf_device *osf, struct device *dev); +/* Serialize start with receive_frame; pending entries own their data. */ +void osf_core_start(struct osf_device *osf); +void osf_core_unregister_iio(struct osf_device *osf); +int osf_core_receive_frame(struct osf_device *osf, const u8 *buf, size_t l= en); +int osf_core_read_latest_sample(struct osf_device *osf, u16 sensor_type, + u16 sensor_index, u16 channel, + s32 *value); + +#endif diff --git a/drivers/iio/opensensorfusion/osf_core_test.c b/drivers/iio/opensensorfusion/osf_core_test.c new file mode 100644 index 000000000000..16121d512cb1 --- /dev/null +++ b/drivers/iio/opensensorfusion/osf_core_test.c @@ -0,0 +1,577 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "osf_core.h" +#include "osf_protocol.h" +#include "osf_stream.h" + +#define OSF_TEST_SENSOR_INDEX 0 +#define OSF_TEST_UNSUPPORTED_INDEX 7 +#define OSF_TEST_SCALE_NANO 1000000 +#define OSF_TEST_FLOOD_COUNT (OSF_MAX_CAPABILITIES + 4) +#define OSF_TEST_MAX_PAYLOAD_LEN \ + (OSF_SENSOR_SAMPLE_BASE_LEN + OSF_MAX_SAMPLE_CHANNELS * sizeof(__le32)) +#define OSF_TEST_MAX_FRAME_LEN \ + (OSF_FRAME_HEADER_LEN + OSF_CAP_REPORT_BASE_LEN + \ + 2 * OSF_CAP_SENSOR_ENTRY_LEN + OSF_FRAME_CRC_LEN) + +struct osf_test_context { + struct osf_device osf; + struct device *dev; + struct iio_dev *indio_dev; + struct iio_buffer *buffer; + bool buffer_enabled; +}; + +struct osf_test_scan_3axis { + s32 values[3]; +}; + +static size_t osf_test_build_frame(u8 *buf, size_t buf_size, + u16 message_type, const u8 *payload, + u32 payload_len, u64 sequence) +{ + size_t frame_len =3D OSF_FRAME_HEADER_LEN + payload_len + + OSF_FRAME_CRC_LEN; + u32 crc; + + if (frame_len > buf_size) + return 0; + + memset(buf, 0, frame_len); + put_unaligned_le32(OSF_FRAME_MAGIC, buf); + buf[4] =3D OSF_PROTOCOL_MAJOR; + buf[5] =3D OSF_PROTOCOL_MINOR; + put_unaligned_le16(OSF_FRAME_HEADER_LEN, buf + 6); + put_unaligned_le16(message_type, buf + 8); + put_unaligned_le32(payload_len, buf + 10); + put_unaligned_le64(sequence, buf + 14); + put_unaligned_le64(sequence * 1000, buf + 22); + memcpy(buf + OSF_FRAME_HEADER_LEN, payload, payload_len); + + crc =3D crc32_le(~0U, buf, OSF_FRAME_HEADER_LEN + payload_len) ^ ~0U; + put_unaligned_le32(crc, buf + OSF_FRAME_HEADER_LEN + payload_len); + + return frame_len; +} + +static size_t osf_test_build_capability_frame(u8 *buf, size_t buf_size, + u64 sequence) +{ + u8 payload[OSF_CAP_REPORT_BASE_LEN + + 2 * OSF_CAP_SENSOR_ENTRY_LEN] =3D { }; + u8 *supported =3D payload + OSF_CAP_REPORT_BASE_LEN; + u8 *unsupported =3D supported + OSF_CAP_SENSOR_ENTRY_LEN; + + put_unaligned_le16(2, payload); + put_unaligned_le16(OSF_SENSOR_ACCELEROMETER, supported); + put_unaligned_le16(OSF_TEST_SENSOR_INDEX, supported + 2); + put_unaligned_le16(3, supported + 4); + put_unaligned_le16(OSF_SAMPLE_FORMAT_S32, supported + 6); + put_unaligned_le32(OSF_TEST_SCALE_NANO, supported + 8); + + put_unaligned_le16(OSF_SENSOR_BAROMETER, unsupported); + put_unaligned_le16(OSF_TEST_UNSUPPORTED_INDEX, unsupported + 2); + put_unaligned_le16(1, unsupported + 4); + put_unaligned_le16(OSF_SAMPLE_FORMAT_S32, unsupported + 6); + put_unaligned_le32(OSF_TEST_SCALE_NANO, unsupported + 8); + + return osf_test_build_frame(buf, buf_size, OSF_MSG_CAPABILITY_REPORT, + payload, sizeof(payload), sequence); +} + +static size_t osf_test_build_sample_frame(u8 *buf, size_t buf_size, + u16 sensor_type, u16 sensor_index, + u16 channel_count, u16 sample_format, + const s32 *values, u64 sequence) +{ + u8 payload[OSF_TEST_MAX_PAYLOAD_LEN] =3D { }; + u32 payload_len; + + if (!channel_count || channel_count > OSF_MAX_SAMPLE_CHANNELS) + return 0; + + put_unaligned_le16(sensor_type, payload); + put_unaligned_le16(sensor_index, payload + 2); + put_unaligned_le16(channel_count, payload + 4); + put_unaligned_le16(sample_format, payload + 6); + put_unaligned_le32(OSF_TEST_SCALE_NANO, payload + 8); + + for (u16 i =3D 0; i < channel_count; i++) + put_unaligned_le32(values[i], + payload + OSF_SENSOR_SAMPLE_BASE_LEN + + i * sizeof(__le32)); + + payload_len =3D OSF_SENSOR_SAMPLE_BASE_LEN + + channel_count * sizeof(__le32); + + return osf_test_build_frame(buf, buf_size, OSF_MSG_SENSOR_SAMPLE, + payload, payload_len, sequence); +} + +static size_t osf_test_build_truncated_sample_frame(u8 *buf, size_t buf_si= ze, + u64 sequence) +{ + u8 payload[OSF_SENSOR_SAMPLE_BASE_LEN + sizeof(__le32)] =3D { }; + + put_unaligned_le16(OSF_SENSOR_ACCELEROMETER, payload); + put_unaligned_le16(OSF_TEST_SENSOR_INDEX, payload + 2); + put_unaligned_le16(3, payload + 4); + put_unaligned_le16(OSF_SAMPLE_FORMAT_S32, payload + 6); + put_unaligned_le32(OSF_TEST_SCALE_NANO, payload + 8); + put_unaligned_le32(42, payload + OSF_SENSOR_SAMPLE_BASE_LEN); + + return osf_test_build_frame(buf, buf_size, OSF_MSG_SENSOR_SAMPLE, + payload, sizeof(payload), sequence); +} + +static int osf_test_submit_sample(struct osf_test_context *ctx, + u16 sensor_type, u16 sensor_index, + u16 channel_count, u16 sample_format, + const s32 *values, u64 sequence) +{ + u8 frame[OSF_TEST_MAX_FRAME_LEN]; + size_t frame_len; + + frame_len =3D osf_test_build_sample_frame(frame, sizeof(frame), + sensor_type, sensor_index, + channel_count, sample_format, + values, sequence); + if (!frame_len) + return -EINVAL; + + return osf_core_receive_frame(&ctx->osf, frame, frame_len); +} + +static int osf_test_read_raw(struct osf_test_context *ctx, + unsigned int channel, int *value) +{ + int value2 =3D 0; + + return ctx->indio_dev->info->read_raw(ctx->indio_dev, + &ctx->indio_dev->channels[channel], + value, &value2, + IIO_CHAN_INFO_RAW); +} + +static int osf_test_remove_scan(struct osf_test_context *ctx, + struct osf_test_scan_3axis *scan) +{ + return iio_pop_from_buffer(ctx->buffer, scan); +} + +static int osf_test_enable_buffer(struct osf_test_context *ctx) +{ + unsigned long *scan_mask; + int ret; + + scan_mask =3D (unsigned long *)ctx->buffer->scan_mask; + for (unsigned int i =3D 0; i < 3; i++) + bitmap_set(scan_mask, ctx->indio_dev->channels[i].scan_index, 1); + ret =3D iio_update_buffers(ctx->indio_dev, ctx->buffer, NULL); + if (!ret) + ctx->buffer_enabled =3D true; + + return ret; +} + +static void osf_test_cleanup(void *data) +{ + struct osf_test_context *ctx =3D data; + + if (ctx->buffer_enabled) { + iio_update_buffers(ctx->indio_dev, NULL, ctx->buffer); + ctx->buffer_enabled =3D false; + } + + osf_core_unregister_iio(&ctx->osf); +} + +static int osf_test_init(struct kunit *test) +{ + struct osf_test_context *ctx; + u8 frame[OSF_TEST_MAX_FRAME_LEN]; + size_t frame_len; + int ret; + + ctx =3D kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + test->priv =3D ctx; + ctx->dev =3D kunit_device_register(test, "osf-core"); + if (IS_ERR(ctx->dev)) + return PTR_ERR(ctx->dev); + if (!ctx->dev) + return -ENOMEM; + + osf_core_init(&ctx->osf, ctx->dev); + osf_core_start(&ctx->osf); + ret =3D kunit_add_action_or_reset(test, osf_test_cleanup, ctx); + if (ret) + return ret; + + frame_len =3D osf_test_build_capability_frame(frame, sizeof(frame), 1); + if (!frame_len) + return -EINVAL; + + ret =3D osf_core_receive_frame(&ctx->osf, frame, frame_len); + if (ret !=3D OSF_STREAM_FRAME_HANDLED) + return ret < 0 ? ret : -EINVAL; + if (ctx->osf.iio_dev_count !=3D 1 || + ctx->osf.capability_cache.capability_count !=3D 1) + return -EINVAL; + + ctx->indio_dev =3D ctx->osf.iio_devs[0].indio_dev; + ctx->buffer =3D ctx->indio_dev->buffer; + if (!ctx->buffer) + return -EINVAL; + + return 0; +} + +static void osf_test_expect_direct_sample(struct kunit *test, + struct osf_test_context *ctx, + const s32 *expected) +{ + int value; + int ret; + + for (unsigned int i =3D 0; i < 3; i++) { + value =3D 0; + ret =3D osf_test_read_raw(ctx, i, &value); + KUNIT_EXPECT_EQ(test, ret, IIO_VAL_INT); + KUNIT_EXPECT_EQ(test, value, expected[i]); + } +} + +static void +osf_rejected_channel_count_preserves_latest_test(struct kunit *test) +{ + struct osf_test_context *ctx =3D test->priv; + const s32 baseline_values[] =3D { 11, 22, 33 }; + const s32 rejected_values[] =3D { -999 }; + struct osf_sensor_sample decoded_sample; + struct osf_latest_sample baseline; + struct osf_test_scan_3axis scan; + struct osf_frame decoded_frame; + u8 rejected_frame[OSF_TEST_MAX_FRAME_LEN]; + size_t decoded_len; + size_t frame_len; + u64 baseline_sequence; + int ret; + + KUNIT_ASSERT_EQ(test, osf_test_enable_buffer(ctx), 0); + KUNIT_ASSERT_TRUE(test, iio_buffer_enabled(ctx->indio_dev)); + + ret =3D osf_test_submit_sample(ctx, OSF_SENSOR_ACCELEROMETER, + OSF_TEST_SENSOR_INDEX, 3, + OSF_SAMPLE_FORMAT_S32, + baseline_values, 2); + KUNIT_ASSERT_EQ(test, ret, OSF_STREAM_FRAME_HANDLED); + KUNIT_ASSERT_EQ(test, ctx->osf.latest_sample_count, 1U); + osf_test_expect_direct_sample(test, ctx, baseline_values); + + KUNIT_ASSERT_EQ(test, osf_test_remove_scan(ctx, &scan), 0); + + baseline =3D ctx->osf.latest_samples[0]; + baseline_sequence =3D ctx->osf.last_sequence; + frame_len =3D osf_test_build_sample_frame(rejected_frame, + sizeof(rejected_frame), + OSF_SENSOR_ACCELEROMETER, + OSF_TEST_SENSOR_INDEX, 1, + OSF_SAMPLE_FORMAT_S32, + rejected_values, 3); + KUNIT_ASSERT_NE(test, frame_len, (size_t)0); + KUNIT_ASSERT_EQ(test, + osf_protocol_decode_frame(rejected_frame, frame_len, + &decoded_frame, &decoded_len), 0); + KUNIT_ASSERT_EQ(test, decoded_len, frame_len); + KUNIT_ASSERT_EQ(test, + osf_protocol_decode_sensor_sample(&decoded_frame, + &decoded_sample), 0); + KUNIT_ASSERT_EQ(test, decoded_sample.channel_count, (u16)1); + ret =3D osf_core_receive_frame(&ctx->osf, rejected_frame, frame_len); + KUNIT_ASSERT_EQ(test, ret, OSF_STREAM_FRAME_REJECTED); + KUNIT_EXPECT_EQ(test, ctx->osf.latest_sample_count, 1U); + KUNIT_EXPECT_MEMEQ(test, &ctx->osf.latest_samples[0], &baseline, + sizeof(baseline)); + KUNIT_EXPECT_EQ(test, ctx->osf.last_sequence, baseline_sequence); + osf_test_expect_direct_sample(test, ctx, baseline_values); + KUNIT_EXPECT_EQ(test, osf_test_remove_scan(ctx, &scan), -EBUSY); +} + +static void +osf_valid_sample_updates_direct_and_buffer_test(struct kunit *test) +{ + struct osf_test_context *ctx =3D test->priv; + const s32 baseline_values[] =3D { 1, 2, 3 }; + const s32 rejected_values[] =3D { -1 }; + const s32 valid_values[] =3D { 101, -202, 303 }; + struct osf_test_scan_3axis scan =3D { }; + int ret; + + KUNIT_ASSERT_EQ(test, osf_test_enable_buffer(ctx), 0); + + ret =3D osf_test_submit_sample(ctx, OSF_SENSOR_ACCELEROMETER, + OSF_TEST_SENSOR_INDEX, 3, + OSF_SAMPLE_FORMAT_S32, + baseline_values, 2); + KUNIT_ASSERT_EQ(test, ret, OSF_STREAM_FRAME_HANDLED); + KUNIT_ASSERT_EQ(test, osf_test_remove_scan(ctx, &scan), 0); + + ret =3D osf_test_submit_sample(ctx, OSF_SENSOR_ACCELEROMETER, + OSF_TEST_SENSOR_INDEX, 1, + OSF_SAMPLE_FORMAT_S32, + rejected_values, 3); + KUNIT_ASSERT_EQ(test, ret, OSF_STREAM_FRAME_REJECTED); + KUNIT_ASSERT_EQ(test, osf_test_remove_scan(ctx, &scan), -EBUSY); + + ret =3D osf_test_submit_sample(ctx, OSF_SENSOR_ACCELEROMETER, + OSF_TEST_SENSOR_INDEX, 3, + OSF_SAMPLE_FORMAT_S32, + valid_values, 4); + KUNIT_ASSERT_EQ(test, ret, OSF_STREAM_FRAME_HANDLED); + osf_test_expect_direct_sample(test, ctx, valid_values); + KUNIT_ASSERT_EQ(test, ctx->indio_dev->scan_bytes, (int)sizeof(scan)); + KUNIT_ASSERT_EQ(test, osf_test_remove_scan(ctx, &scan), 0); + KUNIT_EXPECT_EQ(test, scan.values[0], valid_values[0]); + KUNIT_EXPECT_EQ(test, scan.values[1], valid_values[1]); + KUNIT_EXPECT_EQ(test, scan.values[2], valid_values[2]); + KUNIT_EXPECT_EQ(test, osf_test_remove_scan(ctx, &scan), -EBUSY); +} + +static void osf_unregistered_sample_is_ignored_test(struct kunit *test) +{ + struct osf_test_context *ctx =3D test->priv; + const s32 baseline_values[] =3D { 10, 20, 30 }; + const s32 ignored_values[] =3D { -10, -20, -30 }; + struct osf_latest_sample baseline; + struct osf_test_scan_3axis scan; + s32 value; + int ret; + + KUNIT_ASSERT_EQ(test, osf_test_enable_buffer(ctx), 0); + ret =3D osf_test_submit_sample(ctx, OSF_SENSOR_ACCELEROMETER, + OSF_TEST_SENSOR_INDEX, 3, + OSF_SAMPLE_FORMAT_S32, + baseline_values, 2); + KUNIT_ASSERT_EQ(test, ret, OSF_STREAM_FRAME_HANDLED); + KUNIT_ASSERT_EQ(test, osf_test_remove_scan(ctx, &scan), 0); + baseline =3D ctx->osf.latest_samples[0]; + + ret =3D osf_test_submit_sample(ctx, OSF_SENSOR_ACCELEROMETER, + OSF_TEST_SENSOR_INDEX + 1, 3, + OSF_SAMPLE_FORMAT_S32, + ignored_values, 3); + KUNIT_ASSERT_EQ(test, ret, OSF_STREAM_FRAME_IGNORED); + KUNIT_EXPECT_EQ(test, ctx->osf.iio_dev_count, 1U); + KUNIT_EXPECT_EQ(test, ctx->osf.latest_sample_count, 1U); + KUNIT_EXPECT_MEMEQ(test, &ctx->osf.latest_samples[0], &baseline, + sizeof(baseline)); + osf_test_expect_direct_sample(test, ctx, baseline_values); + ret =3D osf_core_read_latest_sample(&ctx->osf, + OSF_SENSOR_ACCELEROMETER, + OSF_TEST_SENSOR_INDEX + 1, + 0, &value); + KUNIT_EXPECT_EQ(test, ret, -ENODATA); + KUNIT_EXPECT_EQ(test, osf_test_remove_scan(ctx, &scan), -EBUSY); +} + +static void +osf_unaccepted_samples_do_not_exhaust_cache_test(struct kunit *test) +{ + struct osf_test_context *ctx =3D test->priv; + const s32 three_values[] =3D { 7, 8, 9 }; + const s32 one_value[] =3D { 42 }; + struct osf_latest_sample empty_cache[OSF_MAX_CAPABILITIES]; + struct osf_sensor_sample decoded_sample; + struct osf_frame decoded_frame; + u8 malformed_frame[OSF_TEST_MAX_FRAME_LEN]; + size_t decoded_len; + size_t frame_len; + int value; + int ret; + + KUNIT_ASSERT_EQ(test, ctx->osf.latest_sample_count, 0U); + memcpy(empty_cache, ctx->osf.latest_samples, sizeof(empty_cache)); + + for (unsigned int i =3D 0; i < OSF_TEST_FLOOD_COUNT; i++) { + ret =3D osf_test_submit_sample(ctx, OSF_SENSOR_ACCELEROMETER, + OSF_TEST_SENSOR_INDEX + i + 1, + 3, OSF_SAMPLE_FORMAT_S32, + three_values, 100 + i); + KUNIT_ASSERT_EQ(test, ret, OSF_STREAM_FRAME_IGNORED); + } + KUNIT_ASSERT_EQ(test, ctx->osf.latest_sample_count, 0U); + + for (unsigned int i =3D 0; i < OSF_TEST_FLOOD_COUNT; i++) { + ret =3D osf_test_submit_sample(ctx, OSF_SENSOR_BAROMETER, + OSF_TEST_UNSUPPORTED_INDEX, 1, + OSF_SAMPLE_FORMAT_S32, + one_value, 200 + i); + KUNIT_ASSERT_EQ(test, ret, OSF_STREAM_FRAME_IGNORED); + } + KUNIT_ASSERT_EQ(test, ctx->osf.latest_sample_count, 0U); + + for (unsigned int i =3D 0; i < OSF_TEST_FLOOD_COUNT; i++) { + ret =3D osf_test_submit_sample(ctx, OSF_SENSOR_ACCELEROMETER, + OSF_TEST_SENSOR_INDEX, 1, + OSF_SAMPLE_FORMAT_S32, + one_value, 300 + i); + KUNIT_ASSERT_EQ(test, ret, OSF_STREAM_FRAME_REJECTED); + } + KUNIT_ASSERT_EQ(test, ctx->osf.latest_sample_count, 0U); + + frame_len =3D osf_test_build_truncated_sample_frame(malformed_frame, + sizeof(malformed_frame), 400); + KUNIT_ASSERT_NE(test, frame_len, (size_t)0); + KUNIT_ASSERT_EQ(test, + osf_protocol_decode_frame(malformed_frame, frame_len, + &decoded_frame, &decoded_len), 0); + KUNIT_ASSERT_EQ(test, decoded_len, frame_len); + KUNIT_ASSERT_EQ(test, + osf_protocol_decode_sensor_sample(&decoded_frame, + &decoded_sample), + -EMSGSIZE); + + for (unsigned int i =3D 0; i < OSF_TEST_FLOOD_COUNT; i++) { + ret =3D osf_core_receive_frame(&ctx->osf, malformed_frame, + frame_len); + KUNIT_ASSERT_EQ(test, ret, OSF_STREAM_FRAME_REJECTED); + } + KUNIT_ASSERT_EQ(test, ctx->osf.latest_sample_count, 0U); + + KUNIT_EXPECT_MEMEQ(test, ctx->osf.latest_samples, empty_cache, + sizeof(empty_cache)); + KUNIT_EXPECT_EQ(test, ctx->osf.last_sequence, (u64)1); + + ret =3D osf_test_submit_sample(ctx, OSF_SENSOR_ACCELEROMETER, + OSF_TEST_SENSOR_INDEX, 3, + OSF_SAMPLE_FORMAT_S32, + three_values, 500); + KUNIT_ASSERT_EQ(test, ret, OSF_STREAM_FRAME_HANDLED); + KUNIT_ASSERT_EQ(test, ctx->osf.latest_sample_count, 1U); + + for (unsigned int i =3D 0; i < 3; i++) { + value =3D 0; + ret =3D osf_test_read_raw(ctx, i, &value); + KUNIT_ASSERT_EQ(test, ret, IIO_VAL_INT); + KUNIT_EXPECT_EQ(test, value, three_values[i]); + } +} + +static void osf_test_unregister_pending(void *data) +{ + osf_core_unregister_iio(data); +} + +static void osf_early_capability_is_owned_test(struct kunit *test) +{ + struct osf_test_context *ctx =3D test->priv; + struct osf_device *pending; + struct iio_dev *indio_dev; + u8 frame[OSF_TEST_MAX_FRAME_LEN]; + size_t len; + int scale, nano, ret; + + pending =3D kunit_kzalloc(test, sizeof(*pending), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, pending); + osf_core_init(pending, ctx->dev); + ret =3D kunit_add_action_or_reset(test, osf_test_unregister_pending, pend= ing); + KUNIT_ASSERT_EQ(test, ret, 0); + len =3D osf_test_build_capability_frame(frame, sizeof(frame), 10); + KUNIT_ASSERT_NE(test, len, (size_t)0); + KUNIT_EXPECT_EQ(test, osf_core_receive_frame(pending, frame, len), + OSF_STREAM_FRAME_HANDLED); + KUNIT_EXPECT_EQ(test, pending->iio_dev_count, 0U); + KUNIT_ASSERT_TRUE(test, pending->capability_cache.valid); + KUNIT_EXPECT_EQ(test, pending->capability_cache.capability_count, (u16)1); + + /* The stream may reuse its storage before probe opens the registration gate. */ + memset(frame, 0xa5, sizeof(frame)); + osf_core_start(pending); + KUNIT_ASSERT_EQ(test, pending->iio_dev_count, 1U); + indio_dev =3D pending->iio_devs[0].indio_dev; + ret =3D indio_dev->info->read_raw(indio_dev, &indio_dev->channels[0], + &scale, &nano, IIO_CHAN_INFO_SCALE); + KUNIT_EXPECT_EQ(test, ret, IIO_VAL_INT_PLUS_NANO); + KUNIT_EXPECT_EQ(test, scale, 0); + KUNIT_EXPECT_EQ(test, nano, OSF_TEST_SCALE_NANO); + osf_core_start(pending); + KUNIT_EXPECT_EQ(test, pending->iio_dev_count, 1U); + len =3D osf_test_build_capability_frame(frame, sizeof(frame), 11); + KUNIT_EXPECT_EQ(test, osf_core_receive_frame(pending, frame, len), + OSF_STREAM_FRAME_IGNORED); + KUNIT_EXPECT_EQ(test, pending->iio_dev_count, 1U); + KUNIT_EXPECT_EQ(test, pending->latest_sample_count, 0U); +} + +static void osf_early_duplicate_capability_is_rejected_test(struct kunit *= test) +{ + struct osf_test_context *ctx =3D test->priv; + struct osf_device *pending; + u8 payload[OSF_CAP_REPORT_BASE_LEN + 2 * OSF_CAP_SENSOR_ENTRY_LEN] =3D { = }; + u8 frame[OSF_TEST_MAX_FRAME_LEN]; + u8 *entry =3D payload + OSF_CAP_REPORT_BASE_LEN; + size_t len; + int ret; + + pending =3D kunit_kzalloc(test, sizeof(*pending), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, pending); + osf_core_init(pending, ctx->dev); + ret =3D kunit_add_action_or_reset(test, osf_test_unregister_pending, pend= ing); + KUNIT_ASSERT_EQ(test, ret, 0); + put_unaligned_le16(2, payload); + put_unaligned_le16(OSF_SENSOR_ACCELEROMETER, entry); + put_unaligned_le16(3, entry + 4); + put_unaligned_le16(OSF_SAMPLE_FORMAT_S32, entry + 6); + memcpy(entry + OSF_CAP_SENSOR_ENTRY_LEN, entry, OSF_CAP_SENSOR_ENTRY_LEN); + len =3D osf_test_build_frame(frame, sizeof(frame), OSF_MSG_CAPABILITY_REP= ORT, + payload, sizeof(payload), 20); + KUNIT_EXPECT_EQ(test, osf_core_receive_frame(pending, frame, len), + OSF_STREAM_FRAME_REJECTED); + KUNIT_EXPECT_FALSE(test, pending->capability_cache.valid); + KUNIT_EXPECT_EQ(test, pending->iio_dev_count, 0U); + + /* A failed early report must not prevent a subsequent valid report. */ + len =3D osf_test_build_capability_frame(frame, sizeof(frame), 21); + KUNIT_EXPECT_EQ(test, osf_core_receive_frame(pending, frame, len), + OSF_STREAM_FRAME_HANDLED); + osf_core_start(pending); + KUNIT_EXPECT_EQ(test, pending->iio_dev_count, 1U); +} + +static struct kunit_case osf_core_test_cases[] =3D { + KUNIT_CASE(osf_early_capability_is_owned_test), + KUNIT_CASE(osf_early_duplicate_capability_is_rejected_test), + KUNIT_CASE(osf_rejected_channel_count_preserves_latest_test), + KUNIT_CASE(osf_valid_sample_updates_direct_and_buffer_test), + KUNIT_CASE(osf_unregistered_sample_is_ignored_test), + KUNIT_CASE(osf_unaccepted_samples_do_not_exhaust_cache_test), + { } +}; + +static struct kunit_suite osf_core_test_suite =3D { + .name =3D "osf-core", + .init =3D osf_test_init, + .test_cases =3D osf_core_test_cases, +}; + +kunit_test_suite(osf_core_test_suite); + +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/opensensorfusion/osf_iio.c b/drivers/iio/opensensorfusion/osf_iio.c new file mode 100644 index 000000000000..936d7ed7843f --- /dev/null +++ b/drivers/iio/opensensorfusion/osf_iio.c @@ -0,0 +1,336 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "osf_core.h" +#include "osf_iio.h" + +struct osf_iio_sensor_spec { + u16 sensor_type; + u16 channel_count; + const char *name; + const struct iio_chan_spec *channels; + unsigned int num_channels; +}; + +struct osf_iio_state { + const struct osf_iio_sensor_spec *spec; + struct iio_buffer *buffer; + /* Serializes pushes with buffer activation and quiescence. */ + struct mutex buffer_lock; + bool buffer_active; + u32 scale_nano; + u16 sensor_index; + struct osf_device *osf; +}; + +struct osf_iio_scan_3axis { + s32 values[3]; + u32 padding; + aligned_s64 timestamp; +}; + +struct osf_iio_scan_1axis { + s32 value; + u32 padding; + aligned_s64 timestamp; +}; + +#define OSF_MOD_CHAN(_type, _mod, _idx) \ + { \ + .type =3D (_type), \ + .modified =3D 1, \ + .channel2 =3D (_mod), \ + .info_mask_separate =3D BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type =3D BIT(IIO_CHAN_INFO_SCALE), \ + .scan_index =3D (_idx), \ + .scan_type =3D { \ + .sign =3D 's', \ + .realbits =3D 32, \ + .storagebits =3D 32, \ + .endianness =3D IIO_CPU, \ + }, \ + } + +#define OSF_CHAN(_type, _idx) \ + { \ + .type =3D (_type), \ + .info_mask_separate =3D BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type =3D BIT(IIO_CHAN_INFO_SCALE), \ + .scan_index =3D (_idx), \ + .scan_type =3D { \ + .sign =3D 's', \ + .realbits =3D 32, \ + .storagebits =3D 32, \ + .endianness =3D IIO_CPU, \ + }, \ + } + +static const struct iio_chan_spec osf_accel_channels[] =3D { + OSF_MOD_CHAN(IIO_ACCEL, IIO_MOD_X, 0), + OSF_MOD_CHAN(IIO_ACCEL, IIO_MOD_Y, 1), + OSF_MOD_CHAN(IIO_ACCEL, IIO_MOD_Z, 2), + IIO_CHAN_SOFT_TIMESTAMP(3), +}; + +static const struct iio_chan_spec osf_gyro_channels[] =3D { + OSF_MOD_CHAN(IIO_ANGL_VEL, IIO_MOD_X, 0), + OSF_MOD_CHAN(IIO_ANGL_VEL, IIO_MOD_Y, 1), + OSF_MOD_CHAN(IIO_ANGL_VEL, IIO_MOD_Z, 2), + IIO_CHAN_SOFT_TIMESTAMP(3), +}; + +static const struct iio_chan_spec osf_mag_channels[] =3D { + OSF_MOD_CHAN(IIO_MAGN, IIO_MOD_X, 0), + OSF_MOD_CHAN(IIO_MAGN, IIO_MOD_Y, 1), + OSF_MOD_CHAN(IIO_MAGN, IIO_MOD_Z, 2), + IIO_CHAN_SOFT_TIMESTAMP(3), +}; + +static const struct iio_chan_spec osf_temp_channels[] =3D { + OSF_CHAN(IIO_TEMP, 0), + IIO_CHAN_SOFT_TIMESTAMP(1), +}; + +static const struct osf_iio_sensor_spec osf_iio_sensor_specs[] =3D { + { + .sensor_type =3D OSF_SENSOR_ACCELEROMETER, + .channel_count =3D 3, + .name =3D "osf-accel", + .channels =3D osf_accel_channels, + .num_channels =3D ARRAY_SIZE(osf_accel_channels), + }, + { + .sensor_type =3D OSF_SENSOR_GYROSCOPE, + .channel_count =3D 3, + .name =3D "osf-gyro", + .channels =3D osf_gyro_channels, + .num_channels =3D ARRAY_SIZE(osf_gyro_channels), + }, + { + .sensor_type =3D OSF_SENSOR_MAGNETOMETER, + .channel_count =3D 3, + .name =3D "osf-magn", + .channels =3D osf_mag_channels, + .num_channels =3D ARRAY_SIZE(osf_mag_channels), + }, + { + .sensor_type =3D OSF_SENSOR_TEMPERATURE, + .channel_count =3D 1, + .name =3D "osf-temp", + .channels =3D osf_temp_channels, + .num_channels =3D ARRAY_SIZE(osf_temp_channels), + }, +}; + +static const struct osf_iio_sensor_spec * +osf_iio_find_sensor_spec(u16 sensor_type, u16 channel_count) +{ + for (unsigned int i =3D 0; i < ARRAY_SIZE(osf_iio_sensor_specs); i++) { + if (osf_iio_sensor_specs[i].sensor_type =3D=3D sensor_type && + osf_iio_sensor_specs[i].channel_count =3D=3D channel_count) + return &osf_iio_sensor_specs[i]; + } + + return NULL; +} + +bool osf_iio_sensor_supported(u16 sensor_type, u16 channel_count) +{ + if (osf_iio_find_sensor_spec(sensor_type, channel_count)) + return true; + + return false; +} + +const char *osf_iio_sensor_name(u16 sensor_type) +{ + for (unsigned int i =3D 0; i < ARRAY_SIZE(osf_iio_sensor_specs); i++) { + if (osf_iio_sensor_specs[i].sensor_type =3D=3D sensor_type) + return osf_iio_sensor_specs[i].name; + } + + return NULL; +} + +static int osf_iio_read_raw(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, int *val, + int *val2, long mask) +{ + struct osf_iio_state *state =3D iio_priv(indio_dev); + s32 raw; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret =3D osf_core_read_latest_sample(state->osf, + state->spec->sensor_type, + state->sensor_index, + chan->scan_index, &raw); + if (ret) + return ret; + + *val =3D raw; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val =3D state->scale_nano / NANO; + *val2 =3D state->scale_nano % NANO; + return IIO_VAL_INT_PLUS_NANO; + default: + return -EINVAL; + } +} + +static const struct iio_info osf_iio_info =3D { + .read_raw =3D osf_iio_read_raw, +}; + +static int osf_iio_buffer_postenable(struct iio_dev *indio_dev) +{ + struct osf_iio_state *state =3D iio_priv(indio_dev); + + guard(mutex)(&state->buffer_lock); + state->buffer_active =3D true; + + return 0; +} + +static int osf_iio_buffer_predisable(struct iio_dev *indio_dev) +{ + struct osf_iio_state *state =3D iio_priv(indio_dev); + + /* Wait for the current push before the IIO core changes its buffers. */ + guard(mutex)(&state->buffer_lock); + state->buffer_active =3D false; + + return 0; +} + +static const struct iio_buffer_setup_ops osf_iio_buffer_ops =3D { + .postenable =3D osf_iio_buffer_postenable, + .predisable =3D osf_iio_buffer_predisable, +}; + +int osf_iio_register_sensor(struct device *dev, + const struct osf_capability_entry *entry, + struct osf_device *osf, struct iio_dev **indio_dev) +{ + const struct osf_iio_sensor_spec *spec; + struct osf_iio_state *state; + struct iio_dev *iio_dev; + int ret; + + spec =3D osf_iio_find_sensor_spec(entry->sensor_type, + entry->channel_count); + if (!spec) + return -EOPNOTSUPP; + + if (entry->sample_format !=3D OSF_SAMPLE_FORMAT_S32 || + (entry->flags & ~OSF_CAPABILITY_FLAGS_MASK) || + entry->reserved) + return -EOPNOTSUPP; + + iio_dev =3D iio_device_alloc(dev, sizeof(*state)); + if (!iio_dev) + return -ENOMEM; + + state =3D iio_priv(iio_dev); + state->spec =3D spec; + state->scale_nano =3D entry->scale_nano; + state->sensor_index =3D entry->sensor_index; + state->osf =3D osf; + mutex_init(&state->buffer_lock); + + iio_dev->name =3D spec->name; + iio_dev->info =3D &osf_iio_info; + iio_dev->setup_ops =3D &osf_iio_buffer_ops; + iio_dev->modes =3D INDIO_DIRECT_MODE | INDIO_BUFFER_SOFTWARE; + iio_dev->channels =3D spec->channels; + iio_dev->num_channels =3D spec->num_channels; + + state->buffer =3D iio_kfifo_allocate(); + if (!state->buffer) { + ret =3D -ENOMEM; + goto err_free_iio; + } + + ret =3D iio_device_attach_buffer(iio_dev, state->buffer); + if (ret) + goto err_free_buffer; + + ret =3D iio_device_register(iio_dev); + if (ret) + goto err_free_buffer; + + *indio_dev =3D iio_dev; + + return 0; + +err_free_buffer: + iio_kfifo_free(state->buffer); +err_free_iio: + iio_device_free(iio_dev); + + return ret; +} + +void osf_iio_unregister_sensor(struct iio_dev *indio_dev) +{ + struct osf_iio_state *state =3D iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + iio_kfifo_free(state->buffer); + iio_device_free(indio_dev); +} + +int osf_iio_push_sample(struct iio_dev *indio_dev, const s32 *values, + u16 channel_count) +{ + struct osf_iio_state *state =3D iio_priv(indio_dev); + s64 timestamp; + + if (channel_count !=3D state->spec->channel_count) + return -EPROTO; + + guard(mutex)(&state->buffer_lock); + if (!state->buffer_active || !iio_buffer_enabled(indio_dev)) + return 0; + + timestamp =3D iio_get_time_ns(indio_dev); + + switch (channel_count) { + case 1: { + struct osf_iio_scan_1axis scan =3D { + .value =3D values[0], + }; + + return iio_push_to_buffers_with_ts(indio_dev, &scan, + sizeof(scan), timestamp); + } + case 3: { + struct osf_iio_scan_3axis scan =3D { + .values =3D { }, + }; + unsigned int channel, index =3D 0; + + /* Pack the active channels; unused storage remains initialized. */ + for_each_set_bit(channel, indio_dev->active_scan_mask, channel_count) + scan.values[index++] =3D values[channel]; + + return iio_push_to_buffers_with_ts(indio_dev, &scan, + sizeof(scan), timestamp); + } + default: + return -EPROTO; + } +} diff --git a/drivers/iio/opensensorfusion/osf_iio.h b/drivers/iio/opensensorfusion/osf_iio.h new file mode 100644 index 000000000000..d0745167f8e0 --- /dev/null +++ b/drivers/iio/opensensorfusion/osf_iio.h @@ -0,0 +1,22 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +#ifndef _OSF_IIO_H +#define _OSF_IIO_H + +#include + +#include "osf_protocol.h" + +struct device; +struct iio_dev; +struct osf_device; + +int osf_iio_register_sensor(struct device *dev, + const struct osf_capability_entry *entry, + struct osf_device *osf, struct iio_dev **indio_dev); +void osf_iio_unregister_sensor(struct iio_dev *indio_dev); +int osf_iio_push_sample(struct iio_dev *indio_dev, const s32 *values, + u16 channel_count); +bool osf_iio_sensor_supported(u16 sensor_type, u16 channel_count); +const char *osf_iio_sensor_name(u16 sensor_type); + +#endif diff --git a/drivers/iio/opensensorfusion/osf_iio_test.c b/drivers/iio/opensensorfusion/osf_iio_test.c new file mode 100644 index 000000000000..63d3ca1af329 --- /dev/null +++ b/drivers/iio/opensensorfusion/osf_iio_test.c @@ -0,0 +1,325 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "osf_core.h" +#include "osf_iio.h" + +static noinline void osf_test_poison_stack(void) +{ + u8 bytes[1024]; + + memset(bytes, 0xa5, sizeof(bytes)); + barrier_data(bytes); +} + +static void osf_iio_check_scan(struct kunit *test, struct iio_dev *indio_d= ev, + const s32 *values, unsigned int channels, + unsigned int mask, bool ts) +{ + struct iio_buffer *buffer =3D indio_dev->buffer; + unsigned int data_bytes =3D hweight32(mask) * sizeof(s32); + unsigned int ts_offset =3D ALIGN(data_bytes, 8); + unsigned int scan_bytes =3D ts ? ts_offset + 8 : data_bytes; + unsigned int offset =3D 0; + u8 scan[24] __aligned(8); + s64 before, after, timestamp; + int ret; + + bitmap_zero((unsigned long *)buffer->scan_mask, iio_get_masklength(indio_dev)); + for (unsigned int i =3D 0; i < channels; i++) + if (mask & BIT(i)) + set_bit(i, (unsigned long *)buffer->scan_mask); + buffer->scan_timestamp =3D ts; + ret =3D iio_update_buffers(indio_dev, buffer, NULL); + KUNIT_ASSERT_EQ(test, ret, 0); + + kunit_info(test, "device=3D%s mask=3D%u ts=3D%u bytes=3D%u\n", + indio_dev->name, mask, ts, scan_bytes); + memset(scan, 0xa5, sizeof(scan)); + before =3D iio_get_time_ns(indio_dev); + osf_test_poison_stack(); + ret =3D osf_iio_push_sample(indio_dev, values, channels); + after =3D iio_get_time_ns(indio_dev); + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_EQ(test, buffer->bytes_per_datum, scan_bytes); + ret =3D iio_pop_from_buffer(buffer, scan); + KUNIT_EXPECT_EQ(test, ret, 0); + if (ret) + goto disable; + + for (unsigned int i =3D 0; i < channels; i++) { + if (!(mask & BIT(i))) + continue; + KUNIT_EXPECT_MEMEQ(test, scan + offset, &values[i], sizeof(s32)); + offset +=3D sizeof(s32); + } + if (ts) { + for (unsigned int i =3D data_bytes; i < ts_offset; i++) + KUNIT_EXPECT_EQ(test, scan[i], (u8)0); + memcpy(×tamp, scan + ts_offset, sizeof(timestamp)); + KUNIT_EXPECT_GE(test, timestamp, before); + KUNIT_EXPECT_LE(test, timestamp, after); + } + /* Neither kfifo nor the producer may write outside the consumer stride. = */ + for (unsigned int i =3D scan_bytes; i < sizeof(scan); i++) + KUNIT_EXPECT_EQ(test, scan[i], (u8)0xa5); + +disable: + KUNIT_EXPECT_EQ(test, iio_update_buffers(indio_dev, NULL, buffer), 0); +} + +/* Protect timestamp alignment holes and every supported scan layout. */ +static void osf_iio_scan_bytes_test(struct kunit *test) +{ + static const u16 types[] =3D { + OSF_SENSOR_ACCELEROMETER, OSF_SENSOR_GYROSCOPE, + OSF_SENSOR_MAGNETOMETER, OSF_SENSOR_TEMPERATURE, + }; + const s32 values[] =3D { 101, -202, 303 }; + struct osf_capability_entry entry =3D { + .sample_format =3D OSF_SAMPLE_FORMAT_S32, + .scale_nano =3D 1000000, + }; + struct osf_device *osf; + struct device *dev; + + dev =3D kunit_device_register(test, "osf-iio"); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); + osf =3D kunit_kzalloc(test, sizeof(*osf), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, osf); + osf_core_init(osf, dev); + + for (unsigned int t =3D 0; t < ARRAY_SIZE(types); t++) { + unsigned int channels =3D t =3D=3D 3 ? 1 : 3; + struct iio_dev *indio_dev; + int ret; + + entry.sensor_type =3D types[t]; + entry.channel_count =3D channels; + ret =3D osf_iio_register_sensor(dev, &entry, osf, &indio_dev); + KUNIT_ASSERT_EQ(test, ret, 0); + for (unsigned int mask =3D 1; mask < BIT(channels); mask++) + for (unsigned int ts =3D 0; ts < 2; ts++) + osf_iio_check_scan(test, indio_dev, values, channels, mask, ts); + osf_iio_unregister_sensor(indio_dev); + } +} + +struct osf_iio_race { + struct iio_buffer_access_funcs access; + const struct iio_buffer_access_funcs *original; + struct iio_dev *indio_dev; + struct completion entered; + struct completion release; + struct completion config_started; + struct completion config_done; + atomic_t block_store; + atomic_t stores; + atomic_t disabled; + atomic_t bad_store; + int enable_error; + int disable_error; + int config_result; + bool unregister; +}; + +static struct osf_iio_race *osf_iio_race_from_buffer(struct iio_buffer *bu= ffer) +{ + return container_of(buffer->access, struct osf_iio_race, access); +} + +static int osf_iio_test_store(struct iio_buffer *buffer, const void *data) +{ + struct osf_iio_race *race =3D osf_iio_race_from_buffer(buffer); + + if (atomic_xchg(&race->block_store, 0)) { + complete(&race->entered); + if (!wait_for_completion_timeout(&race->release, 5 * HZ)) + return -ETIMEDOUT; + } + if (atomic_read(&race->disabled)) + atomic_inc(&race->bad_store); + atomic_inc(&race->stores); + return race->original->store_to(buffer, data); +} + +static int osf_iio_test_enable(struct iio_buffer *buffer, struct iio_dev *indio_dev) +{ + struct osf_iio_race *race =3D osf_iio_race_from_buffer(buffer); + + if (race->enable_error) + return race->enable_error; + atomic_set(&race->disabled, 0); + return 0; +} + +static int osf_iio_test_disable(struct iio_buffer *buffer, struct iio_dev *indio_dev) +{ + struct osf_iio_race *race =3D osf_iio_race_from_buffer(buffer); + + atomic_set(&race->disabled, 1); + return race->disable_error; +} + +static int osf_iio_test_producer(void *data) +{ + struct osf_iio_race *race =3D data; + const s32 values[] =3D { 101, -202, 303 }; + + while (!kthread_should_stop()) { + osf_iio_push_sample(race->indio_dev, values, ARRAY_SIZE(values)); + cond_resched(); + } + return 0; +} + +static int osf_iio_test_configure(void *data) +{ + struct osf_iio_race *race =3D data; + + complete(&race->config_started); + if (race->unregister) { + osf_iio_unregister_sensor(race->indio_dev); + race->config_result =3D 0; + } else { + race->config_result =3D iio_update_buffers(race->indio_dev, NULL, + race->indio_dev->buffer); + } + complete(&race->config_done); + while (!kthread_should_stop()) + msleep(20); + return 0; +} + +static void osf_iio_expect_quiesce(struct kunit *test, struct osf_iio_race *race) +{ + struct task_struct *config; + unsigned long waited; + + config =3D kthread_run(osf_iio_test_configure, race, "osf-cfg-test"); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, config); + KUNIT_EXPECT_NE(test, wait_for_completion_timeout(&race->config_started, HZ), 0UL); + waited =3D wait_for_completion_timeout(&race->config_done, msecs_to_jiffies(20)); + KUNIT_EXPECT_EQ(test, waited, 0UL); + complete(&race->release); + KUNIT_EXPECT_NE(test, wait_for_completion_timeout(&race->config_done, HZ), 0UL); + kthread_stop(config); + KUNIT_EXPECT_EQ(test, race->config_result, 0); +} + +/* Run real IIO configuration and kfifo code against an independent producer. */ +static void osf_iio_buffer_lifetime_test(struct kunit *test) +{ + struct osf_capability_entry entry =3D { + .sensor_type =3D OSF_SENSOR_ACCELEROMETER, + .channel_count =3D 3, + .sample_format =3D OSF_SAMPLE_FORMAT_S32, + .scale_nano =3D 1000000, + }; + const s32 values[] =3D { 101, -202, 303 }; + struct task_struct *producer; + struct osf_iio_race *race; + struct osf_device *osf; + struct iio_dev *indio_dev; + struct iio_buffer *buffer; + struct device *dev; + int stores, ret; + + dev =3D kunit_device_register(test, "osf-race"); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); + osf =3D kunit_kzalloc(test, sizeof(*osf), GFP_KERNEL); + race =3D kunit_kzalloc(test, sizeof(*race), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, osf); + KUNIT_ASSERT_NOT_NULL(test, race); + osf_core_init(osf, dev); + ret =3D osf_iio_register_sensor(dev, &entry, osf, &indio_dev); + KUNIT_ASSERT_EQ(test, ret, 0); + buffer =3D indio_dev->buffer; + race->indio_dev =3D indio_dev; + race->original =3D buffer->access; + race->access =3D *buffer->access; + race->access.store_to =3D osf_iio_test_store; + race->access.enable =3D osf_iio_test_enable; + race->access.disable =3D osf_iio_test_disable; + buffer->access =3D &race->access; + init_completion(&race->entered); + init_completion(&race->release); + init_completion(&race->config_started); + init_completion(&race->config_done); + atomic_set(&race->disabled, 1); + set_bit(0, (unsigned long *)buffer->scan_mask); + KUNIT_EXPECT_EQ(test, iio_update_buffers(indio_dev, buffer, NULL), 0); + + atomic_set(&race->block_store, 1); + producer =3D kthread_run(osf_iio_test_producer, race, "osf-rx-test"); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, producer); + KUNIT_EXPECT_NE(test, wait_for_completion_timeout(&race->entered, HZ), 0U= L); + osf_iio_expect_quiesce(test, race); + + for (unsigned int i =3D 0; i < 100; i++) { + bitmap_zero((unsigned long *)buffer->scan_mask, iio_get_masklength(indio_dev)); + *(unsigned long *)buffer->scan_mask =3D (i % 7) + 1; + buffer->scan_timestamp =3D i & 1; + KUNIT_EXPECT_EQ(test, iio_update_buffers(indio_dev, buffer, NULL), 0); + cond_resched(); + KUNIT_EXPECT_EQ(test, iio_update_buffers(indio_dev, NULL, buffer), 0); + } + + race->enable_error =3D -EIO; + KUNIT_EXPECT_EQ(test, iio_update_buffers(indio_dev, buffer, NULL), -EIO); + stores =3D atomic_read(&race->stores); + KUNIT_EXPECT_EQ(test, osf_iio_push_sample(indio_dev, values, 3), 0); + KUNIT_EXPECT_EQ(test, atomic_read(&race->stores), stores); + race->enable_error =3D 0; + KUNIT_EXPECT_EQ(test, iio_update_buffers(indio_dev, buffer, NULL), 0); + race->disable_error =3D -EIO; + KUNIT_EXPECT_EQ(test, iio_update_buffers(indio_dev, NULL, buffer), -EIO); + stores =3D atomic_read(&race->stores); + KUNIT_EXPECT_EQ(test, osf_iio_push_sample(indio_dev, values, 3), 0); + KUNIT_EXPECT_EQ(test, atomic_read(&race->stores), stores); + race->disable_error =3D 0; + + KUNIT_EXPECT_EQ(test, iio_update_buffers(indio_dev, buffer, NULL), 0); + /* Retain ownership while testing unregister against an admitted push. */ + get_device(&indio_dev->dev); + reinit_completion(&race->entered); + reinit_completion(&race->release); + reinit_completion(&race->config_started); + reinit_completion(&race->config_done); + atomic_set(&race->block_store, 1); + KUNIT_EXPECT_NE(test, wait_for_completion_timeout(&race->entered, HZ), 0U= L); + race->unregister =3D true; + osf_iio_expect_quiesce(test, race); + kthread_stop(producer); + KUNIT_EXPECT_EQ(test, atomic_read(&race->bad_store), 0); + KUNIT_EXPECT_GT(test, atomic_read(&race->stores), 0); + buffer->access =3D race->original; + put_device(&indio_dev->dev); +} + +static struct kunit_case osf_iio_test_cases[] =3D { + KUNIT_CASE(osf_iio_scan_bytes_test), + KUNIT_CASE(osf_iio_buffer_lifetime_test), + { } +}; + +static struct kunit_suite osf_iio_test_suite =3D { + .name =3D "osf-iio", + .test_cases =3D osf_iio_test_cases, +}; + +kunit_test_suite(osf_iio_test_suite); + +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/opensensorfusion/osf_serdev.c b/drivers/iio/opensensorfusion/osf_serdev.c new file mode 100644 index 000000000000..ae983b5557c6 --- /dev/null +++ b/drivers/iio/opensensorfusion/osf_serdev.c @@ -0,0 +1,162 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "osf_core.h" +#include "osf_stream.h" + +#define OSF_SERDEV_BAUD 115200 + +struct osf_serdev { + struct serdev_device *serdev; + struct osf_device osf; + struct osf_stream stream; + /* Serializes early capability delivery with the registration gate. */ + struct mutex rx_lock; +}; + +static int osf_serdev_receive_frame(void *context, const u8 *buf, size_t l= en) +{ + struct osf_device *osf =3D context; + + return osf_core_receive_frame(osf, buf, len); +} + +static size_t osf_serdev_receive_buf(struct serdev_device *serdev, + const u8 *buf, size_t count) +{ + struct osf_serdev *osf_uart =3D serdev_device_get_drvdata(serdev); + const struct osf_stream_stats *stats; + u64 authenticated_before; + int ret; + + guard(mutex)(&osf_uart->rx_lock); + authenticated_before =3D osf_uart->stream.stats.authenticated_frames; + ret =3D osf_stream_receive_bytes(&osf_uart->stream, buf, count); + stats =3D &osf_uart->stream.stats; + + if (ret || stats->authenticated_frames !=3D authenticated_before) + dev_dbg_ratelimited(&serdev->dev, + "rx count=3D%zu authenticated=3D%llu handled=3D%llu ignored=3D%llu rejected=3D%llu bad_magic=3D%llu bad_crc=3D%llu dropped=3D%llu ret=3D%d\n", + count, stats->authenticated_frames, + stats->handled_frames, stats->ignored_frames, + stats->rejected_frames, + stats->bad_magic_resyncs, + stats->bad_crc_frames, + stats->dropped_bytes, ret); + + return count; +} + +static const struct serdev_device_ops osf_serdev_ops =3D { + .receive_buf =3D osf_serdev_receive_buf, +}; + +static void osf_serdev_disable_vcc(void *vcc) +{ + regulator_disable(vcc); +} + +static int osf_serdev_probe(struct serdev_device *serdev) +{ + struct device *dev =3D &serdev->dev; + struct osf_serdev *osf_uart; + struct regulator *vcc; + unsigned int baudrate; + int ret; + + osf_uart =3D devm_kzalloc(dev, sizeof(*osf_uart), GFP_KERNEL); + if (!osf_uart) + return -ENOMEM; + + vcc =3D devm_regulator_get(dev, "vcc"); + if (IS_ERR(vcc)) + return dev_err_probe(dev, PTR_ERR(vcc), + "failed to get vcc regulator\n"); + + mutex_init(&osf_uart->rx_lock); + osf_uart->serdev =3D serdev; + osf_core_init(&osf_uart->osf, dev); + osf_stream_init(&osf_uart->stream, osf_serdev_receive_frame, + &osf_uart->osf); + + serdev_device_set_drvdata(serdev, osf_uart); + serdev_device_set_client_ops(serdev, &osf_serdev_ops); + + ret =3D serdev_device_open(serdev); + if (ret) { + osf_stream_reset(&osf_uart->stream); + osf_core_unregister_iio(&osf_uart->osf); + + return ret; + } + + baudrate =3D serdev_device_set_baudrate(serdev, OSF_SERDEV_BAUD); + if (baudrate !=3D OSF_SERDEV_BAUD) + dev_warn(dev, "requested %u baud, controller set %u\n", + OSF_SERDEV_BAUD, baudrate); + + serdev_device_set_flow_control(serdev, false); + + ret =3D regulator_enable(vcc); + if (ret) { + dev_err_probe(dev, ret, "failed to enable vcc regulator\n"); + goto err_close; + } + + ret =3D devm_add_action_or_reset(dev, osf_serdev_disable_vcc, vcc); + if (ret) + goto err_close; + + /* No fallible probe steps remain when IIO children become visible. */ + scoped_guard(mutex, &osf_uart->rx_lock) + osf_core_start(&osf_uart->osf); + + return 0; + +err_close: + serdev_device_close(serdev); + osf_stream_reset(&osf_uart->stream); + osf_core_unregister_iio(&osf_uart->osf); + + return ret; +} + +static void osf_serdev_remove(struct serdev_device *serdev) +{ + struct osf_serdev *osf_uart =3D serdev_device_get_drvdata(serdev); + + /* Stop the RX producer before unregistering IIO consumers. */ + serdev_device_close(serdev); + osf_stream_reset(&osf_uart->stream); + osf_core_unregister_iio(&osf_uart->osf); +} + +static const struct of_device_id osf_serdev_of_match[] =3D { + { .compatible =3D "opensensorfusion,osf" }, + { } +}; +MODULE_DEVICE_TABLE(of, osf_serdev_of_match); + +static struct serdev_device_driver osf_serdev_driver =3D { + .probe =3D osf_serdev_probe, + .remove =3D osf_serdev_remove, + .driver =3D { + .name =3D "open-sensor-fusion-uart", + .of_match_table =3D osf_serdev_of_match, + }, +}; +module_serdev_device_driver(osf_serdev_driver); + +MODULE_DESCRIPTION("Open Sensor Fusion IIO driver"); +MODULE_LICENSE("GPL"); --=20 2.43.0