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
// Copyright 2020-2021, Cerno
// Licensed under the MIT License
// See the LICENSE file or <http://opensource.org/licenses/MIT>

#![doc = include_str!("../README.md")]
#![warn(missing_debug_implementations)]
#![warn(missing_docs)]
#![warn(rust_2018_idioms)]
#![deny(clippy::all)]
#![deny(clippy::pedantic)]
#![deny(clippy::cargo)]
#![allow(clippy::cast_possible_wrap)]
#![allow(clippy::cast_sign_loss)]

use std::{
    fs::File,
    os::unix::io::{AsRawFd, FromRawFd, OwnedFd, RawFd},
};

mod ioctl;
use ioctl::dma_heap_alloc;
use ioctl::dma_heap_allocation_data;

use log::debug;
use strum_macros::Display;

/// Error Type for dma-heap
#[derive(thiserror::Error, Debug)]
pub enum Error {
    /// The requested DMA Heap doesn't exist
    #[error("The Requested DMA Heap Type ({0}) doesn't exist: {1}")]
    Missing(HeapKind, String),

    /// An Error occured while accessing the DMA Heap
    #[error("An Error occurred while accessing the DMA Heap")]
    Access(std::io::Error),

    /// The allocation is invalid
    #[error("The requested allocation is invalid: {0} bytes")]
    InvalidAllocation(usize),

    /// There is no memory left to allocate from the DMA Heap
    #[error("No Memory Left in the Heap")]
    NoMemoryLeft,
}

impl From<std::io::Error> for Error {
    fn from(err: std::io::Error) -> Self {
        Self::Access(err)
    }
}

/// Generic Result type with [Error] as its error variant
pub type Result<T> = std::result::Result<T, Error>;

/// Various Types of DMA-Buf Heap
#[derive(Clone, Copy, Debug, Display)]
pub enum HeapKind {
    /// A Heap backed by the Contiguous Memory Allocator in the Linux kernel, returning physically
    /// contiguous, cached, buffers
    Cma,

    /// A Heap backed by the vmalloc allocator in the Linux kernel, returning virtually contiguous,
    /// cached, buffers
    System,
}

/// Our DMA-Buf Heap
#[derive(Debug)]
pub struct Heap {
    file: File,
    name: HeapKind,
}

impl Heap {
    /// Opens A DMA-Buf Heap of the specified type
    ///
    /// # Errors
    ///
    /// Will return [Error] if the Heap Type is not found in the system, or if the open call fails.
    pub fn new(name: HeapKind) -> Result<Self> {
        let path = match name {
            HeapKind::Cma => "/dev/dma_heap/linux,cma",
            HeapKind::System => "/dev/dma_heap/system",
        };

        debug!("Using the {} DMA-Buf Heap, at {}", name, path);

        let file = File::open(path).map_err(|err| match err.kind() {
            std::io::ErrorKind::NotFound => Error::Missing(name, String::from(path)),
            _ => Error::from(err),
        })?;

        debug!("Heap found!");

        Ok(Self { file, name })
    }

    /// Allocates a DMA-Buf from the Heap with the specified size
    ///
    /// # Panics
    ///
    /// If the errno returned by the underlying `ioctl()` cannot be decoded
    /// into an `std::io::Error`.
    ///
    /// # Errors
    ///
    /// Will return [Error] if the underlying ioctl fails.
    pub fn allocate(&self, len: usize) -> Result<OwnedFd> {
        let mut fd_flags = nix::fcntl::OFlag::empty();

        fd_flags.insert(nix::fcntl::OFlag::O_CLOEXEC);
        fd_flags.insert(nix::fcntl::OFlag::O_RDWR);

        let mut data = dma_heap_allocation_data {
            len: len as u64,
            fd_flags: fd_flags.bits() as u32,
            ..dma_heap_allocation_data::default()
        };

        debug!("Allocating Buffer of size {} on {} Heap", len, self.name);

        unsafe { dma_heap_alloc(self.file.as_raw_fd(), &mut data) }.map_err(|err| {
            let err: std::io::Error = err.try_into().unwrap();

            match err.kind() {
                std::io::ErrorKind::InvalidInput => Error::InvalidAllocation(len),
                std::io::ErrorKind::OutOfMemory => Error::NoMemoryLeft,
                _ => Error::from(err),
            }
        })?;

        debug!("Allocation succeeded, Buffer File Descriptor {}", data.fd);

        // Safe because we have confirmed that the ioctl has succeeded, thus
        // the FD is valid.
        Ok(unsafe { OwnedFd::from_raw_fd(data.fd as RawFd) })
    }
}