summaryrefslogtreecommitdiff
path: root/drivers/gpu/drm/nova/file.rs
blob: a05264917a0b2dd390b924729c4f17f135c43d3b (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
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
// SPDX-License-Identifier: GPL-2.0

use crate::driver::{
    DrmRegData,
    NovaDevice,
    NovaDriver, //
};
use crate::gem::NovaObject;
use kernel::{
    alloc::flags::*,
    drm::{
        self,
        gem::BaseObject,
        Registered, //
    },
    num::casts::{
        arch::IntoSafeCastArch,
        IntoSafeCast, //
    },
    prelude::*,
    transmute::AsBytes,
    uaccess::UserSlice,
    uapi,
};

pub(crate) struct File;

/// GPU information returned to userspace.
#[repr(transparent)]
struct GpuInfo(uapi::drm_nova_info_gpu);

/// Copies `name` into the zero initialised, fixed size uAPI buffer `dst`, keeping it
/// NUL-terminated.
///
/// Fails with [`ENAMETOOLONG`] if `name` does not fit in `dst` with room for the terminator.
fn copy_name(dst: &mut [u8], name: &str) -> Result {
    let bytes = name.as_bytes();

    if bytes.len() >= dst.len() {
        return Err(ENAMETOOLONG);
    }
    dst[..bytes.len()].copy_from_slice(bytes);

    Ok(())
}

impl GpuInfo {
    /// Collects the GPU information reported to userspace.
    ///
    /// This is fallible so that unexpected GSP behaviour, such as a malformed name string, is
    /// reported to userspace instead of being silently replaced with a made up value. When adding
    /// a field, take care that a value the GSP simply does not provide, for example because the
    /// firmware predates it, does not cause a failure. Such fields must fall back to zero or
    /// another documented default instead.
    fn new(reg_data: &DrmRegData<'_>) -> Result<Self> {
        let spec = reg_data.api.with(|api| api.get_ref().spec());
        let gsp_static_info = reg_data.api.with(|api| api.get_ref().gsp_static_info());

        let mut info = uapi::drm_nova_info_gpu {
            architecture: spec.chipset.arch().into(),
            chipid: spec.chipset.into(),
            vram_size: gsp_static_info.vram_size(),
            vram_bar_size: reg_data.api.with(|api| api.bar1_size())?,
            gpu_gid: gsp_static_info.gpu_gid,
            ..pin_init::zeroed()
        };

        copy_name(
            &mut info.gpu_name,
            gsp_static_info.gpu_name().map_err(|_| EINVAL)?,
        )?;

        copy_name(
            &mut info.gpu_short_name,
            gsp_static_info.gpu_short_name().map_err(|_| EINVAL)?,
        )?;

        Ok(Self(info))
    }
}

// SAFETY: `GpuInfo` has no interior mutability, and there are no uninitialized bytes.
unsafe impl AsBytes for GpuInfo {}

fn write_info<T: AsBytes>(info: &mut uapi::drm_nova_info, value: T) -> Result {
    // A NULL data pointer requests the size of the information structure.
    if info.data == 0 {
        info.size = size_of::<T>().try_into()?;
        return Ok(());
    }

    let mut writer = UserSlice::new(
        UserPtr::from_addr(info.data.into_safe_cast_arch()),
        info.size.into_safe_cast(),
    )
    .writer();

    info.size = writer.write_truncated(&value)?.try_into()?;

    Ok(())
}

impl drm::file::DriverFile for File {
    type Driver = NovaDriver;

    fn open(_dev: &NovaDevice) -> Result<Pin<KBox<Self>>> {
        Ok(KBox::new(Self, GFP_KERNEL)?.into())
    }
}

impl File {
    /// IOCTL: gem_create: Create a new DRM GEM object.
    pub(crate) fn gem_create(
        dev: &NovaDevice<Registered>,
        _reg_data: &DrmRegData<'_>,
        req: &mut uapi::drm_nova_gem_create,
        file: &drm::File<File>,
    ) -> Result<u32> {
        let obj = NovaObject::new(dev, req.size.try_into()?)?;

        req.handle = obj.create_handle(file)?;

        Ok(0)
    }

    /// IOCTL: gem_info: Query GEM metadata.
    pub(crate) fn gem_info(
        _dev: &NovaDevice<Registered>,
        _reg_data: &DrmRegData<'_>,
        req: &mut uapi::drm_nova_gem_info,
        file: &drm::File<File>,
    ) -> Result<u32> {
        let bo = NovaObject::lookup_handle(file, req.handle)?;

        req.size = bo.size().try_into()?;

        Ok(0)
    }

    /// IOCTL: info: Query device information.
    pub(crate) fn info(
        _dev: &NovaDevice<Registered>,
        reg_data: &DrmRegData<'_>,
        info: &mut uapi::drm_nova_info,
        _file: &drm::File<File>,
    ) -> Result<u32> {
        match info.id {
            uapi::DRM_NOVA_INFO_GPU => write_info(info, GpuInfo::new(reg_data)?)?,
            _ => return Err(EINVAL),
        }

        Ok(0)
    }
}