From nobody Thu Oct 9 16:40:33 2025 Received: from mailout2.w1.samsung.com (mailout2.w1.samsung.com [210.118.77.12]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 5D1C92E7F1D for ; Tue, 17 Jun 2025 14:08:41 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=210.118.77.12 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1750169324; cv=none; b=SFSGerQ1hK4uzjQ+H8D4FAEDHDLJigjqLIe3kZV2zNh/WlQng/Y+RcjCLICBcAEmPmgW/ElMpZBTiJH9+Ex+3rdOU7P9yRmnmduK7KCogsuQddqNNUS84Cpt61iF4g98BQx/P3V94ZctLf+jF1CbwuUoDAYfZhrGQ2cKSj7PzC4= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1750169324; c=relaxed/simple; bh=PqPmG7LLWKvnVKOK34Udaecl39RMqDb2VheKIq5I+RM=; h=From:Date:Subject:MIME-Version:Message-Id:In-Reply-To:To:Cc: Content-Type:References; b=akEPfZK4PLj7phHKl5MxRdGPbobzY/dOUIMi1Ga5+UMVszUGnQ0qNnDdqKHPxcEvF7lewP0VTbNmwQKssZrTews6RXKVHgjKeybwyjFWnRA4EQ4SK6qfPEc8LS/+y0gGXPvfigVPtPrBL2NARc7RtfcgyFOx6mSGmolKGVD4+UI= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=samsung.com; spf=pass smtp.mailfrom=samsung.com; dkim=pass (1024-bit key) header.d=samsung.com header.i=@samsung.com header.b=buWb5B0X; arc=none smtp.client-ip=210.118.77.12 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=samsung.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=samsung.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=samsung.com header.i=@samsung.com header.b="buWb5B0X" Received: from eucas1p1.samsung.com (unknown [182.198.249.206]) by mailout2.w1.samsung.com (KnoxPortal) with ESMTP id 20250617140839euoutp023d39d79c036914046c85b0ce91933c36~J2bkXYwZP0208502085euoutp028 for ; Tue, 17 Jun 2025 14:08:39 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 mailout2.w1.samsung.com 20250617140839euoutp023d39d79c036914046c85b0ce91933c36~J2bkXYwZP0208502085euoutp028 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=samsung.com; s=mail20170921; t=1750169319; bh=HLGeBBxVh4s0amVT/Rdjdx5pFJtAmPQ9fVS1AXrdlMc=; h=From:Date:Subject:In-Reply-To:To:Cc:References:From; b=buWb5B0X4ad8hr+YSOoN9fzlMT8HpgN/2lGeLJoP9LCa3XLVlaIMjWABW8MRZIN7t TWgeG+/B0m/fOPNFTqGrB7iifdOJoEIfwGbOTzc4SdKGEzN3SauEjQX7erTSv3aTth SJwY2p9aOVnuAu6q0AT14DAW6mnwZwacfXJ4z8Tw= Received: from eusmtip1.samsung.com (unknown [203.254.199.221]) by eucas1p2.samsung.com (KnoxPortal) with ESMTPA id 20250617140838eucas1p2a31af5a73297580c2421263c1c6ba700~J2bjpps3Z1901919019eucas1p28; Tue, 17 Jun 2025 14:08:38 +0000 (GMT) Received: from AMDC4942.eu.corp.samsungelectronics.net (unknown [106.210.136.40]) by eusmtip1.samsung.com (KnoxPortal) with ESMTPA id 20250617140837eusmtip19080d4986e3ed16c4afcd038c58f6574~J2biW8HbF2540625406eusmtip1J; Tue, 17 Jun 2025 14:08:37 +0000 (GMT) From: Michal Wilczynski Date: Tue, 17 Jun 2025 16:07:26 +0200 Subject: [PATCH v3 3/9] rust: pwm: Add driver operations trait and registration support Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Message-Id: <20250617-rust-next-pwm-working-fan-for-sending-v3-3-1cca847c6f9f@samsung.com> In-Reply-To: <20250617-rust-next-pwm-working-fan-for-sending-v3-0-1cca847c6f9f@samsung.com> To: =?utf-8?q?Uwe_Kleine-K=C3=B6nig?= , Miguel Ojeda , Alex Gaynor , Boqun Feng , Gary Guo , =?utf-8?q?Bj=C3=B6rn_Roy_Baron?= , Andreas Hindborg , Alice Ryhl , Trevor Gross , Danilo Krummrich , Michal Wilczynski , Drew Fustini , Guo Ren , Fu Wei , Rob Herring , Krzysztof Kozlowski , Conor Dooley , Paul Walmsley , Palmer Dabbelt , Albert Ou , Alexandre Ghiti , Marek Szyprowski , Benno Lossin , Michael Turquette , Stephen Boyd , Benno Lossin Cc: linux-kernel@vger.kernel.org, linux-pwm@vger.kernel.org, rust-for-linux@vger.kernel.org, linux-riscv@lists.infradead.org, devicetree@vger.kernel.org, linux-clk@vger.kernel.org X-Mailer: b4 0.15-dev X-CMS-MailID: 20250617140838eucas1p2a31af5a73297580c2421263c1c6ba700 X-Msg-Generator: CA Content-Type: text/plain; charset="utf-8" X-RootMTR: 20250617140838eucas1p2a31af5a73297580c2421263c1c6ba700 X-EPHeader: CA X-CMS-RootMailID: 20250617140838eucas1p2a31af5a73297580c2421263c1c6ba700 References: <20250617-rust-next-pwm-working-fan-for-sending-v3-0-1cca847c6f9f@samsung.com> Complete the PWM abstraction layer by adding the final components required to implement and register a driver. The main additions are: - PwmOps Trait: An interface that drivers can implement to provide their hardware specific logic. It mirrors the C pwm_ops interface, providing hooks for standard PWM operations like apply, request, and waveform handling. - FFI VTable and Adapter: The Adapter struct, PwmOpsVTable wrapper, and create_pwm_ops function are introduced. This scaffolding handles the unsafe FFI translation, bridging the gap between the idiomatic PwmOps trait and the C kernel's function-pointer-based vtable. - Registration Guard: A final RAII guard that uses the vtable to safely register a Chip with the PWM core via pwmchip_add. Its Drop implementation guarantees that pwmchip_remove is always called, preventing resource leaks. With this patch, the PWM abstraction layer is now complete and ready to be used for writing PWM chip drivers in Rust. Signed-off-by: Michal Wilczynski --- rust/kernel/pwm.rs | 471 +++++++++++++++++++++++++++++++++++++++++++++++++= +++- 1 file changed, 468 insertions(+), 3 deletions(-) diff --git a/rust/kernel/pwm.rs b/rust/kernel/pwm.rs index 8c4c27a4e4fc9da2fc8ea5015df2a315cfc6b932..64210411450cd3d964d21f1e712= fcfd4d8aed988 100644 --- a/rust/kernel/pwm.rs +++ b/rust/kernel/pwm.rs @@ -8,12 +8,12 @@ =20 use crate::{ bindings, - device, - error, + device::{self, Bound}, + error::{self, to_result}, prelude::*, types::{ARef, AlwaysRefCounted, ForeignOwnable, Opaque}, }; -use core::{convert::TryFrom, ptr::NonNull}; +use core::{convert::TryFrom, marker::PhantomData, mem::ManuallyDrop, ptr::= NonNull}; =20 /// Maximum size for the hardware-specific waveform representation buffer. /// @@ -419,3 +419,468 @@ unsafe impl Send for Chip {} // kernel locks, which the C core is responsible for. Any interior mutabil= ity is // handled and synchronized by the C kernel code. unsafe impl Sync for Chip {} + +/// Manages the registration of a PWM chip, ensuring `pwmchip_remove` is c= alled on drop. +pub struct Registration { + chip: ManuallyDrop>, +} + +impl Registration { + /// Registers a PWM chip (obtained via `Chip::new`) with the PWM subsy= stem. + /// + /// Takes an [`ARef`]. On `Drop` of the returned `Registration` = object, + /// `pwmchip_remove` is called for the chip. + pub fn new(chip: ARef, ops_vtable: &'static PwmOpsVTable) -> Res= ult { + // Get the raw C pointer from ARef. + let c_chip_ptr =3D chip.as_raw().cast::(); + + // SAFETY: `c_chip_ptr` is valid (guaranteed by ARef existing). + // `ops_vtable.as_raw()` provides a valid `*const bindings::pwm_op= s`. + // `bindings::__pwmchip_add` preconditions (valid pointers, ops se= t on chip) are met. + unsafe { + (*c_chip_ptr).ops =3D ops_vtable.as_raw(); + to_result(bindings::__pwmchip_add(c_chip_ptr, core::ptr::null_= mut()))?; + } + Ok(Registration { + chip: ManuallyDrop::new(chip), + }) + } +} + +impl Drop for Registration { + fn drop(&mut self) { + let chip =3D &**self.chip; + let chip_raw: *mut bindings::pwm_chip =3D chip.as_raw(); + + // SAFETY: `chip_raw` points to a chip that was successfully regis= tered via `Self::new`. + // `bindings::pwmchip_remove` is the correct C function to unregis= ter it. + unsafe { + bindings::pwmchip_remove(chip_raw); + ManuallyDrop::drop(&mut self.chip); // Drops the ARef + } + } +} + +/// Trait defining the operations for a PWM driver. +pub trait PwmOps: 'static + Sized { + /// The driver-specific hardware representation of a waveform. + /// + /// This type must be [`Copy`], [`Default`], and fit within [`WFHW_MAX= _SIZE`]. + type WfHw: Copy + Default; + + /// Optional hook to atomically apply a new PWM config. + fn apply( + _chip: &mut Chip, + _pwm: &mut Device, + _state: &State, + _parent_dev: &device::Device, + ) -> Result { + Err(ENOTSUPP) + } + + /// Optional hook for when a PWM device is requested. + fn request(_chip: &mut Chip, _pwm: &mut Device, _parent_dev: &device::= Device) -> Result { + Ok(()) + } + + /// Optional hook for when a PWM device is freed. + fn free(_chip: &mut Chip, _pwm: &mut Device, _parent_dev: &device::Dev= ice) {} + + /// Optional hook for capturing a PWM signal. + fn capture( + _chip: &mut Chip, + _pwm: &mut Device, + _result: &mut bindings::pwm_capture, + _timeout: usize, + _parent_dev: &device::Device, + ) -> Result { + Err(ENOTSUPP) + } + + /// Optional hook to get the current hardware state. + fn get_state( + _chip: &mut Chip, + _pwm: &mut Device, + _state: &mut State, + _parent_dev: &device::Device, + ) -> Result { + Err(ENOTSUPP) + } + + /// Convert a generic waveform to the hardware-specific representation. + /// This is typically a pure calculation and does not perform I/O. + fn round_waveform_tohw( + _chip: &mut Chip, + _pwm: &mut Device, + _wf: &Waveform, + ) -> Result<(c_int, Self::WfHw)> { + Err(ENOTSUPP) + } + + /// Convert a hardware-specific representation back to a generic wavef= orm. + /// This is typically a pure calculation and does not perform I/O. + fn round_waveform_fromhw( + _chip: &mut Chip, + _pwm: &Device, + _wfhw: &Self::WfHw, + _wf: &mut Waveform, + ) -> Result { + Err(ENOTSUPP) + } + + /// Read the current hardware configuration into the hardware-specific= representation. + fn read_waveform( + _chip: &mut Chip, + _pwm: &mut Device, + _parent_dev: &device::Device, + ) -> Result { + Err(ENOTSUPP) + } + + /// Write a hardware-specific waveform configuration to the hardware. + fn write_waveform( + _chip: &mut Chip, + _pwm: &mut Device, + _wfhw: &Self::WfHw, + _parent_dev: &device::Device, + ) -> Result { + Err(ENOTSUPP) + } +} +/// Bridges Rust `PwmOps` to the C `pwm_ops` vtable. +struct Adapter { + _p: PhantomData, +} + +impl Adapter { + /// # Safety + /// + /// `wfhw_ptr` must be valid for writes of `size_of::()` byte= s. + unsafe fn serialize_wfhw(wfhw: &T::WfHw, wfhw_ptr: *mut c_void) -> Res= ult { + let size =3D core::mem::size_of::(); + if size > WFHW_MAX_SIZE { + return Err(EINVAL); + } + + // SAFETY: The caller ensures `wfhw_ptr` is valid for `size` bytes. + unsafe { + core::ptr::copy_nonoverlapping(wfhw as *const _ as *const u8, = wfhw_ptr.cast(), size); + } + + Ok(()) + } + + /// # Safety + /// + /// `wfhw_ptr` must be valid for reads of `size_of::()` bytes. + unsafe fn deserialize_wfhw(wfhw_ptr: *const c_void) -> Result= { + let size =3D core::mem::size_of::(); + if size > WFHW_MAX_SIZE { + return Err(EINVAL); + } + + let mut wfhw =3D T::WfHw::default(); + // SAFETY: The caller ensures `wfhw_ptr` is valid for `size` bytes. + unsafe { + core::ptr::copy_nonoverlapping(wfhw_ptr.cast(), &mut wfhw as *= mut _ as *mut u8, size); + } + + Ok(wfhw) + } + + /// # Safety + /// + /// Pointers from C must be valid. + unsafe extern "C" fn apply_callback( + c: *mut bindings::pwm_chip, + p: *mut bindings::pwm_device, + s: *const bindings::pwm_state, + ) -> c_int { + // SAFETY: This block relies on the function's safety contract: th= e C caller + // provides valid pointers. `Chip::from_ptr` and `Device::from_ptr= ` are `unsafe fn` + // whose preconditions are met by this contract. + let (chip, pwm) =3D unsafe { (Chip::from_ptr(c), Device::from_ptr(= p)) }; + let parent_dev =3D match chip.parent_device() { + Some(dev) =3D> dev, + None =3D> { + return EINVAL.to_errno(); + } + }; + + // SAFETY: The PWM core guarantees callbacks only happen on a live= , bound device. + let bound_parent =3D + unsafe { &*(parent_dev as *const device::Device as *const devi= ce::Device) }; + + // SAFETY: The state provided by the callback is guaranteed to be = valid + let state =3D State::from_c(unsafe { *s }); + match T::apply(chip, pwm, &state, bound_parent) { + Ok(()) =3D> 0, + Err(e) =3D> e.to_errno(), + } + } + + /// # Safety + /// + /// Pointers from C must be valid. + unsafe extern "C" fn request_callback( + c: *mut bindings::pwm_chip, + p: *mut bindings::pwm_device, + ) -> c_int { + // SAFETY: PWM core guarentees `c` and `p` are valid pointers. + let (chip, pwm) =3D unsafe { (Chip::from_ptr(c), Device::from_ptr(= p)) }; + let parent_dev =3D match chip.parent_device() { + Some(dev) =3D> dev, + None =3D> { + return EINVAL.to_errno(); + } + }; + + let bound_parent =3D + // SAFETY: The PWM core guarantees the device is bound during callbacks. + unsafe { &*(parent_dev as *const device::Device as *const devi= ce::Device) }; + match T::request(chip, pwm, bound_parent) { + Ok(()) =3D> 0, + Err(e) =3D> e.to_errno(), + } + } + + /// # Safety + /// + /// Pointers from C must be valid. + unsafe extern "C" fn free_callback(c: *mut bindings::pwm_chip, p: *mut= bindings::pwm_device) { + // SAFETY: Relies on the function's contract that `c` and `p` are = valid pointers. + let (chip, pwm) =3D unsafe { (Chip::from_ptr(c), Device::from_ptr(= p)) }; + let parent_dev =3D match chip.parent_device() { + Some(dev) =3D> dev, + None =3D> { + return; + } + }; + + let bound_parent =3D + // SAFETY: The PWM core guarantees the device is bound during callbacks. + unsafe { &*(parent_dev as *const device::Device as *const devi= ce::Device) }; + T::free(chip, pwm, bound_parent); + } + + /// # Safety + /// + /// Pointers from C must be valid. + unsafe extern "C" fn capture_callback( + c: *mut bindings::pwm_chip, + p: *mut bindings::pwm_device, + res: *mut bindings::pwm_capture, + timeout: usize, + ) -> c_int { + // SAFETY: Relies on the function's contract that `c` and `p` are = valid pointers. + let (chip, pwm, result) =3D unsafe { (Chip::from_ptr(c), Device::f= rom_ptr(p), &mut *res) }; + let parent_dev =3D match chip.parent_device() { + Some(dev) =3D> dev, + None =3D> { + return EINVAL.to_errno(); + } + }; + + let bound_parent =3D + // SAFETY: The PWM core guarantees the device is bound during cal= lbacks. + unsafe { &*(parent_dev as *const device::Device as *const devi= ce::Device) }; + match T::capture(chip, pwm, result, timeout, bound_parent) { + Ok(()) =3D> 0, + Err(e) =3D> e.to_errno(), + } + } + + /// # Safety + /// + /// Pointers from C must be valid. + unsafe extern "C" fn get_state_callback( + c: *mut bindings::pwm_chip, + p: *mut bindings::pwm_device, + s: *mut bindings::pwm_state, + ) -> c_int { + // SAFETY: Relies on the function's contract that `c` and `p` are = valid pointers. + let (chip, pwm) =3D unsafe { (Chip::from_ptr(c), Device::from_ptr(= p)) }; + let parent_dev =3D match chip.parent_device() { + Some(dev) =3D> dev, + None =3D> { + return EINVAL.to_errno(); + } + }; + let bound_parent =3D + // SAFETY: The PWM core guarantees the device is bound during callbacks. + unsafe { &*(parent_dev as *const device::Device as *const devi= ce::Device) }; + let mut rust_state =3D State::new(); + match T::get_state(chip, pwm, &mut rust_state, bound_parent) { + Ok(()) =3D> { + // SAFETY: `s` is guaranteed valid by the C caller. + unsafe { + *s =3D rust_state.0; + }; + 0 + } + Err(e) =3D> e.to_errno(), + } + } + + /// # Safety + /// + /// Pointers from C must be valid. + unsafe extern "C" fn round_waveform_tohw_callback( + c: *mut bindings::pwm_chip, + p: *mut bindings::pwm_device, + w: *const bindings::pwm_waveform, + wh: *mut c_void, + ) -> c_int { + // SAFETY: Relies on the function's contract that `c` and `p` are = valid pointers. + let (chip, pwm, wf) =3D + unsafe { (Chip::from_ptr(c), Device::from_ptr(p), Waveform::fr= om(*w)) }; + match T::round_waveform_tohw(chip, pwm, &wf) { + Ok((status, wfhw)) =3D> { + // SAFETY: `wh` is valid per this function's safety contra= ct. + if unsafe { Self::serialize_wfhw(&wfhw, wh) }.is_err() { + return EINVAL.to_errno(); + } + status + } + Err(e) =3D> e.to_errno(), + } + } + + /// # Safety + /// + /// Pointers from C must be valid. + unsafe extern "C" fn round_waveform_fromhw_callback( + c: *mut bindings::pwm_chip, + p: *mut bindings::pwm_device, + wh: *const c_void, + w: *mut bindings::pwm_waveform, + ) -> c_int { + // SAFETY: Relies on the function's contract that `c` and `p` are = valid pointers. + let (chip, pwm) =3D unsafe { (Chip::from_ptr(c), Device::from_ptr(= p)) }; + // SAFETY: `deserialize_wfhw`'s safety contract is met by this fun= ction's contract. + let wfhw =3D match unsafe { Self::deserialize_wfhw(wh) } { + Ok(v) =3D> v, + Err(e) =3D> return e.to_errno(), + }; + + let mut rust_wf =3D Waveform::default(); + match T::round_waveform_fromhw(chip, pwm, &wfhw, &mut rust_wf) { + Ok(ret) =3D> { + // SAFETY: `w` is guaranteed valid by the C caller. + unsafe { + *w =3D rust_wf.into(); + }; + ret + } + Err(e) =3D> e.to_errno(), + } + } + + /// # Safety + /// + /// Pointers from C must be valid. + unsafe extern "C" fn read_waveform_callback( + c: *mut bindings::pwm_chip, + p: *mut bindings::pwm_device, + wh: *mut c_void, + ) -> c_int { + // SAFETY: Relies on the function's contract that `c` and `p` are = valid pointers. + let (chip, pwm) =3D unsafe { (Chip::from_ptr(c), Device::from_ptr(= p)) }; + let parent_dev =3D match chip.parent_device() { + Some(dev) =3D> dev, + None =3D> { + return EINVAL.to_errno(); + } + }; + + let bound_parent =3D + // SAFETY: The PWM core guarantees the device is bound during callbacks. + unsafe { &*(parent_dev as *const device::Device as *const devi= ce::Device) }; + match T::read_waveform(chip, pwm, bound_parent) { + // SAFETY: `wh` is valid per this function's safety contract. + Ok(wfhw) =3D> match unsafe { Self::serialize_wfhw(&wfhw, wh) }= { + Ok(()) =3D> 0, + Err(e) =3D> e.to_errno(), + }, + Err(e) =3D> e.to_errno(), + } + } + + /// # Safety + /// + /// Pointers from C must be valid. + unsafe extern "C" fn write_waveform_callback( + c: *mut bindings::pwm_chip, + p: *mut bindings::pwm_device, + wh: *const c_void, + ) -> c_int { + // SAFETY: Relies on the function's contract that `c` and `p` are = valid pointers. + let (chip, pwm) =3D unsafe { (Chip::from_ptr(c), Device::from_ptr(= p)) }; + let parent_dev =3D match chip.parent_device() { + Some(dev) =3D> dev, + None =3D> { + return EINVAL.to_errno(); + } + }; + + let bound_parent =3D + // SAFETY: The PWM core guarantees the device is bound during cal= lbacks. + unsafe { &*(parent_dev as *const device::Device as *const devi= ce::Device) }; + // SAFETY: `wh` is valid per this function's safety contract. + let wfhw =3D match unsafe { Self::deserialize_wfhw(wh) } { + Ok(v) =3D> v, + Err(e) =3D> return e.to_errno(), + }; + match T::write_waveform(chip, pwm, &wfhw, bound_parent) { + Ok(()) =3D> 0, + Err(e) =3D> e.to_errno(), + } + } +} + +/// VTable structure wrapper for PWM operations. +/// Mirrors [`struct pwm_ops`](srctree/include/linux/pwm.h). +#[repr(transparent)] +pub struct PwmOpsVTable(Opaque); + +// SAFETY: PwmOpsVTable is Send. The vtable contains only function pointers +// and a size, which are simple data types that can be safely moved across +// threads. The thread-safety of calling these functions is handled by the +// kernel's locking mechanisms. +unsafe impl Send for PwmOpsVTable {} + +// SAFETY: PwmOpsVTable is Sync. The vtable is immutable after it is creat= ed, +// so it can be safely referenced and accessed concurrently by multiple th= reads +// e.g. to read the function pointers. +unsafe impl Sync for PwmOpsVTable {} + +impl PwmOpsVTable { + /// Returns a raw pointer to the underlying `pwm_ops` struct. + pub(crate) fn as_raw(&self) -> *const bindings::pwm_ops { + self.0.get() + } +} + +/// Creates a PWM operations vtable for a type `T` that implements `PwmOps= `. +/// +/// This is used to bridge Rust trait implementations to the C `struct pwm= _ops` +/// expected by the kernel. +pub const fn create_pwm_ops() -> PwmOpsVTable { + // SAFETY: `core::mem::zeroed()` is unsafe. For `pwm_ops`, all fields = are + // `Option` or data, so a zeroed pattern (None/0) = is valid initially. + let mut ops: bindings::pwm_ops =3D unsafe { core::mem::zeroed() }; + + ops.apply =3D Some(Adapter::::apply_callback); + ops.request =3D Some(Adapter::::request_callback); + ops.free =3D Some(Adapter::::free_callback); + ops.capture =3D Some(Adapter::::capture_callback); + ops.get_state =3D Some(Adapter::::get_state_callback); + + ops.round_waveform_tohw =3D Some(Adapter::::round_waveform_tohw_cal= lback); + ops.round_waveform_fromhw =3D Some(Adapter::::round_waveform_fromhw= _callback); + ops.read_waveform =3D Some(Adapter::::read_waveform_callback); + ops.write_waveform =3D Some(Adapter::::write_waveform_callback); + ops.sizeof_wfhw =3D core::mem::size_of::(); + + PwmOpsVTable(Opaque::new(ops)) +} --=20 2.34.1