From nobody Mon Sep 28 10:44:09 2026 Received: from mail-43172.protonmail.ch (mail-43172.protonmail.ch [185.70.43.172]) (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 7DE6B33A9D6 for ; Sun, 23 Aug 2026 10:51:10 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=185.70.43.172 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787482273; cv=none; b=PXpkog5+GGTm1VvRtJLH2VEzq59z/llheKOI61DdIYv4lshBBqJ3TcXww+G2HvUMeiDNiQXkmrRK0DfDip20HIV2tqH46o7gl1AGiD0EsYhDLSwWPSrvTPz6iyWGWCV1n9SOdDaC5i2Lrmk4fik+twOADukfXvnOyGtK8LipIdo= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787482273; c=relaxed/simple; bh=+sDav60/7pt8/HE2JYkGOPK4IzU3WO2QSuIFtXJRTL0=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=BhuR1rVgjzxOUhL0Niq/6GQHCb6KMGsb0RhP9cKOlXuBJEGqjxTBPgQSCJCQsCERhwAgEPnqK5ox4Nd4hII3FPQ3ORG4D3YOEaZz4hwm6l8iUlMJNDosemIicfRYerr08W5nT13c6XwMFJi28kADvruOaqVVS8NJsjXGAXXn0I8= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=onurozkan.dev; spf=pass smtp.mailfrom=onurozkan.dev; dkim=pass (2048-bit key) header.d=onurozkan.dev header.i=@onurozkan.dev header.b=ESEovgHG; arc=none smtp.client-ip=185.70.43.172 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=onurozkan.dev Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=onurozkan.dev Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=onurozkan.dev header.i=@onurozkan.dev header.b="ESEovgHG" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=onurozkan.dev; s=protonmail; t=1787482261; x=1787741461; bh=Hur3ADIaCcusTzxbTebC/Ylg9Grzu5lROGSsmsYOWzQ=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References:From:To: Cc:Date:Subject:Reply-To:Feedback-ID:Message-ID:BIMI-Selector; b=ESEovgHGVHLijgbnL8oti06E/PO9OGSetFj3QMVIW7khpByMuL8p70O1dMRg4Q0Ls kvFlw49DLC8xVA6rt+CmNYqnkv0A/XeKidWYIjv2uQblVLTbOSYT15hcjikSlxuT3c kUfYBmfWeK0+UqBPnsvSJ0cWkznNWxoc2t7ODbjdbTQ2cMHNMMY3L0ia9R8wiJ18So Ek1nEgEWugDcAEwfw/bCVxmu9mczizYCXqigwy29+FIvUHgvoV5uC8QGMdPw/1Ww+J VycWmnKceXnGQxNkqdCSsCaNqF1rxyUYmwdKFueFHN7QNspcPm+ZbsRjgXX6FNGZ99 vCghstrT2Ap/Q== X-Pm-Submission-Id: 4hSW6n54NPz1DDX4 From: =?UTF-8?q?Onur=20=C3=96zkan?= To: linux-kernel@vger.kernel.org, rust-for-linux@vger.kernel.org, dri-devel@lists.freedesktop.org Cc: dakr@kernel.org, aliceryhl@google.com, daniel.almeida@collabora.com, airlied@gmail.com, simona@ffwll.ch, ojeda@kernel.org, boqun@kernel.org, gary@garyguo.net, bjorn3_gh@protonmail.com, lossin@kernel.org, a.hindborg@kernel.org, tmgross@umich.edu, =?UTF-8?q?Onur=20=C3=96zkan?= Subject: [PATCH 1/2] drm/tyr: move reset work to platform driver data Date: Sun, 23 Aug 2026 13:50:07 +0300 Message-ID: <20260823-tyr-gpu-unplug-b4-v1-1-e99c07d4c785@onurozkan.dev> X-Mailer: git-send-email 2.51.2 In-Reply-To: <20260823-tyr-gpu-unplug-b4-v1-0-e99c07d4c785@onurozkan.dev> References: <20260823-tyr-gpu-unplug-b4-v1-0-e99c07d4c785@onurozkan.dev> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Move ResetHandle out of DRM registration data and store it as the first field of platform driver data. This makes platform teardown drain queued or running reset work before dropping the DRM registration and the resources it owns. Signed-off-by: Onur =C3=96zkan Reviewed-by: Daniel Almeida --- drivers/gpu/drm/tyr/driver.rs | 172 ++++++++++++++++++++++----------------= ---- 1 file changed, 89 insertions(+), 83 deletions(-) diff --git a/drivers/gpu/drm/tyr/driver.rs b/drivers/gpu/drm/tyr/driver.rs index c326192f8af2..92ed0de0b4e4 100644 --- a/drivers/gpu/drm/tyr/driver.rs +++ b/drivers/gpu/drm/tyr/driver.rs @@ -51,8 +51,13 @@ =20 #[pin_data(PinnedDrop)] pub(crate) struct TyrPlatformDriverData<'bound> { - _device: ARef, + // `ResetHandle::drop()` drains queued/running works and this must hap= pen + // before clocks/regulators are dropped. So keep this field before the= m to + // ensure the correct drop order. + #[pin] + _reset: reset::ResetHandle<'bound>, _reg: drm::Registration<'bound, TyrDrmDriver>, + _device: ARef, } =20 /// Resources kept alive by the DRM registration. @@ -61,12 +66,6 @@ pub(crate) struct TyrDrmRegistrationData<'bound> { /// Parent platform device. pub(crate) pdev: &'bound platform::Device, =20 - // `ResetHandle::drop()` drains queued/running works and this must hap= pen - // before clocks/regulators are dropped. So keep this field before the= m to - // ensure the correct drop order. - #[pin] - pub(crate) reset: reset::ResetHandle<'bound>, - /// Firmware sections. pub(crate) fw: Arc>, =20 @@ -101,82 +100,89 @@ fn probe<'bound>( pdev: &'bound platform::Device>, _info: Option<&'bound Self::IdInfo>, ) -> impl PinInit, Error> + 'bound { - let core_clk =3D Clk::get(pdev.as_ref(), Some(c"core"))?; - let stacks_clk =3D OptionalClk::get(pdev.as_ref(), Some(c"stacks")= )?; - let coregroup_clk =3D OptionalClk::get(pdev.as_ref(), Some(c"coreg= roup"))?; - - core_clk.prepare_enable()?; - stacks_clk.prepare_enable()?; - coregroup_clk.prepare_enable()?; - - let mali_regulator =3D Regulator::::get(pdev.a= s_ref(), c"mali")?; - let sram_regulator =3D Regulator::::get(pdev.a= s_ref(), c"sram")?; - - let request =3D pdev.io_request_by_index(0).ok_or(ENODEV)?; - - let hw =3D Arc::pin_init( - reset::HwGate::new(request.iomap_sized::()?), - GFP_KERNEL, - )?; - - reset::run_reset(pdev.as_ref(), &hw)?; - - let gpu_info =3D { - let hw_guard =3D hw.access(); - let gpu_info =3D GpuInfo::new(hw_guard.iomem()); - gpu_info.log(pdev.as_ref()); - gpu_info - }; - - let pa_bits =3D MMU_FEATURES::from_raw(gpu_info.mmu_features) - .pa_bits() - .get(); - // SAFETY: No concurrent DMA allocations or mappings can be made b= ecause - // the device is still being probed and therefore isn't being used= by - // other threads of execution. - unsafe { pdev.dma_set_mask_and_coherent(DmaMask::try_new(pa_bits)?= )? }; - - let unreg_dev =3D drm::UnregisteredDevice::::new(pde= v, Ok(()))?; - - let mmu =3D Mmu::new(hw.clone(), &gpu_info)?; - - let firmware =3D Firmware::new(pdev, hw.clone(), &unreg_dev, mmu.a= s_arc_borrow(), &gpu_info)?; - - firmware.boot()?; - firmware.enable_global_interface(&gpu_info, &core_clk)?; - - let reg_data =3D try_pin_init!(TyrDrmRegistrationData { - pdev, - // SAFETY: `ResetHandle` is stored in registration data cr= eated with `new_with_lt` - // and is dropped before the borrowed device and MMIO refe= rences expire. - reset <- unsafe { reset::ResetHandle::new(pdev, hw.clone()= )? }, - fw: firmware, - clks <- new_mutex!(Clocks { - core: core_clk, - stacks: stacks_clk, - coregroup: coregroup_clk, - }), - regulators <- new_mutex!(Regulators { - _mali: mali_regulator, - _sram: sram_regulator, - }), - gpu_info, - }); - - // SAFETY: `reg` is stored in the platform driver data and is not = leaked or - // forgotten, so it is dropped before the `'bound` registration da= ta can become - // invalid. - let reg =3D unsafe { drm::Registration::new_with_lt(pdev.as_ref(),= unreg_dev, reg_data, 0)? }; - - let driver =3D TyrPlatformDriverData { - _device: reg.device().into(), - _reg: reg, - }; - - // We need this to be dev_info!() because dev_dbg!() does not work= at - // all in Rust for now, and we need to see whether probe succeeded. - dev_info!(pdev, "Tyr initialized correctly.\n"); - Ok(driver) + pin_init::pin_init_scope(move || { + let core_clk =3D Clk::get(pdev.as_ref(), Some(c"core"))?; + let stacks_clk =3D OptionalClk::get(pdev.as_ref(), Some(c"stac= ks"))?; + let coregroup_clk =3D OptionalClk::get(pdev.as_ref(), Some(c"c= oregroup"))?; + + core_clk.prepare_enable()?; + stacks_clk.prepare_enable()?; + coregroup_clk.prepare_enable()?; + + let mali_regulator =3D Regulator::::get(pd= ev.as_ref(), c"mali")?; + let sram_regulator =3D Regulator::::get(pd= ev.as_ref(), c"sram")?; + + let request =3D pdev.io_request_by_index(0).ok_or(ENODEV)?; + + let hw =3D Arc::pin_init( + reset::HwGate::new(request.iomap_sized::()?), + GFP_KERNEL, + )?; + + reset::run_reset(pdev.as_ref(), &hw)?; + + let gpu_info =3D { + let hw_guard =3D hw.access(); + let gpu_info =3D GpuInfo::new(hw_guard.iomem()); + gpu_info.log(pdev.as_ref()); + gpu_info + }; + + let pa_bits =3D MMU_FEATURES::from_raw(gpu_info.mmu_features) + .pa_bits() + .get(); + // SAFETY: No concurrent DMA allocations or mappings can be ma= de because + // the device is still being probed and therefore isn't being = used by + // other threads of execution. + unsafe { pdev.dma_set_mask_and_coherent(DmaMask::try_new(pa_bi= ts)?)? }; + + let unreg_dev =3D drm::UnregisteredDevice::::new= (pdev, Ok(()))?; + + let mmu =3D Mmu::new(hw.clone(), &gpu_info)?; + + let firmware =3D + Firmware::new(pdev, hw.clone(), &unreg_dev, mmu.as_arc_bor= row(), &gpu_info)?; + + firmware.boot()?; + firmware.enable_global_interface(&gpu_info, &core_clk)?; + + let reg_data =3D try_pin_init!(TyrDrmRegistrationData { + pdev, + fw: firmware, + clks <- new_mutex!(Clocks { + core: core_clk, + stacks: stacks_clk, + coregroup: coregroup_clk, + }), + regulators <- new_mutex!(Regulators { + _mali: mali_regulator, + _sram: sram_regulator, + }), + gpu_info, + }); + + // SAFETY: `reg` is stored in the platform driver data and is = not leaked or + // forgotten, so it is dropped before the `'bound` registratio= n data can become + // invalid. + let reg =3D + unsafe { drm::Registration::new_with_lt(pdev.as_ref(), unr= eg_dev, reg_data, 0)? }; + let device =3D reg.device().into(); + + let driver =3D try_pin_init!(TyrPlatformDriverData { + // SAFETY: `ResetHandle` is stored in platform driver data= and is + // dropped before the borrowed device and MMIO references = expire. + _reset <- unsafe { reset::ResetHandle::new(pdev, hw)? }, + _reg: reg, + _device: device, + _: { + // We need this to be dev_info!() because dev_dbg!() d= oes not work at + // all in Rust for now, and we need to see whether pro= be succeeded. + dev_info!(pdev, "Tyr initialized correctly.\n"); + }, + }); + + Ok(driver) + }) } } =20 --=20 2.51.2 From nobody Mon Sep 28 10:44:09 2026 Received: from mail-106112.protonmail.ch (mail-106112.protonmail.ch [79.135.106.112]) (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 9D917374E5B; Sun, 23 Aug 2026 10:51:11 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=79.135.106.112 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787482274; cv=none; b=cRfiKrYAENYNdLYa7dX42kdB69AsM/6RHMzEWj5RAS3lJE5TvMwrGQbJoOAFL6D0+8hnyuXxuvSF/kPtY5YOt7scIthstyVyWNEEPZxnh3RvlReZtg7ffWpIxMSidk0Byelsih/zo+r46eEUY8px/Aq7FzbWG5e5+ZC9vbKGz4c= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787482274; c=relaxed/simple; bh=oLzS/Igw/aZQHGD+SUtGCySa4eUScVnegvA/P3xYDqU=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=rmOynpL9Q4JWiZDIG+sCFp7TxIzo9owK84NKy9eJDKLanPvYGbKh0oBcp1WpLsoQzg1Sr/4kRq5hHyLzrK2eab/BpL7QWVRIkaenTgxD9D//4WaiI0ABK0d3Gyebwj2faWe6RJXWzk3+lpZnrQuSrjTHD7wS1gUt0urXoJ1U7K4= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=onurozkan.dev; spf=pass smtp.mailfrom=onurozkan.dev; dkim=pass (2048-bit key) header.d=onurozkan.dev header.i=@onurozkan.dev header.b=geTsZfBG; arc=none smtp.client-ip=79.135.106.112 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=onurozkan.dev Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=onurozkan.dev Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=onurozkan.dev header.i=@onurozkan.dev header.b="geTsZfBG" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=onurozkan.dev; s=protonmail; t=1787482263; x=1787741463; bh=ndGal1cXVZUbezixECVIKJYKCvb9q7L6KKpWhIR5QSk=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References:From:To: Cc:Date:Subject:Reply-To:Feedback-ID:Message-ID:BIMI-Selector; b=geTsZfBGJutpILb5vWiA2fufhYvDRzHo/ZXng50lfiSWLz/DrTJMzm7irYZkACsTA ZO6WPrET4ylLh+BqI007YyHVlHV/Q3jyBstnET+CfBFzXFonEz5LIhojaJLlyw+gYZ FLHJrYN2se0Mhyn9Q5I+c7f9d4//HHn07eUtSoqj8GHWO8cPZQzezK6CK7hiwtV+Vt T+34yrmjwAvZhYDQbAeOM5hBHL9GsnFdygNuIMbmKiV7JyA1kvy1ONvIwEt2q3Etdj jDOFNHpLSD0t6Omduf2c8sCqBDmh6CkXbE9Fa6rjtCKaXrXms4o88i/DRTCrxtehK+ QNw+oYDwxWPGQ== X-Pm-Submission-Id: 4hSW6r4w0dz1DDWs From: =?UTF-8?q?Onur=20=C3=96zkan?= To: linux-kernel@vger.kernel.org, rust-for-linux@vger.kernel.org, dri-devel@lists.freedesktop.org Cc: dakr@kernel.org, aliceryhl@google.com, daniel.almeida@collabora.com, airlied@gmail.com, simona@ffwll.ch, ojeda@kernel.org, boqun@kernel.org, gary@garyguo.net, bjorn3_gh@protonmail.com, lossin@kernel.org, a.hindborg@kernel.org, tmgross@umich.edu, =?UTF-8?q?Onur=20=C3=96zkan?= Subject: [PATCH 2/2] drm/tyr: unplug the GPU when reset fails Date: Sun, 23 Aug 2026 13:50:08 +0300 Message-ID: <20260823-tyr-gpu-unplug-b4-v1-2-e99c07d4c785@onurozkan.dev> X-Mailer: git-send-email 2.51.2 In-Reply-To: <20260823-tyr-gpu-unplug-b4-v1-0-e99c07d4c785@onurozkan.dev> References: <20260823-tyr-gpu-unplug-b4-v1-0-e99c07d4c785@onurozkan.dev> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Wrap the DRM registration in Arc> and share it with the reset controller. If a reset fails, stop reset scheduling and revoke the registration. Dropping the registration calls drm_dev_unplug() which prevents new DRM critical sections and waits for existing ones before the registration data is released. Signed-off-by: Onur =C3=96zkan --- drivers/gpu/drm/tyr/driver.rs | 12 +++++++----- drivers/gpu/drm/tyr/reset.rs | 32 +++++++++++++++++++++----------- 2 files changed, 28 insertions(+), 16 deletions(-) diff --git a/drivers/gpu/drm/tyr/driver.rs b/drivers/gpu/drm/tyr/driver.rs index 92ed0de0b4e4..03ad00a89420 100644 --- a/drivers/gpu/drm/tyr/driver.rs +++ b/drivers/gpu/drm/tyr/driver.rs @@ -22,6 +22,7 @@ prelude::*, regulator, regulator::Regulator, + revocable::Revocable, sizes::SZ_2M, sync::{ aref::ARef, @@ -56,7 +57,7 @@ pub(crate) struct TyrPlatformDriverData<'bound> { // ensure the correct drop order. #[pin] _reset: reset::ResetHandle<'bound>, - _reg: drm::Registration<'bound, TyrDrmDriver>, + _reg: Arc>>, _device: ARef, } =20 @@ -161,17 +162,18 @@ fn probe<'bound>( gpu_info, }); =20 - // SAFETY: `reg` is stored in the platform driver data and is = not leaked or - // forgotten, so it is dropped before the `'bound` registratio= n data can become - // invalid. + // SAFETY: `reg` is wrapped in a `Revocable` owned by the plat= form driver + // data and its reset handle. Neither is leaked or forgotten, = so `reg` is + // dropped before the `'bound` registration data can become in= valid. let reg =3D unsafe { drm::Registration::new_with_lt(pdev.as_ref(), unr= eg_dev, reg_data, 0)? }; let device =3D reg.device().into(); + let reg =3D Arc::pin_init(Revocable::new(reg), GFP_KERNEL)?; =20 let driver =3D try_pin_init!(TyrPlatformDriverData { // SAFETY: `ResetHandle` is stored in platform driver data= and is // dropped before the borrowed device and MMIO references = expire. - _reset <- unsafe { reset::ResetHandle::new(pdev, hw)? }, + _reset <- unsafe { reset::ResetHandle::new(pdev, hw, reg.c= lone())? }, _reg: reg, _device: device, _: { diff --git a/drivers/gpu/drm/tyr/reset.rs b/drivers/gpu/drm/tyr/reset.rs index 1abcd25877d3..50690daec31a 100644 --- a/drivers/gpu/drm/tyr/reset.rs +++ b/drivers/gpu/drm/tyr/reset.rs @@ -28,12 +28,14 @@ Bound, Device, // }, + drm, io::{ poll, Io, // }, platform, prelude::*, + revocable::Revocable, sync::{ atomic::{ Atomic, @@ -53,7 +55,10 @@ }; =20 use crate::{ - driver::IoMem, + driver::{ + IoMem, + TyrDrmDriver, // + }, gpu, regs::gpu_control::*, // }; @@ -87,6 +92,8 @@ struct Controller<'ctrl> { state: Atomic, /// Shared gate that coordinates hardware access with GPU reset. hw: Arc>, + /// DRM registration revoked when a reset fails. + registration: Arc>>, } =20 impl<'ctrl> ScopedWorkItem for Controller<'ctrl> { @@ -100,11 +107,13 @@ impl<'ctrl> Controller<'ctrl> { fn new( pdev: &'ctrl platform::Device, hw: Arc>, + registration: Arc= >>, ) -> impl PinInit { try_pin_init!(Self { pdev, state: Atomic::new(ResetState::Idle), hw, + registration, }) } =20 @@ -119,8 +128,8 @@ fn try_transition(&self, from: ResetState, to: ResetSta= te) -> bool { /// If the pending reset cannot be claimed, the worker returns immedia= tely. /// /// It first claims [`ResetState::Pending`], then waits for earlier ha= rdware - /// accesses to complete before issuing the reset and returning the wo= rker - /// state to [`ResetState::Idle`]. + /// accesses to complete before issuing the reset. A successful reset = returns + /// the worker state to [`ResetState::Idle`]; a failure unplugs the DR= M device. /// /// Panthor reference: /// - drivers/gpu/drm/panthor/panthor_device.c::panthor_device_reset_w= ork() @@ -134,16 +143,13 @@ fn reset_work(&self) { let reset_result =3D run_reset(self.pdev.as_ref(), &self.hw); =20 if let Err(e) =3D reset_result { - dev_err!(self.pdev, "GPU reset failed: {:?}\n", e); - - // TODO: Unplug the GPU. - // There is no API for unplugging the GPU and this is unreacha= ble - // for now since there are no hardware users for reset API. + dev_err!(self.pdev, "GPU reset failed, unplugging the GPU: {:?= }\n", e); + let _ =3D self.try_transition(ResetState::InProgress, ResetSta= te::ShuttingDown); + self.registration.revoke(); } else { dev_dbg!(self.pdev, "GPU reset completed.\n"); + let _ =3D self.try_transition(ResetState::InProgress, ResetSta= te::Idle); } - - let _ =3D self.try_transition(ResetState::InProgress, ResetState::= Idle); } } =20 @@ -168,9 +174,13 @@ impl<'reset> ResetHandle<'reset> { pub(crate) unsafe fn new( pdev: &'reset platform::Device, hw: Arc>, + registration: Arc>>, ) -> Result> { Ok(try_pin_init!(Self { - controller <- kernel::new_scoped_work!("tyr::reset", Controlle= r::new(pdev, hw)), + controller <- kernel::new_scoped_work!( + "tyr::reset", + Controller::new(pdev, hw, registration) + ), // SAFETY: The caller guarantees the handle is dropped. wq: unsafe { ScopedQueue::new(c"tyr-reset-wq")? }, })) --=20 2.51.2