maia_httpd/
uio.rs

1//! UIO device access.
2//!
3//! This module is used to work with UIO devices.
4
5use anyhow::Result;
6use std::os::unix::io::AsRawFd;
7use std::path::Path;
8use tokio::fs;
9use tokio::io::{AsyncReadExt, AsyncWriteExt};
10
11/// UIO device.
12///
13/// This struct represents an UIO device.
14#[derive(Debug)]
15pub struct Uio {
16    num: usize,
17    file: fs::File,
18}
19
20/// UIO device mapping.
21///
22/// This struct corresponds to a memory-mapped IO region of an UIO device, and
23/// gives access to the region. Dropping this struct unmaps the region.
24#[derive(Debug, Clone)]
25pub struct Mapping {
26    base: *mut libc::c_void,
27    effective: *mut libc::c_void,
28    map_size: usize,
29}
30
31impl Uio {
32    /// Opens an UIO using its number.
33    ///
34    /// This function opens the UIO device `/dev/uio<num>`, where `num` is the
35    /// parameter indicating the UIO device number.
36    pub async fn from_num(num: usize) -> Result<Uio> {
37        let file = fs::OpenOptions::new()
38            .read(true)
39            .write(true)
40            .open(format!("/dev/uio{num}"))
41            .await?;
42        Ok(Uio { num, file })
43    }
44
45    /// Opens an UIO using its name.
46    ///
47    /// This function searches in `/sys/class/uio` an UIO device whose name
48    /// matches the indicated one and opens it.
49    pub async fn from_name(name: &str) -> Result<Uio> {
50        match Self::find_by_name(name).await? {
51            Some(num) => Self::from_num(num).await,
52            None => anyhow::bail!("UIO device not found"),
53        }
54    }
55
56    async fn find_by_name(name: &str) -> Result<Option<usize>> {
57        let mut entries = fs::read_dir(Path::new("/sys/class/uio")).await?;
58        while let Some(entry) = entries.next_entry().await? {
59            let file_name = entry.file_name();
60            let uio = file_name
61                .to_str()
62                .ok_or_else(|| anyhow::anyhow!("file name is not valid UTF8"))?;
63            if let Some(num) = uio
64                .strip_prefix("uio")
65                .and_then(|a| a.parse::<usize>().ok())
66            {
67                let mut path = entry.path();
68                path.push("name");
69                let this_name = fs::read_to_string(path).await?;
70                if this_name.trim_end() == name {
71                    return Ok(Some(num));
72                }
73            }
74        }
75        Ok(None)
76    }
77
78    /// Maps a memory mapping of an UIO device.
79    ///
80    /// The `mapping` number is the number that corresponds to the mapping, as
81    /// listed in `/sys/class/uio/uio*/maps/map<mapping>`. Mappings are numbered
82    /// sequentially for each device, so devices that only support one mapping
83    /// use `0` as the value for `mapping`.
84    pub async fn map_mapping(&self, mapping: usize) -> Result<Mapping> {
85        let offset = mapping * page_size::get();
86        let fd = self.file.as_raw_fd();
87        let map_size = self.map_size(mapping).await?;
88
89        let base = unsafe {
90            match libc::mmap(
91                std::ptr::null_mut::<libc::c_void>(),
92                map_size,
93                libc::PROT_READ | libc::PROT_WRITE,
94                libc::MAP_SHARED,
95                fd,
96                offset as libc::off_t,
97            ) {
98                libc::MAP_FAILED => anyhow::bail!("mmap UIO failed"),
99                x => x,
100            }
101        };
102        let effective_offset = isize::try_from(self.map_offset(mapping).await?)?;
103        let effective = unsafe { base.offset(effective_offset) };
104        Ok(Mapping {
105            base,
106            effective,
107            map_size,
108        })
109    }
110
111    async fn read_mapping_hex(&self, mapping: usize, fname: &str) -> Result<usize> {
112        let n = fs::read_to_string(format!(
113            "/sys/class/uio/uio{}/maps/map{}/{}",
114            self.num, mapping, fname
115        ))
116        .await?;
117        Ok(usize::from_str_radix(
118            n.strip_prefix("0x")
119                .ok_or_else(|| anyhow::anyhow!("prefix 0x not present"))?
120                .trim_end(),
121            16,
122        )?)
123    }
124
125    /// Gives the size of a UIO mapping.
126    ///
127    /// The map size is obtained from the file
128    /// `/sys/class/uio/uio*/maps/map*/size`.
129    pub async fn map_size(&self, mapping: usize) -> Result<usize> {
130        self.read_mapping_hex(mapping, "size").await
131    }
132
133    /// Gives the offset of a UIO mapping.
134    ///
135    /// The offset is obtained from the file
136    /// `/sys/class/uio/uio*/maps/map*/offset`.
137    pub async fn map_offset(&self, mapping: usize) -> Result<usize> {
138        self.read_mapping_hex(mapping, "offset").await
139    }
140
141    /// Gives the address of a UIO mapping.
142    ///
143    /// The address is obtained from the file
144    /// `/sys/class/uio/uio*/maps/map*/address`.
145    pub async fn map_addr(&self, mapping: usize) -> Result<usize> {
146        self.read_mapping_hex(mapping, "addr").await
147    }
148
149    /// Enables interrupts.
150    ///
151    /// This function enables the interrupts for an UIO device by writing a `1`
152    /// to the corresponding character device.
153    pub async fn irq_enable(&mut self) -> Result<()> {
154        let bytes = 1u32.to_ne_bytes();
155        self.file.write_all(&bytes).await?;
156        Ok(())
157    }
158
159    /// Disables interrupts.
160    ///
161    /// This function disables the interrupts for an UIO device by writing a `0`
162    /// to the corresponding character device.
163    pub async fn irq_disable(&mut self) -> Result<()> {
164        let bytes = 0u32.to_ne_bytes();
165        self.file.write_all(&bytes).await?;
166        Ok(())
167    }
168
169    /// Waits for an interrupt.
170    ///
171    /// This function waits for an interrupt from a UIO device by reading from
172    /// the corresponding character device.
173    pub async fn irq_wait(&mut self) -> Result<u32> {
174        let mut bytes = [0; 4];
175        self.file.read_exact(&mut bytes).await?;
176        Ok(u32::from_ne_bytes(bytes))
177    }
178}
179
180impl Mapping {
181    /// Gives the virtual address of the mapping.
182    ///
183    /// This function returns a pointer to the beginning of the virtual address
184    /// space to which the device IO is mapped.
185    pub fn addr(&self) -> *mut libc::c_void {
186        self.effective
187    }
188}
189
190/// Unmaps the UIO device mapping.
191impl Drop for Mapping {
192    fn drop(&mut self) {
193        unsafe {
194            // TODO: control failure
195            libc::munmap(self.base, self.map_size);
196        }
197    }
198}