Skip to content
This repository was archived by the owner on Mar 7, 2021. It is now read-only.

Commit 7c35577

Browse files
committed
Support registering a FileOperations for a chrdev
1 parent b82edd9 commit 7c35577

File tree

10 files changed

+333
-8
lines changed

10 files changed

+333
-8
lines changed

build.rs

+32-1
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,16 @@ use bindgen;
22
use cc;
33
use shlex;
44

5-
use std::env;
5+
use std::io::{BufRead, BufReader};
66
use std::path::PathBuf;
77
use std::process::Command;
8+
use std::{env, fs};
89

910
const INCLUDED_TYPES: &[&str] = &["file_system_type", "mode_t", "umode_t", "ctl_table"];
1011
const INCLUDED_FUNCTIONS: &[&str] = &[
12+
"cdev_add",
13+
"cdev_alloc",
14+
"cdev_del",
1115
"register_filesystem",
1216
"unregister_filesystem",
1317
"krealloc",
@@ -35,6 +39,7 @@ const INCLUDED_VARS: &[&str] = &[
3539
"BINDINGS_GFP_KERNEL",
3640
"KERN_INFO",
3741
"VERIFY_WRITE",
42+
"LINUX_VERSION_CODE",
3843
];
3944
const OPAQUE_TYPES: &[&str] = &[
4045
// These need to be opaque because they're both packed and aligned, which rustc
@@ -44,6 +49,30 @@ const OPAQUE_TYPES: &[&str] = &[
4449
"xregs_state",
4550
];
4651

52+
fn kernel_version_code(major: u8, minor: u8, patch: u8) -> u64 {
53+
((major as u64) << 16) | ((minor as u64) << 8) | (patch as u64)
54+
}
55+
56+
fn handle_kernel_version_cfg(bindings_path: &PathBuf) {
57+
let f = BufReader::new(fs::File::open(bindings_path).unwrap());
58+
let mut version = None;
59+
for line in f.lines() {
60+
let line = line.unwrap();
61+
if line.starts_with("pub const LINUX_VERSION_CODE") {
62+
let mut parts = line.split(" = ");
63+
parts.next();
64+
let raw_version = parts.next().unwrap();
65+
// Remove the trailing semi-colon
66+
version = Some(raw_version[..raw_version.len() - 1].parse::<u64>().unwrap());
67+
break;
68+
}
69+
}
70+
let version = version.expect("Couldn't find kernel version");
71+
if version >= kernel_version_code(4, 15, 0) {
72+
println!("cargo:rustc-cfg=kernel_4_15_0_or_greataer")
73+
}
74+
}
75+
4776
fn main() {
4877
println!("rerun-if-env-changed=KDIR");
4978
let output = String::from_utf8(
@@ -90,6 +119,8 @@ fn main() {
90119
.write_to_file(out_path.join("bindings.rs"))
91120
.expect("Couldn't write bindings!");
92121

122+
handle_kernel_version_cfg(&out_path.join("bindings.rs"));
123+
93124
let mut builder = cc::Build::new();
94125
println!("cargo:rerun-if-env-changed=CLANG");
95126
builder.compiler(env::var("CLANG").unwrap_or("clang".to_string()));

src/bindings_helper.h

+2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
#include <linux/cdev.h>
12
#include <linux/fs.h>
23
#include <linux/module.h>
34
#include <linux/slab.h>
45
#include <linux/uaccess.h>
6+
#include <linux/version.h>
57

68
// Bindgen gets confused at certain things
79
//

src/c_types.rs

+3
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ pub type c_ulong = u64;
1111
pub type c_ulonglong = u64;
1212
pub type c_ushort = u16;
1313
pub type c_schar = i8;
14+
pub type c_size_t = usize;
15+
pub type c_ssize_t = isize;
16+
1417
// See explanation in rust/src/libstd/os/raw.rs
1518
#[repr(u8)]
1619
pub enum c_void {

src/chrdev.rs

+143-5
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
use core::ops::Range;
2+
use core::ptr;
3+
4+
use alloc::boxed::Box;
5+
use alloc::sync::Arc;
26

37
use crate::bindings;
48
use crate::c_types;
5-
use crate::error;
9+
use crate::error::{Error, KernelResult};
10+
use crate::user_ptr::{UserSlicePtr, UserSlicePtrWriter};
611

712
pub struct DeviceNumberRegion {
813
dev: bindings::dev_t,
@@ -13,9 +18,9 @@ impl DeviceNumberRegion {
1318
pub fn allocate(
1419
minors: Range<usize>,
1520
name: &'static str,
16-
) -> error::KernelResult<DeviceNumberRegion> {
21+
) -> KernelResult<Arc<DeviceNumberRegion>> {
1722
if !name.ends_with('\x00') {
18-
return Err(error::Error::EINVAL);
23+
return Err(Error::EINVAL);
1924
}
2025

2126
let count = minors.end - minors.start;
@@ -29,9 +34,33 @@ impl DeviceNumberRegion {
2934
)
3035
};
3136
if res != 0 {
32-
return Err(error::Error::from_kernel_errno(res));
37+
return Err(Error::from_kernel_errno(res));
38+
}
39+
return Ok(Arc::new(DeviceNumberRegion { dev, count }));
40+
}
41+
42+
pub fn register_device<T: FileOperations>(
43+
self: &Arc<Self>,
44+
) -> KernelResult<DeviceRegistration> {
45+
let cdev = unsafe { bindings::cdev_alloc() };
46+
if cdev.is_null() {
47+
return Err(Error::ENOMEM);
48+
}
49+
unsafe {
50+
(*cdev).owner = &mut bindings::__this_module;
51+
(*cdev).ops = &T::VTABLE.0 as *const bindings::file_operations;
52+
}
53+
let cdev = DeviceRegistration {
54+
cdev,
55+
_region: Arc::clone(&self),
56+
};
57+
// TODO: Need to handle multiple register_device calls by going through the devs and
58+
// erroring if we run out.
59+
let rc = unsafe { bindings::cdev_add(cdev.cdev, self.dev, 1) };
60+
if rc != 0 {
61+
return Err(Error::from_kernel_errno(rc));
3362
}
34-
return Ok(DeviceNumberRegion { dev, count });
63+
return Ok(cdev);
3564
}
3665
}
3766

@@ -42,3 +71,112 @@ impl Drop for DeviceNumberRegion {
4271
}
4372
}
4473
}
74+
75+
pub struct DeviceRegistration {
76+
_region: Arc<DeviceNumberRegion>,
77+
cdev: *mut bindings::cdev,
78+
}
79+
80+
unsafe impl Sync for DeviceRegistration {}
81+
82+
impl Drop for DeviceRegistration {
83+
fn drop(&mut self) {
84+
unsafe {
85+
bindings::cdev_del(self.cdev);
86+
}
87+
}
88+
}
89+
90+
pub struct FileOperationsVtable(bindings::file_operations);
91+
92+
unsafe extern "C" fn open_callback<T: FileOperations>(
93+
_inode: *mut bindings::inode,
94+
file: *mut bindings::file,
95+
) -> c_types::c_int {
96+
let f = match T::open() {
97+
Ok(f) => Box::new(f),
98+
Err(e) => return e.to_kernel_errno(),
99+
};
100+
(*file).private_data = Box::into_raw(f) as *mut c_types::c_void;
101+
return 0;
102+
}
103+
104+
unsafe extern "C" fn read_callback<T: FileOperations>(
105+
file: *mut bindings::file,
106+
buf: *mut c_types::c_char,
107+
len: c_types::c_size_t,
108+
offset: *mut bindings::loff_t,
109+
) -> c_types::c_ssize_t {
110+
let mut data = match UserSlicePtr::new(buf as *mut c_types::c_void, len) {
111+
Ok(ptr) => ptr.writer(),
112+
Err(e) => return e.to_kernel_errno() as c_types::c_ssize_t,
113+
};
114+
let f = &*((*file).private_data as *const T);
115+
// TODO: Pass offset to read()?
116+
match f.read(&mut data) {
117+
Ok(()) => {
118+
let written = len - data.len();
119+
(*offset) += written as bindings::loff_t;
120+
return written as c_types::c_ssize_t;
121+
}
122+
Err(e) => return e.to_kernel_errno() as c_types::c_ssize_t,
123+
};
124+
}
125+
126+
unsafe extern "C" fn release_callback<T: FileOperations>(
127+
_inode: *mut bindings::inode,
128+
file: *mut bindings::file,
129+
) -> c_types::c_int {
130+
let ptr = (*file).private_data as *mut T;
131+
(*file).private_data = ptr::null_mut();
132+
Box::from_raw(ptr);
133+
return 0;
134+
}
135+
136+
impl FileOperationsVtable {
137+
pub const fn new<T: FileOperations>() -> FileOperationsVtable {
138+
return FileOperationsVtable(bindings::file_operations {
139+
open: Some(open_callback::<T>),
140+
read: Some(read_callback::<T>),
141+
release: Some(release_callback::<T>),
142+
143+
check_flags: None,
144+
clone_file_range: None,
145+
compat_ioctl: None,
146+
copy_file_range: None,
147+
dedupe_file_range: None,
148+
fallocate: None,
149+
fasync: None,
150+
flock: None,
151+
flush: None,
152+
fsync: None,
153+
get_unmapped_area: None,
154+
iterate: None,
155+
iterate_shared: None,
156+
llseek: None,
157+
lock: None,
158+
mmap: None,
159+
#[cfg(kernel_4_15_0_or_greataer)]
160+
mmap_supported_flags: 0,
161+
owner: ptr::null_mut(),
162+
poll: None,
163+
read_iter: None,
164+
sendpage: None,
165+
setfl: None,
166+
setlease: None,
167+
show_fdinfo: None,
168+
splice_read: None,
169+
splice_write: None,
170+
unlocked_ioctl: None,
171+
write: None,
172+
write_iter: None,
173+
});
174+
}
175+
}
176+
177+
pub trait FileOperations: Sync + Sized {
178+
const VTABLE: FileOperationsVtable;
179+
180+
fn open() -> KernelResult<Self>;
181+
fn read(&self, buf: &mut UserSlicePtrWriter) -> KernelResult<()>;
182+
}

src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#![no_std]
2-
#![feature(allocator_api, alloc_error_handler)]
2+
#![feature(allocator_api, alloc_error_handler, arbitrary_self_types, const_fn)]
33

44
extern crate alloc;
55

src/user_ptr.rs

+4
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,10 @@ impl UserSlicePtrReader {
125125
pub struct UserSlicePtrWriter(*mut c_types::c_void, usize);
126126

127127
impl UserSlicePtrWriter {
128+
pub fn len(&self) -> usize {
129+
return self.1;
130+
}
131+
128132
pub fn write(&mut self, data: &[u8]) -> error::KernelResult<()> {
129133
if data.len() > self.1 || data.len() > u32::MAX as usize {
130134
return Err(error::Error::EFAULT);

tests/chrdev-region-allocation/src/lib.rs

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
#![no_std]
22
#![feature(const_str_as_bytes)]
33

4+
extern crate alloc;
5+
6+
use alloc::sync::Arc;
7+
48
use linux_kernel_module;
59

610
struct ChrdevRegionAllocationTestModule {
7-
_dev: linux_kernel_module::chrdev::DeviceNumberRegion,
11+
_dev: Arc<linux_kernel_module::chrdev::DeviceNumberRegion>,
812
}
913

1014
impl linux_kernel_module::KernelModule for ChrdevRegionAllocationTestModule {

tests/chrdev/Cargo.toml

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
[package]
2+
name = "chrdev-tests"
3+
version = "0.1.0"
4+
authors = ["Alex Gaynor <[email protected]>", "Geoffrey Thomas <[email protected]>"]
5+
edition = "2018"
6+
7+
[lib]
8+
crate-type = ["staticlib"]
9+
test = false
10+
11+
[dependencies]
12+
linux-kernel-module = { path = "../.." }
13+
14+
[dev-dependencies]
15+
kernel-module-testlib = { path = "../../testlib" }
16+
libc = "0.2.58"

tests/chrdev/src/lib.rs

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
#![no_std]
2+
#![feature(const_str_as_bytes)]
3+
4+
extern crate alloc;
5+
6+
use alloc::sync::Arc;
7+
8+
use linux_kernel_module;
9+
10+
struct CycleFile;
11+
12+
impl linux_kernel_module::chrdev::FileOperations for CycleFile {
13+
const VTABLE: linux_kernel_module::chrdev::FileOperationsVtable =
14+
linux_kernel_module::chrdev::FileOperationsVtable::new::<Self>();
15+
16+
fn open() -> linux_kernel_module::KernelResult<Self> {
17+
return Ok(CycleFile);
18+
}
19+
20+
fn read(
21+
&self,
22+
buf: &mut linux_kernel_module::user_ptr::UserSlicePtrWriter,
23+
) -> linux_kernel_module::KernelResult<()> {
24+
for c in b"123456789".iter().cycle().take(buf.len()) {
25+
buf.write(&[*c])?;
26+
}
27+
return Ok(());
28+
}
29+
}
30+
31+
struct ChrdevTestModule {
32+
_dev_region: Arc<linux_kernel_module::chrdev::DeviceNumberRegion>,
33+
_chrdev_registration: linux_kernel_module::chrdev::DeviceRegistration,
34+
}
35+
36+
impl linux_kernel_module::KernelModule for ChrdevTestModule {
37+
fn init() -> linux_kernel_module::KernelResult<Self> {
38+
let dev_region =
39+
linux_kernel_module::chrdev::DeviceNumberRegion::allocate(0..1, "chrdev-tests\x00")?;
40+
let chrdev_registration = dev_region.register_device::<CycleFile>()?;
41+
Ok(ChrdevTestModule {
42+
_dev_region: dev_region,
43+
_chrdev_registration: chrdev_registration,
44+
})
45+
}
46+
}
47+
48+
linux_kernel_module::kernel_module!(
49+
ChrdevTestModule,
50+
author: "Alex Gaynor and Geoffrey Thomas",
51+
description: "A module for testing character devices",
52+
license: "GPL"
53+
);

0 commit comments

Comments
 (0)