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
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
//! UIO device access.
//!
//! This module is used to work with UIO devices.

use anyhow::Result;
use std::os::unix::io::AsRawFd;
use std::path::Path;
use tokio::fs;
use tokio::io::{AsyncReadExt, AsyncWriteExt};

/// UIO device.
///
/// This struct represents an UIO device.
#[derive(Debug)]
pub struct Uio {
    num: usize,
    file: fs::File,
}

/// UIO device mapping.
///
/// This struct corresponds to a memory-mapped IO region of an UIO device, and
/// gives access to the region. Dropping this struct unmaps the region.
#[derive(Debug, Clone)]
pub struct Mapping {
    base: *mut libc::c_void,
    effective: *mut libc::c_void,
    map_size: usize,
}

impl Uio {
    /// Opens an UIO using its number.
    ///
    /// This function opens the UIO device `/dev/uio<num>`, where `num` is the
    /// parameter indicating the UIO device number.
    pub async fn from_num(num: usize) -> Result<Uio> {
        let file = fs::OpenOptions::new()
            .read(true)
            .write(true)
            .open(format!("/dev/uio{num}"))
            .await?;
        Ok(Uio { num, file })
    }

    /// Opens an UIO using its name.
    ///
    /// This function searches in `/sys/class/uio` an UIO device whose name
    /// matches the indicated one and opens it.
    pub async fn from_name(name: &str) -> Result<Uio> {
        match Self::find_by_name(name).await? {
            Some(num) => Self::from_num(num).await,
            None => anyhow::bail!("UIO device not found"),
        }
    }

    async fn find_by_name(name: &str) -> Result<Option<usize>> {
        let mut entries = fs::read_dir(Path::new("/sys/class/uio")).await?;
        while let Some(entry) = entries.next_entry().await? {
            let file_name = entry.file_name();
            let uio = file_name
                .to_str()
                .ok_or_else(|| anyhow::anyhow!("file name is not valid UTF8"))?;
            if let Some(num) = uio
                .strip_prefix("uio")
                .and_then(|a| a.parse::<usize>().ok())
            {
                let mut path = entry.path();
                path.push("name");
                let this_name = fs::read_to_string(path).await?;
                if this_name.trim_end() == name {
                    return Ok(Some(num));
                }
            }
        }
        Ok(None)
    }

    /// Maps a memory mapping of an UIO device.
    ///
    /// The `mapping` number is the number that corresponds to the mapping, as
    /// listed in `/sys/class/uio/uio*/maps/map<mapping>`. Mappings are numbered
    /// sequentially for each device, so devices that only support one mapping
    /// use `0` as the value for `mapping`.
    pub async fn map_mapping(&self, mapping: usize) -> Result<Mapping> {
        let offset = mapping * page_size::get();
        let fd = self.file.as_raw_fd();
        let map_size = self.map_size(mapping).await?;

        let base = unsafe {
            match libc::mmap(
                std::ptr::null_mut::<libc::c_void>(),
                map_size,
                libc::PROT_READ | libc::PROT_WRITE,
                libc::MAP_SHARED,
                fd,
                offset as libc::off_t,
            ) {
                libc::MAP_FAILED => anyhow::bail!("mmap UIO failed"),
                x => x,
            }
        };
        let effective_offset = isize::try_from(self.map_offset(mapping).await?)?;
        let effective = unsafe { base.offset(effective_offset) };
        Ok(Mapping {
            base,
            effective,
            map_size,
        })
    }

    async fn read_mapping_hex(&self, mapping: usize, fname: &str) -> Result<usize> {
        let n = fs::read_to_string(format!(
            "/sys/class/uio/uio{}/maps/map{}/{}",
            self.num, mapping, fname
        ))
        .await?;
        Ok(usize::from_str_radix(
            n.strip_prefix("0x")
                .ok_or_else(|| anyhow::anyhow!("prefix 0x not present"))?
                .trim_end(),
            16,
        )?)
    }

    /// Gives the size of a UIO mapping.
    ///
    /// The map size is obtained from the file
    /// `/sys/class/uio/uio*/maps/map*/size`.
    pub async fn map_size(&self, mapping: usize) -> Result<usize> {
        self.read_mapping_hex(mapping, "size").await
    }

    /// Gives the offset of a UIO mapping.
    ///
    /// The offset is obtained from the file
    /// `/sys/class/uio/uio*/maps/map*/offset`.
    pub async fn map_offset(&self, mapping: usize) -> Result<usize> {
        self.read_mapping_hex(mapping, "offset").await
    }

    /// Gives the address of a UIO mapping.
    ///
    /// The address is obtained from the file
    /// `/sys/class/uio/uio*/maps/map*/address`.
    pub async fn map_addr(&self, mapping: usize) -> Result<usize> {
        self.read_mapping_hex(mapping, "addr").await
    }

    /// Enables interrupts.
    ///
    /// This function enables the interrupts for an UIO device by writing a `1`
    /// to the corresponding character device.
    pub async fn irq_enable(&mut self) -> Result<()> {
        let bytes = 1u32.to_ne_bytes();
        self.file.write_all(&bytes).await?;
        Ok(())
    }

    /// Disables interrupts.
    ///
    /// This function disables the interrupts for an UIO device by writing a `0`
    /// to the corresponding character device.
    pub async fn irq_disable(&mut self) -> Result<()> {
        let bytes = 0u32.to_ne_bytes();
        self.file.write_all(&bytes).await?;
        Ok(())
    }

    /// Waits for an interrupt.
    ///
    /// This function waits for an interrupt from a UIO device by reading from
    /// the corresponding character device.
    pub async fn irq_wait(&mut self) -> Result<u32> {
        let mut bytes = [0; 4];
        self.file.read_exact(&mut bytes).await?;
        Ok(u32::from_ne_bytes(bytes))
    }
}

impl Mapping {
    /// Gives the virtual address of the mapping.
    ///
    /// This function returns a pointer to the beginning of the virtual address
    /// space to which the device IO is mapped.
    pub fn addr(&self) -> *mut libc::c_void {
        self.effective
    }
}

/// Unmaps the UIO device mapping.
impl Drop for Mapping {
    fn drop(&mut self) {
        unsafe {
            // TODO: control failure
            libc::munmap(self.base, self.map_size);
        }
    }
}