Ruiqurm blog

Rust for Linux笔记-2

2024-11-04


今天看看rust-next分支下面的几个宏。目前写驱动用得比较多的应该时前面三个。其他的偏向于写宏时候的 工具。

module

use kernel::prelude::*;
module!{
    type: MyDeviceDriverModule,
    name: "my_device_driver_module",
    author: "Rust for Linux Contributors",
    description: "My device driver requires firmware",
    license: "GPL",
    firmware: ["my_device_firmware1.bin", "my_device_firmware2.bin"],
}
struct MyDeviceDriverModule;

这个宏会按照一定的模板去生成rust代码。

生成__init和__exit函数

模块初始化时,会类型linux驱动调用init和exit(或者register和unregister)。这里rfl封装了一层, 不是直接调用用户提供的函数,而是去调用impl了Module trait提供的初始化函数,初始化完成后,将 其绑定到一个静态全局变量上。

这里还考虑了如果CONFIG(Module)没开的情况(下面没贴出来)

static mut __MOD: Option<{type_}> = None;

#[no_mangle]
#[link_section = \".init.text\"]
pub unsafe extern \"C\" fn init_module() -> core::ffi::c_int {{
    // SAFETY: This function is inaccessible to the outside due to the double
    // module wrapping it. It is called exactly once by the C side via its
    // unique name.
    unsafe {{ __init() }}
}}

#[used]
#[link_section = \".init.data\"]
static __UNIQUE_ID___addressable_init_module: unsafe extern \"C\" fn() -> i32 = init_module;


unsafe fn __init() -> core::ffi::c_int {{
    match <{type_} as kernel::Module>::init(&super::super::THIS_MODULE) {{
        Ok(m) => {{
            // SAFETY: No data race, since `__MOD` can only be accessed by this
            // module and there only `__init` and `__exit` access it. These
            // functions are only called once and `__exit` cannot be called
            // before or during `__init`.
            unsafe {{
                __MOD = Some(m);
            }}
            return 0;
        }}
        Err(e) => {{
            return e.to_errno();
        }}
    }}
}}

对于exit的情况,这里不是像C一样调用某个函数,而是等待drop函数自动调用。也就是用户需要为自己实现 的type实现drop。最后会将__MOD置为None.

生成mod_info

在模板的最后面是一些mod_info。目前支持author, descripten,license等等。

modinfo使用一个ModInfoBuilder去构建。最后的mod信息会保存到buffer中。

struct ModInfoBuilder<'a> {
    module: &'a str,
    counter: usize,
    buffer: String,
}

每次写入会调用emit_base,其基本模板如下:

{cfg}
#[doc(hidden)]
#[link_section = \".modinfo\"]
#[used]
pub static __{module}_{counter}: [u8; {length}] = *{string};

vtable

rust的trait和kernel的vtable十分相似,他们的区别之处在于二者对于未实现函数的表示。Rust中是提 供默认实现(一般是返回Error::EINVAL),而linux则返回NULL函数指针。

这个宏的主要作用是为rust trait生成HAS_XXX的宏。对于trait,所有的HAS_*都为false;对于impl, 实现了的function为定义其HAS_*变量为true。

OperationsVTable

block/mq中实现了一个OperationVTable的例子。

#[macros::vtable]
pub trait Operations: Sized {
    /// Called by the kernel to queue a request with the driver. If `is_last` is
    /// `false`, the driver is allowed to defer committing the request.
    fn queue_rq(rq: ARef<Request<Self>>, is_last: bool) -> Result;

    /// Called by the kernel to indicate that queued requests should be submitted.
    fn commit_rqs();

    /// Called by the kernel to poll the device for completed requests. Only
    /// used for poll queues.
    fn poll() -> bool {
        crate::build_error(crate::error::VTABLE_DEFAULT_ERROR)
    }
}

pub(crate) struct OperationsVTable<T: Operations>(PhantomData<T>);
impl<T: Operations> OperationsVTable<T> {
 // ...
    unsafe extern "C" fn commit_rqs_callback(_hctx: *mut bindings::blk_mq_hw_ctx) {
        T::commit_rqs()
    }
}

Operations是rust用户需要实现的trait,而下面的VTable是rfl生成的用于和内核沟通的抽象层。

Vtable中实现了许多*_callback风格的函数,这是让c侧调用的函数。这些callback函数还负责将c指针类 型转为rust的安全抽象,最后去调用用户写的rust trait函数。

impl中还有一个VTABLE变量。这个变量将上面的callback存到FFI的blk_mq_ops结构体中,该结构体是 由Rust bindgen产生的。

impl<T: Operations> OperationsVTable<T> {
   // ...
   const VTABLE: bindings::blk_mq_ops = bindings::blk_mq_ops {
        queue_rq: Some(Self::queue_rq_callback),
        queue_rqs: None,
        commit_rqs: Some(Self::commit_rqs_callback),
        get_budget: None,
        put_budget: None,
        set_rq_budget_token: None,
        get_rq_budget_token: None,
        timeout: None,
        poll: if T::HAS_POLL {
            Some(Self::poll_callback)
        } else {
            None
        },
        complete: Some(Self::complete_callback),
        init_hctx: Some(Self::init_hctx_callback),
        exit_hctx: Some(Self::exit_hctx_callback),
        init_request: Some(Self::init_request_callback),
        exit_request: Some(Self::exit_request_callback),
        cleanup_rq: None,
        busy: None,
        map_queues: None,
        #[cfg(CONFIG_BLK_DEBUG_FS)]
        show_rq: None,
    };
}

/// FFI interface
#[repr(C)]
#[derive(Default, Copy, Clone)]
pub struct blk_mq_ops {
    pub queue_rq: ::core::option::Option<
        unsafe extern "C" fn(
            arg1: *mut blk_mq_hw_ctx,
            arg2: *const blk_mq_queue_data,
        ) -> blk_status_t,
    >,
    pub commit_rqs: ::core::option::Option<unsafe extern "C" fn(arg1: *mut blk_mq_hw_ctx)>,
    pub queue_rqs: ::core::option::Option<unsafe extern "C" fn(rqlist: *mut *mut request)>,
    pub get_budget:
        ::core::option::Option<unsafe extern "C" fn(arg1: *mut request_queue) -> core::ffi::c_int>,
    pub put_budget: ::core::option::Option<
        unsafe extern "C" fn(arg1: *mut request_queue, arg2: core::ffi::c_int),
    >,
    pub set_rq_budget_token:
        ::core::option::Option<unsafe extern "C" fn(arg1: *mut request, arg2: core::ffi::c_int)>,
    pub get_rq_budget_token:
        ::core::option::Option<unsafe extern "C" fn(arg1: *mut request) -> core::ffi::c_int>,
    pub timeout:
        ::core::option::Option<unsafe extern "C" fn(arg1: *mut request) -> blk_eh_timer_return>,
    pub poll: ::core::option::Option<
        unsafe extern "C" fn(
            arg1: *mut blk_mq_hw_ctx,
            arg2: *mut io_comp_batch,
        ) -> core::ffi::c_int,
    >,
    pub complete: ::core::option::Option<unsafe extern "C" fn(arg1: *mut request)>,
    pub init_hctx: ::core::option::Option<
        unsafe extern "C" fn(
            arg1: *mut blk_mq_hw_ctx,
            arg2: *mut core::ffi::c_void,
            arg3: core::ffi::c_uint,
        ) -> core::ffi::c_int,
    >,
    pub exit_hctx: ::core::option::Option<
        unsafe extern "C" fn(arg1: *mut blk_mq_hw_ctx, arg2: core::ffi::c_uint),
    >,
    pub init_request: ::core::option::Option<
        unsafe extern "C" fn(
            set: *mut blk_mq_tag_set,
            arg1: *mut request,
            arg2: core::ffi::c_uint,
            arg3: core::ffi::c_uint,
        ) -> core::ffi::c_int,
    >,
    pub exit_request: ::core::option::Option<
        unsafe extern "C" fn(set: *mut blk_mq_tag_set, arg1: *mut request, arg2: core::ffi::c_uint),
    >,
    pub cleanup_rq: ::core::option::Option<unsafe extern "C" fn(arg1: *mut request)>,
    pub busy: ::core::option::Option<unsafe extern "C" fn(arg1: *mut request_queue) -> bool_>,
    pub map_queues: ::core::option::Option<unsafe extern "C" fn(set: *mut blk_mq_tag_set)>,
    pub show_rq: ::core::option::Option<unsafe extern "C" fn(m: *mut seq_file, rq: *mut request)>,
}

使用时,将VTABLE传给c一侧的结构体初始化。之后就是C通过FFI接口调用rust了。

ops: OperationsVTable::<T>::build()

pin_data

文档中说和pin-project-lite比较类似。需要这个结构时使用#[pin_data]修饰整个结构体,对于特定的 field,使用#[pin]去修饰field

paste 和 concat

paste

提供了一个固定的写法用于实现宏中的复制功能。文档中主要举了生成相同前缀宏和生成相同内容函数的两个 例子(手动泛型..)

concat

这个也是个宏工具,提供的是拼接的功能。文档中指出生成前缀也可以拼接方式实现

这个方式应该比较c-style