← Documents Documentation/usb/gadget_printer.rst GitHub 원문 ↗

Linux 6.18.37 · USB

Linux USB 프린터 가젯 드라이버

임베디드 Linux 프린터 펌웨어를 위한 USB Printer Gadget의 모듈 설정, /dev/g_printer 데이터 I/O와 상태 ioctl 예제를 설명합니다.

Source pathDocumentation/usb/gadget_printer.rst
Source versionLinux v6.18.37
TranslationDUJINLABS 전문 번역 + 해설

요약·해설과 원문, 전문 번역을 서로 분리했습니다. API 이름, symbol, source path는 원문 표기를 사용합니다.

1. 요약·해설

원문의 핵심 논리와 kernel programming 관점의 보충 설명입니다. 아래의 전문 번역과는 별도로 작성했습니다.

요약·해설

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를 그대로 쓰지 말고 정식으로 할당된 식별자를 사용해야 합니다.

2. 영어 원문 전체

번역 기준이 된 Linux v6.18.37 원문입니다. 줄 번호는 이 버전의 파일 좌표입니다.

원문 전체 펼치기
1 ===============================
2 Linux USB Printer Gadget Driver
3 ===============================
4
5 06/04/2007
6
7 Copyright (C) 2007 Craig W. Nadler <[email protected]>
8
9
10
11 General
12 =======
13
14 This driver may be used if you are writing printer firmware using Linux as
15 the embedded OS. This driver has nothing to do with using a printer with
16 your Linux host system.
17
18 You will need a USB device controller and a Linux driver for it that accepts
19 a gadget / "device class" driver using the Linux USB Gadget API. After the
20 USB device controller driver is loaded then load the printer gadget driver.
21 This will present a printer interface to the USB Host that your USB Device
22 port is connected to.
23
24 This driver is structured for printer firmware that runs in user mode. The
25 user mode printer firmware will read and write data from the kernel mode
26 printer gadget driver using a device file. The printer returns a printer status
27 byte when the USB HOST sends a device request to get the printer status. The
28 user space firmware can read or write this status byte using a device file
29 /dev/g_printer . Both blocking and non-blocking read/write calls are supported.
30
31
32
33
34 Howto Use This Driver
35 =====================
36
37 To load the USB device controller driver and the printer gadget driver. The
38 following example uses the Netchip 2280 USB device controller driver::
39
40 modprobe net2280
41 modprobe g_printer
42
43
44 The follow command line parameter can be used when loading the printer gadget
45 (ex: modprobe g_printer idVendor=0x0525 idProduct=0xa4a8 ):
46
47 idVendor
48 This is the Vendor ID used in the device descriptor. The default is
49 the Netchip vendor id 0x0525. YOU MUST CHANGE TO YOUR OWN VENDOR ID
50 BEFORE RELEASING A PRODUCT. If you plan to release a product and don't
51 already have a Vendor ID please see www.usb.org for details on how to
52 get one.
53
54 idProduct
55 This is the Product ID used in the device descriptor. The default
56 is 0xa4a8, you should change this to an ID that's not used by any of
57 your other USB products if you have any. It would be a good idea to
58 start numbering your products starting with say 0x0001.
59
60 bcdDevice
61 This is the version number of your product. It would be a good idea
62 to put your firmware version here.
63
64 iManufacturer
65 A string containing the name of the Vendor.
66
67 iProduct
68 A string containing the Product Name.
69
70 iSerialNum
71 A string containing the Serial Number. This should be changed for
72 each unit of your product.
73
74 iPNPstring
75 The PNP ID string used for this printer. You will want to set
76 either on the command line or hard code the PNP ID string used for
77 your printer product.
78
79 qlen
80 The number of 8k buffers to use per endpoint. The default is 10, you
81 should tune this for your product. You may also want to tune the
82 size of each buffer for your product.
83
84
85
86
87 Using The Example Code
88 ======================
89
90 This example code talks to stdout, instead of a print engine.
91
92 To compile the test code below:
93
94 1) save it to a file called prn_example.c
95 2) compile the code with the follow command::
96
97 gcc prn_example.c -o prn_example
98
99
100
101 To read printer data from the host to stdout::
102
103 # prn_example -read_data
104
105
106 To write printer data from a file (data_file) to the host::
107
108 # cat data_file | prn_example -write_data
109
110
111 To get the current printer status for the gadget driver:::
112
113 # prn_example -get_status
114
115 Printer status is:
116 Printer is NOT Selected
117 Paper is Out
118 Printer OK
119
120
121 To set printer to Selected/On-line::
122
123 # prn_example -selected
124
125
126 To set printer to Not Selected/Off-line::
127
128 # prn_example -not_selected
129
130
131 To set paper status to paper out::
132
133 # prn_example -paper_out
134
135
136 To set paper status to paper loaded::
137
138 # prn_example -paper_loaded
139
140
141 To set error status to printer OK::
142
143 # prn_example -no_error
144
145
146 To set error status to ERROR::
147
148 # prn_example -error
149
150
151
152
153 Example Code
154 ============
155
156 ::
157
158
159 #include <stdio.h>
160 #include <stdlib.h>
161 #include <fcntl.h>
162 #include <linux/poll.h>
163 #include <sys/ioctl.h>
164 #include <linux/usb/g_printer.h>
165
166 #define PRINTER_FILE "/dev/g_printer"
167 #define BUF_SIZE 512
168
169
170 /*
171 * 'usage()' - Show program usage.
172 */
173
174 static void
175 usage(const char *option) /* I - Option string or NULL */
176 {
177 if (option) {
178 fprintf(stderr,"prn_example: Unknown option \"%s\"!\n",
179 option);
180 }
181
182 fputs("\n", stderr);
183 fputs("Usage: prn_example -[options]\n", stderr);
184 fputs("Options:\n", stderr);
185 fputs("\n", stderr);
186 fputs("-get_status Get the current printer status.\n", stderr);
187 fputs("-selected Set the selected status to selected.\n", stderr);
188 fputs("-not_selected Set the selected status to NOT selected.\n",
189 stderr);
190 fputs("-error Set the error status to error.\n", stderr);
191 fputs("-no_error Set the error status to NO error.\n", stderr);
192 fputs("-paper_out Set the paper status to paper out.\n", stderr);
193 fputs("-paper_loaded Set the paper status to paper loaded.\n",
194 stderr);
195 fputs("-read_data Read printer data from driver.\n", stderr);
196 fputs("-write_data Write printer sata to driver.\n", stderr);
197 fputs("-NB_read_data (Non-Blocking) Read printer data from driver.\n",
198 stderr);
199 fputs("\n\n", stderr);
200
201 exit(1);
202 }
203
204
205 static int
206 read_printer_data()
207 {
208 struct pollfd fd[1];
209
210 /* Open device file for printer gadget. */
211 fd[0].fd = open(PRINTER_FILE, O_RDWR);
212 if (fd[0].fd < 0) {
213 printf("Error %d opening %s\n", fd[0].fd, PRINTER_FILE);
214 close(fd[0].fd);
215 return(-1);
216 }
217
218 fd[0].events = POLLIN | POLLRDNORM;
219
220 while (1) {
221 static char buf[BUF_SIZE];
222 int bytes_read;
223 int retval;
224
225 /* Wait for up to 1 second for data. */
226 retval = poll(fd, 1, 1000);
227
228 if (retval && (fd[0].revents & POLLRDNORM)) {
229
230 /* Read data from printer gadget driver. */
231 bytes_read = read(fd[0].fd, buf, BUF_SIZE);
232
233 if (bytes_read < 0) {
234 printf("Error %d reading from %s\n",
235 fd[0].fd, PRINTER_FILE);
236 close(fd[0].fd);
237 return(-1);
238 } else if (bytes_read > 0) {
239 /* Write data to standard OUTPUT (stdout). */
240 fwrite(buf, 1, bytes_read, stdout);
241 fflush(stdout);
242 }
243
244 }
245
246 }
247
248 /* Close the device file. */
249 close(fd[0].fd);
250
251 return 0;
252 }
253
254
255 static int
256 write_printer_data()
257 {
258 struct pollfd fd[1];
259
260 /* Open device file for printer gadget. */
261 fd[0].fd = open (PRINTER_FILE, O_RDWR);
262 if (fd[0].fd < 0) {
263 printf("Error %d opening %s\n", fd[0].fd, PRINTER_FILE);
264 close(fd[0].fd);
265 return(-1);
266 }
267
268 fd[0].events = POLLOUT | POLLWRNORM;
269
270 while (1) {
271 int retval;
272 static char buf[BUF_SIZE];
273 /* Read data from standard INPUT (stdin). */
274 int bytes_read = fread(buf, 1, BUF_SIZE, stdin);
275
276 if (!bytes_read) {
277 break;
278 }
279
280 while (bytes_read) {
281
282 /* Wait for up to 1 second to sent data. */
283 retval = poll(fd, 1, 1000);
284
285 /* Write data to printer gadget driver. */
286 if (retval && (fd[0].revents & POLLWRNORM)) {
287 retval = write(fd[0].fd, buf, bytes_read);
288 if (retval < 0) {
289 printf("Error %d writing to %s\n",
290 fd[0].fd,
291 PRINTER_FILE);
292 close(fd[0].fd);
293 return(-1);
294 } else {
295 bytes_read -= retval;
296 }
297
298 }
299
300 }
301
302 }
303
304 /* Wait until the data has been sent. */
305 fsync(fd[0].fd);
306
307 /* Close the device file. */
308 close(fd[0].fd);
309
310 return 0;
311 }
312
313
314 static int
315 read_NB_printer_data()
316 {
317 int fd;
318 static char buf[BUF_SIZE];
319 int bytes_read;
320
321 /* Open device file for printer gadget. */
322 fd = open(PRINTER_FILE, O_RDWR|O_NONBLOCK);
323 if (fd < 0) {
324 printf("Error %d opening %s\n", fd, PRINTER_FILE);
325 close(fd);
326 return(-1);
327 }
328
329 while (1) {
330 /* Read data from printer gadget driver. */
331 bytes_read = read(fd, buf, BUF_SIZE);
332 if (bytes_read <= 0) {
333 break;
334 }
335
336 /* Write data to standard OUTPUT (stdout). */
337 fwrite(buf, 1, bytes_read, stdout);
338 fflush(stdout);
339 }
340
341 /* Close the device file. */
342 close(fd);
343
344 return 0;
345 }
346
347
348 static int
349 get_printer_status()
350 {
351 int retval;
352 int fd;
353
354 /* Open device file for printer gadget. */
355 fd = open(PRINTER_FILE, O_RDWR);
356 if (fd < 0) {
357 printf("Error %d opening %s\n", fd, PRINTER_FILE);
358 close(fd);
359 return(-1);
360 }
361
362 /* Make the IOCTL call. */
363 retval = ioctl(fd, GADGET_GET_PRINTER_STATUS);
364 if (retval < 0) {
365 fprintf(stderr, "ERROR: Failed to set printer status\n");
366 return(-1);
367 }
368
369 /* Close the device file. */
370 close(fd);
371
372 return(retval);
373 }
374
375
376 static int
377 set_printer_status(unsigned char buf, int clear_printer_status_bit)
378 {
379 int retval;
380 int fd;
381
382 retval = get_printer_status();
383 if (retval < 0) {
384 fprintf(stderr, "ERROR: Failed to get printer status\n");
385 return(-1);
386 }
387
388 /* Open device file for printer gadget. */
389 fd = open(PRINTER_FILE, O_RDWR);
390
391 if (fd < 0) {
392 printf("Error %d opening %s\n", fd, PRINTER_FILE);
393 close(fd);
394 return(-1);
395 }
396
397 if (clear_printer_status_bit) {
398 retval &= ~buf;
399 } else {
400 retval |= buf;
401 }
402
403 /* Make the IOCTL call. */
404 if (ioctl(fd, GADGET_SET_PRINTER_STATUS, (unsigned char)retval)) {
405 fprintf(stderr, "ERROR: Failed to set printer status\n");
406 return(-1);
407 }
408
409 /* Close the device file. */
410 close(fd);
411
412 return 0;
413 }
414
415
416 static int
417 display_printer_status()
418 {
419 char printer_status;
420
421 printer_status = get_printer_status();
422 if (printer_status < 0) {
423 fprintf(stderr, "ERROR: Failed to get printer status\n");
424 return(-1);
425 }
426
427 printf("Printer status is:\n");
428 if (printer_status & PRINTER_SELECTED) {
429 printf(" Printer is Selected\n");
430 } else {
431 printf(" Printer is NOT Selected\n");
432 }
433 if (printer_status & PRINTER_PAPER_EMPTY) {
434 printf(" Paper is Out\n");
435 } else {
436 printf(" Paper is Loaded\n");
437 }
438 if (printer_status & PRINTER_NOT_ERROR) {
439 printf(" Printer OK\n");
440 } else {
441 printf(" Printer ERROR\n");
442 }
443
444 return(0);
445 }
446
447
448 int
449 main(int argc, char *argv[])
450 {
451 int i; /* Looping var */
452 int retval = 0;
453
454 /* No Args */
455 if (argc == 1) {
456 usage(0);
457 exit(0);
458 }
459
460 for (i = 1; i < argc && !retval; i ++) {
461
462 if (argv[i][0] != '-') {
463 continue;
464 }
465
466 if (!strcmp(argv[i], "-get_status")) {
467 if (display_printer_status()) {
468 retval = 1;
469 }
470
471 } else if (!strcmp(argv[i], "-paper_loaded")) {
472 if (set_printer_status(PRINTER_PAPER_EMPTY, 1)) {
473 retval = 1;
474 }
475
476 } else if (!strcmp(argv[i], "-paper_out")) {
477 if (set_printer_status(PRINTER_PAPER_EMPTY, 0)) {
478 retval = 1;
479 }
480
481 } else if (!strcmp(argv[i], "-selected")) {
482 if (set_printer_status(PRINTER_SELECTED, 0)) {
483 retval = 1;
484 }
485
486 } else if (!strcmp(argv[i], "-not_selected")) {
487 if (set_printer_status(PRINTER_SELECTED, 1)) {
488 retval = 1;
489 }
490
491 } else if (!strcmp(argv[i], "-error")) {
492 if (set_printer_status(PRINTER_NOT_ERROR, 1)) {
493 retval = 1;
494 }
495
496 } else if (!strcmp(argv[i], "-no_error")) {
497 if (set_printer_status(PRINTER_NOT_ERROR, 0)) {
498 retval = 1;
499 }
500
501 } else if (!strcmp(argv[i], "-read_data")) {
502 if (read_printer_data()) {
503 retval = 1;
504 }
505
506 } else if (!strcmp(argv[i], "-write_data")) {
507 if (write_printer_data()) {
508 retval = 1;
509 }
510
511 } else if (!strcmp(argv[i], "-NB_read_data")) {
512 if (read_NB_printer_data()) {
513 retval = 1;
514 }
515
516 } else {
517 usage(argv[i]);
518 retval = 1;
519 }
520 }
521
522 exit(retval);
523 }
524

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를 모두 사용할 수 있습니다.

프린터 가젯 데이터 경로
사용자 공간 프린터 펌웨어가 /dev/g_printer 사용커널 printer gadget 드라이버가 데이터·상태 처리UDC 드라이버가 USB device controller 제어USB 호스트가 printer interface로 열거인쇄 데이터와 상태 요청이 양방향으로 전달

사용자 공간 프린터 펌웨어와 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의 크기도 제품 특성에 맞게 조정합니다.

프린터 가젯 모듈 매개변수
매개변수기본값·형식용도
idVendor0x0525제품 소유자의 USB Vendor ID
idProduct0xa4a8제품별 고유 Product ID
bcdDeviceBCD version제품 또는 펌웨어 version
iManufacturer문자열Vendor 이름
iProduct문자열Product 이름
iSerialNum문자열unit별 serial number
iPNPstring문자열프린터 PNP ID
qlen10endpoint당 8 KiB 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` 비트를 설정하거나 해제해 정상 또는 오류 상태를 나타냅니다.

상태 명령은 현재 상태 바이트의 다른 비트를 보존하면서 지정한 비트만 변경합니다.

prn_example 명령
Option동작
-read_data호스트 데이터를 읽어 stdout으로 출력
-write_datastdin 데이터를 호스트로 전송
-get_status현재 프린터 상태 표시
-selectedSelected/Online 설정
-not_selectedSelected 해제/Offline
-paper_outPaper Empty 설정
-paper_loadedPaper Empty 해제
-no_errorNot Error 설정
-errorNot Error 해제

문서에 제시된 사용자 공간 시험 명령입니다.

프린터 상태 비트의 의미
비트 symbol1일 때0일 때
PRINTER_SELECTEDPrinter is SelectedPrinter is NOT Selected
PRINTER_PAPER_EMPTYPaper is OutPaper is Loaded
PRINTER_NOT_ERRORPrinter OKPrinter 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` 오타이지만 실행 코드와 원문 보존을 위해 수정하지 않습니다.

예제의 I/O 인터페이스
항목값·API역할
PRINTER_FILE/dev/g_printer가젯 문자 장치
BUF_SIZE512사용자 공간 전송 buffer
pollPOLLIN/POLLOUTblocking 준비 상태 대기
ioctlGET/SET status프린터 상태 바이트 제어

고정 경로와 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 코드는 일반 실행에서는 도달하지 않지만 원문 예제 구조를 그대로 보존합니다.

Blocking read 경로
/dev/g_printer를 O_RDWR로 openPOLLIN | POLLRDNORM 등록poll로 최대 1초 대기준비되면 최대 512바이트 readfwrite와 fflush로 stdout 출력오류가 없으면 대기 반복

호스트의 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()`로 데이터가 전송될 때까지 기다리고 장치를 닫습니다.

Blocking write 경로
stdin에서 최대 512바이트 freadPOLLWRNORM 이벤트를 poll준비되면 남은 바이트 write부분 write만큼 잔여 길이 감소buffer 전체가 기록될 때까지 반복EOF 후 fsync하고 장치 close

표준 입력을 호스트 방향 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에 연결됩니다.

Blocking과 non-blocking read
함수Open flag데이터가 없을 때
read_printer_dataO_RDWRpoll로 계속 대기
read_NB_printer_dataO_RDWR | O_NONBLOCKread가 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;
  }

프린터 상태 읽기

348-375

`get_printer_status()`는 장치를 열고 `ioctl(fd, GADGET_GET_PRINTER_STATUS)`를 호출합니다.

ioctl 반환값이 현재 printer status byte이며 음수이면 실패로 처리합니다.

오류 메시지는 원문에서 `Failed to set printer status`라고 되어 있지만 실제 호출은 상태 조회입니다. 코드 보존을 위해 원문은 수정하지 않고 해설에서만 의미를 바로잡습니다.

성공하면 descriptor를 닫고 상태값을 그대로 호출자에게 반환합니다.

Printer gadget status ioctl
ioctl방향용도
GADGET_GET_PRINTER_STATUSkernel → userspace현재 상태 바이트 조회
GADGET_SET_PRINTER_STATUSuserspace → kernel변경된 상태 바이트 설정

예제에서 사용하는 두 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을 반환합니다.

상태 비트의 read-modify-write
GADGET_GET_PRINTER_STATUS로 현재 바이트 조회변경할 bit mask를 buf로 수신clear이면 AND NOT, set이면 OR 적용GADGET_SET_PRINTER_STATUS로 전체 바이트 기록장치 close 후 결과 반환

선택한 비트만 바꾸고 나머지 상태를 보존합니다.

  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라는 점에 주의해야 합니다.

display_printer_status 출력
조건거짓
status & PRINTER_SELECTEDPrinter is SelectedPrinter is NOT Selected
status & PRINTER_PAPER_EMPTYPaper is OutPaper is Loaded
status & PRINTER_NOT_ERRORPrinter OKPrinter ERROR

세 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를 출력하고 실패로 처리합니다.

마지막에는 누적 결과값으로 프로세스를 종료합니다.

main의 option dispatch
Option호출·동작
-get_statusdisplay_printer_status
-paper_loaded / -paper_outPRINTER_PAPER_EMPTY clear / set
-selected / -not_selectedPRINTER_SELECTED set / clear
-error / -no_errorPRINTER_NOT_ERROR clear / set
-read_dataread_printer_data
-write_datawrite_printer_data
-NB_read_dataread_NB_printer_data

각 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);
  }