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

Commit 6d10d76

Browse files
committed
Support registering a FileOperations for a chrdev
1 parent 2349935 commit 6d10d76

File tree

9 files changed

+324
-8
lines changed

9 files changed

+324
-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_init",
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

+145-6
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,44 @@
11
use core::convert::TryInto;
22
use core::ops::Range;
3+
use core::{mem, ptr};
4+
5+
use alloc::boxed::Box;
6+
use alloc::vec;
7+
use alloc::vec::Vec;
38

49
use crate::bindings;
510
use crate::c_types;
6-
use crate::error;
11+
use crate::error::{Error, KernelResult};
12+
use crate::user_ptr::{UserSlicePtr, UserSlicePtrWriter};
713

8-
pub fn builder(name: &'static str, minors: Range<u16>) -> error::KernelResult<Builder> {
14+
pub fn builder(name: &'static str, minors: Range<u16>) -> KernelResult<Builder> {
915
if !name.ends_with('\x00') {
10-
return Err(error::Error::EINVAL);
16+
return Err(Error::EINVAL);
1117
}
1218

13-
return Ok(Builder { name, minors });
19+
return Ok(Builder {
20+
name,
21+
minors,
22+
file_ops: vec![],
23+
});
1424
}
1525

1626
pub struct Builder {
1727
name: &'static str,
1828
minors: Range<u16>,
29+
file_ops: Vec<&'static FileOperationsVtable>,
1930
}
2031

2132
impl Builder {
22-
pub fn build(self) -> error::KernelResult<Registration> {
33+
pub fn register_device<T: FileOperations>(mut self) -> Builder {
34+
if self.file_ops.len() >= self.minors.len() {
35+
panic!("More devices registered than minor numbers allocated.")
36+
}
37+
self.file_ops.push(&T::VTABLE);
38+
return self;
39+
}
40+
41+
pub fn build(self) -> KernelResult<Registration> {
2342
let mut dev: bindings::dev_t = 0;
2443
let res = unsafe {
2544
bindings::alloc_chrdev_region(
@@ -30,24 +49,144 @@ impl Builder {
3049
)
3150
};
3251
if res != 0 {
33-
return Err(error::Error::from_kernel_errno(res));
52+
return Err(Error::from_kernel_errno(res));
53+
}
54+
55+
// Turn this into a boxed slice immediately because the kernel stores pointers into it, and
56+
// so that data should never be moved.
57+
let mut cdevs = vec![unsafe { mem::zeroed() }; self.file_ops.len()].into_boxed_slice();
58+
for (i, file_op) in self.file_ops.iter().enumerate() {
59+
unsafe {
60+
bindings::cdev_init(&mut cdevs[i], &file_op.0);
61+
cdevs[i].owner = &mut bindings::__this_module;
62+
let rc = bindings::cdev_add(&mut cdevs[i], dev + i as bindings::dev_t, 1);
63+
if rc != 0 {
64+
// Clean up the ones that were allocated.
65+
for j in 0..=i {
66+
bindings::cdev_del(&mut cdevs[j]);
67+
}
68+
return Err(Error::from_kernel_errno(rc));
69+
}
70+
}
3471
}
72+
3573
return Ok(Registration {
3674
dev,
3775
count: self.minors.len(),
76+
cdevs,
3877
});
3978
}
4079
}
4180

4281
pub struct Registration {
4382
dev: bindings::dev_t,
4483
count: usize,
84+
cdevs: Box<[bindings::cdev]>,
4585
}
4686

87+
// This is safe because Registration doesn't actually expose any methods.
88+
unsafe impl Sync for Registration {}
89+
4790
impl Drop for Registration {
4891
fn drop(&mut self) {
4992
unsafe {
93+
for dev in self.cdevs.iter_mut() {
94+
bindings::cdev_del(dev);
95+
}
5096
bindings::unregister_chrdev_region(self.dev, self.count as _);
5197
}
5298
}
5399
}
100+
101+
pub struct FileOperationsVtable(bindings::file_operations);
102+
103+
unsafe extern "C" fn open_callback<T: FileOperations>(
104+
_inode: *mut bindings::inode,
105+
file: *mut bindings::file,
106+
) -> c_types::c_int {
107+
let f = match T::open() {
108+
Ok(f) => Box::new(f),
109+
Err(e) => return e.to_kernel_errno(),
110+
};
111+
(*file).private_data = Box::into_raw(f) as *mut c_types::c_void;
112+
return 0;
113+
}
114+
115+
unsafe extern "C" fn read_callback<T: FileOperations>(
116+
file: *mut bindings::file,
117+
buf: *mut c_types::c_char,
118+
len: c_types::c_size_t,
119+
offset: *mut bindings::loff_t,
120+
) -> c_types::c_ssize_t {
121+
let mut data = match UserSlicePtr::new(buf as *mut c_types::c_void, len) {
122+
Ok(ptr) => ptr.writer(),
123+
Err(e) => return e.to_kernel_errno() as c_types::c_ssize_t,
124+
};
125+
let f = &*((*file).private_data as *const T);
126+
// TODO: Pass offset to read()?
127+
match f.read(&mut data) {
128+
Ok(()) => {
129+
let written = len - data.len();
130+
(*offset) += written as bindings::loff_t;
131+
return written as c_types::c_ssize_t;
132+
}
133+
Err(e) => return e.to_kernel_errno() as c_types::c_ssize_t,
134+
};
135+
}
136+
137+
unsafe extern "C" fn release_callback<T: FileOperations>(
138+
_inode: *mut bindings::inode,
139+
file: *mut bindings::file,
140+
) -> c_types::c_int {
141+
let ptr = mem::replace(&mut (*file).private_data, ptr::null_mut());
142+
drop(Box::from_raw(ptr as *mut T));
143+
return 0;
144+
}
145+
146+
impl FileOperationsVtable {
147+
pub const fn new<T: FileOperations>() -> FileOperationsVtable {
148+
return FileOperationsVtable(bindings::file_operations {
149+
open: Some(open_callback::<T>),
150+
read: Some(read_callback::<T>),
151+
release: Some(release_callback::<T>),
152+
153+
check_flags: None,
154+
clone_file_range: None,
155+
compat_ioctl: None,
156+
copy_file_range: None,
157+
dedupe_file_range: None,
158+
fallocate: None,
159+
fasync: None,
160+
flock: None,
161+
flush: None,
162+
fsync: None,
163+
get_unmapped_area: None,
164+
iterate: None,
165+
iterate_shared: None,
166+
llseek: None,
167+
lock: None,
168+
mmap: None,
169+
#[cfg(kernel_4_15_0_or_greataer)]
170+
mmap_supported_flags: 0,
171+
owner: ptr::null_mut(),
172+
poll: None,
173+
read_iter: None,
174+
sendpage: None,
175+
setfl: None,
176+
setlease: None,
177+
show_fdinfo: None,
178+
splice_read: None,
179+
splice_write: None,
180+
unlocked_ioctl: None,
181+
write: None,
182+
write_iter: None,
183+
});
184+
}
185+
}
186+
187+
pub trait FileOperations: Sync + Sized {
188+
const VTABLE: FileOperationsVtable;
189+
190+
fn open() -> KernelResult<Self>;
191+
fn read(&self, buf: &mut UserSlicePtrWriter) -> KernelResult<()>;
192+
}

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, 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/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

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

0 commit comments

Comments
 (0)