Created
November 14, 2025 23:33
-
-
Save samcday/d23aaf95be678bcdd1dd62b4b79861ea to your computer and use it in GitHub Desktop.
Rust FunctionFS + DMA-BUF example
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| [package] | |
| name = "ffs-dmabuf-example" | |
| version = "0.1.0" | |
| edition = "2024" | |
| autobins = true | |
| [dependencies] | |
| usb-gadget = "0.7.6" | |
| anyhow = "1.0.100" | |
| dma-heap = "0.4.1" | |
| nix = { version = "0.30.1", features = ["ioctl", "poll"] } | |
| dma-buf = "0.5.0" | |
| rusb = "0.9.4" | |
| libc = "0.2.177" | |
| [[bin]] | |
| name = "gadget" | |
| path = "gadget.rs" | |
| [[bin]] | |
| name = "host" | |
| path = "host.rs" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| use anyhow::Context; | |
| use dma_heap::HeapKind; | |
| use nix::errno::Errno; | |
| use nix::poll::{PollFd, PollFlags, PollTimeout, poll}; | |
| use nix::{ioctl_readwrite, ioctl_write_ptr}; | |
| use std::fs::File; | |
| use std::io::Write; | |
| use std::os::fd::{AsFd, AsRawFd, FromRawFd, OwnedFd, RawFd}; | |
| use usb_gadget::function::custom::{Custom, Endpoint, EndpointDirection, Interface}; | |
| use usb_gadget::{Class, Config, Gadget, Id, Strings}; | |
| #[repr(C, packed)] | |
| pub struct UsbFfsDmabufTransferReq { | |
| pub fd: libc::c_int, | |
| pub flags: u32, | |
| pub length: u64, | |
| } | |
| const BUF_LEN: usize = 64; | |
| const FUNCTIONFS_IOC_MAGIC: u8 = b'g'; | |
| const FUNCTIONFS_DMABUF_ATTACH_NR: u8 = 131; | |
| const FUNCTIONFS_DMABUF_TRANSFER_NR: u8 = 133; | |
| ioctl_write_ptr!( | |
| ffs_dmabuf_attach, | |
| FUNCTIONFS_IOC_MAGIC, | |
| FUNCTIONFS_DMABUF_ATTACH_NR, | |
| RawFd | |
| ); | |
| ioctl_write_ptr!( | |
| ffs_dmabuf_transfer, | |
| FUNCTIONFS_IOC_MAGIC, | |
| FUNCTIONFS_DMABUF_TRANSFER_NR, | |
| UsbFfsDmabufTransferReq | |
| ); | |
| #[repr(C)] | |
| pub struct dma_buf_export_sync_file_req { | |
| pub flags: u32, | |
| pub fd: i32, | |
| } | |
| pub const DMA_BUF_SYNC_READ: u32 = 1 << 0; | |
| pub const DMA_BUF_SYNC_WRITE: u32 = 1 << 1; | |
| pub const DMA_BUF_SYNC_RW: u32 = DMA_BUF_SYNC_READ | DMA_BUF_SYNC_WRITE; | |
| ioctl_readwrite!( | |
| dma_buf_export_sync_file, | |
| b'b', | |
| 2, | |
| dma_buf_export_sync_file_req | |
| ); | |
| pub fn wait_for_dmabuf_completion(dmabuf_fd: RawFd) -> anyhow::Result<()> { | |
| let sync_fd = unsafe { | |
| let mut req = dma_buf_export_sync_file_req { | |
| flags: DMA_BUF_SYNC_RW, | |
| fd: -1, | |
| }; | |
| dma_buf_export_sync_file(dmabuf_fd, &mut req).map_err(|e| Errno::from_raw(e as _))?; | |
| if req.fd < 0 { | |
| return Err(Errno::EINVAL.into()); | |
| } | |
| OwnedFd::from_raw_fd(req.fd.as_raw_fd()) | |
| }; | |
| let mut fds = [PollFd::new(sync_fd.as_fd(), PollFlags::POLLIN)]; | |
| loop { | |
| let ret = poll(&mut fds, PollTimeout::NONE)?; | |
| match ret { | |
| 0 => continue, | |
| _ => { | |
| let revents = fds[0].revents().unwrap_or(PollFlags::empty()); | |
| if revents.contains(PollFlags::POLLERR) { | |
| return Err(Errno::EIO.into()); | |
| } | |
| if revents.intersects(PollFlags::POLLIN) { | |
| break; | |
| } | |
| } | |
| } | |
| } | |
| Ok(()) | |
| } | |
| fn main() -> anyhow::Result<()> { | |
| usb_gadget::remove_all()?; | |
| let system = dma_heap::Heap::new(HeapKind::System)?; | |
| let rx_buf = dma_buf::DmaBuf::from(system.allocate(BUF_LEN)?); | |
| let tx_buf = dma_buf::DmaBuf::from(system.allocate(BUF_LEN)?); | |
| let mut tx_buf_map = tx_buf.memory_map()?; | |
| let (_, rx_dir) = EndpointDirection::host_to_device(); | |
| let (_, tx_dir) = EndpointDirection::device_to_host(); | |
| fn bulk_ep(dir: EndpointDirection) -> Endpoint { | |
| let mut ep = Endpoint::bulk(dir); | |
| ep.max_packet_size_hs = BUF_LEN as _; | |
| ep.max_packet_size_ss = BUF_LEN as _; | |
| ep | |
| } | |
| let rx_ep = bulk_ep(rx_dir); | |
| let tx_ep = bulk_ep(tx_dir); | |
| let mut builder = Custom::builder().with_interface( | |
| Interface::new(Class::vendor_specific(123, 123), "smoo") | |
| .with_endpoint(rx_ep) | |
| .with_endpoint(tx_ep), | |
| ); | |
| builder.ffs_no_init = true; | |
| let (ffs_descs, ffs_strings) = builder.ffs_descriptors_and_strings()?; | |
| let (mut custom, handle) = builder.build(); | |
| let klass = Class::new(255, 255, 3); | |
| let id = Id::new(0xDEAD, 0xBEEF); | |
| let strings = Strings::new("foo", "bar", "bacon"); | |
| let udc = usb_gadget::default_udc()?; | |
| println!("binding to {:?}", udc); | |
| let gadget = | |
| Gadget::new(klass, id, strings).with_config(Config::new("config").with_function(handle)); | |
| let reg = gadget.register().context("register")?; | |
| let mut ep0 = File::options() | |
| .read(true) | |
| .write(true) | |
| .open(custom.ffs_dir()?.join("ep0"))?; | |
| ep0.write_all(&ffs_descs)?; | |
| ep0.write_all(&ffs_strings)?; | |
| reg.bind(Some(&udc))?; | |
| let rx = File::options() | |
| .read(true) | |
| .write(true) | |
| .open(custom.ffs_dir()?.join("ep1"))?; | |
| let tx = File::options() | |
| .read(true) | |
| .write(true) | |
| .open(custom.ffs_dir()?.join("ep2"))?; | |
| // Attach DMA-BUFs to the bulk endpoints. | |
| unsafe { ffs_dmabuf_attach(rx.as_raw_fd(), &rx_buf.as_raw_fd()) }?; | |
| unsafe { ffs_dmabuf_attach(tx.as_raw_fd(), &tx_buf_map.as_raw_fd()) }?; | |
| println!("queueing write on bulk IN"); | |
| // write some data into TX buffer, with proper begin/end CPU access via ioctls | |
| tx_buf_map | |
| .write( | |
| move |buf, _arg: Option<()>| { | |
| let mut v: u8 = 1; | |
| for b in buf { | |
| *b = v; | |
| v = v.wrapping_add(1); | |
| } | |
| Ok(()) | |
| }, | |
| None::<_>, | |
| ) | |
| .map_err(|e| anyhow::anyhow!("dma-buf write err: {}", e))?; | |
| unsafe { | |
| ffs_dmabuf_transfer( | |
| tx.as_raw_fd(), | |
| &UsbFfsDmabufTransferReq { | |
| fd: tx_buf_map.as_raw_fd(), | |
| flags: 0, | |
| length: BUF_LEN as _, | |
| }, | |
| )?; | |
| } | |
| println!("ffs_dmabuf_transfer()"); | |
| wait_for_dmabuf_completion(tx_buf_map.as_raw_fd())?; | |
| println!("queueing read on bulk OUT"); | |
| unsafe { | |
| ffs_dmabuf_transfer( | |
| rx.as_raw_fd(), | |
| &UsbFfsDmabufTransferReq { | |
| fd: rx_buf.as_raw_fd(), | |
| flags: 0, | |
| length: BUF_LEN as _, | |
| }, | |
| )?; | |
| } | |
| println!("ffs_dmabuf_transfer()"); | |
| wait_for_dmabuf_completion(rx_buf.as_raw_fd())?; | |
| let rx_buf_map = rx_buf.memory_map()?; | |
| rx_buf_map | |
| .read( | |
| move |buf, _arg: Option<()>| { | |
| println!("finally: {:?}", &buf[0..BUF_LEN]); | |
| Ok(()) | |
| }, | |
| None::<_>, | |
| ) | |
| .map_err(|e| anyhow::anyhow!("dma-buf read err: {}", e))?; | |
| Ok(()) | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| use anyhow::Context; | |
| use rusb::{Direction, TransferType}; | |
| use std::time::Duration; | |
| fn main() -> anyhow::Result<()> { | |
| let handle = rusb::open_device_with_vid_pid(0xDEAD, 0xBEEF).unwrap(); | |
| let desc = handle.device().active_config_descriptor()?; | |
| let mut data_rx = None; | |
| let mut data_tx = None; | |
| for intf in desc.interfaces() { | |
| for ep in intf.descriptors().next().unwrap().endpoint_descriptors() { | |
| println!("ep: {:?}", ep); | |
| if ep.transfer_type() == TransferType::Bulk { | |
| if Direction::In == ep.direction() { | |
| data_rx = Some(ep.address()); | |
| } else { | |
| data_tx = Some(ep.address()); | |
| } | |
| } | |
| } | |
| } | |
| let data_rx = data_rx.unwrap(); | |
| let data_tx = data_tx.unwrap(); | |
| handle.claim_interface(0)?; | |
| let mut buf = [0u8; 64]; | |
| // read the incoming data. | |
| let read = handle.read_bulk(data_rx, &mut buf, Duration::from_secs(0))?; | |
| println!("read {} from bulk IN: {:?}", read, buf); | |
| for (v, b) in buf.iter_mut().enumerate() { | |
| *b = (v % 256) as _; | |
| } | |
| let written = handle | |
| .write_bulk(data_tx, &buf, Duration::from_secs(0)) | |
| .context("write bulk OUT")?; | |
| println!("wrote {} to bulk OUT", written); | |
| Ok(()) | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment