요약·해설과 원문, 전문 번역을 서로 분리했습니다. API 이름, symbol, source path는 원문 표기를 사용합니다.
1. 요약·해설
원문의 핵심 논리와 kernel programming 관점의 보충 설명입니다. 아래의 전문 번역과는 별도로 작성했습니다.
2. 영어 원문 전체
번역 기준이 된 Linux v6.18.37 원문입니다. 줄 번호는 이 버전의 파일 좌표입니다.
원문 전체 펼치기
===============================
Linux USB Printer Gadget Driver
===============================
06/04/2007
Copyright (C) 2007 Craig W. Nadler <[email protected]>
General
=======
This driver may be used if you are writing printer firmware using Linux as
the embedded OS. This driver has nothing to do with using a printer with
your Linux host system.
You will need a USB device controller and a Linux driver for it that accepts
a gadget / "device class" driver using the Linux USB Gadget API. After the
USB device controller driver is loaded then load the printer gadget driver.
This will present a printer interface to the USB Host that your USB Device
port is connected to.
This driver is structured for printer firmware that runs in user mode. The
user mode printer firmware will read and write data from the kernel mode
printer gadget driver using a device file. The printer returns a printer status
byte when the USB HOST sends a device request to get the printer status. The
user space firmware can read or write this status byte using a device file
/dev/g_printer . Both blocking and non-blocking read/write calls are supported.
Howto Use This Driver
=====================
To load the USB device controller driver and the printer gadget driver. The
following example uses the Netchip 2280 USB device controller driver::
modprobe net2280
modprobe g_printer
The follow command line parameter can be used when loading the printer gadget
(ex: modprobe g_printer idVendor=0x0525 idProduct=0xa4a8 ):
idVendor
This is the Vendor ID used in the device descriptor. The default is
the Netchip vendor id 0x0525. YOU MUST CHANGE TO YOUR OWN VENDOR ID
BEFORE RELEASING A PRODUCT. If you plan to release a product and don't
already have a Vendor ID please see www.usb.org for details on how to
get one.
idProduct
This is the Product ID used in the device descriptor. The default
is 0xa4a8, you should change this to an ID that's not used by any of
your other USB products if you have any. It would be a good idea to
start numbering your products starting with say 0x0001.
bcdDevice
This is the version number of your product. It would be a good idea
to put your firmware version here.
iManufacturer
A string containing the name of the Vendor.
iProduct
A string containing the Product Name.
iSerialNum
A string containing the Serial Number. This should be changed for
each unit of your product.
iPNPstring
The PNP ID string used for this printer. You will want to set
either on the command line or hard code the PNP ID string used for
your printer product.
qlen
The number of 8k buffers to use per endpoint. The default is 10, you
should tune this for your product. You may also want to tune the
size of each buffer for your product.
Using The Example Code
======================
This example code talks to stdout, instead of a print engine.
To compile the test code below:
1) save it to a file called prn_example.c
2) compile the code with the follow command::
gcc prn_example.c -o prn_example
To read printer data from the host to stdout::
# prn_example -read_data
To write printer data from a file (data_file) to the host::
# cat data_file | prn_example -write_data
To get the current printer status for the gadget driver:::
# prn_example -get_status
Printer status is:
Printer is NOT Selected
Paper is Out
Printer OK
To set printer to Selected/On-line::
# prn_example -selected
To set printer to Not Selected/Off-line::
# prn_example -not_selected
To set paper status to paper out::
# prn_example -paper_out
To set paper status to paper loaded::
# prn_example -paper_loaded
To set error status to printer OK::
# prn_example -no_error
To set error status to ERROR::
# prn_example -error
Example Code
============
::
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <linux/poll.h>
#include <sys/ioctl.h>
#include <linux/usb/g_printer.h>
#define PRINTER_FILE "/dev/g_printer"
#define BUF_SIZE 512
/*
* 'usage()' - Show program usage.
*/
static void
usage(const char *option) /* I - Option string or NULL */
{
if (option) {
fprintf(stderr,"prn_example: Unknown option \"%s\"!\n",
option);
}
fputs("\n", stderr);
fputs("Usage: prn_example -[options]\n", stderr);
fputs("Options:\n", stderr);
fputs("\n", stderr);
fputs("-get_status Get the current printer status.\n", stderr);
fputs("-selected Set the selected status to selected.\n", stderr);
fputs("-not_selected Set the selected status to NOT selected.\n",
stderr);
fputs("-error Set the error status to error.\n", stderr);
fputs("-no_error Set the error status to NO error.\n", stderr);
fputs("-paper_out Set the paper status to paper out.\n", stderr);
fputs("-paper_loaded Set the paper status to paper loaded.\n",
stderr);
fputs("-read_data Read printer data from driver.\n", stderr);
fputs("-write_data Write printer sata to driver.\n", stderr);
fputs("-NB_read_data (Non-Blocking) Read printer data from driver.\n",
stderr);
fputs("\n\n", stderr);
exit(1);
}
static int
read_printer_data()
{
struct pollfd fd[1];
/* Open device file for printer gadget. */
fd[0].fd = open(PRINTER_FILE, O_RDWR);
if (fd[0].fd < 0) {
printf("Error %d opening %s\n", fd[0].fd, PRINTER_FILE);
close(fd[0].fd);
return(-1);
}
fd[0].events = POLLIN | POLLRDNORM;
while (1) {
static char buf[BUF_SIZE];
int bytes_read;
int retval;
/* Wait for up to 1 second for data. */
retval = poll(fd, 1, 1000);
if (retval && (fd[0].revents & POLLRDNORM)) {
/* Read data from printer gadget driver. */
bytes_read = read(fd[0].fd, buf, BUF_SIZE);
if (bytes_read < 0) {
printf("Error %d reading from %s\n",
fd[0].fd, PRINTER_FILE);
close(fd[0].fd);
return(-1);
} else if (bytes_read > 0) {
/* Write data to standard OUTPUT (stdout). */
fwrite(buf, 1, bytes_read, stdout);
fflush(stdout);
}
}
}
/* Close the device file. */
close(fd[0].fd);
return 0;
}
static int
write_printer_data()
{
struct pollfd fd[1];
/* Open device file for printer gadget. */
fd[0].fd = open (PRINTER_FILE, O_RDWR);
if (fd[0].fd < 0) {
printf("Error %d opening %s\n", fd[0].fd, PRINTER_FILE);
close(fd[0].fd);
return(-1);
}
fd[0].events = POLLOUT | POLLWRNORM;
while (1) {
int retval;
static char buf[BUF_SIZE];
/* Read data from standard INPUT (stdin). */
int bytes_read = fread(buf, 1, BUF_SIZE, stdin);
if (!bytes_read) {
break;
}
while (bytes_read) {
/* Wait for up to 1 second to sent data. */
retval = poll(fd, 1, 1000);
/* Write data to printer gadget driver. */
if (retval && (fd[0].revents & POLLWRNORM)) {
retval = write(fd[0].fd, buf, bytes_read);
if (retval < 0) {
printf("Error %d writing to %s\n",
fd[0].fd,
PRINTER_FILE);
close(fd[0].fd);
return(-1);
} else {
bytes_read -= retval;
}
}
}
}
/* Wait until the data has been sent. */
fsync(fd[0].fd);
/* Close the device file. */
close(fd[0].fd);
return 0;
}
static int
read_NB_printer_data()
{
int fd;
static char buf[BUF_SIZE];
int bytes_read;
/* Open device file for printer gadget. */
fd = open(PRINTER_FILE, O_RDWR|O_NONBLOCK);
if (fd < 0) {
printf("Error %d opening %s\n", fd, PRINTER_FILE);
close(fd);
return(-1);
}
while (1) {
/* Read data from printer gadget driver. */
bytes_read = read(fd, buf, BUF_SIZE);
if (bytes_read <= 0) {
break;
}
/* Write data to standard OUTPUT (stdout). */
fwrite(buf, 1, bytes_read, stdout);
fflush(stdout);
}
/* Close the device file. */
close(fd);
return 0;
}
static int
get_printer_status()
{
int retval;
int fd;
/* Open device file for printer gadget. */
fd = open(PRINTER_FILE, O_RDWR);
if (fd < 0) {
printf("Error %d opening %s\n", fd, PRINTER_FILE);
close(fd);
return(-1);
}
/* Make the IOCTL call. */
retval = ioctl(fd, GADGET_GET_PRINTER_STATUS);
if (retval < 0) {
fprintf(stderr, "ERROR: Failed to set printer status\n");
return(-1);
}
/* Close the device file. */
close(fd);
return(retval);
}
static int
set_printer_status(unsigned char buf, int clear_printer_status_bit)
{
int retval;
int fd;
retval = get_printer_status();
if (retval < 0) {
fprintf(stderr, "ERROR: Failed to get printer status\n");
return(-1);
}
/* Open device file for printer gadget. */
fd = open(PRINTER_FILE, O_RDWR);
if (fd < 0) {
printf("Error %d opening %s\n", fd, PRINTER_FILE);
close(fd);
return(-1);
}
if (clear_printer_status_bit) {
retval &= ~buf;
} else {
retval |= buf;
}
/* Make the IOCTL call. */
if (ioctl(fd, GADGET_SET_PRINTER_STATUS, (unsigned char)retval)) {
fprintf(stderr, "ERROR: Failed to set printer status\n");
return(-1);
}
/* Close the device file. */
close(fd);
return 0;
}
static int
display_printer_status()
{
char printer_status;
printer_status = get_printer_status();
if (printer_status < 0) {
fprintf(stderr, "ERROR: Failed to get printer status\n");
return(-1);
}
printf("Printer status is:\n");
if (printer_status & PRINTER_SELECTED) {
printf(" Printer is Selected\n");
} else {
printf(" Printer is NOT Selected\n");
}
if (printer_status & PRINTER_PAPER_EMPTY) {
printf(" Paper is Out\n");
} else {
printf(" Paper is Loaded\n");
}
if (printer_status & PRINTER_NOT_ERROR) {
printf(" Printer OK\n");
} else {
printf(" Printer ERROR\n");
}
return(0);
}
int
main(int argc, char *argv[])
{
int i; /* Looping var */
int retval = 0;
/* No Args */
if (argc == 1) {
usage(0);
exit(0);
}
for (i = 1; i < argc && !retval; i ++) {
if (argv[i][0] != '-') {
continue;
}
if (!strcmp(argv[i], "-get_status")) {
if (display_printer_status()) {
retval = 1;
}
} else if (!strcmp(argv[i], "-paper_loaded")) {
if (set_printer_status(PRINTER_PAPER_EMPTY, 1)) {
retval = 1;
}
} else if (!strcmp(argv[i], "-paper_out")) {
if (set_printer_status(PRINTER_PAPER_EMPTY, 0)) {
retval = 1;
}
} else if (!strcmp(argv[i], "-selected")) {
if (set_printer_status(PRINTER_SELECTED, 0)) {
retval = 1;
}
} else if (!strcmp(argv[i], "-not_selected")) {
if (set_printer_status(PRINTER_SELECTED, 1)) {
retval = 1;
}
} else if (!strcmp(argv[i], "-error")) {
if (set_printer_status(PRINTER_NOT_ERROR, 1)) {
retval = 1;
}
} else if (!strcmp(argv[i], "-no_error")) {
if (set_printer_status(PRINTER_NOT_ERROR, 0)) {
retval = 1;
}
} else if (!strcmp(argv[i], "-read_data")) {
if (read_printer_data()) {
retval = 1;
}
} else if (!strcmp(argv[i], "-write_data")) {
if (write_printer_data()) {
retval = 1;
}
} else if (!strcmp(argv[i], "-NB_read_data")) {
if (read_NB_printer_data()) {
retval = 1;
}
} else {
usage(argv[i]);
retval = 1;
}
}
exit(retval);
}
3. 한국어 전문 번역
영어 원문의 문단 순서와 의미를 유지한 전체 번역입니다. 코드, 함수명, symbol과 URL은 원문 표기를 유지합니다.
제목과 문서 정보
1-10이 문서는 Linux USB Printer Gadget 드라이버를 설명하며 작성일은 2007년 6월 4일입니다.
저작권은 Craig W. Nadler에게 있으며 작성자 이메일은 영어 원문에 그대로 보존됩니다.
===============================
Linux USB Printer Gadget Driver
===============================
06/04/2007
Copyright (C) 2007 Craig W. Nadler <[email protected]>
일반 구조
11-33이 드라이버는 Linux를 임베디드 운영체제로 사용하는 프린터 펌웨어를 작성할 때 사용합니다. Linux 호스트에 일반 USB 프린터를 연결해 출력하는 호스트 드라이버와는 관계가 없습니다.
가젯 장치에는 USB device controller와 Linux USB Gadget API의 gadget 또는 device-class driver를 받아들이는 UDC 드라이버가 필요합니다.
먼저 USB device controller 드라이버를 적재하고 그 다음 printer gadget 드라이버를 적재하면, device port에 연결된 USB 호스트에 printer interface가 나타납니다.
프린터 펌웨어는 사용자 모드에서 실행하는 구조입니다. 사용자 공간 펌웨어는 `/dev/g_printer` 장치 파일을 통해 커널의 printer gadget 드라이버와 인쇄 데이터를 읽고 씁니다.
호스트가 프린터 상태를 요청하면 드라이버는 상태 바이트를 반환합니다. 사용자 공간은 같은 장치 파일로 상태 바이트를 읽거나 쓸 수 있고 blocking과 non-blocking read/write를 모두 사용할 수 있습니다.
사용자 공간 프린터 펌웨어와 USB 호스트 사이의 경로입니다.
General
=======
This driver may be used if you are writing printer firmware using Linux as
the embedded OS. This driver has nothing to do with using a printer with
your Linux host system.
You will need a USB device controller and a Linux driver for it that accepts
a gadget / "device class" driver using the Linux USB Gadget API. After the
USB device controller driver is loaded then load the printer gadget driver.
This will present a printer interface to the USB Host that your USB Device
port is connected to.
This driver is structured for printer firmware that runs in user mode. The
user mode printer firmware will read and write data from the kernel mode
printer gadget driver using a device file. The printer returns a printer status
byte when the USB HOST sends a device request to get the printer status. The
user space firmware can read or write this status byte using a device file
/dev/g_printer . Both blocking and non-blocking read/write calls are supported.
드라이버 적재
34-45예제는 Netchip 2280 USB device controller를 사용합니다. `modprobe net2280`으로 UDC 드라이버를 먼저 적재하고 `modprobe g_printer`로 프린터 가젯을 적재합니다.
`g_printer`를 적재할 때 `idVendor`, `idProduct` 같은 module parameter를 명령행에 지정할 수 있습니다.
예제 `modprobe g_printer idVendor=0x0525 idProduct=0xa4a8`은 기본 식별자를 명시적으로 넘기는 형태입니다.
Howto Use This Driver
=====================
To load the USB device controller driver and the printer gadget driver. The
following example uses the Netchip 2280 USB device controller driver::
modprobe net2280
modprobe g_printer
The follow command line parameter can be used when loading the printer gadget
(ex: modprobe g_printer idVendor=0x0525 idProduct=0xa4a8 ):
g_printer 모듈 매개변수
46-86`idVendor`는 device descriptor의 Vendor ID입니다. 기본값 `0x0525`는 Netchip의 ID이므로 제품을 출시하기 전에 반드시 자신에게 할당된 Vendor ID로 바꿔야 합니다.
Vendor ID가 없다면 USB-IF 절차에 따라 발급받아야 하며, 다른 조직의 ID나 예제 기본값을 제품에 그대로 사용하면 안 됩니다.
`idProduct`는 Product ID입니다. 기본값은 `0xa4a8`이며 자신이 판매하는 다른 USB 제품과 겹치지 않는 값으로 바꿔야 합니다. 제품 번호를 `0x0001`부터 체계적으로 할당하는 방식을 고려할 수 있습니다.
`bcdDevice`는 제품 version 번호입니다. 펌웨어 version을 이 값에 넣으면 호스트가 장치 revision을 구분할 수 있습니다.
`iManufacturer`는 vendor 이름 문자열이고 `iProduct`는 product 이름 문자열입니다.
`iSerialNum`은 serial number 문자열이며 제품의 각 unit마다 고유한 값으로 바꿔야 합니다.
`iPNPstring`은 프린터의 PNP ID 문자열입니다. 명령행에서 지정하거나 제품의 PNP ID를 소스에 고정할 수 있습니다.
`qlen`은 endpoint마다 사용할 8 KiB buffer 수입니다. 기본값은 10이며 제품의 처리량과 메모리 요구에 맞게 조정해야 합니다.
필요하다면 queue 길이뿐 아니라 각 buffer의 크기도 제품 특성에 맞게 조정합니다.
descriptor 식별자, 문자열과 endpoint queue 설정입니다.
idVendor
This is the Vendor ID used in the device descriptor. The default is
the Netchip vendor id 0x0525. YOU MUST CHANGE TO YOUR OWN VENDOR ID
BEFORE RELEASING A PRODUCT. If you plan to release a product and don't
already have a Vendor ID please see www.usb.org for details on how to
get one.
idProduct
This is the Product ID used in the device descriptor. The default
is 0xa4a8, you should change this to an ID that's not used by any of
your other USB products if you have any. It would be a good idea to
start numbering your products starting with say 0x0001.
bcdDevice
This is the version number of your product. It would be a good idea
to put your firmware version here.
iManufacturer
A string containing the name of the Vendor.
iProduct
A string containing the Product Name.
iSerialNum
A string containing the Serial Number. This should be changed for
each unit of your product.
iPNPstring
The PNP ID string used for this printer. You will want to set
either on the command line or hard code the PNP ID string used for
your printer product.
qlen
The number of 8k buffers to use per endpoint. The default is 10, you
should tune this for your product. You may also want to tune the
size of each buffer for your product.
예제 코드 빌드
87-100예제 프로그램은 실제 print engine 대신 표준 출력과 통신합니다. 호스트에서 받은 인쇄 데이터를 확인하거나 표준 입력 데이터를 호스트로 보내는 시험 도구입니다.
코드를 `prn_example.c`로 저장한 뒤 `gcc prn_example.c -o prn_example`로 컴파일합니다.
프로그램은 `/dev/g_printer`를 열므로 printer gadget 드라이버가 적재되어 장치 파일이 준비된 상태에서 실행해야 합니다.
Using The Example Code
======================
This example code talks to stdout, instead of a print engine.
To compile the test code below:
1) save it to a file called prn_example.c
2) compile the code with the follow command::
gcc prn_example.c -o prn_example
예제 프로그램 사용법
101-152`prn_example -read_data`는 USB 호스트가 보낸 printer data를 읽어 표준 출력으로 내보냅니다.
`cat data_file | prn_example -write_data`는 파일 내용을 표준 입력으로 받아 printer gadget 드라이버에 기록하고 호스트로 전송합니다.
`prn_example -get_status`는 현재 프린터 상태를 읽어 Selected, Paper, Error 세 항목을 사람이 읽을 수 있는 문장으로 표시합니다.
`-selected`와 `-not_selected`는 프린터의 online 선택 상태를 각각 설정하거나 해제합니다.
`-paper_out`과 `-paper_loaded`는 용지 없음 비트를 각각 설정하거나 해제합니다.
`-no_error`와 `-error`는 `PRINTER_NOT_ERROR` 비트를 설정하거나 해제해 정상 또는 오류 상태를 나타냅니다.
상태 명령은 현재 상태 바이트의 다른 비트를 보존하면서 지정한 비트만 변경합니다.
문서에 제시된 사용자 공간 시험 명령입니다.
상태를 표시할 때 각 bit가 해석되는 방식입니다.
To read printer data from the host to stdout::
# prn_example -read_data
To write printer data from a file (data_file) to the host::
# cat data_file | prn_example -write_data
To get the current printer status for the gadget driver:::
# prn_example -get_status
Printer status is:
Printer is NOT Selected
Paper is Out
Printer OK
To set printer to Selected/On-line::
# prn_example -selected
To set printer to Not Selected/Off-line::
# prn_example -not_selected
To set paper status to paper out::
# prn_example -paper_out
To set paper status to paper loaded::
# prn_example -paper_loaded
To set error status to printer OK::
# prn_example -no_error
To set error status to ERROR::
# prn_example -error
예제 헤더, 상수와 usage
153-204예제는 표준 I/O, 메모리·프로세스 종료, 파일 제어, `poll`, `ioctl`과 printer gadget ABI에 필요한 헤더를 포함합니다.
`PRINTER_FILE`은 `/dev/g_printer`, `BUF_SIZE`는 512로 정의합니다.
`usage()`는 알 수 없는 option을 받으면 오류를 출력하고 지원하는 모든 명령을 안내한 뒤 exit status 1로 종료합니다.
원문 사용법 문자열의 `Write printer sata to driver`는 명백한 `data` 오타이지만 실행 코드와 원문 보존을 위해 수정하지 않습니다.
고정 경로와 API 역할을 정리했습니다.
Example Code
============
::
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <linux/poll.h>
#include <sys/ioctl.h>
#include <linux/usb/g_printer.h>
#define PRINTER_FILE "/dev/g_printer"
#define BUF_SIZE 512
/*
* 'usage()' - Show program usage.
*/
static void
usage(const char *option) /* I - Option string or NULL */
{
if (option) {
fprintf(stderr,"prn_example: Unknown option \"%s\"!\n",
option);
}
fputs("\n", stderr);
fputs("Usage: prn_example -[options]\n", stderr);
fputs("Options:\n", stderr);
fputs("\n", stderr);
fputs("-get_status Get the current printer status.\n", stderr);
fputs("-selected Set the selected status to selected.\n", stderr);
fputs("-not_selected Set the selected status to NOT selected.\n",
stderr);
fputs("-error Set the error status to error.\n", stderr);
fputs("-no_error Set the error status to NO error.\n", stderr);
fputs("-paper_out Set the paper status to paper out.\n", stderr);
fputs("-paper_loaded Set the paper status to paper loaded.\n",
stderr);
fputs("-read_data Read printer data from driver.\n", stderr);
fputs("-write_data Write printer sata to driver.\n", stderr);
fputs("-NB_read_data (Non-Blocking) Read printer data from driver.\n",
stderr);
fputs("\n\n", stderr);
exit(1);
}
Blocking printer data 읽기
205-254`read_printer_data()`는 `/dev/g_printer`를 `O_RDWR`로 열고 실패하면 오류를 출력한 뒤 `-1`을 반환합니다.
장치 descriptor의 event를 `POLLIN | POLLRDNORM`으로 설정하고 무한 루프에서 최대 1초 동안 `poll()`합니다.
읽기 가능한 이벤트가 오면 최대 512바이트를 읽습니다. read 오류에서는 장치를 닫고 `-1`을 반환합니다.
읽은 바이트가 있으면 `fwrite()`로 stdout에 그대로 쓰고 `fflush()`로 즉시 출력합니다. 이 데이터는 호스트가 프린터로 전송한 인쇄 stream입니다.
함수의 무한 루프 뒤 close 코드는 일반 실행에서는 도달하지 않지만 원문 예제 구조를 그대로 보존합니다.
호스트의 printer data가 표준 출력으로 나오는 순서입니다.
static int
read_printer_data()
{
struct pollfd fd[1];
/* Open device file for printer gadget. */
fd[0].fd = open(PRINTER_FILE, O_RDWR);
if (fd[0].fd < 0) {
printf("Error %d opening %s\n", fd[0].fd, PRINTER_FILE);
close(fd[0].fd);
return(-1);
}
fd[0].events = POLLIN | POLLRDNORM;
while (1) {
static char buf[BUF_SIZE];
int bytes_read;
int retval;
/* Wait for up to 1 second for data. */
retval = poll(fd, 1, 1000);
if (retval && (fd[0].revents & POLLRDNORM)) {
/* Read data from printer gadget driver. */
bytes_read = read(fd[0].fd, buf, BUF_SIZE);
if (bytes_read < 0) {
printf("Error %d reading from %s\n",
fd[0].fd, PRINTER_FILE);
close(fd[0].fd);
return(-1);
} else if (bytes_read > 0) {
/* Write data to standard OUTPUT (stdout). */
fwrite(buf, 1, bytes_read, stdout);
fflush(stdout);
}
}
}
/* Close the device file. */
close(fd[0].fd);
return 0;
}
Blocking printer data 쓰기
255-313`write_printer_data()`는 장치를 `O_RDWR`로 열고 `POLLOUT | POLLWRNORM` 쓰기 이벤트를 감시합니다.
외부 루프에서 stdin을 최대 512바이트씩 읽으며 EOF를 만나면 전송 루프를 끝냅니다.
한 buffer를 모두 쓸 때까지 내부 루프에서 `poll()`로 최대 1초씩 기다리고, 쓰기 가능하면 남은 바이트를 장치에 기록합니다.
부분 write가 발생할 수 있으므로 실제로 기록한 `retval`만큼 `bytes_read`에서 빼고 남은 데이터가 0이 될 때까지 반복합니다.
모든 입력을 처리한 뒤 `fsync()`로 데이터가 전송될 때까지 기다리고 장치를 닫습니다.
표준 입력을 호스트 방향 printer data로 보내는 순서입니다.
static int
write_printer_data()
{
struct pollfd fd[1];
/* Open device file for printer gadget. */
fd[0].fd = open (PRINTER_FILE, O_RDWR);
if (fd[0].fd < 0) {
printf("Error %d opening %s\n", fd[0].fd, PRINTER_FILE);
close(fd[0].fd);
return(-1);
}
fd[0].events = POLLOUT | POLLWRNORM;
while (1) {
int retval;
static char buf[BUF_SIZE];
/* Read data from standard INPUT (stdin). */
int bytes_read = fread(buf, 1, BUF_SIZE, stdin);
if (!bytes_read) {
break;
}
while (bytes_read) {
/* Wait for up to 1 second to sent data. */
retval = poll(fd, 1, 1000);
/* Write data to printer gadget driver. */
if (retval && (fd[0].revents & POLLWRNORM)) {
retval = write(fd[0].fd, buf, bytes_read);
if (retval < 0) {
printf("Error %d writing to %s\n",
fd[0].fd,
PRINTER_FILE);
close(fd[0].fd);
return(-1);
} else {
bytes_read -= retval;
}
}
}
}
/* Wait until the data has been sent. */
fsync(fd[0].fd);
/* Close the device file. */
close(fd[0].fd);
return 0;
}
Non-blocking printer data 읽기
314-347`read_NB_printer_data()`는 `/dev/g_printer`를 `O_RDWR | O_NONBLOCK`으로 열어 기다리지 않는 read를 사용합니다.
반복문은 최대 512바이트를 읽고 반환값이 0 이하이면 즉시 끝납니다. 데이터가 있으면 stdout에 쓰고 flush합니다.
blocking 함수처럼 `poll()`로 새 데이터를 기다리지 않으므로 호출 시점에 queue에 쌓인 데이터만 비운 뒤 반환합니다.
이 함수는 `-NB_read_data` option에 연결됩니다.
두 예제 함수의 대기 동작 차이입니다.
static int
read_NB_printer_data()
{
int fd;
static char buf[BUF_SIZE];
int bytes_read;
/* Open device file for printer gadget. */
fd = open(PRINTER_FILE, O_RDWR|O_NONBLOCK);
if (fd < 0) {
printf("Error %d opening %s\n", fd, PRINTER_FILE);
close(fd);
return(-1);
}
while (1) {
/* Read data from printer gadget driver. */
bytes_read = read(fd, buf, BUF_SIZE);
if (bytes_read <= 0) {
break;
}
/* Write data to standard OUTPUT (stdout). */
fwrite(buf, 1, bytes_read, stdout);
fflush(stdout);
}
/* Close the device file. */
close(fd);
return 0;
}
프린터 상태 읽기
348-375`get_printer_status()`는 장치를 열고 `ioctl(fd, GADGET_GET_PRINTER_STATUS)`를 호출합니다.
ioctl 반환값이 현재 printer status byte이며 음수이면 실패로 처리합니다.
오류 메시지는 원문에서 `Failed to set printer status`라고 되어 있지만 실제 호출은 상태 조회입니다. 코드 보존을 위해 원문은 수정하지 않고 해설에서만 의미를 바로잡습니다.
성공하면 descriptor를 닫고 상태값을 그대로 호출자에게 반환합니다.
예제에서 사용하는 두 ioctl입니다.
static int
get_printer_status()
{
int retval;
int fd;
/* Open device file for printer gadget. */
fd = open(PRINTER_FILE, O_RDWR);
if (fd < 0) {
printf("Error %d opening %s\n", fd, PRINTER_FILE);
close(fd);
return(-1);
}
/* Make the IOCTL call. */
retval = ioctl(fd, GADGET_GET_PRINTER_STATUS);
if (retval < 0) {
fprintf(stderr, "ERROR: Failed to set printer status\n");
return(-1);
}
/* Close the device file. */
close(fd);
return(retval);
}
프린터 상태 비트 변경
376-415`set_printer_status()`는 먼저 `get_printer_status()`로 현재 바이트를 읽어 다른 상태 비트를 보존합니다.
`clear_printer_status_bit`가 참이면 `retval &= ~buf`로 지정 비트를 지우고, 거짓이면 `retval |= buf`로 비트를 설정합니다.
변경한 값을 `GADGET_SET_PRINTER_STATUS` ioctl의 세 번째 인수로 전달합니다.
조회·open·set ioctl 중 하나라도 실패하면 오류를 출력하고 `-1`을 반환합니다.
성공하면 장치를 닫고 0을 반환합니다.
선택한 비트만 바꾸고 나머지 상태를 보존합니다.
static int
set_printer_status(unsigned char buf, int clear_printer_status_bit)
{
int retval;
int fd;
retval = get_printer_status();
if (retval < 0) {
fprintf(stderr, "ERROR: Failed to get printer status\n");
return(-1);
}
/* Open device file for printer gadget. */
fd = open(PRINTER_FILE, O_RDWR);
if (fd < 0) {
printf("Error %d opening %s\n", fd, PRINTER_FILE);
close(fd);
return(-1);
}
if (clear_printer_status_bit) {
retval &= ~buf;
} else {
retval |= buf;
}
/* Make the IOCTL call. */
if (ioctl(fd, GADGET_SET_PRINTER_STATUS, (unsigned char)retval)) {
fprintf(stderr, "ERROR: Failed to set printer status\n");
return(-1);
}
/* Close the device file. */
close(fd);
return 0;
}
프린터 상태 표시
416-447`display_printer_status()`는 현재 상태 바이트를 가져와 세 bit를 차례로 검사합니다.
`PRINTER_SELECTED`가 설정되면 Selected, 아니면 NOT Selected를 출력합니다.
`PRINTER_PAPER_EMPTY`가 설정되면 Paper is Out, 아니면 Paper is Loaded를 출력합니다.
`PRINTER_NOT_ERROR`가 설정되면 Printer OK, 아니면 Printer ERROR를 출력합니다. 정상 상태가 1인 active-high NOT_ERROR bit라는 점에 주의해야 합니다.
세 status bit의 출력 문구입니다.
static int
display_printer_status()
{
char printer_status;
printer_status = get_printer_status();
if (printer_status < 0) {
fprintf(stderr, "ERROR: Failed to get printer status\n");
return(-1);
}
printf("Printer status is:\n");
if (printer_status & PRINTER_SELECTED) {
printf(" Printer is Selected\n");
} else {
printf(" Printer is NOT Selected\n");
}
if (printer_status & PRINTER_PAPER_EMPTY) {
printf(" Paper is Out\n");
} else {
printf(" Paper is Loaded\n");
}
if (printer_status & PRINTER_NOT_ERROR) {
printf(" Printer OK\n");
} else {
printf(" Printer ERROR\n");
}
return(0);
}
main option dispatch
448-523`main()`은 인수가 없으면 `usage()`를 호출합니다. 이후 각 인수를 순회하며 `-`로 시작하지 않는 값은 건너뜁니다.
`-get_status`는 `display_printer_status()`를 호출합니다.
`-paper_loaded`, `-not_selected`, `-error`는 해당 bit를 지우고, `-paper_out`, `-selected`, `-no_error`는 해당 bit를 설정하도록 `set_printer_status()`를 호출합니다.
`-read_data`, `-write_data`, `-NB_read_data`는 각각 blocking read, blocking write, non-blocking read 함수를 호출합니다.
어느 함수든 실패하면 `retval`을 1로 바꾸어 추가 option 처리를 멈추며, 알 수 없는 option도 usage를 출력하고 실패로 처리합니다.
마지막에는 누적 결과값으로 프로세스를 종료합니다.
각 option이 호출하는 함수와 status bit 동작입니다.
int
main(int argc, char *argv[])
{
int i; /* Looping var */
int retval = 0;
/* No Args */
if (argc == 1) {
usage(0);
exit(0);
}
for (i = 1; i < argc && !retval; i ++) {
if (argv[i][0] != '-') {
continue;
}
if (!strcmp(argv[i], "-get_status")) {
if (display_printer_status()) {
retval = 1;
}
} else if (!strcmp(argv[i], "-paper_loaded")) {
if (set_printer_status(PRINTER_PAPER_EMPTY, 1)) {
retval = 1;
}
} else if (!strcmp(argv[i], "-paper_out")) {
if (set_printer_status(PRINTER_PAPER_EMPTY, 0)) {
retval = 1;
}
} else if (!strcmp(argv[i], "-selected")) {
if (set_printer_status(PRINTER_SELECTED, 0)) {
retval = 1;
}
} else if (!strcmp(argv[i], "-not_selected")) {
if (set_printer_status(PRINTER_SELECTED, 1)) {
retval = 1;
}
} else if (!strcmp(argv[i], "-error")) {
if (set_printer_status(PRINTER_NOT_ERROR, 1)) {
retval = 1;
}
} else if (!strcmp(argv[i], "-no_error")) {
if (set_printer_status(PRINTER_NOT_ERROR, 0)) {
retval = 1;
}
} else if (!strcmp(argv[i], "-read_data")) {
if (read_printer_data()) {
retval = 1;
}
} else if (!strcmp(argv[i], "-write_data")) {
if (write_printer_data()) {
retval = 1;
}
} else if (!strcmp(argv[i], "-NB_read_data")) {
if (read_NB_printer_data()) {
retval = 1;
}
} else {
usage(argv[i]);
retval = 1;
}
}
exit(retval);
}
요약·해설
gadget_printer.rst:1-523`g_printer`는 임베디드 Linux 장치를 USB 프린터로 보이게 하는 device-side 드라이버입니다. UDC 드라이버 뒤에 가젯 드라이버를 적재하고, 제품 고유 VID/PID·문자열·PNP ID와 endpoint queue를 설정해야 합니다.
사용자 공간 프린터 펌웨어는 `/dev/g_printer`를 통해 호스트의 인쇄 데이터를 읽고 응답 데이터를 씁니다. blocking `poll()` 방식과 `O_NONBLOCK` 방식이 모두 가능하며 상태 바이트는 GET/SET ioctl로 제어합니다.
예제는 Selected, Paper Empty, Not Error 세 bit를 read-modify-write 방식으로 바꾸어 다른 상태를 보존합니다. 제품화할 때는 예제 Vendor ID를 그대로 쓰지 말고 정식으로 할당된 식별자를 사용해야 합니다.