Skip to main content

printer/
printer.rs

1//! Printer example userspace application based on [prn_example](https://docs.kernel.org/6.6/usb/gadget_printer.html#example-code)
2//!
3//! Creates and binds a printer gadget function, then reads data from the device file created by the
4//! gadget to stdout. Will exit after printing a set number of pages.
5
6use rustix::ioctl::{self, opcode};
7use std::{
8    fs::{File, OpenOptions},
9    io::{self, Read, Result, Write},
10    thread,
11    time::Duration,
12};
13
14use usb_gadget::{
15    default_udc,
16    function::printer::{Printer, StatusFlags, GADGET_GET_PRINTER_STATUS, GADGET_SET_PRINTER_STATUS},
17    Class, Config, Gadget, Id, RegGadget, Strings, GADGET_IOC_MAGIC,
18};
19
20// Printer read buffer size, best equal to EP wMaxPacketSize
21const BUF_SIZE: usize = 512;
22// Printer device path - 0 assumes we are the only printer gadget!
23const DEV_PATH: &str = "/dev/g_printer0";
24// Pages to 'print' before exiting
25const PRINT_EXIT_COUNT: u8 = 1;
26// Default printer status
27const DEFAULT_STATUS: StatusFlags =
28    StatusFlags::from_bits_truncate(StatusFlags::NOT_ERROR.bits() | StatusFlags::SELECTED.bits());
29
30fn ioctl_read_printer_status(file: &File) -> Result<u8> {
31    let getter = unsafe {
32        ioctl::Getter::<{ opcode::read::<u8>(GADGET_IOC_MAGIC, GADGET_GET_PRINTER_STATUS) }, u8>::new()
33    };
34    Ok(unsafe { ioctl::ioctl(file, getter) }?)
35}
36
37fn ioctl_write_printer_status(file: &File, status: u8) -> Result<()> {
38    let mut value = status;
39    unsafe {
40        ioctl::ioctl(
41            file,
42            ioctl::Updater::<{ opcode::read_write::<u8>(GADGET_IOC_MAGIC, GADGET_SET_PRINTER_STATUS) }, _>::new(
43                &mut value,
44            ),
45        )
46    }?;
47    Ok(())
48}
49
50fn create_printer_gadget() -> Result<RegGadget> {
51    usb_gadget::remove_all().expect("cannot remove all gadgets");
52
53    let udc = default_udc().expect("cannot get UDC");
54    let mut builder = Printer::builder();
55    builder.pnp_string = Some("Rust PNP".to_string());
56
57    let (_, func) = builder.build();
58    let reg = Gadget::new(
59        Class::INTERFACE_SPECIFIC,
60        Id::LINUX_FOUNDATION_COMPOSITE,
61        Strings::new("Clippy Manufacturer", "Rusty Printer", "RUST0123456"),
62    )
63    .with_config(Config::new("Config 1").with_function(func))
64    .bind(&udc)?;
65
66    Ok(reg)
67}
68
69fn read_printer_data(file: &mut File) -> Result<()> {
70    let mut buf = [0u8; BUF_SIZE];
71    let mut printed = 0;
72    println!("Will exit after printing {} pages...", PRINT_EXIT_COUNT);
73
74    loop {
75        let bytes_read = match file.read(&mut buf) {
76            Ok(bytes_read) if bytes_read > 0 => bytes_read,
77            _ => break,
78        };
79        io::stdout().write_all(&buf[..bytes_read])?;
80        io::stdout().flush()?;
81
82        // check if %%EOF is in the buffer
83        if buf.windows(5).any(|w| w == b"%%EOF") {
84            printed += 1;
85            if printed == PRINT_EXIT_COUNT {
86                println!("Printed {} pages, exiting.", PRINT_EXIT_COUNT);
87                break;
88            }
89        }
90    }
91
92    Ok(())
93}
94
95fn set_printer_status(file: &File, flags: StatusFlags, clear: bool) -> Result<StatusFlags> {
96    let mut status = get_printer_status(file)?;
97    if clear {
98        status.remove(flags);
99    } else {
100        status.insert(flags);
101    }
102    let bits = status.bits();
103    log::debug!("Setting printer status: {:08b}", bits);
104    ioctl_write_printer_status(file, bits)?;
105    Ok(StatusFlags::from_bits_truncate(bits))
106}
107
108fn get_printer_status(file: &File) -> Result<StatusFlags> {
109    let status = ioctl_read_printer_status(file)?;
110    log::debug!("Got printer status: {:08b}", status);
111    let status = StatusFlags::from_bits_truncate(status);
112    Ok(status)
113}
114
115fn print_status(status: StatusFlags) {
116    println!("Printer status is:");
117    if status.contains(StatusFlags::SELECTED) {
118        println!("     Printer is Selected");
119    } else {
120        println!("     Printer is NOT Selected");
121    }
122    if status.contains(StatusFlags::PAPER_EMPTY) {
123        println!("     Paper is Out");
124    } else {
125        println!("     Paper is Loaded");
126    }
127    if status.contains(StatusFlags::NOT_ERROR) {
128        println!("     Printer OK");
129    } else {
130        println!("     Printer ERROR");
131    }
132}
133
134fn main() -> Result<()> {
135    env_logger::init();
136
137    // create printer gadget, will unbind on drop
138    let g_printer = create_printer_gadget().map_err(|e| {
139        eprintln!("Failed to create printer gadget: {e}");
140        e
141    })?;
142    println!("Printer gadget created: {}", g_printer.path().display());
143
144    // wait for device file creation
145    println!("Attempt open device path: {DEV_PATH}");
146    let mut count = 0;
147    let mut file = loop {
148        thread::sleep(Duration::from_secs(1));
149
150        match OpenOptions::new().read(true).write(true).open(DEV_PATH) {
151            Ok(file) => break file,
152            Err(_) if count < 5 => count += 1,
153            Err(err) => {
154                return Err(io::Error::new(
155                    io::ErrorKind::NotFound,
156                    format!("Printer {DEV_PATH} not found or cannot open: {err}"),
157                ))
158            }
159        }
160    };
161
162    print_status(set_printer_status(&file, DEFAULT_STATUS, false)?);
163    if let Err(e) = read_printer_data(&mut file) {
164        return Err(io::Error::other(format!("Failed to read data from {DEV_PATH}: {e}")));
165    }
166
167    Ok(())
168}