Skip to content

Commit eafcc70

Browse files
authored
RwLock::read for UFT hits, kstats use AtomicU64 (#636)
This PR replaces the per-`Port` `KMutex` with a `KRwLock`. This allows ioctls to read port state without preventing packets from being served. This also prevents successful UFT hits from blocking one another on the port for any length of time. A requirement is that we move to atomic accesses on kstat values, which allows us to update kstats from outside a write-lock on the port. Contention according to lockstat is solved, and apparently the time spent in `xde_rx`/`xde_mc_tx` is reduced -- we're no longer: * S2C -- the 3rd and 10th most spin-happy locks, * C2S -- the 2rd and 3rd most spin-happy locks. After some refactoring, this has moved from 2.8->2.9 Gbps (pre-LSO) on `glasgow`. Nor are we egregiously blocking on the port lock. Closes #435.
1 parent 0f8e145 commit eafcc70

File tree

4 files changed

+300
-254
lines changed

4 files changed

+300
-254
lines changed

crates/illumos-sys-hdrs/src/lib.rs

+36-26
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// License, v. 2.0. If a copy of the MPL was not distributed with this
33
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
44

5-
// Copyright 2024 Oxide Computer Company
5+
// Copyright 2025 Oxide Computer Company
66
#![cfg_attr(feature = "kernel", feature(extern_types))]
77
#![allow(non_camel_case_types)]
88
#![no_std]
@@ -13,6 +13,10 @@ pub mod kernel;
1313
pub use kernel::*;
1414

1515
use core::ptr;
16+
use core::sync::atomic::AtomicI32;
17+
use core::sync::atomic::AtomicI64;
18+
use core::sync::atomic::AtomicU32;
19+
use core::sync::atomic::AtomicU64;
1620

1721
// The following are "C type" aliases for native Rust types so that
1822
// the native illumos structures may be defined almost verbatim to the
@@ -117,8 +121,27 @@ impl kstat_named_t {
117121
Self::default()
118122
}
119123

120-
pub fn val_u64(&self) -> u64 {
121-
unsafe { self.value._u64 }
124+
/// Validates at compile time whether `self._u64` can be safely used as
125+
/// an AtomicU64.
126+
const KSTAT_ATOMIC_U64_SAFE: () = if align_of::<KStatNamedValue>() % 8 == 0
127+
{
128+
} else {
129+
panic!("Platform does not meet u64 8B alignment for AtomicU64");
130+
};
131+
132+
/// Interprets the value of this named kstat as a `u64`.
133+
///
134+
/// # SAFETY
135+
/// Callers must ensure that `self` is interpreted as only *one* class of
136+
/// atomic integer. Mixed-width atomic read/writes on the same kstat are
137+
/// undefined behaviour.
138+
#[inline(always)]
139+
#[allow(clippy::let_unit_value)]
140+
pub unsafe fn as_u64(&self) -> &AtomicU64 {
141+
_ = Self::KSTAT_ATOMIC_U64_SAFE;
142+
// SAFETY: KStatNamedValue must have 8B alignment on target platform.
143+
// Validated by compile time check in `KSTAT_ATOMIC_U64_SAFE`.
144+
unsafe { &self.value._u64 }
122145
}
123146
}
124147

@@ -132,33 +155,20 @@ impl Default for kstat_named_t {
132155
}
133156
}
134157

158+
use core::mem::ManuallyDrop;
159+
135160
#[repr(C)]
136161
pub union KStatNamedValue {
137162
_c: [c_char; 16],
138-
_i32: i32,
139-
_u32: u32,
140-
_i64: i64,
141-
_u64: u64,
142-
}
143-
144-
impl core::ops::AddAssign<u64> for KStatNamedValue {
145-
#[inline]
146-
fn add_assign(&mut self, other: u64) {
147-
unsafe { self._u64 += other };
148-
}
149-
}
150163

151-
impl core::ops::SubAssign<u64> for KStatNamedValue {
152-
#[inline]
153-
fn sub_assign(&mut self, other: u64) {
154-
unsafe { self._u64 -= other };
155-
}
156-
}
157-
158-
impl KStatNamedValue {
159-
pub fn set_u64(&mut self, val: u64) {
160-
self._u64 = val;
161-
}
164+
// ManuallyDrop or Copy are required in a union (to guarantee
165+
// no drop code runs).
166+
// We know that atomic integer types have no `Drop` impl, so
167+
// these are equivalent to the `Atomic` types.
168+
_i32: ManuallyDrop<AtomicI32>,
169+
_u32: ManuallyDrop<AtomicU32>,
170+
_i64: ManuallyDrop<AtomicI64>,
171+
_u64: ManuallyDrop<AtomicU64>,
162172
}
163173

164174
pub const KSTAT_FLAG_VIRTUAL: c_int = 0x1;

lib/opte/src/ddi/kstat.rs

+32-36
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// License, v. 2.0. If a copy of the MPL was not distributed with this
33
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
44

5-
// Copyright 2023 Oxide Computer Company
5+
// Copyright 2025 Oxide Computer Company
66

77
//! Export Rust structs as illumos kstats.
88
//!
@@ -11,6 +11,8 @@ use alloc::boxed::Box;
1111
use alloc::string::String;
1212
use core::fmt;
1313
use core::fmt::Display;
14+
use core::sync::atomic::AtomicU64;
15+
use core::sync::atomic::Ordering;
1416

1517
pub use kstat_macro::KStatProvider;
1618

@@ -29,7 +31,7 @@ cfg_if! {
2931
///
3032
/// An implementation of this trait acts as a provider of "named"
3133
/// kstats (i.e. `KSTAT_TYPE_NAMED` in `kstat_create(9F)`). The kstats
32-
/// are always virtual (`KSTAT_FLAG_VRITUAL`), meaning that the
34+
/// are always virtual (`KSTAT_FLAG_VIRTUAL`), meaning that the
3335
/// allocation of the kstat data is performed by the caller (Rust).
3436
///
3537
/// Rather than implementing this trait manually, the kstat-macro
@@ -217,39 +219,18 @@ impl KStatU64 {
217219
Ok(())
218220
}
219221

220-
pub fn new() -> Self {
221-
Self { inner: kstat_named_t::new() }
222-
}
223-
224-
pub fn set(&mut self, val: u64) {
225-
self.inner.value.set_u64(val);
226-
}
227-
228-
pub fn val(&self) -> u64 {
229-
self.inner.val_u64()
230-
}
231-
}
232-
233-
#[cfg(all(not(feature = "std"), not(test)))]
234-
impl core::ops::AddAssign<u64> for KStatU64 {
235-
#[inline]
236-
fn add_assign(&mut self, other: u64) {
237-
self.inner.value += other;
238-
}
239-
}
240-
241-
#[cfg(all(not(feature = "std"), not(test)))]
242-
impl core::ops::SubAssign<u64> for KStatU64 {
243-
#[inline]
244-
fn sub_assign(&mut self, other: u64) {
245-
self.inner.value -= other;
222+
#[inline(always)]
223+
fn inner(&self) -> &AtomicU64 {
224+
// SAFETY: only `inner.as_u64` is called on this type, so mixed-width
225+
// reads/writes are disallowed.
226+
unsafe { self.inner.as_u64() }
246227
}
247228
}
248229

249230
#[cfg(any(feature = "std", test))]
250231
#[derive(Default)]
251232
pub struct KStatU64 {
252-
value: u64,
233+
value: AtomicU64,
253234
}
254235

255236
#[cfg(any(feature = "std", test))]
@@ -258,30 +239,45 @@ impl KStatU64 {
258239
Ok(())
259240
}
260241

242+
#[inline(always)]
243+
fn inner(&self) -> &AtomicU64 {
244+
&self.value
245+
}
246+
}
247+
248+
impl KStatU64 {
261249
pub fn new() -> Self {
262250
Self::default()
263251
}
264252

265-
pub fn set(&mut self, val: u64) {
266-
self.value = val;
253+
pub fn set(&self, val: u64) {
254+
self.inner().store(val, Ordering::Relaxed)
267255
}
268256

269257
pub fn val(&self) -> u64 {
270-
self.value
258+
self.inner().load(Ordering::Relaxed)
259+
}
260+
261+
pub fn incr(&self, val: u64) {
262+
self.inner().fetch_add(val, Ordering::Relaxed);
263+
}
264+
265+
pub fn decr(&self, val: u64) {
266+
self.inner().fetch_sub(val, Ordering::Relaxed);
271267
}
272268
}
273269

274-
#[cfg(any(feature = "std", test))]
275270
impl core::ops::AddAssign<u64> for KStatU64 {
271+
#[inline]
276272
fn add_assign(&mut self, other: u64) {
277-
self.value += other;
273+
self.incr(other);
278274
}
279275
}
280276

281-
#[cfg(any(feature = "std", test))]
282277
impl core::ops::SubAssign<u64> for KStatU64 {
278+
#[inline]
283279
fn sub_assign(&mut self, other: u64) {
284-
self.value -= other;
280+
self.decr(other);
285281
}
286282
}
287283

lib/opte/src/engine/layer.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -745,7 +745,7 @@ impl Layer {
745745

746746
// Unwrap: We know this is fine because the stat names are
747747
// generated from the LayerStats structure.
748-
let mut stats = KStatNamed::new(
748+
let stats = KStatNamed::new(
749749
"xde",
750750
&format!("{}_{}", port, name),
751751
LayerStats::new(),

0 commit comments

Comments
 (0)