-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add printer gadget and example (#13)
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
Showing
5 changed files
with
280 additions
and
0 deletions.
There are no files selected for viewing
This file contains 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
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))) | ||
} | ||
} | ||
} |
This file contains 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
This file contains 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
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() | ||
} | ||
} |
This file contains 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
This file contains 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
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(); | ||
} |