summaryrefslogtreecommitdiff
path: root/rust/kernel/delay.rs
blob: 1e987fa659419b738b500b26a2d001842a682362 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
// SPDX-License-Identifier: GPL-2.0

//! Delay functions for operations like sleeping.
//!
//! C header: [`include/linux/delay.h`](../../../../include/linux/delay.h)

use crate::bindings;
use core::{cmp::min, time::Duration};

const MILLIS_PER_SEC: u64 = 1_000;

fn coarse_sleep_conversion(duration: Duration) -> core::ffi::c_uint {
    let milli_as_nanos = Duration::MILLISECOND.subsec_nanos();

    // Rounds the nanosecond component of `duration` up to the nearest millisecond.
    let nanos_as_millis = duration.subsec_nanos().wrapping_add(milli_as_nanos - 1) / milli_as_nanos;

    // Saturates the second component of `duration` to `c_uint::MAX`.
    let seconds_as_millis = min(
        duration.as_secs().saturating_mul(MILLIS_PER_SEC),
        u64::from(core::ffi::c_uint::MAX),
    ) as core::ffi::c_uint;

    seconds_as_millis.saturating_add(nanos_as_millis)
}

/// Sleeps safely even with waitqueue interruptions.
///
/// This function forwards the call to the C side `msleep` function. As a result,
/// `duration` will be rounded up to the nearest millisecond if granularity less
/// than a millisecond is provided. Any [`Duration`] that exceeds
/// [`c_uint::MAX`][core::ffi::c_uint::MAX] in milliseconds is saturated.
///
/// # Examples
///
// Keep these in sync with `test_coarse_sleep_examples`.
/// ```
/// # use core::time::Duration;
/// # use kernel::delay::coarse_sleep;
/// coarse_sleep(Duration::ZERO);                   // Equivalent to `msleep(0)`.
/// coarse_sleep(Duration::from_nanos(1));          // Equivalent to `msleep(1)`.
///
/// coarse_sleep(Duration::from_nanos(1_000_000));  // Equivalent to `msleep(1)`.
/// coarse_sleep(Duration::from_nanos(1_000_001));  // Equivalent to `msleep(2)`.
/// coarse_sleep(Duration::from_nanos(1_999_999));  // Equivalent to `msleep(2)`.
///
/// coarse_sleep(Duration::from_millis(1));         // Equivalent to `msleep(1)`.
/// coarse_sleep(Duration::from_millis(2));         // Equivalent to `msleep(2)`.
///
/// coarse_sleep(Duration::from_secs(1));           // Equivalent to `msleep(1000)`.
/// coarse_sleep(Duration::new(1, 1));              // Equivalent to `msleep(1001)`.
/// coarse_sleep(Duration::new(1, 2));              // Equivalent to `msleep(1001)`.
/// ```
pub fn coarse_sleep(duration: Duration) {
    // SAFETY: `msleep` is safe for all values of its argument.
    unsafe { bindings::msleep(coarse_sleep_conversion(duration)) }
}

#[cfg(test)]
mod tests {
    use super::{coarse_sleep_conversion, MILLIS_PER_SEC};
    use core::time::Duration;

    #[test]
    fn test_coarse_sleep_examples() {
        // Keep these in sync with `coarse_sleep`'s `# Examples` section.

        assert_eq!(coarse_sleep_conversion(Duration::ZERO), 0);
        assert_eq!(coarse_sleep_conversion(Duration::from_nanos(1)), 1);

        assert_eq!(coarse_sleep_conversion(Duration::from_nanos(1_000_000)), 1);
        assert_eq!(coarse_sleep_conversion(Duration::from_nanos(1_000_001)), 2);
        assert_eq!(coarse_sleep_conversion(Duration::from_nanos(1_999_999)), 2);

        assert_eq!(coarse_sleep_conversion(Duration::from_millis(1)), 1);
        assert_eq!(coarse_sleep_conversion(Duration::from_millis(2)), 2);

        assert_eq!(coarse_sleep_conversion(Duration::from_secs(1)), 1000);
        assert_eq!(coarse_sleep_conversion(Duration::new(1, 1)), 1001);
        assert_eq!(coarse_sleep_conversion(Duration::new(1, 2)), 1001);
    }

    #[test]
    fn test_coarse_sleep_saturation() {
        assert!(
            coarse_sleep_conversion(Duration::new(
                core::ffi::c_uint::MAX as u64 / MILLIS_PER_SEC,
                0
            )) < core::ffi::c_uint::MAX
        );
        assert_eq!(
            coarse_sleep_conversion(Duration::new(
                core::ffi::c_uint::MAX as u64 / MILLIS_PER_SEC,
                999_999_999
            )),
            core::ffi::c_uint::MAX
        );

        assert_eq!(
            coarse_sleep_conversion(Duration::MAX),
            core::ffi::c_uint::MAX
        );
    }
}