common: Support for common status / media reporting query.
Outputs in text form or JSON. See README for details.
This commit is contained in:
parent
619d0dd487
commit
3b962df076
51
README
51
README
|
@ -204,7 +204,7 @@
|
|||
scheme type generated at runtime, set the OLD_URI_SCHEME environment
|
||||
variable to either 0 or 1, as appropriate.
|
||||
|
||||
***************************************************************************
|
||||
***************************************************************************
|
||||
Standalone usage:
|
||||
|
||||
This backend is set up as a multi-call executable; that is to say
|
||||
|
@ -307,6 +307,55 @@
|
|||
Finally, BACKEND_QUIET can be set to a non-zero value to silence all
|
||||
output other than warnings and errors.
|
||||
|
||||
***************************************************************************
|
||||
Standalone status queries:
|
||||
|
||||
If you just want to query the printer and media status in a
|
||||
printer-independent manner, the backend supports reporting a standard
|
||||
set of data in either textual or JSON format. This is used with the
|
||||
BACKEND_STATS_ONLY environment variable:
|
||||
|
||||
BACKEND_STATS_ONLY=? BACKEND=backend [SERIAL=??] ./gutenprint53+usb
|
||||
BACKEND_STATS_ONLY=? [SERIAL=??] ./backend
|
||||
|
||||
BACKEND_STATS_ONLY=1 will report in textual format, for example:
|
||||
|
||||
Backend: HiTi Photo Printers
|
||||
Version: 0.100 / 0.16
|
||||
Timestamp: 2019-1 2-13 14:54:28
|
||||
Manufacturer: HiTi
|
||||
Model: HiTi:Usb Photo Printer:HiTi P520L
|
||||
Serial Number: 2WC4A1013968279
|
||||
Firmware Version: 1.21.0.i
|
||||
Printer Status: Idle
|
||||
Lifetime Prints: 194
|
||||
Media 0 Type: 4x6
|
||||
Media 0 Level: 313 / 500
|
||||
|
||||
BACKEND_STATS_ONLY=2 will report in JSON format, for example:
|
||||
|
||||
{
|
||||
"backend": "HiTi Photo Printers",
|
||||
"version": "0.100 / 0.16",
|
||||
"timestamp": "2019-12-13 14:57:38",
|
||||
"manufacturer": "HiTi",
|
||||
"model": "HiTi:Usb Photo Printer:HiTi P520L",
|
||||
"serial": "2WC4A1013968279",
|
||||
"firmware": "1.21.0.i",
|
||||
"status": "Idle",
|
||||
"counters": {
|
||||
"lifetime": 194
|
||||
},
|
||||
"media": [
|
||||
{
|
||||
"type": "4x6",
|
||||
"level": "OK",
|
||||
"levelnow": 313,
|
||||
"levelmax": 500
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
***************************************************************************
|
||||
BACKEND=canonselphy
|
||||
|
||||
|
|
371
backend_common.c
371
backend_common.c
|
@ -31,7 +31,7 @@
|
|||
#include <errno.h>
|
||||
#include <signal.h>
|
||||
|
||||
#define BACKEND_VERSION "0.99"
|
||||
#define BACKEND_VERSION "0.100"
|
||||
#ifndef URI_PREFIX
|
||||
#error "Must Define URI_PREFIX"
|
||||
#endif
|
||||
|
@ -847,6 +847,94 @@ static int query_markers(struct dyesub_backend *backend, void *ctx, int full)
|
|||
return CUPS_BACKEND_OK;
|
||||
}
|
||||
|
||||
static void dump_stats(struct dyesub_backend *backend, struct printerstats *stats, int json)
|
||||
{
|
||||
int i;
|
||||
struct tm *tm;
|
||||
char tmbuf[64];
|
||||
tm = localtime(&stats->timestamp);
|
||||
|
||||
strftime(tmbuf, sizeof(tmbuf), "%Y-%m-%d %H:%M:%S", tm);
|
||||
|
||||
if (json) {
|
||||
fprintf(stdout, "{\n");
|
||||
fprintf(stdout, "\t\"backend\": \"%s\",\n", backend->name);
|
||||
fprintf(stdout, "\t\"version\": \"%s / %s\",\n", BACKEND_VERSION, backend->version);
|
||||
fprintf(stdout, "\t\"timestamp\": \"%s\",\n", tmbuf);
|
||||
if (stats->mfg)
|
||||
fprintf(stdout, "\t\"manufacturer\": \"%s\",\n", stats->mfg);
|
||||
if (stats->model)
|
||||
fprintf(stdout, "\t\"model\": \"%s\",\n", stats->model);
|
||||
if (stats->serial)
|
||||
fprintf(stdout, "\t\"serial\": \"%s\",\n", stats->serial);
|
||||
if (stats->fwver)
|
||||
fprintf(stdout, "\t\"firmware\": \"%s\",\n", stats->fwver);
|
||||
if (stats->status)
|
||||
fprintf(stdout, "\t\"status\": \"%s\",\n", stats->status);
|
||||
|
||||
fprintf(stdout, "\t\"counters\": {\n");
|
||||
if (stats->cnt_life >= 0)
|
||||
fprintf(stdout, "\t\t\"lifetime\": %d\n", stats->cnt_life);
|
||||
fprintf(stdout, "\t},\n");
|
||||
fprintf(stdout, "\t\"media\": [\n");
|
||||
fprintf(stdout, "\t\t{\n");
|
||||
for (i = 0 ; i < stats->decks ; i++) {
|
||||
fprintf(stdout, "\t\t\t\"type\": \"%s\",\n", stats->mediatype[i]);
|
||||
switch (stats->levelnow[i]) {
|
||||
case CUPS_MARKER_UNKNOWN:
|
||||
fprintf(stdout, "\t\t\t\"level\": \"Unknown\"\n");
|
||||
break;
|
||||
case CUPS_MARKER_UNAVAILABLE:
|
||||
fprintf(stdout, "\t\t\t\"level\": \"Unavailable\"\n");
|
||||
break;
|
||||
case CUPS_MARKER_UNKNOWN_OK:
|
||||
fprintf(stdout, "\t\t\t\"level\": \"OK\"\n");
|
||||
break;
|
||||
default:
|
||||
if (stats->levelnow[i] >= 0 && stats->levelmax[i] > 0) {
|
||||
fprintf(stdout, "\t\t\t\"level\": \"OK\",\n");
|
||||
fprintf(stdout, "\t\t\t\"levelnow\": %d,\n", stats->levelnow[i]);
|
||||
fprintf(stdout, "\t\t\t\"levelmax\": %d\n", stats->levelmax[i]);
|
||||
} else {
|
||||
fprintf(stdout, "\t\t\t\"level\": \"Illegal value\"\n");
|
||||
}
|
||||
break;
|
||||
}
|
||||
fprintf(stdout, "\t\t}%c\n", (i < (stats->decks -1) ? ',': ' '));
|
||||
}
|
||||
fprintf(stdout, "\t]\n");
|
||||
fprintf(stdout, "}\n");
|
||||
} else {
|
||||
fprintf(stdout, "Backend: %s\n", backend->name);
|
||||
fprintf(stdout, "Version: %s / %s\n", BACKEND_VERSION, backend->version);
|
||||
fprintf(stdout, "Timestamp: %s\n", tmbuf);
|
||||
if (stats->mfg)
|
||||
fprintf(stdout, "Manufacturer: %s\n", stats->mfg);
|
||||
if (stats->model)
|
||||
fprintf(stdout, "Model: %s\n", stats->model);
|
||||
if (stats->serial)
|
||||
fprintf(stdout, "Serial Number: %s\n", stats->serial);
|
||||
if (stats->fwver)
|
||||
fprintf(stdout, "Firmware Version: %s\n", stats->fwver);
|
||||
if (stats->status)
|
||||
fprintf(stdout, "Printer Status: %s\n", stats->status);
|
||||
if (stats->cnt_life >= 0) {
|
||||
fprintf(stdout, "Lifetime Prints: %d\n", stats->cnt_life);
|
||||
}
|
||||
for (i = 0 ; i < stats->decks ; i++) {
|
||||
fprintf(stdout, "Media %d Type: %s\n", i, stats->mediatype[i]);
|
||||
if (stats->levelnow[i] == CUPS_MARKER_UNKNOWN_OK)
|
||||
fprintf(stdout, "Media %d Level: OK\n", i);
|
||||
else if (stats->levelnow[i] == CUPS_MARKER_UNKNOWN)
|
||||
fprintf(stdout, "Media %d Level: Unknown\n", i);
|
||||
else if (stats->levelnow[i] == CUPS_MARKER_UNAVAILABLE)
|
||||
fprintf(stdout, "Media %d Level: Unavailable\n", i);
|
||||
else if (stats->levelnow[i] >= 0 && stats->levelmax[i] > 0)
|
||||
fprintf(stdout, "Media %d Level: %d / %d\n", i, stats->levelnow[i], stats->levelmax[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void print_license_blurb(void)
|
||||
{
|
||||
const char *license = "\n\
|
||||
|
@ -973,6 +1061,135 @@ int parse_cmdstream(struct dyesub_backend *backend, void *backend_ctx, int fd)
|
|||
return CUPS_BACKEND_OK;
|
||||
};
|
||||
|
||||
static int handle_input(struct dyesub_backend *backend, void *backend_ctx,
|
||||
char *fname, char *uri, char *type)
|
||||
{
|
||||
int ret = CUPS_BACKEND_OK;
|
||||
int i;
|
||||
const void *job = NULL;
|
||||
int data_fd = fileno(stdin);
|
||||
int current_page = 0;
|
||||
|
||||
if (!fname) {
|
||||
if (uri && strlen(uri))
|
||||
ERROR("ERROR: No input file specified\n");
|
||||
ret = CUPS_BACKEND_FAILED;
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (ncopies < 1) {
|
||||
ERROR("ERROR: need to have at least 1 copy!\n");
|
||||
ret = CUPS_BACKEND_FAILED;
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* Open file if not STDIN */
|
||||
if (strcmp("-", fname)) {
|
||||
data_fd = open(fname, O_RDONLY);
|
||||
if (data_fd < 0) {
|
||||
perror("ERROR:Can't open input file");
|
||||
ret = CUPS_BACKEND_FAILED;
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
|
||||
/* Ensure we're using BLOCKING I/O */
|
||||
i = fcntl(data_fd, F_GETFL, 0);
|
||||
if (i < 0) {
|
||||
perror("ERROR:Can't open input");
|
||||
ret = CUPS_BACKEND_FAILED;
|
||||
goto done;
|
||||
}
|
||||
i &= ~O_NONBLOCK;
|
||||
i = fcntl(data_fd, F_SETFL, i);
|
||||
if (i < 0) {
|
||||
perror("ERROR:Can't open input");
|
||||
ret = CUPS_BACKEND_FAILED;
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* Ignore SIGPIPE */
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
signal(SIGTERM, sigterm_handler);
|
||||
|
||||
/* Time for the main processing loop */
|
||||
INFO("Printing started (%d copies)\n", ncopies);
|
||||
|
||||
/* See if it's a CUPS command stream, and if yes, handle it! */
|
||||
if (type && !strcmp("application/vnd.cups-command", type))
|
||||
{
|
||||
ret = parse_cmdstream(backend, backend_ctx, data_fd);
|
||||
goto done;
|
||||
}
|
||||
|
||||
newpage:
|
||||
|
||||
/* Read in data */
|
||||
if ((ret = backend->read_parse(backend_ctx, &job, data_fd, ncopies))) {
|
||||
if (current_page)
|
||||
goto done_multiple;
|
||||
else
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* The backend parser might not return a job due to job dependencies.
|
||||
Try and read another page. */
|
||||
if (!job)
|
||||
goto newpage;
|
||||
|
||||
/* Create our own joblist if necessary */
|
||||
if (!(backend->flags & BACKEND_FLAG_JOBLIST)) {
|
||||
struct dyesub_joblist *jlist = dyesub_joblist_create(backend, backend_ctx);
|
||||
if (!jlist)
|
||||
goto done;
|
||||
dyesub_joblist_addjob(jlist, job);
|
||||
job = jlist;
|
||||
}
|
||||
|
||||
/* Dump the full marker dump */
|
||||
ret = query_markers(backend, backend_ctx, !current_page);
|
||||
if (ret)
|
||||
goto done;
|
||||
|
||||
INFO("Printing page %d\n", ++current_page);
|
||||
|
||||
if (test_mode >= TEST_MODE_NOPRINT ) {
|
||||
WARNING("**** TEST MODE, bypassing printing!\n");
|
||||
} else {
|
||||
ret = dyesub_joblist_print(job);
|
||||
}
|
||||
|
||||
dyesub_joblist_cleanup(job);
|
||||
|
||||
if (ret)
|
||||
goto done;
|
||||
|
||||
/* Log the completed page */
|
||||
if (!uri || !strlen(uri))
|
||||
PAGE("%d %d\n", current_page, ncopies);
|
||||
|
||||
/* Dump a marker status update */
|
||||
ret = query_markers(backend, backend_ctx, !current_page);
|
||||
if (ret)
|
||||
goto done;
|
||||
|
||||
/* Since we have no way of telling if there's more data remaining
|
||||
to be read (without actually trying to read it), always assume
|
||||
multiple print jobs. */
|
||||
goto newpage;
|
||||
|
||||
done_multiple:
|
||||
close(data_fd);
|
||||
|
||||
/* Done printing, log the total number of pages */
|
||||
if (!uri || !strlen(uri))
|
||||
PAGE("total %d\n", current_page * ncopies);
|
||||
ret = CUPS_BACKEND_OK;
|
||||
|
||||
done:
|
||||
return ret;
|
||||
}
|
||||
|
||||
int main (int argc, char **argv)
|
||||
{
|
||||
struct libusb_context *ctx = NULL;
|
||||
|
@ -985,18 +1202,12 @@ int main (int argc, char **argv)
|
|||
uint8_t endp_up, endp_down;
|
||||
uint8_t iface, altset;
|
||||
|
||||
int data_fd = fileno(stdin);
|
||||
|
||||
const void *job = NULL;
|
||||
|
||||
int i;
|
||||
|
||||
int ret = CUPS_BACKEND_OK;
|
||||
|
||||
int found = -1;
|
||||
int jobid = 0;
|
||||
int current_page = 0;
|
||||
|
||||
int stats_only = 0;
|
||||
char *uri;
|
||||
char *type;
|
||||
char *fname = NULL;
|
||||
|
@ -1006,6 +1217,8 @@ int main (int argc, char **argv)
|
|||
/* Handle environment variables */
|
||||
if (getenv("BACKEND_QUIET"))
|
||||
quiet = atoi(getenv("BACKEND_QUIET"));
|
||||
if (getenv("BACKEND_STATS_ONLY"))
|
||||
stats_only = atoi(getenv("BACKEND_STATS_ONLY"));
|
||||
if (getenv("DYESUB_DEBUG"))
|
||||
dyesub_debug = atoi(getenv("DYESUB_DEBUG"));
|
||||
if (getenv("EXTRA_PID"))
|
||||
|
@ -1032,6 +1245,9 @@ int main (int argc, char **argv)
|
|||
exit(1);
|
||||
}
|
||||
|
||||
if (stats_only)
|
||||
quiet = 1;
|
||||
|
||||
DEBUG("Multi-Call Dye-sublimation CUPS Backend version %s\n",
|
||||
BACKEND_VERSION);
|
||||
DEBUG("Copyright 2007-2019 Solomon Peachy\n");
|
||||
|
@ -1146,14 +1362,14 @@ int main (int argc, char **argv)
|
|||
}
|
||||
|
||||
/* If we don't have a valid backend, print help and terminate */
|
||||
if (!backend) {
|
||||
if (!backend && !stats_only) {
|
||||
print_help(argv[0], NULL); // probes all devices
|
||||
ret = CUPS_BACKEND_OK;
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* If we're in standalone mode, print help only if no args */
|
||||
if (!uri || !strlen(uri)) {
|
||||
if ((!uri || !strlen(uri)) && !stats_only) {
|
||||
if (argc < 2) {
|
||||
print_help(argv[0], backend); // probes all devices
|
||||
ret = CUPS_BACKEND_OK;
|
||||
|
@ -1247,128 +1463,31 @@ bypass:
|
|||
|
||||
// STATE("+org.gutenprint.attached-to-device\n");
|
||||
|
||||
/* Dump stats only */
|
||||
if (stats_only && backend->query_stats) {
|
||||
struct printerstats stats;
|
||||
memset(&stats, 0, sizeof(stats));
|
||||
|
||||
stats.timestamp = time(NULL);
|
||||
ret = backend->query_stats(backend_ctx, &stats);
|
||||
if (ret)
|
||||
goto done_claimed;
|
||||
dump_stats(backend, &stats, stats_only -1);
|
||||
if (stats.status)
|
||||
free(stats.status); // only dynamic member..
|
||||
goto done_claimed;
|
||||
}
|
||||
|
||||
if (!uri || !strlen(uri)) {
|
||||
if (backend->cmdline_arg(backend_ctx, argc, argv) < 0)
|
||||
if (backend->cmdline_arg(backend_ctx, argc, argv))
|
||||
goto done_claimed;
|
||||
|
||||
/* Grab the filename */
|
||||
fname = argv[optind]; // XXX do this a smarter way?
|
||||
}
|
||||
|
||||
if (!fname) {
|
||||
if (uri && strlen(uri))
|
||||
ERROR("ERROR: No input file specified\n");
|
||||
goto done_claimed;
|
||||
}
|
||||
|
||||
if (ncopies < 1) {
|
||||
ERROR("ERROR: need to have at least 1 copy!\n");
|
||||
ret = CUPS_BACKEND_FAILED;
|
||||
goto done_claimed;
|
||||
}
|
||||
|
||||
/* Open file if not STDIN */
|
||||
if (strcmp("-", fname)) {
|
||||
data_fd = open(fname, O_RDONLY);
|
||||
if (data_fd < 0) {
|
||||
perror("ERROR:Can't open input file");
|
||||
ret = CUPS_BACKEND_FAILED;
|
||||
goto done_claimed;
|
||||
}
|
||||
}
|
||||
|
||||
/* Ensure we're using BLOCKING I/O */
|
||||
i = fcntl(data_fd, F_GETFL, 0);
|
||||
if (i < 0) {
|
||||
perror("ERROR:Can't open input");
|
||||
ret = CUPS_BACKEND_FAILED;
|
||||
goto done;
|
||||
}
|
||||
i &= ~O_NONBLOCK;
|
||||
i = fcntl(data_fd, F_SETFL, i);
|
||||
if (i < 0) {
|
||||
perror("ERROR:Can't open input");
|
||||
ret = CUPS_BACKEND_FAILED;
|
||||
goto done_claimed;
|
||||
}
|
||||
|
||||
/* Ignore SIGPIPE */
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
signal(SIGTERM, sigterm_handler);
|
||||
|
||||
/* Time for the main processing loop */
|
||||
INFO("Printing started (%d copies)\n", ncopies);
|
||||
|
||||
/* See if it's a CUPS command stream, and if yes, handle it! */
|
||||
if (type && !strcmp("application/vnd.cups-command", type))
|
||||
{
|
||||
ret = parse_cmdstream(backend, backend_ctx, data_fd);
|
||||
goto done_claimed;
|
||||
}
|
||||
|
||||
newpage:
|
||||
|
||||
/* Read in data */
|
||||
if ((ret = backend->read_parse(backend_ctx, &job, data_fd, ncopies))) {
|
||||
if (current_page)
|
||||
goto done_multiple;
|
||||
else
|
||||
goto done_claimed;
|
||||
}
|
||||
|
||||
/* The backend parser might not return a job due to job dependencies.
|
||||
Try and read another page. */
|
||||
if (!job)
|
||||
goto newpage;
|
||||
|
||||
/* Create our own joblist if necessary */
|
||||
if (!(backend->flags & BACKEND_FLAG_JOBLIST)) {
|
||||
struct dyesub_joblist *jlist = dyesub_joblist_create(backend, backend_ctx);
|
||||
if (!jlist)
|
||||
goto done_claimed;
|
||||
dyesub_joblist_addjob(jlist, job);
|
||||
job = jlist;
|
||||
}
|
||||
|
||||
/* Dump the full marker dump */
|
||||
ret = query_markers(backend, backend_ctx, !current_page);
|
||||
if (ret)
|
||||
goto done_claimed;
|
||||
|
||||
INFO("Printing page %d\n", ++current_page);
|
||||
|
||||
if (test_mode >= TEST_MODE_NOPRINT ) {
|
||||
WARNING("**** TEST MODE, bypassing printing!\n");
|
||||
} else {
|
||||
ret = dyesub_joblist_print(job);
|
||||
}
|
||||
|
||||
dyesub_joblist_cleanup(job);
|
||||
|
||||
if (ret)
|
||||
goto done_claimed;
|
||||
|
||||
/* Log the completed page */
|
||||
if (!uri || !strlen(uri))
|
||||
PAGE("%d %d\n", current_page, ncopies);
|
||||
|
||||
/* Dump a marker status update */
|
||||
ret = query_markers(backend, backend_ctx, !current_page);
|
||||
if (ret)
|
||||
goto done_claimed;
|
||||
|
||||
/* Since we have no way of telling if there's more data remaining
|
||||
to be read (without actually trying to read it), always assume
|
||||
multiple print jobs. */
|
||||
goto newpage;
|
||||
|
||||
done_multiple:
|
||||
close(data_fd);
|
||||
|
||||
/* Done printing, log the total number of pages */
|
||||
if (!uri || !strlen(uri))
|
||||
PAGE("total %d\n", current_page * ncopies);
|
||||
ret = CUPS_BACKEND_OK;
|
||||
/* Parse the file passed in */
|
||||
ret = handle_input(backend, backend_ctx, fname, uri, type);
|
||||
|
||||
done_claimed:
|
||||
if (test_mode < TEST_MODE_NOATTACH)
|
||||
|
@ -1447,7 +1566,7 @@ minimal:
|
|||
for (i = 0 ; i < marker_count; i++) {
|
||||
int val;
|
||||
if (markers[i].levelmax <= 0 || markers[i].levelnow < 0)
|
||||
val = (markers[i].levelnow <= 0) ? markers[i].levelnow : -1;
|
||||
val = (markers[i].levelnow <= 0) ? markers[i].levelnow : CUPS_MARKER_UNAVAILABLE;
|
||||
else if (markers[i].levelmax == 100)
|
||||
val = markers[i].levelnow;
|
||||
else
|
||||
|
|
|
@ -32,6 +32,8 @@
|
|||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <time.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
|
@ -198,6 +200,20 @@ struct marker {
|
|||
int numtype; /* Numerical type, (-1 for unknown) */
|
||||
};
|
||||
|
||||
struct printerstats {
|
||||
time_t timestamp;
|
||||
const char *mfg; /* Manufacturer */
|
||||
const char *model; /* Model */
|
||||
const char *serial; /* Serial Number */
|
||||
const char *fwver; /* Firmware Version */
|
||||
char *status; /* Printer status */
|
||||
uint8_t decks; /* Number of "decks" (1 or 2) */
|
||||
const char *mediatype[2]; /* Media Type */
|
||||
int32_t levelmax[2]; /* Max media count (-1 if unknown) */
|
||||
int32_t levelnow[2]; /* Remaining media count (-1 if unknown) */
|
||||
int32_t cnt_life; /* Lifetime prints */
|
||||
};
|
||||
|
||||
#define BACKEND_FLAG_JOBLIST 0x00000001
|
||||
#define BACKEND_FLAG_BADISERIAL 0x00000002
|
||||
|
||||
|
@ -218,6 +234,7 @@ struct dyesub_backend {
|
|||
int (*main_loop)(void *ctx, const void *job);
|
||||
int (*query_serno)(struct libusb_device_handle *dev, uint8_t endp_up, uint8_t endp_down, int iface, char *buf, int buf_len); /* Optional */
|
||||
int (*query_markers)(void *ctx, struct marker **markers, int *count);
|
||||
int (*query_stats)(void *ctx, struct printerstats *stats); /* Optional */
|
||||
const struct device_id devices[];
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in New Issue