* [PATCH 1/2] printer: Introduce printer subsystem
2022-01-13 11:56 [PATCH 0/2] Introduce printer subsystem and USB printer device Ruien Zhang
@ 2022-01-13 11:56 ` Ruien Zhang
2022-01-13 11:56 ` [PATCH 2/2] usb-printer: Introduce USB printer class Ruien Zhang
2022-01-14 9:32 ` [PATCH 0/2] Introduce printer subsystem and USB printer device Gerd Hoffmann
2 siblings, 0 replies; 7+ messages in thread
From: Ruien Zhang @ 2022-01-13 11:56 UTC (permalink / raw)
To: peter.maydell, richard.henderson, kraxel, eblake, pbonzini, berrange
Cc: qemu-devel, zhangruien
From: zhangruien <zhangruien@bytedance.com>
This patch describes the skeleton of QEMU printer subsystem with a
dummy builtin driver.
Signed-off-by: zhangruien <zhangruien@bytedance.com>
---
MAINTAINERS | 7 ++
include/printer/printer.h | 42 ++++++++++
meson.build | 12 ++-
meson_options.txt | 3 +
printer/builtin.c | 61 +++++++++++++++
printer/meson.build | 14 ++++
printer/printer.c | 191 ++++++++++++++++++++++++++++++++++++++++++++++
printer/trace-events | 5 ++
printer/trace.h | 1 +
qapi/meson.build | 1 +
qapi/printer.json | 47 ++++++++++++
qapi/qapi-schema.json | 1 +
qemu-options.hx | 8 ++
softmmu/vl.c | 4 +
14 files changed, 396 insertions(+), 1 deletion(-)
create mode 100644 include/printer/printer.h
create mode 100644 printer/builtin.c
create mode 100644 printer/meson.build
create mode 100644 printer/printer.c
create mode 100644 printer/trace-events
create mode 100644 printer/trace.h
create mode 100644 qapi/printer.json
diff --git a/MAINTAINERS b/MAINTAINERS
index c98a61caee..689f20d740 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -3086,6 +3086,13 @@ F: hw/core/clock-vmstate.c
F: hw/core/qdev-clock.c
F: docs/devel/clocks.rst
+Printer Subsystem
+M: Ruien Zhang <zhangruien@bytedance.com>
+S: Maintained
+F: include/printer
+F: printer
+F: qapi/printer.json
+
Usermode Emulation
------------------
Overall usermode emulation
diff --git a/include/printer/printer.h b/include/printer/printer.h
new file mode 100644
index 0000000000..c8afbc64c8
--- /dev/null
+++ b/include/printer/printer.h
@@ -0,0 +1,42 @@
+/*
+ * QEMU Printer subsystem header
+ *
+ * Copyright (c) 2022 ByteDance, Inc.
+ *
+ * Author:
+ * Ruien Zhang <zhangruien@bytedance.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#ifndef QEMU_PRINTER_H
+#define QEMU_PRINTER_H
+
+#include "hw/qdev-properties.h"
+#include "hw/qdev-properties-system.h"
+#include "qapi/qapi-types-printer.h"
+
+#define TYPE_PRINTERDEV "printerdev"
+
+struct QEMUPrinter {
+ Object *parent_obj;
+
+ char *model;
+ Printerdev *dev;
+
+ QLIST_ENTRY(QEMUPrinter) list;
+};
+
+OBJECT_DECLARE_TYPE(QEMUPrinter, QEMUPrinterClass, PRINTERDEV)
+
+struct QEMUPrinterClass {
+ ObjectClass parent_class;
+};
+
+void qemu_printer_new_from_opts(const char *opt);
+void qemu_printer_del(QEMUPrinter *printer);
+const char *qemu_printer_id(QEMUPrinter *printer);
+QEMUPrinter *qemu_printer_by_id(const char *id);
+
+#endif /* QEMU_PRINTER_H */
diff --git a/meson.build b/meson.build
index c1b1db1e28..b3db26190d 100644
--- a/meson.build
+++ b/meson.build
@@ -2397,6 +2397,7 @@ genh += hxdep
authz_ss = ss.source_set()
blockdev_ss = ss.source_set()
block_ss = ss.source_set()
+printer_ss = ss.source_set()
chardev_ss = ss.source_set()
common_ss = ss.source_set()
common_user_ss = ss.source_set()
@@ -2455,6 +2456,7 @@ if have_system
'audio',
'backends',
'backends/tpm',
+ 'printer',
'chardev',
'ebpf',
'hw/9pfs',
@@ -2574,6 +2576,7 @@ endif
subdir('audio')
subdir('io')
+subdir('printer')
subdir('chardev')
subdir('fsdev')
subdir('dump')
@@ -2843,6 +2846,13 @@ libqmp = static_library('qmp', qmp_ss.sources() + genh,
qmp = declare_dependency(link_whole: [libqmp])
+printer_ss = printer_ss.apply(config_host, strict: false)
+libprinter = static_library('printer', printer_ss.sources() + genh,
+ name_suffix: 'fa',
+ build_by_default: false)
+
+printer = declare_dependency(link_whole: libprinter)
+
libchardev = static_library('chardev', chardev_ss.sources() + genh,
name_suffix: 'fa',
dependencies: [gnutls],
@@ -2869,7 +2879,7 @@ foreach m : block_mods + softmmu_mods
install_dir: qemu_moddir)
endforeach
-softmmu_ss.add(authz, blockdev, chardev, crypto, io, qmp)
+softmmu_ss.add(authz, blockdev, printer, chardev, crypto, io, qmp)
common_ss.add(qom, qemuutil)
common_ss.add_all(when: 'CONFIG_SOFTMMU', if_true: [softmmu_ss])
diff --git a/meson_options.txt b/meson_options.txt
index 921967eddb..5b3b502798 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -208,3 +208,6 @@ option('fdt', type: 'combo', value: 'auto',
option('selinux', type: 'feature', value: 'auto',
description: 'SELinux support in qemu-nbd')
+
+option('printer', type: 'feature', value: 'auto',
+ description: 'Printer subsystem support')
diff --git a/printer/builtin.c b/printer/builtin.c
new file mode 100644
index 0000000000..bc33a1d363
--- /dev/null
+++ b/printer/builtin.c
@@ -0,0 +1,61 @@
+/*
+ * QEMU Builtin printer backend
+ *
+ * Copyright (c) 2022 ByteDance, Inc.
+ *
+ * Author:
+ * Ruien Zhang <zhangruien@bytedance.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/module.h"
+#include "qemu/log.h"
+#include "qemu/main-loop.h"
+#include "qom/object.h"
+#include "qapi/error.h"
+#include "qapi/visitor.h"
+#include "qapi/qapi-visit-printer.h"
+#include "printer/printer.h"
+#include "trace.h"
+
+#define TYPE_PRINTER_BUILTIN TYPE_PRINTERDEV"-builtin"
+
+typedef struct PrinterBuiltin {
+ QEMUPrinter parent;
+
+ void *opaque; /* used by driver itself */
+} PrinterBuiltin;
+
+DECLARE_INSTANCE_CHECKER(PrinterBuiltin, PRINTER_BUILTIN_DEV,
+ TYPE_PRINTER_BUILTIN)
+
+static void printer_builtin_init(Object *obj)
+{
+}
+
+static void printer_builtin_finalize(Object *obj)
+{
+}
+
+static void printer_builtin_class_init(ObjectClass *oc, void *data)
+{
+}
+
+static const TypeInfo printer_builtin_type_info = {
+ .name = TYPE_PRINTER_BUILTIN,
+ .parent = TYPE_PRINTERDEV,
+ .instance_size = sizeof(PrinterBuiltin),
+ .instance_init = printer_builtin_init,
+ .instance_finalize = printer_builtin_finalize,
+ .class_init = printer_builtin_class_init,
+};
+
+static void register_types(void)
+{
+ type_register_static(&printer_builtin_type_info);
+}
+
+type_init(register_types);
diff --git a/printer/meson.build b/printer/meson.build
new file mode 100644
index 0000000000..9814de2a57
--- /dev/null
+++ b/printer/meson.build
@@ -0,0 +1,14 @@
+printer_ss.add([files(
+ 'printer.c',
+)])
+
+printer_modules = {}
+foreach m : [
+ ['builtin', files('builtin.c')],
+]
+ module_ss = ss.source_set()
+ module_ss.add(m[1])
+ printer_modules += {m[0] : module_ss}
+endforeach
+
+modules += {'printer': printer_modules}
diff --git a/printer/printer.c b/printer/printer.c
new file mode 100644
index 0000000000..2d3f57a6e1
--- /dev/null
+++ b/printer/printer.c
@@ -0,0 +1,191 @@
+/*
+ * QEMU Printer subsystem
+ *
+ * Copyright (c) 2022 ByteDance, Inc.
+ *
+ * Author:
+ * Ruien Zhang <zhangruien@bytedance.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/help_option.h"
+#include "qemu/iov.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "qemu/qemu-print.h"
+#include "qom/object.h"
+#include "qapi/error.h"
+#include "qapi/visitor.h"
+#include "qapi/qobject-input-visitor.h"
+#include "qapi/qapi-visit-printer.h"
+#include "printer/printer.h"
+#include "trace.h"
+
+static QLIST_HEAD(, QEMUPrinter) qemu_printers;
+
+const char *qemu_printer_id(QEMUPrinter *printer)
+{
+ if (printer->dev && printer->dev->id) {
+ return printer->dev->id;
+ }
+
+ return "";
+}
+
+QEMUPrinter *qemu_printer_by_id(const char *id)
+{
+ QEMUPrinter *printer;
+
+ if (!id) {
+ return NULL;
+ }
+
+ QLIST_FOREACH(printer, &qemu_printers, list) {
+ if (!strcmp(qemu_printer_id(printer), id)) {
+ return printer;
+ }
+ }
+
+ return NULL;
+}
+
+static const QEMUPrinterClass *printer_get_class(const char *typename,
+ Error **errp)
+{
+ ObjectClass *oc;
+
+ oc = module_object_class_by_name(typename);
+
+ if (!object_class_dynamic_cast(oc, TYPE_PRINTERDEV)) {
+ error_setg(errp, "%s: missing %s implementation",
+ TYPE_PRINTERDEV, typename);
+ return NULL;
+ }
+
+ if (object_class_is_abstract(oc)) {
+ error_setg(errp, "%s: %s is abstract type", TYPE_PRINTERDEV, typename);
+ return NULL;
+ }
+
+ return PRINTERDEV_CLASS(oc);
+}
+
+static QEMUPrinter *qemu_printer_new(Printerdev *dev, Error **errp)
+{
+ Object *obj;
+ QEMUPrinter *printer = NULL;
+ g_autofree char *typename = NULL;
+ const char *driver = PrinterdevDriver_str(dev->driver);
+
+ typename = g_strdup_printf("%s-%s", TYPE_PRINTERDEV, driver);
+ if (!printer_get_class(typename, errp)) {
+ return NULL;
+ }
+
+ obj = object_new(typename);
+ if (!obj) {
+ return NULL;
+ }
+
+ printer = PRINTERDEV(obj);
+ printer->dev = dev;
+
+ QLIST_INSERT_HEAD(&qemu_printers, printer, list);
+ trace_qemu_printer_new(qemu_printer_id(printer), typename);
+
+ return printer;
+}
+
+typedef struct PrinterdevClassFE {
+ void (*fn)(const char *name, void *opaque);
+ void *opaque;
+} PrinterdevClassFE;
+
+static void printerdev_class_foreach(ObjectClass *klass, void *opaque)
+{
+ PrinterdevClassFE *fe = opaque;
+
+ assert(g_str_has_prefix(object_class_get_name(klass), TYPE_PRINTERDEV"-"));
+ fe->fn(object_class_get_name(klass) + 11, fe->opaque);
+}
+
+static void printerdev_name_foreach(void (*fn)(const char *name, void *opaque),
+ void *opaque)
+{
+ PrinterdevClassFE fe = { .fn = fn, .opaque = opaque };
+
+ object_class_foreach(printerdev_class_foreach, TYPE_PRINTERDEV, false, &fe);
+}
+
+static void help_string_append(const char *name, void *opaque)
+{
+ GString *str = opaque;
+
+ g_string_append_printf(str, "\n %s", name);
+}
+
+void qemu_printer_new_from_opts(const char *opt)
+{
+ Printerdev *dev;
+
+ if (opt && is_help_option(opt)) {
+ GString *str = g_string_new("");
+
+ printerdev_name_foreach(help_string_append, str);
+
+ qemu_printf("Available printerdev backend types: %s\n", str->str);
+ g_string_free(str, true);
+ return;
+ }
+
+ Visitor *v = qobject_input_visitor_new_str(opt, "driver", &error_fatal);
+ visit_type_Printerdev(v, NULL, &dev, &error_fatal);
+ visit_free(v);
+
+ if (qemu_printer_by_id(dev->id)) {
+ error_setg(&error_fatal, "%s: id %s already existed",
+ TYPE_PRINTERDEV, dev->id);
+ }
+
+ if (!qemu_printer_new(dev, &error_fatal)) {
+ qapi_free_Printerdev(dev);
+ }
+}
+
+void qemu_printer_del(QEMUPrinter *printer)
+{
+ trace_qemu_printer_del(qemu_printer_id(printer));
+
+ QLIST_REMOVE(printer, list);
+ qapi_free_Printerdev(printer->dev);
+ object_unref(printer);
+}
+
+
+static void printer_init(Object *obj)
+{
+}
+
+static void printer_finalize(Object *obj)
+{
+}
+
+static const TypeInfo printer_type_info = {
+ .name = TYPE_PRINTERDEV,
+ .parent = TYPE_OBJECT,
+ .instance_size = sizeof(QEMUPrinter),
+ .instance_init = printer_init,
+ .instance_finalize = printer_finalize,
+ .abstract = true,
+ .class_size = sizeof(QEMUPrinterClass),
+};
+
+static void register_types(void)
+{
+ type_register_static(&printer_type_info);
+}
+
+type_init(register_types);
diff --git a/printer/trace-events b/printer/trace-events
new file mode 100644
index 0000000000..e453bbe691
--- /dev/null
+++ b/printer/trace-events
@@ -0,0 +1,5 @@
+# See docs/devel/tracing.rst for syntax documentation.
+
+# printer.c
+qemu_printer_new(const char *dev, char *typename) "%s: new printer with type %s"
+qemu_printer_del(const char *dev) "%s: delete printer"
diff --git a/printer/trace.h b/printer/trace.h
new file mode 100644
index 0000000000..9717d37ac7
--- /dev/null
+++ b/printer/trace.h
@@ -0,0 +1 @@
+#include "trace/trace-printer.h"
diff --git a/qapi/meson.build b/qapi/meson.build
index c0c49c15e4..f85af6b7d6 100644
--- a/qapi/meson.build
+++ b/qapi/meson.build
@@ -59,6 +59,7 @@ if have_system
'rdma',
'rocker',
'tpm',
+ 'printer',
]
endif
if have_system or have_tools
diff --git a/qapi/printer.json b/qapi/printer.json
new file mode 100644
index 0000000000..9c2ecfe874
--- /dev/null
+++ b/qapi/printer.json
@@ -0,0 +1,47 @@
+# -*- mode: python -*-
+#
+# Copyright (C) 2022 Ruien Zhang <zhangruien@bytedance.com>
+#
+# This work is licensed under the terms of the GNU GPL, version 2 or later.
+# See the COPYING file in the top-level directory.
+
+##
+# = Printer
+##
+
+##
+# @PrinterBuiltinOptions:
+#
+# Options of the builtin printer.
+#
+# Since: 6.3
+##
+{ 'struct': 'PrinterBuiltinOptions',
+ 'data': {} }
+
+##
+# @PrinterdevDriver:
+#
+# An enumeration of possible printer backend drivers.
+#
+# Since: 6.3
+##
+{ 'enum': 'PrinterdevDriver',
+ 'data': [ 'builtin' ] }
+
+##
+# @Printerdev:
+#
+# Captures the configuration of a printer device.
+#
+# @id: identifier for monitor commands.
+#
+# Since: 6.3
+##
+{ 'union': 'Printerdev',
+ 'base': {
+ 'id': 'str',
+ 'driver': 'PrinterdevDriver'},
+ 'discriminator': 'driver',
+ 'data': {
+ 'builtin': 'PrinterBuiltinOptions' } }
diff --git a/qapi/qapi-schema.json b/qapi/qapi-schema.json
index 4912b9744e..114b6a80cb 100644
--- a/qapi/qapi-schema.json
+++ b/qapi/qapi-schema.json
@@ -93,3 +93,4 @@
{ 'include': 'audio.json' }
{ 'include': 'acpi.json' }
{ 'include': 'pci.json' }
+{ 'include': 'printer.json' }
diff --git a/qemu-options.hx b/qemu-options.hx
index ec90505d84..448a456f86 100644
--- a/qemu-options.hx
+++ b/qemu-options.hx
@@ -3564,6 +3564,14 @@ The available backends are:
traffic identified by a name (preferably a fqdn).
ERST
+DEFHEADING(Printer device options:)
+
+DEF("printerdev", HAS_ARG, QEMU_OPTION_printerdev,
+ "-printerdev help\n"
+ "-printerdev builtin,id=id\n"
+ , QEMU_ARCH_ALL
+)
+
DEFHEADING()
#ifdef CONFIG_TPM
diff --git a/softmmu/vl.c b/softmmu/vl.c
index a8cad43691..67b3c48fa1 100644
--- a/softmmu/vl.c
+++ b/softmmu/vl.c
@@ -94,6 +94,7 @@
#ifdef CONFIG_VIRTFS
#include "fsdev/qemu-fsdev.h"
#endif
+#include "printer/printer.h"
#include "sysemu/qtest.h"
#include "disas/disas.h"
@@ -3247,6 +3248,9 @@ void qemu_init(int argc, char **argv, char **envp)
qemu_opt_get(opts, "mount_tag"), &error_abort);
break;
}
+ case QEMU_OPTION_printerdev:
+ qemu_printer_new_from_opts(optarg);
+ break;
case QEMU_OPTION_serial:
add_device_config(DEV_SERIAL, optarg);
default_serial = 0;
--
2.11.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH 2/2] usb-printer: Introduce USB printer class
2022-01-13 11:56 [PATCH 0/2] Introduce printer subsystem and USB printer device Ruien Zhang
2022-01-13 11:56 ` [PATCH 1/2] printer: Introduce printer subsystem Ruien Zhang
@ 2022-01-13 11:56 ` Ruien Zhang
2022-01-14 9:32 ` [PATCH 0/2] Introduce printer subsystem and USB printer device Gerd Hoffmann
2 siblings, 0 replies; 7+ messages in thread
From: Ruien Zhang @ 2022-01-13 11:56 UTC (permalink / raw)
To: peter.maydell, richard.henderson, kraxel, eblake, pbonzini, berrange
Cc: qemu-devel, zhangruien
From: zhangruien <zhangruien@bytedance.com>
The USB printer device emulation is currently provided with:
1) Definitions and corresponding action handlers of class-specific
requests with essential descriptors in USB Printer Class
Specification 1.1 [1].
2) Extended definitions of interface protocol and class-specific
descriptors in IPP-over-USB protocol 1.0 [2].
A usb printer device can be assembled with the following example of
command-line arguments:
-device piix4-usb-uhci,id=uhci,bus=pci.0 \
-device usb-printer,id=usb-printer0,printerdev=printer0,bus=uhci.0,terminal=printer \
-printerdev builtin,id=printer0
[1]: https://www.usb.org/sites/default/files/usbprint11a021811.pdf
[2]: https://www.usb.org/document-library/ipp-protocol-10
Signed-off-by: zhangruien <zhangruien@bytedance.com>
---
docs/system/devices/usb.rst | 3 +
hw/usb/Kconfig | 5 +
hw/usb/dev-printer.c | 423 ++++++++++++++++++++++++++++++++++++++++++++
hw/usb/meson.build | 1 +
hw/usb/trace-events | 11 ++
include/hw/usb/printer.h | 93 ++++++++++
6 files changed, 536 insertions(+)
create mode 100644 hw/usb/dev-printer.c
create mode 100644 include/hw/usb/printer.h
diff --git a/docs/system/devices/usb.rst b/docs/system/devices/usb.rst
index afb7d6c226..6e87c3be11 100644
--- a/docs/system/devices/usb.rst
+++ b/docs/system/devices/usb.rst
@@ -199,6 +199,9 @@ option or the ``device_add`` monitor command. Available devices are:
``u2f-{emulated,passthru}``
Universal Second Factor device
+``usb-printer``
+ USB printer device
+
Physical port addressing
^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/hw/usb/Kconfig b/hw/usb/Kconfig
index 53f8283ffd..1b5a953cae 100644
--- a/hw/usb/Kconfig
+++ b/hw/usb/Kconfig
@@ -133,3 +133,8 @@ config XLNX_USB_SUBSYS
bool
default y if XLNX_VERSAL
select USB_DWC3
+
+config USB_PRINTER
+ bool
+ default y
+ depends on USB
diff --git a/hw/usb/dev-printer.c b/hw/usb/dev-printer.c
new file mode 100644
index 0000000000..5905615961
--- /dev/null
+++ b/hw/usb/dev-printer.c
@@ -0,0 +1,423 @@
+/*
+ * USB Printer Device emulation
+ *
+ * Copyright (c) 2022 ByteDance, Inc.
+ *
+ * Author:
+ * Ruien Zhang <zhangruien@bytedance.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+/*
+ * References:
+ * Universal Serial Bus Device Class Definition for Printing Devices,
+ * version 1.1
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/module.h"
+#include "qemu/log.h"
+#include "qom/object.h"
+#include "qapi/error.h"
+#include "migration/vmstate.h"
+#include "hw/qdev-properties.h"
+#include "hw/usb.h"
+#include "hw/usb/printer.h"
+#include "printer/printer.h"
+#include "desc.h"
+#include "trace.h"
+
+#define USBPRINTER_VENDOR_NUM 0x46f4 /* CRC16() of "QEMU" */
+#define USBPRINTER_PRODUCT_NUM 0xa1f3
+
+enum {
+ STR_MANUFACTURER = 1,
+ STR_PRODUCT,
+ STR_SERIALNUMBER,
+ STR_CONFIG_FULL,
+ STR_CONFIG_HIGH,
+};
+
+static const USBDescStrings desc_strings = {
+ [STR_MANUFACTURER] = "QEMU",
+ [STR_PRODUCT] = "QEMU USB Printer",
+ [STR_SERIALNUMBER] = "1",
+ [STR_CONFIG_FULL] = "Full speed config (usb 1.1)",
+ [STR_CONFIG_HIGH] = "High speed config (usb 2.0)",
+};
+
+/*
+ * 5. Standard Descriptors
+ *
+ * "Printer Class devices support the following standard USB descriptors:
+ * - Device. Each printer has one device descriptor.
+ * - Configuration. Each device has one default configuration descriptor which
+ * supports at least one interface.
+ * - Interface. A printer device has a single data interface with possible
+ * alternates.
+ * - Endpoint. A printer device supports the following endpoints:
+ * - Bulk OUT endpoint. Used for transfer of PDL/PCP data.
+ * - Optional Bulk IN endpoint. Provides status and other return information."
+ */
+static const USBDescIface desc_iface_full = {
+ .bInterfaceNumber = 0,
+ .bNumEndpoints = EP_NUMS_2,
+ .bInterfaceClass = USB_CLASS_PRINTER,
+ .bInterfaceSubClass = SC_PRINTERS,
+ .bInterfaceProtocol = PC_PROTOCOL_BIDIR_1284_4,
+ .eps = (USBDescEndpoint[]) {
+ {
+ .bEndpointAddress = USB_DIR_OUT | EP_NUM_BULK_OUT,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ .wMaxPacketSize = 64,
+ },{
+ .bEndpointAddress = USB_DIR_IN | EP_NUM_BULK_IN,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ .wMaxPacketSize = 64,
+ },
+ },
+};
+
+static const USBDescDevice desc_device_full = {
+ .bcdUSB = 0x0200,
+ .bMaxPacketSize0 = 8,
+ .bNumConfigurations = 1,
+ .confs = (USBDescConfig[]) {
+ {
+ .bNumInterfaces = 1,
+ .bConfigurationValue = 1,
+ .iConfiguration = STR_CONFIG_FULL,
+ .bmAttributes = USB_CFG_ATT_ONE | USB_CFG_ATT_SELFPOWER,
+ .nif = 1,
+ .ifs = &desc_iface_full,
+ },
+ },
+};
+
+static const USBDescIface desc_iface_high = {
+ .bInterfaceNumber = 0,
+ .bNumEndpoints = EP_NUMS_2,
+ .bInterfaceClass = USB_CLASS_PRINTER,
+ .bInterfaceSubClass = SC_PRINTERS,
+ .bInterfaceProtocol = PC_PROTOCOL_BIDIR_1284_4,
+ .eps = (USBDescEndpoint[]) {
+ {
+ .bEndpointAddress = USB_DIR_OUT | EP_NUM_BULK_OUT,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ .wMaxPacketSize = 512,
+ },{
+ .bEndpointAddress = USB_DIR_IN | EP_NUM_BULK_IN,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ .wMaxPacketSize = 512,
+ },
+ },
+};
+
+static const USBDescDevice desc_device_high = {
+ .bcdUSB = 0x0200,
+ .bMaxPacketSize0 = 64,
+ .bNumConfigurations = 1,
+ .confs = (USBDescConfig[]) {
+ {
+ .bNumInterfaces = 1,
+ .bConfigurationValue = 1,
+ .iConfiguration = STR_CONFIG_HIGH,
+ .bmAttributes = USB_CFG_ATT_ONE | USB_CFG_ATT_SELFPOWER,
+ .nif = 1,
+ .ifs = &desc_iface_high,
+ },
+ },
+};
+
+static const USBDesc desc_printer = {
+ .id = {
+ .idVendor = USB_CLASS_PRINTER,
+ .idProduct = USBPRINTER_PRODUCT_NUM,
+ .bcdDevice = 0,
+ .iManufacturer = STR_MANUFACTURER,
+ .iProduct = STR_PRODUCT,
+ .iSerialNumber = STR_SERIALNUMBER,
+ },
+ .full = &desc_device_full,
+ .high = &desc_device_high,
+ .str = desc_strings,
+};
+
+struct USBPrinterState {
+ /* qemu interfaces */
+ USBDevice dev;
+
+ /* state */
+ QEMUPrinter *printer;
+
+ /* properties */
+ char *printerdev;
+ char *terminal;
+};
+
+#define TYPE_USB_PRINTER "usb-printer"
+OBJECT_DECLARE_SIMPLE_TYPE(USBPrinterState, USB_PRINTER)
+
+static void usb_printer_handle_reset(USBDevice *dev)
+{
+ USBBus *bus = usb_bus_from_device(dev);
+ trace_usb_printer_handle_reset(bus->busnr, dev->addr);
+}
+
+/*
+ * 4.2.1 GET_DEVICE_ID (bRequest = 0)
+ * "This class-specific request returns a device ID string that is compatible
+ * with IEEE 1284. See IEEE 1284 for syntax and formatting information."
+ */
+#define USB_PRINTER_DEVICE_ID_QEMU "QEMU Printer"
+#define USB_PRINTER_DEVICE_ID_QEMU_LEN \
+ strlen(USB_PRINTER_DEVICE_ID_QEMU)
+#define USB_PRINTER_DEVICE_ID_QEMU_LEN_IEEE_1284 \
+ (2 + USB_PRINTER_DEVICE_ID_QEMU_LEN)
+
+static const USBPrinterDeviceIDStrings usb_printer_device_ids = {
+ [USB_PRINTER_DEVICE_ID_DEFAULT] = USB_PRINTER_DEVICE_ID_QEMU,
+};
+
+static int usb_printer_get_device_id(USBDevice *dev, int request, int value,
+ int index, int length, uint8_t *data)
+{
+ USBBus *bus = usb_bus_from_device(dev);
+
+ *((uint16_t *)data) = cpu_to_be16(USB_PRINTER_DEVICE_ID_QEMU_LEN_IEEE_1284);
+ memcpy(data + 2, usb_printer_device_ids[USB_PRINTER_DEVICE_ID_DEFAULT],
+ USB_PRINTER_DEVICE_ID_QEMU_LEN);
+
+ trace_usb_printer_get_device_id(bus->busnr, dev->addr);
+
+ return USB_PRINTER_DEVICE_ID_QEMU_LEN_IEEE_1284;
+}
+
+/*
+ * 4.2.2 GET_PORT_STATUS (bRequest = 1)
+ *
+ * "Note: Some USB printers may not always be able to determine this
+ * information. In this case, they should return benign status of
+ * “Paper Not Empty,” “Selected,” and “No Error.”"
+ */
+static int usb_printer_get_port_status(USBDevice *dev, int request, int value,
+ int index, int length, uint8_t *data)
+{
+ USBBus *bus = usb_bus_from_device(dev);
+
+ *((uint8_t *)data) = PAPER_NOT_EMPTY | SELECTED | NO_ERROR;
+ trace_usb_printer_get_port_status(bus->busnr, dev->addr);
+ return 1;
+}
+
+/*
+ * TODO: 4.2.3 SOFT_RESET (bRequest = 2)
+ *
+ * "This class-specific request flushes all buffers and resets the Bulk OUT
+ * and Bulk IN pipes to their default states. This request clears all stall
+ * conditions. This reset does NOT change the USB addressing or USB
+ * configuration."
+ */
+static int usb_printer_handle_soft_reset(USBDevice *dev, int request, int value,
+ int index, int length, uint8_t *data)
+{
+ USBBus *bus = usb_bus_from_device(dev);
+
+ trace_usb_printer_handle_soft_reset(bus->busnr, dev->addr);
+ return 0;
+}
+
+static void usb_printer_handle_control(USBDevice *dev, USBPacket *p,
+ int request, int value, int index,
+ int length, uint8_t *data)
+{
+ USBBus *bus = usb_bus_from_device(dev);
+ int ret = 0;
+
+ ret = usb_desc_handle_control(dev, p, request, value, index, length, data);
+ if (ret >= 0) {
+ return;
+ }
+
+ switch (request) {
+ case ClassInterfaceRequest | USBPRINTER_GET_DEVICE_ID:
+ ret = usb_printer_get_device_id(dev, request, value, index,
+ length, data);
+ if (ret < 0) {
+ goto error;
+ }
+ break;
+
+ case ClassInterfaceRequest | USBPRINTER_GET_PORT_STATUS:
+ ret = usb_printer_get_port_status(dev, request, value, index,
+ length, data);
+ if (ret < 0) {
+ goto error;
+ }
+ break;
+
+ case ClassInterfaceOutRequestCompat1_0 | USBPRINTER_SOFT_RESET:
+ /* fall through */
+ case ClassInterfaceOutRequest | USBPRINTER_SOFT_RESET:
+ ret = usb_printer_handle_soft_reset(dev, request, value, index,
+ length, data);
+ if (ret < 0) {
+ goto error;
+ }
+ break;
+
+ default:
+ qemu_log_mask(LOG_UNIMP, "%s: request %x not implemented\n",
+ TYPE_USB_PRINTER, request);
+ goto error;
+ }
+
+ p->actual_length = ret;
+ p->status = USB_RET_SUCCESS;
+ return;
+
+error:
+ trace_usb_printer_handle_control_error(bus->busnr, dev->addr, request,
+ value, index, length);
+ p->status = USB_RET_STALL;
+}
+
+static void usb_printer_handle_data_out(USBDevice *dev, USBPacket *p)
+{
+ USBBus *bus = usb_bus_from_device(dev);
+ QEMUIOVector *iov = p->combined ? &p->combined->iov : &p->iov;
+
+ p->status = USB_RET_SUCCESS;
+ trace_usb_printer_handle_data_out(bus->busnr, dev->addr, iov->size);
+}
+
+/*
+ * 5.4.2 Bulk IN Endpoint
+ *
+ * "The Bulk IN endpoint is used to return any data generated by the PDL
+ * or PCP to the host. If the printer supports a PCP, such as IEEE-1284.1
+ * or IEEE-1284.4, this endpoint will return status or other printer-related
+ * information."
+ */
+static void usb_printer_handle_data_in(USBDevice *dev, USBPacket *p)
+{
+ USBBus *bus = usb_bus_from_device(dev);
+ QEMUIOVector *iov = p->combined ? &p->combined->iov : &p->iov;
+
+ p->status = USB_RET_SUCCESS;
+ trace_usb_printer_handle_data_in(bus->busnr, dev->addr, iov->size);
+}
+
+static void usb_printer_handle_data(USBDevice *dev, USBPacket *p)
+{
+ USBBus *bus = usb_bus_from_device(dev);
+
+ switch (p->pid) {
+ case USB_TOKEN_OUT:
+ switch (p->ep->nr) {
+ case EP_NUM_BULK_OUT:
+ usb_printer_handle_data_out(dev, p);
+ return;
+
+ default:
+ goto fail;
+ }
+ break;
+
+ case USB_TOKEN_IN:
+ switch (p->ep->nr) {
+ case EP_NUM_BULK_IN:
+ usb_printer_handle_data_in(dev, p);
+ return;
+
+ default:
+ goto fail;
+ }
+ break;
+
+ default:
+ fail:
+ p->status = USB_RET_STALL;
+ break;
+ }
+
+ if (p->status == USB_RET_STALL) {
+ fprintf(stderr, "usbprinter: failed data transaction: "
+ "pid 0x%x ep 0x%x len 0x%zx\n",
+ p->pid, p->ep->nr, p->iov.size);
+ }
+
+ trace_usb_printer_handle_data(bus->busnr, dev->addr, p->pid, p->ep->nr);
+}
+
+static void usb_printer_unrealize(USBDevice *dev)
+{
+}
+
+static void usb_printer_realize(USBDevice *dev, Error **errp)
+{
+ USBPrinterState *s = USB_PRINTER(dev);
+ if (!s->terminal || strcmp(s->terminal, "printer")) {
+ error_setg(errp, "%s: support terminal printer only", TYPE_USB_PRINTER);
+ return;
+ }
+
+ s->printer = qemu_printer_by_id(s->printerdev);
+ if (!s->printer) {
+ error_setg(errp, "%s: invalid printerdev %s",
+ TYPE_USB_PRINTER, s->printerdev);
+ return;
+ }
+
+ dev->usb_desc = &desc_printer;
+
+ usb_desc_create_serial(dev);
+ usb_desc_init(dev);
+ s->dev.opaque = s;
+}
+
+/* TODO: set alternates on IPP-over-USB */
+static void usb_printer_set_interface(USBDevice *dev, int iface,
+ int old, int value)
+{
+ USBBus *bus = usb_bus_from_device(dev);
+ trace_usb_printer_set_interface(bus->busnr, dev->addr, iface, old, value);
+}
+
+static Property usb_printer_properties[] = {
+ DEFINE_PROP_STRING("printerdev", USBPrinterState, printerdev),
+ DEFINE_PROP_STRING("terminal", USBPrinterState, terminal),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void usb_printer_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ USBDeviceClass *k = USB_DEVICE_CLASS(klass);
+
+ device_class_set_props(dc, usb_printer_properties);
+ set_bit(DEVICE_CATEGORY_USB, dc->categories);
+ k->product_desc = "QEMU USB Printer Interface";
+ k->realize = usb_printer_realize;
+ k->handle_reset = usb_printer_handle_reset;
+ k->handle_control = usb_printer_handle_control;
+ k->handle_data = usb_printer_handle_data;
+ k->unrealize = usb_printer_unrealize;
+ k->set_interface = usb_printer_set_interface;
+}
+
+static const TypeInfo usb_printer_info = {
+ .name = TYPE_USB_PRINTER,
+ .parent = TYPE_USB_DEVICE,
+ .instance_size = sizeof(USBPrinterState),
+ .class_init = usb_printer_class_init,
+};
+
+static void usb_printer_register_types(void)
+{
+ type_register_static(&usb_printer_info);
+}
+
+type_init(usb_printer_register_types)
diff --git a/hw/usb/meson.build b/hw/usb/meson.build
index de853d780d..f79d5e1f74 100644
--- a/hw/usb/meson.build
+++ b/hw/usb/meson.build
@@ -44,6 +44,7 @@ softmmu_ss.add(when: 'CONFIG_USB_STORAGE_UAS', if_true: files('dev-uas.c'))
softmmu_ss.add(when: 'CONFIG_USB_AUDIO', if_true: files('dev-audio.c'))
softmmu_ss.add(when: 'CONFIG_USB_SERIAL', if_true: files('dev-serial.c'))
softmmu_ss.add(when: 'CONFIG_USB_NETWORK', if_true: files('dev-network.c'))
+softmmu_ss.add(when: 'CONFIG_USB_PRINTER', if_true: files('dev-printer.c'))
softmmu_ss.add(when: ['CONFIG_POSIX', 'CONFIG_USB_STORAGE_MTP'], if_true: files('dev-mtp.c'))
# smartcard
diff --git a/hw/usb/trace-events b/hw/usb/trace-events
index b8287b63f1..e3fed30c43 100644
--- a/hw/usb/trace-events
+++ b/hw/usb/trace-events
@@ -345,3 +345,14 @@ usb_serial_set_baud(int bus, int addr, int baud) "dev %d:%u baud rate %d"
usb_serial_set_data(int bus, int addr, int parity, int data, int stop) "dev %d:%u parity %c, data bits %d, stop bits %d"
usb_serial_set_flow_control(int bus, int addr, int index) "dev %d:%u flow control %d"
usb_serial_set_xonxoff(int bus, int addr, uint8_t xon, uint8_t xoff) "dev %d:%u xon 0x%x xoff 0x%x"
+
+# dev-printer.c
+usb_printer_handle_reset(int bus, int addr) "dev %d:%u reset"
+usb_printer_get_device_id(int bus, int addr) "dev %d:%u get device id"
+usb_printer_get_port_status(int bus, int addr) "dev %d:%u get port status"
+usb_printer_handle_soft_reset(int bus, int addr) "dev %d:%u soft reset"
+usb_printer_handle_control_error(int bus, int addr, int request, int value, int index, int length) "dev %d:%u handle control error, request 0x%x, value 0x%x, index 0x%x, length 0x%x"
+usb_printer_handle_data(int bus, int addr, int pid, int ep) "dev %d:%u data, pid 0x%x, ep %d"
+usb_printer_handle_data_out(int bus, int addr, int size) "dev %d:%u data out, size %d"
+usb_printer_handle_data_in(int bus, int addr, int size) "dev %d:%u data in, size %d"
+usb_printer_set_interface(int bus, int addr, int iface, int old, int value) "dev %d:%u set interface %d, old %d, value %d"
diff --git a/include/hw/usb/printer.h b/include/hw/usb/printer.h
new file mode 100644
index 0000000000..0b14e11b8f
--- /dev/null
+++ b/include/hw/usb/printer.h
@@ -0,0 +1,93 @@
+/*
+ * USB Printer Device emulation
+ *
+ * Copyright (c) 2022 ByteDance, Inc.
+ *
+ * Author:
+ * Ruien Zhang <zhangruien@bytedance.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+/*
+ * References:
+ * Universal Serial Bus Device Class Definition for Printing Devices,
+ * version 1.1
+ * USB Print Interface Class IPP Protocol Specification, revision 1.0
+ */
+
+#ifndef HW_USB_PRINTER_H
+#define HW_USB_PRINTER_H
+
+/* 4.2 Class-Specific Requests */
+#define USBPRINTER_GET_DEVICE_ID 0
+#define USBPRINTER_GET_PORT_STATUS 1
+#define USBPRINTER_SOFT_RESET 2
+
+typedef enum {
+ USB_PRINTER_DEVICE_ID_DEFAULT,
+ USB_PRINTER_DEVICE_ID_MAX
+} USBPrinterDeviceIDType;
+
+typedef const char *USBPrinterDeviceIDStrings[USB_PRINTER_DEVICE_ID_MAX];
+
+/* 4.2.2 GET_PORT_STATUS (bRequest = 1) */
+#define PAPER_EMPTY (1 << 5)
+#define PAPER_NOT_EMPTY (0 << 5)
+#define SELECTED (1 << 4)
+#define NOT_SELECTED (0 << 4)
+#define NO_ERROR (1 << 3)
+#define ERROR (0 << 3)
+
+/*
+ * 4.2.3 SOFT_RESET (bRequest = 2)
+ *
+ * "Note: Version 1.0 of the specification incorrectly stated that the
+ * bmReqestType for SOFT_RESET was 00100011B. Version 1.1 Host software
+ * implementers should be prepared for USB printers that expect this
+ * request code, and version 1.1 device implementers should be prepared
+ * for host software that issues this request code."
+ */
+#define ClassInterfaceOutRequestCompat1_0 \
+ ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_OTHER) << 8)
+
+/* 5.3 Interface Descriptors */
+#define EP_NUMS_1 0x01
+#define EP_NUMS_2 0x02
+#define EP_NUM_BULK_OUT 0x01
+#define EP_NUM_BULK_IN 0x02
+#define SC_PRINTERS 0x01
+#define PC_PROTOCOL_UNIDIR 0x01
+#define PC_PROTOCOL_BIDIR 0x02
+#define PC_PROTOCOL_BIDIR_1284_4 0x03
+#define PC_PROTOCOL_IPP_USB 0x04
+#define PC_VENDOR_SPECIFIC 0xff
+
+/* 4.3 Device Info Descriptor: A Class Specific Descriptor */
+#define DEV_INFO_DESC_CHECK_LEN(bLength) \
+ QEMU_BUILD_BUG_ON((bLength) < 10)
+
+#define DEV_INFO_DESC_CHECK_NUM_DESCS(bNumDescriptors) \
+ QEMU_BUILD_BUG_ON((bNumDescriptors) < 1)
+
+#define DEV_INFO_DESC_CHECK_OPT_CT(bCapabilitiesType) \
+ QEMU_BUILD_BUG_ON((bCapabilitiesType) < 0x20 || \
+ (bCapabilitiesType) > 0xff)
+
+#define IPP_USB_CT_BASIC 0x00
+
+#define IPP_USB_CAP_BASIC_PRINT (1 << 0)
+#define IPP_USB_CAP_BASIC_SCAN (1 << 1)
+#define IPP_USB_CAP_BASIC_FAX (1 << 2)
+#define IPP_USB_CAP_BASIC_OTHER (1 << 3)
+#define IPP_USB_CAP_BASIC_ANY_HTTP_1_1_OVER_USB (1 << 4)
+
+#define IPP_USB_CAP_BASIC_AUTH_NONE (0x00 << 5)
+#define IPP_USB_CAP_BASIC_AUTH_USERNAME_PASSWORD (0x01 << 5)
+#define IPP_USB_CAP_BASIC_AUTH_RESERVED (0x02 << 5)
+#define IPP_USB_CAP_BASIC_AUTH_NEGOTIATE (0x03 << 5)
+
+/* TODO: IPP string table in IPP server implementation */
+
+#endif /* HW_USB_PRINTER_H */
--
2.11.0
^ permalink raw reply related [flat|nested] 7+ messages in thread