Skip to content

Commit

Permalink
Add printer gadget and example (#13)
Browse files Browse the repository at this point in the history
Printer gadget is fairly simple. Included example based on prn_example in kernel docs. I put some const I used in the example that I think are useful in the lib too; ioctl ID bytes.
  • Loading branch information
tuna-f1sh authored Dec 6, 2024
1 parent 39bfa24 commit 340c514
Show file tree
Hide file tree
Showing 5 changed files with 280 additions and 0 deletions.
153 changes: 153 additions & 0 deletions examples/printer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
//! Printer example userspace application based on [prn_example](https://docs.kernel.org/6.6/usb/gadget_printer.html#example-code)
//!
//! Creates and binds a printer gadget function, then reads data from the device file created by the gadget to stdout. Will exit after printing a set number of pages.
use nix::{ioctl_read, ioctl_readwrite};
use std::fs::{File, OpenOptions};
use std::io::{self, Read, Write};
use std::os::unix::io::AsRawFd;
use std::path::PathBuf;

use usb_gadget::function::printer::{Printer, StatusFlags, GADGET_GET_PRINTER_STATUS, GADGET_SET_PRINTER_STATUS};
use usb_gadget::{default_udc, Class, Config, Gadget, Id, RegGadget, Strings, GADGET_IOC_MAGIC};

// Printer read buffer size, best equal to EP wMaxPacketSize
const BUF_SIZE: usize = 512;
// Printer device path - 0 assumes we are the only printer gadget!
const DEV_PATH: &str = "/dev/g_printer0";
// Pages to 'print' before exiting
const PRINT_EXIT_COUNT: u8 = 1;
// Default printer status
const DEFAULT_STATUS: StatusFlags =
StatusFlags::from_bits_truncate(StatusFlags::NOT_ERROR.bits() | StatusFlags::SELECTED.bits());

// ioctl read/write for printer status
ioctl_read!(ioctl_read_printer_status, GADGET_IOC_MAGIC, GADGET_GET_PRINTER_STATUS, u8);
ioctl_readwrite!(ioctl_write_printer_status, GADGET_IOC_MAGIC, GADGET_SET_PRINTER_STATUS, u8);

fn create_printer_gadget() -> io::Result<RegGadget> {
usb_gadget::remove_all().expect("cannot remove all gadgets");

let udc = default_udc().expect("cannot get UDC");
let mut builder = Printer::builder();
builder.pnp_string = Some("Rust PNP".to_string());

let (_, func) = builder.build();
let reg =
// Linux Foundation VID Gadget PID
Gadget::new(Class::interface_specific(), Id::new(0x1d6b, 0x0104), Strings::new("Clippy Manufacturer", "Rusty Printer", "RUST0123456"))
.with_config(Config::new("Config 1")
.with_function(func))
.bind(&udc)?;

Ok(reg)
}

fn read_printer_data(file: &mut File) -> io::Result<()> {
let mut buf = [0u8; BUF_SIZE];
let mut printed = 0;
println!("Will exit after printing {} pages...", PRINT_EXIT_COUNT);

loop {
let bytes_read = match file.read(&mut buf) {
Ok(bytes_read) if bytes_read > 0 => bytes_read,
_ => break,
};
io::stdout().write_all(&buf[..bytes_read])?;
io::stdout().flush()?;

// check if %%EOF is in the buffer
if buf.windows(5).any(|w| w == b"%%EOF") {
printed += 1;
if printed == PRINT_EXIT_COUNT {
println!("Printed {} pages, exiting.", PRINT_EXIT_COUNT);
break;
}
}
}

Ok(())
}

fn set_printer_status(file: &File, flags: StatusFlags, clear: bool) -> io::Result<StatusFlags> {
let mut status = get_printer_status(file)?;
if clear {
status.remove(flags);
} else {
status.insert(flags);
}
let mut bits = status.bits();
log::debug!("Setting printer status: {:08b}", bits);
unsafe { ioctl_write_printer_status(file.as_raw_fd(), &mut bits) }?;
Ok(StatusFlags::from_bits_truncate(bits))
}

fn get_printer_status(file: &File) -> io::Result<StatusFlags> {
let mut status = 0;
unsafe { ioctl_read_printer_status(file.as_raw_fd(), &mut status) }?;
log::debug!("Got printer status: {:08b}", status);
let status = StatusFlags::from_bits_truncate(status);
Ok(status)
}

fn print_status(status: StatusFlags) {
println!("Printer status is:");
if status.contains(StatusFlags::SELECTED) {
println!(" Printer is Selected");
} else {
println!(" Printer is NOT Selected");
}
if status.contains(StatusFlags::PAPER_EMPTY) {
println!(" Paper is Out");
} else {
println!(" Paper is Loaded");
}
if status.contains(StatusFlags::NOT_ERROR) {
println!(" Printer OK");
} else {
println!(" Printer ERROR");
}
}

fn main() -> io::Result<()> {
env_logger::init();

// create var printer gadget, will unbind on drop
let g_printer = create_printer_gadget().map_err(|e| {
eprintln!("Failed to create printer gadget: {:?}", e);
e
})?;
println!("Printer gadget created: {:?}", g_printer.path());

// wait for sysfs device to create
let mut count = 0;
let mut dev_path = None;
println!("Attempt open device path: {}", DEV_PATH);
while count < 5 {
std::thread::sleep(std::time::Duration::from_secs(1));
// test open access
if let Ok(_) = OpenOptions::new().read(true).write(true).open(&DEV_PATH) {
dev_path = Some(PathBuf::from(DEV_PATH));
break;
}
count += 1;
}

match dev_path {
Some(pp) => {
let mut file = OpenOptions::new().read(true).write(true).open(&pp)?;

print_status(set_printer_status(&file, DEFAULT_STATUS, false)?);
if let Err(e) = read_printer_data(&mut file) {
Err(io::Error::new(
io::ErrorKind::Other,
format!("Failed to read data from {}: {:?}", pp.display(), e),
))
} else {
Ok(())
}
}
_ => {
Err(io::Error::new(io::ErrorKind::NotFound, format!("Printer {} not found or cannot open", DEV_PATH)))
}
}
}
1 change: 1 addition & 0 deletions src/function/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ pub mod midi;
pub mod msd;
pub mod net;
pub mod other;
pub mod printer;
pub mod serial;
pub mod util;
pub mod video;
Expand Down
102 changes: 102 additions & 0 deletions src/function/printer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
//! Printer function.
//!
//! The Linux kernel configuration option `CONFIG_USB_CONFIGFS_F_PRINTER` must be enabled.
//!
//! A device file at /dev/g_printerN will be created for each instance of the function, where N instance number. See 'examples/printer.rs' for an example.
use bitflags::bitflags;
use std::{ffi::OsString, io::Result};

use super::{
util::{FunctionDir, Status},
Function, Handle,
};

/// Get printer status ioctrl ID
pub const GADGET_GET_PRINTER_STATUS: u8 = 0x21;
/// Set printer status ioctrl ID
pub const GADGET_SET_PRINTER_STATUS: u8 = 0x22;

bitflags! {
#[derive(Clone, Copy, Debug)]
#[non_exhaustive]
/// [Printer status flags](https://www.usb.org/sites/default/files/usbprint11a021811.pdf)
pub struct StatusFlags: u8 {
/// Printer not in error state
const NOT_ERROR = (1 << 3);
/// Printer selected
const SELECTED = (1 << 4);
/// Printer out of paper
const PAPER_EMPTY = (1 << 5);
}
}

/// Builder for USB printer function.
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct PrinterBuilder {
/// The PNP ID string used for this printer.
pub pnp_string: Option<String>,
/// The number of 8k buffers to use per endpoint. The default is 10.
pub qlen: Option<u8>,
}

impl PrinterBuilder {
/// Build the USB function.
///
/// The returned handle must be added to a USB gadget configuration.
pub fn build(self) -> (Printer, Handle) {
let dir = FunctionDir::new();
(Printer { dir: dir.clone() }, Handle::new(PrinterFunction { builder: self, dir }))
}
}

#[derive(Debug)]
struct PrinterFunction {
builder: PrinterBuilder,
dir: FunctionDir,
}

impl Function for PrinterFunction {
fn driver(&self) -> OsString {
"printer".into()
}

fn dir(&self) -> FunctionDir {
self.dir.clone()
}

fn register(&self) -> Result<()> {
if let Some(pnp_string) = &self.builder.pnp_string {
self.dir.write("pnp_string", pnp_string)?;
}
if let Some(qlen) = self.builder.qlen {
self.dir.write("q_len", qlen.to_string())?;
}

Ok(())
}
}

/// USB printer function.
#[derive(Debug)]
pub struct Printer {
dir: FunctionDir,
}

impl Printer {
/// Creates a new USB printer builder.
pub fn builder() -> PrinterBuilder {
PrinterBuilder { pnp_string: None, qlen: None }
}

/// Creates a new USB printer function and handle with f_printer defaults
pub fn new(self) -> (Printer, Handle) {
Self::builder().build()
}

/// Access to registration status.
pub fn status(&self) -> Status {
self.dir.status()
}
}
3 changes: 3 additions & 0 deletions src/gadget.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ use crate::{
Speed,
};

/// USB gadget ioctl magic byte.
pub const GADGET_IOC_MAGIC: u8 = b'g';

/// USB gadget or interface class.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Class {
Expand Down
21 changes: 21 additions & 0 deletions tests/printer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
mod common;
use common::*;

use usb_gadget::function::printer::Printer;

#[test]
fn printer() {
init();

// Keyboard printer description
let mut builder = Printer::builder();
builder.pnp_string = Some("Rust Printer".to_string());
builder.qlen = Some(20);
let (printer, func) = builder.build();

let reg = reg(func);

println!("printer device at {}", printer.status().path().unwrap().display());

unreg(reg).unwrap();
}

0 comments on commit 340c514

Please sign in to comment.