Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add printer gadget and example #13

Merged
merged 10 commits into from
Dec 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;

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();
}
Loading