* [PATCH v3 1/3] lspci: Add printing info in JSON format
2018-02-18 23:12 [PATCH v3 0/3] lspci: Add support of JSON output format Viktor Prutyanov
@ 2018-02-18 23:12 ` Viktor Prutyanov
2018-02-18 23:12 ` [PATCH v3 2/3] lspci: Add PCI info output " Viktor Prutyanov
` (2 subsequent siblings)
3 siblings, 0 replies; 5+ messages in thread
From: Viktor Prutyanov @ 2018-02-18 23:12 UTC (permalink / raw)
To: linux-pci, mj; +Cc: Viktor Prutyanov
This patch adds saving objects and lists and printing them in JSON format
Signed-off-by: Viktor Prutyanov <viktor.prutyanov@virtuozzo.com>
---
common.c | 2 +-
ls-info.c | 328 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
lspci.h | 54 ++++++++++
pciutils.h | 2 +-
4 files changed, 384 insertions(+), 2 deletions(-)
create mode 100644 ls-info.c
diff --git a/common.c b/common.c
index 8ea52fa..9654feb 100644
--- a/common.c
+++ b/common.c
@@ -45,7 +45,7 @@ xrealloc(void *ptr, unsigned int howmuch)
}
char *
-xstrdup(char *str)
+xstrdup(const char *str)
{
int len = strlen(str) + 1;
char *copy = xmalloc(len);
diff --git a/ls-info.c b/ls-info.c
new file mode 100644
index 0000000..e1430aa
--- /dev/null
+++ b/ls-info.c
@@ -0,0 +1,328 @@
+/*
+ * The PCI Utilities -- Save PCI info
+ *
+ * Copyright (c) 2017 Virtuozzo International GmbH
+ *
+ * Can be freely distributed and used under the terms of the GNU GPL.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdarg.h>
+
+#include "lspci.h"
+
+struct info_list *
+info_list_create(enum info_val_type type)
+{
+ struct info_list *list = xmalloc(sizeof(struct info_list));
+
+ list->node = NULL;
+ list->type = type;
+
+ return list;
+}
+
+struct info_list *
+info_list_create_in_obj(struct info_obj *parent_obj, char *key, enum info_val_type type)
+{
+ struct info_list *list = info_list_create(type);
+
+ info_obj_add_list(parent_obj, key, list);
+
+ return list;
+}
+
+static void
+info_list_delete(struct info_list *list)
+{
+ struct info_list_node *node = list->node, *next;
+
+ while (node)
+ {
+ switch (list->type)
+ {
+ case INFO_VAL_STRING:
+ free(node->val.str);
+ break;
+ case INFO_VAL_LIST:
+ info_list_delete(node->val.list);
+ break;
+ case INFO_VAL_OBJECT:
+ info_obj_delete(node->val.obj);
+ break;
+ default:
+ break;
+ }
+ next = node->next;
+ free(node);
+ node = next;
+ }
+ free(list);
+}
+
+struct info_obj *
+info_obj_create(void)
+{
+ struct info_obj *obj = xmalloc(sizeof(struct info_obj));
+
+ obj->pair = NULL;
+
+ return obj;
+}
+
+struct info_obj *
+info_obj_create_in_obj(struct info_obj *parent_obj, char *key)
+{
+ struct info_obj *obj = info_obj_create();
+
+ info_obj_add_obj(parent_obj, key, obj);
+
+ return obj;
+}
+
+static void
+info_pair_delete(struct info_pair *pair)
+{
+ switch (pair->type)
+ {
+ case INFO_VAL_STRING:
+ free(pair->val.str);
+ break;
+ case INFO_VAL_LIST:
+ info_list_delete(pair->val.list);
+ break;
+ case INFO_VAL_OBJECT:
+ info_obj_delete(pair->val.obj);
+ break;
+ default:
+ break;
+ }
+ free(pair->key);
+ free(pair);
+}
+
+void
+info_obj_delete(struct info_obj *obj)
+{
+ struct info_pair *pair = obj->pair, *next;
+
+ while (pair)
+ {
+ next = pair->next;
+ info_pair_delete(pair);
+ pair = next;
+ }
+ free(obj);
+}
+
+void
+info_obj_delete_pair(struct info_obj *obj, char *key)
+{
+ struct info_pair *pair = obj->pair, *next, *prev = NULL;
+
+ while (pair)
+ {
+ next = pair->next;
+ if (!strcmp(pair->key, key))
+ {
+ info_pair_delete(pair);
+ if (prev)
+ prev->next = next;
+ else
+ obj->pair = next;
+ break;
+ }
+ prev = pair;
+ pair = next;
+ }
+}
+
+static struct info_list_node *
+info_list_add_node(struct info_list *list)
+{
+ struct info_list_node *new_node = xmalloc(sizeof(struct info_list_node));
+
+ new_node->next = NULL;
+
+ if (list->node)
+ {
+ struct info_list_node *node;
+
+ for (node = list->node; node && node->next; node = node->next);
+ node->next = new_node;
+ }
+ else
+ list->node = new_node;
+
+ return new_node;
+}
+
+void
+info_list_add_str(struct info_list *list, const char *str)
+{
+ struct info_list_node *new_node = info_list_add_node(list);
+
+ new_node->val.str = xstrdup(str);
+}
+
+void
+info_list_add_obj(struct info_list *list, struct info_obj *obj)
+{
+ struct info_list_node *new_node = info_list_add_node(list);
+
+ new_node->val.obj = obj;
+}
+
+static struct info_pair *
+info_obj_add_pair(struct info_obj *obj, const char *key, enum info_val_type type)
+{
+ struct info_pair *new_pair = xmalloc(sizeof(struct info_pair));
+ new_pair->key = xstrdup(key);
+ new_pair->next = NULL;
+ new_pair->type = type;
+
+ if (obj->pair)
+ {
+ struct info_pair *pair;
+
+ for (pair = obj->pair; pair && pair->next; pair = pair->next);
+ pair->next = new_pair;
+ }
+ else
+ obj->pair = new_pair;
+
+ return new_pair;
+}
+
+void
+info_obj_add_flag(struct info_obj *obj, const char *key, char flag)
+{
+ struct info_pair *new_pair = info_obj_add_pair(obj, key, INFO_VAL_FLAG);
+
+ new_pair->val.flag = flag;
+}
+
+void
+info_obj_add_str(struct info_obj *obj, const char *key, const char *str)
+{
+ struct info_pair *new_pair = info_obj_add_pair(obj, key, INFO_VAL_STRING);
+
+ new_pair->val.str = xstrdup(str);
+}
+
+void
+info_obj_add_fmt_buf_str(struct info_obj *obj, const char *key, char *buf, size_t size, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vsnprintf(buf, size, fmt, ap);
+ va_end(ap);
+
+ info_obj_add_str(obj, key, buf);
+}
+
+void
+info_obj_add_fmt_str(struct info_obj *obj, const char *key, size_t size, const char *fmt, ...)
+{
+ va_list ap;
+ char *buf = xmalloc(size);
+
+ va_start(ap, fmt);
+ vsnprintf(buf, size, fmt, ap);
+ va_end(ap);
+
+ info_obj_add_str(obj, key, buf);
+ free(buf);
+}
+
+void
+info_obj_add_list(struct info_obj *obj, const char *key, struct info_list *list)
+{
+ struct info_pair *new_pair = info_obj_add_pair(obj, key, INFO_VAL_LIST);
+
+ new_pair->val.list = list;
+}
+
+void
+info_obj_add_obj(struct info_obj *obj, const char *key, struct info_obj *new_obj)
+{
+ struct info_pair *new_pair = info_obj_add_pair(obj, key, INFO_VAL_OBJECT);
+
+ new_pair->val.obj = new_obj;
+}
+
+static void
+info_pair_print_json(struct info_pair *pair);
+
+void
+info_obj_print_json(struct info_obj *obj)
+{
+ struct info_pair *pair;
+
+ printf("{");
+ for (pair = obj->pair; pair; pair = pair->next)
+ {
+ info_pair_print_json(pair);
+ if (pair->next)
+ printf(", ");
+ }
+ printf("}");
+}
+
+static void
+info_list_print_json(struct info_list *list)
+{
+ struct info_list_node *node;
+
+ printf("[");
+ for (node = list->node; node; node = node->next)
+ {
+ switch (list->type)
+ {
+ case INFO_VAL_STRING:
+ printf("\"%s\"", node->val.str);
+ break;
+ case INFO_VAL_LIST:
+ info_list_print_json(node->val.list);
+ break;
+ case INFO_VAL_OBJECT:
+ info_obj_print_json(node->val.obj);
+ break;
+ case INFO_VAL_FLAG:
+ printf("%s", node->val.flag ? "true" : "false");
+ break;
+ default:
+ break;
+ }
+ if (node->next)
+ printf(", ");
+ }
+ printf("]");
+}
+
+static void
+info_pair_print_json(struct info_pair *pair)
+{
+ printf("\"%s\": ", pair->key);
+ switch (pair->type)
+ {
+ case INFO_VAL_STRING:
+ printf("\"%s\"", pair->val.str);
+ break;
+ case INFO_VAL_LIST:
+ info_list_print_json(pair->val.list);
+ break;
+ case INFO_VAL_OBJECT:
+ info_obj_print_json(pair->val.obj);
+ break;
+ case INFO_VAL_FLAG:
+ printf("%s", (pair->val.flag == '+') ? "true" :
+ ((pair->val.flag == '-') ? "false" : "null"));
+ break;
+ default:
+ break;
+ }
+}
diff --git a/lspci.h b/lspci.h
index bcd007e..ba5a56b 100644
--- a/lspci.h
+++ b/lspci.h
@@ -74,6 +74,60 @@ void show_ext_caps(struct device *d, int type);
void show_vendor_caps(struct device *d, int where, int cap);
+/* ls-info.c */
+
+enum info_val_type {
+ INFO_VAL_STRING,
+ INFO_VAL_OBJECT,
+ INFO_VAL_LIST,
+ INFO_VAL_FLAG
+};
+
+struct info_obj {
+ struct info_pair *pair;
+};
+
+union info_val {
+ char *str;
+ struct info_obj *obj;
+ struct info_list *list;
+ char flag;
+};
+
+struct info_pair {
+ char *key;
+ enum info_val_type type;
+ union info_val val;
+ struct info_pair *next;
+};
+
+struct info_list {
+ enum info_val_type type;
+ struct info_list_node *node;
+};
+
+struct info_list_node {
+ union info_val val;
+ struct info_list_node *next;
+};
+
+struct info_obj *info_obj_create(void);
+struct info_obj *info_obj_create_in_obj(struct info_obj *parent_obj, char *key);
+void info_obj_add_str(struct info_obj *obj, const char *key, const char *str);
+void info_obj_add_list(struct info_obj *obj, const char *key, struct info_list *list);
+void info_obj_add_obj(struct info_obj *obj, const char *key, struct info_obj *new_obj);
+void info_obj_add_flag(struct info_obj *obj, const char *key, char flag);
+void info_obj_add_fmt_str(struct info_obj *obj, const char *key, size_t size, const char *fmt, ...);
+void info_obj_add_fmt_buf_str(struct info_obj *obj, const char *key, char *buf, size_t size, const char *fmt, ...);
+void info_obj_print_json(struct info_obj *obj);
+void info_obj_delete_pair(struct info_obj *obj, char *key);
+void info_obj_delete(struct info_obj *obj);
+
+struct info_list *info_list_create(enum info_val_type type);
+struct info_list *info_list_create_in_obj(struct info_obj *parent_obj, char *key, enum info_val_type type);
+void info_list_add_str(struct info_list *list, const char *str);
+void info_list_add_obj(struct info_list *list, struct info_obj *obj);
+
/* ls-kernel.c */
void show_kernel_machine(struct device *d UNUSED);
diff --git a/pciutils.h b/pciutils.h
index e433e6b..53a868b 100644
--- a/pciutils.h
+++ b/pciutils.h
@@ -22,7 +22,7 @@ extern const char program_name[];
void die(char *msg, ...) NONRET PCI_PRINTF(1,2);
void *xmalloc(unsigned int howmuch);
void *xrealloc(void *ptr, unsigned int howmuch);
-char *xstrdup(char *str);
+char *xstrdup(const char *str);
int parse_generic_option(int i, struct pci_access *pacc, char *optarg);
#ifdef PCI_HAVE_PM_INTEL_CONF
--
2.14.3
^ permalink raw reply related [flat|nested] 5+ messages in thread* [PATCH v3 2/3] lspci: Add PCI info output in JSON format
2018-02-18 23:12 [PATCH v3 0/3] lspci: Add support of JSON output format Viktor Prutyanov
2018-02-18 23:12 ` [PATCH v3 1/3] lspci: Add printing info in JSON format Viktor Prutyanov
@ 2018-02-18 23:12 ` Viktor Prutyanov
2018-02-18 23:12 ` [PATCH v3 3/3] lspci: Add JSON PCI Express capabilities Viktor Prutyanov
2018-03-11 15:03 ` [PATCH v3 0/3] lspci: Add support of JSON output format viktor.prutyanov
3 siblings, 0 replies; 5+ messages in thread
From: Viktor Prutyanov @ 2018-02-18 23:12 UTC (permalink / raw)
To: linux-pci, mj; +Cc: Viktor Prutyanov
This patch adds '-J' option for output in JSON format.
When this option is enabled, the output contains the same data as without it,
except for capabilities.
Signed-off-by: Viktor Prutyanov <viktor.prutyanov@virtuozzo.com>
---
Makefile | 3 +-
ls-kernel.c | 24 +++
lspci.c | 701 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
lspci.h | 1 +
lspci.man | 3 +
5 files changed, 730 insertions(+), 2 deletions(-)
diff --git a/Makefile b/Makefile
index 8c7edb7..5bdf84d 100644
--- a/Makefile
+++ b/Makefile
@@ -69,7 +69,7 @@ force:
lib/config.h lib/config.mk:
cd lib && ./configure
-lspci: lspci.o ls-vpd.o ls-caps.o ls-caps-vendor.o ls-ecaps.o ls-kernel.o ls-tree.o ls-map.o common.o lib/$(PCILIB)
+lspci: lspci.o ls-vpd.o ls-caps.o ls-caps-vendor.o ls-ecaps.o ls-kernel.o ls-tree.o ls-map.o ls-info.o common.o lib/$(PCILIB)
setpci: setpci.o common.o lib/$(PCILIB)
LSPCIINC=lspci.h pciutils.h $(PCIINC)
@@ -80,6 +80,7 @@ ls-ecaps.o: ls-ecaps.c $(LSPCIINC)
ls-kernel.o: ls-kernel.c $(LSPCIINC)
ls-tree.o: ls-tree.c $(LSPCIINC)
ls-map.o: ls-map.c $(LSPCIINC)
+ls-info.o: ls-info.c $(LSPCIINC)
setpci.o: setpci.c pciutils.h $(PCIINC)
common.o: common.c pciutils.h $(PCIINC)
diff --git a/ls-kernel.c b/ls-kernel.c
index ecacd0e..b0ded3b 100644
--- a/ls-kernel.c
+++ b/ls-kernel.c
@@ -302,6 +302,25 @@ show_kernel_machine(struct device *d)
printf("Module:\t%s\n", module);
}
+void
+fill_info_kernel(struct info_obj *dev_obj, struct device *d)
+{
+ char buf[DRIVER_BUF_SIZE];
+ const char *driver, *module;
+
+ if (driver = find_driver(d, buf))
+ info_obj_add_str(dev_obj, "Driver", driver);
+
+ if (!show_kernel_init())
+ return;
+
+ struct info_list *mod_list = info_list_create(INFO_VAL_STRING);
+ while (module = next_module_filtered(d))
+ info_list_add_str(mod_list, module);
+
+ info_obj_add_list(dev_obj, "Modules", mod_list);
+}
+
#else
void
@@ -319,5 +338,10 @@ show_kernel_cleanup(void)
{
}
+void
+fill_info_kernel(struct info_obj *dev_obj UNUSED, struct device *d UNUSED)
+{
+}
+
#endif
diff --git a/lspci.c b/lspci.c
index b50c76a..ae50a85 100644
--- a/lspci.c
+++ b/lspci.c
@@ -25,11 +25,12 @@ static int opt_domains; /* Show domain numbers (0=disabled, 1=auto-detected, 2
static int opt_kernel; /* Show kernel drivers */
static int opt_query_dns; /* Query the DNS (0=disabled, 1=enabled, 2=refresh cache) */
static int opt_query_all; /* Query the DNS for all entries */
+static int opt_json;
char *opt_pcimap; /* Override path to Linux modules.pcimap */
const char program_name[] = "lspci";
-static char options[] = "nvbxs:d:ti:mgp:qkMDQ" GENERIC_OPTIONS ;
+static char options[] = "nvbxs:d:ti:mgp:qkJMDQ" GENERIC_OPTIONS ;
static char help_msg[] =
"Usage: lspci [<switches>]\n"
@@ -48,6 +49,7 @@ static char help_msg[] =
"-xxxx\t\tShow hex-dump of the 4096-byte extended config space (root only)\n"
"-b\t\tBus-centric view (addresses and IRQ's as seen by the bus)\n"
"-D\t\tAlways show domain numbers\n"
+"-J\t\tUse JSON output format\n"
"\n"
"Resolving of device ID's to names:\n"
"-n\t\tShow numeric ID's\n"
@@ -257,6 +259,16 @@ show_slot_name(struct device *d)
printf("%02x:%02x.%d", p->bus, p->dev, p->func);
}
+static void
+fill_slot_name(struct device *d, char *buf, size_t size)
+{
+ struct pci_dev *p = d->dev;
+
+ if (!opt_machine ? opt_domains : (p->domain || opt_domains >= 2))
+ snprintf(buf, size, "%04x:", p->domain);
+ snprintf(buf, size, "%02x:%02x.%d", p->bus, p->dev, p->func);
+}
+
void
get_subid(struct device *d, word *subvp, word *subdp)
{
@@ -343,6 +355,24 @@ show_size(u64 x)
printf(" [size=%u%s]", (unsigned)x, suffix[i]);
}
+static void
+fill_size(char *buf, size_t size, u64 x)
+{
+ static const char suffix[][2] = { "", "K", "M", "G", "T" };
+ unsigned i;
+ if (!x)
+ {
+ snprintf(buf, size, "0");
+ return;
+ }
+ for (i = 0; i < (sizeof(suffix) / sizeof(*suffix) - 1); i++) {
+ if (x % 1024)
+ break;
+ x /= 1024;
+ }
+ snprintf(buf, size, "%u%s", (unsigned)x, suffix[i]);
+}
+
static void
show_range(char *prefix, u64 base, u64 limit, int is_64bit)
{
@@ -369,6 +399,21 @@ show_range(char *prefix, u64 base, u64 limit, int is_64bit)
putchar('\n');
}
+static void
+fill_range(char *buf, size_t size, u64 base, u64 limit, int is_64bit)
+{
+ if (base > limit)
+ {
+ snprintf(buf, size, "None");
+ return;
+ }
+
+ if (is_64bit)
+ snprintf(buf, size, "%016" PCI_U64_FMT_X "-%016" PCI_U64_FMT_X, base, limit);
+ else
+ snprintf(buf, size, "%08x-%08x", (unsigned) base, (unsigned) limit);
+}
+
static void
show_bases(struct device *d, int cnt)
{
@@ -840,6 +885,30 @@ show_hex_dump(struct device *d)
}
}
+static void
+fill_info_hex_dump(struct info_obj *dev_obj, struct device *d)
+{
+ unsigned int i, cnt;
+ char buf[3] = {0};
+ struct info_list *hex_dump_list = info_list_create(INFO_VAL_STRING);
+
+ cnt = d->config_cached;
+ if (opt_hex >= 3 && config_fetch(d, cnt, 256-cnt))
+ {
+ cnt = 256;
+ if (opt_hex >= 4 && config_fetch(d, 256, 4096-256))
+ cnt = 4096;
+ }
+
+ for (i=0; i<cnt; i++)
+ {
+ snprintf(buf, sizeof(buf), "%02x", get_conf_byte(d, i));
+ info_list_add_str(hex_dump_list, buf);
+ }
+
+ info_obj_add_list(dev_obj, "hexdump", hex_dump_list);
+}
+
static void
print_shell_escaped(char *c)
{
@@ -945,6 +1014,631 @@ show(void)
show_device(d);
}
+static void
+fill_info_machine(struct info_obj *dev_obj, struct device *d)
+{
+ struct pci_dev *p = d->dev;
+ int c;
+ word sv_id, sd_id;
+ char buf[128];
+
+ get_subid(d, &sv_id, &sd_id);
+
+ fill_slot_name(d, buf, sizeof(buf));
+ info_obj_add_str(dev_obj, "Slot", buf);
+
+ pci_lookup_name(pacc, buf, sizeof(buf), PCI_LOOKUP_CLASS, p->device_class);
+ info_obj_add_str(dev_obj, "Class", buf);
+ pci_lookup_name(pacc, buf, sizeof(buf), PCI_LOOKUP_VENDOR, p->vendor_id, p->device_id);
+ info_obj_add_str(dev_obj, "Vendor", buf);
+ pci_lookup_name(pacc, buf, sizeof(buf), PCI_LOOKUP_DEVICE, p->vendor_id, p->device_id);
+ info_obj_add_str(dev_obj, "Device", buf);
+
+ if (sv_id && sv_id != 0xffff)
+ {
+ pci_lookup_name(pacc, buf, sizeof(buf), PCI_LOOKUP_SUBSYSTEM | PCI_LOOKUP_VENDOR, sv_id);
+ info_obj_add_str(dev_obj, "SVendor", buf);
+ pci_lookup_name(pacc, buf, sizeof(buf), PCI_LOOKUP_SUBSYSTEM | PCI_LOOKUP_DEVICE, p->vendor_id, p->device_id, sv_id, sd_id);
+ info_obj_add_str(dev_obj, "SDevice", buf);
+ }
+ else if (!verbose)
+ {
+ info_obj_add_str(dev_obj, "SVendor", "");
+ info_obj_add_str(dev_obj, "SDevice", "");
+ }
+
+ if (c = get_conf_byte(d, PCI_REVISION_ID))
+ {
+ snprintf(buf, sizeof(buf), "%02x", c);
+ info_obj_add_str(dev_obj, "Rev", buf);
+ }
+ if (c = get_conf_byte(d, PCI_CLASS_PROG))
+ {
+ snprintf(buf, sizeof(buf), "%02x", c);
+ info_obj_add_str(dev_obj, "ProgIf", buf);
+ }
+
+ if (opt_kernel)
+ fill_info_kernel(dev_obj, d);
+
+ if (verbose)
+ {
+ pci_fill_info(p, PCI_FILL_PHYS_SLOT | PCI_FILL_NUMA_NODE);
+ if (p->phy_slot)
+ info_obj_add_str(dev_obj, "PhySlot", p->phy_slot);
+ if (p->numa_node != -1)
+ {
+ snprintf(buf, sizeof(buf), "%d", p->numa_node);
+ info_obj_add_str(dev_obj, "NUMAnode", buf);
+ }
+ }
+}
+
+static void
+fill_info_terse(struct info_obj *dev_obj, struct device *d)
+{
+ struct pci_dev *p = d->dev;
+
+ fill_info_machine(dev_obj, d);
+
+ if (verbose || opt_kernel)
+ {
+ word subsys_v, subsys_d;
+ char ssnamebuf[256];
+
+ if (p->label)
+ info_obj_add_str(dev_obj, "DeviceName", p->label);
+ info_obj_delete_pair(dev_obj, "SDevice");
+ info_obj_delete_pair(dev_obj, "SVendor");
+ get_subid(d, &subsys_v, &subsys_d);
+ if (subsys_v && subsys_v != 0xffff)
+ info_obj_add_str(dev_obj, "Subsystem",
+ pci_lookup_name(pacc, ssnamebuf, sizeof(ssnamebuf),
+ PCI_LOOKUP_SUBSYSTEM | PCI_LOOKUP_VENDOR | PCI_LOOKUP_DEVICE,
+ p->vendor_id, p->device_id, subsys_v, subsys_d));
+ }
+}
+
+static void
+fill_info_rom(struct info_obj *dev_obj, struct device *d, int reg)
+{
+ struct pci_dev *p = d->dev;
+ pciaddr_t rom = p->rom_base_addr;
+ pciaddr_t len = (p->known_fields & PCI_FILL_SIZES) ? p->rom_size : 0;
+ pciaddr_t ioflg = (p->known_fields & PCI_FILL_IO_FLAGS) ? p->rom_flags : 0;
+ u32 flg = get_conf_long(d, reg);
+ word cmd = get_conf_word(d, PCI_COMMAND);
+ int virtual = 0;
+ char buf[64];
+ struct info_obj *rom_obj;
+ struct info_list *attrs_list;
+
+ if (!rom && !flg && !len)
+ return;
+
+ rom_obj = info_obj_create_in_obj(dev_obj, "ROM");
+ attrs_list = info_list_create(INFO_VAL_STRING);
+ info_obj_add_list(rom_obj, "attrs", attrs_list);
+
+ if (ioflg & PCI_IORESOURCE_PCI_EA_BEI)
+ info_list_add_str(attrs_list, "[enhanced]");
+ else if ((rom & PCI_ROM_ADDRESS_MASK) && !(flg & PCI_ROM_ADDRESS_MASK))
+ {
+ info_list_add_str(attrs_list, "[virtual]");
+ flg = rom;
+ virtual = 1;
+ }
+
+ if (rom & PCI_ROM_ADDRESS_MASK)
+ info_obj_add_fmt_buf_str(rom_obj, "at", buf, sizeof(buf), PCIADDR_T_FMT, rom & PCI_ROM_ADDRESS_MASK);
+ else if (flg & PCI_ROM_ADDRESS_MASK)
+ info_list_add_str(attrs_list, "<ignored>");
+ else
+ info_list_add_str(attrs_list, "<unassigned>");
+
+ if (!(flg & PCI_ROM_ADDRESS_ENABLE))
+ info_list_add_str(attrs_list, "[disabled]");
+ else if (!virtual && !(cmd & PCI_COMMAND_MEMORY))
+ info_list_add_str(attrs_list, "[disabled by cmd]");
+
+ fill_size(buf, sizeof(buf), len);
+ info_obj_add_str(rom_obj, "size", buf);
+}
+
+static void
+fill_info_bases(struct info_obj *dev_obj, struct device *d, int cnt)
+{
+ struct pci_dev *p = d->dev;
+ word cmd = get_conf_word(d, PCI_COMMAND);
+ int i;
+ int virtual = 0;
+ char buf[64];
+ struct info_obj *bases_obj = info_obj_create_in_obj(dev_obj, "bases");
+ struct info_list *regions_list = info_list_create(INFO_VAL_OBJECT);
+
+ info_obj_add_list(bases_obj, "regions", regions_list);
+
+ for (i=0; i<cnt; i++)
+ {
+ struct info_obj *region_obj;
+ struct info_list *attrs_list;
+
+ pciaddr_t pos = p->base_addr[i];
+ pciaddr_t len = (p->known_fields & PCI_FILL_SIZES) ? p->size[i] : 0;
+ pciaddr_t ioflg = (p->known_fields & PCI_FILL_IO_FLAGS) ? p->flags[i] : 0;
+ u32 flg = get_conf_long(d, PCI_BASE_ADDRESS_0 + 4*i);
+ if (flg == 0xffffffff)
+ flg = 0;
+ if (!pos && !flg && !len)
+ continue;
+
+ region_obj = info_obj_create();
+ info_list_add_obj(regions_list, region_obj);
+ attrs_list = info_list_create(INFO_VAL_STRING);
+ info_obj_add_list(region_obj, "attrs", attrs_list);
+
+ if (ioflg & PCI_IORESOURCE_PCI_EA_BEI)
+ info_list_add_str(attrs_list, "[enhanced]");
+ else if (pos && !flg) /* Reported by the OS, but not by the device */
+ {
+ info_list_add_str(attrs_list, "[virtual]");
+ flg = pos;
+ virtual = 1;
+ }
+ if (flg & PCI_BASE_ADDRESS_SPACE_IO)
+ {
+ pciaddr_t a = pos & PCI_BASE_ADDRESS_IO_MASK;
+ if (a || (cmd & PCI_COMMAND_IO))
+ info_obj_add_fmt_buf_str(region_obj, "io-ports-at", buf, sizeof(buf), PCIADDR_PORT_FMT, a);
+ else if (flg & PCI_BASE_ADDRESS_IO_MASK)
+ info_list_add_str(attrs_list, "<ignored>");
+ else
+ info_list_add_str(attrs_list, "<unassigned>");
+ if (!virtual && !(cmd & PCI_COMMAND_IO))
+ info_list_add_str(attrs_list, "[disabled]");
+ }
+ else
+ {
+ int t = flg & PCI_BASE_ADDRESS_MEM_TYPE_MASK;
+ pciaddr_t a = pos & PCI_ADDR_MEM_MASK;
+ int done = 0;
+ u32 z = 0;
+
+ if (t == PCI_BASE_ADDRESS_MEM_TYPE_64)
+ {
+ if (i >= cnt - 1)
+ {
+ info_list_add_str(attrs_list, "<invalid-64bit-slot>");
+ done = 1;
+ }
+ else
+ {
+ i++;
+ z = get_conf_long(d, PCI_BASE_ADDRESS_0 + 4*i);
+ }
+ }
+ if (!done)
+ {
+ if (a)
+ info_obj_add_fmt_buf_str(region_obj, "memory-at", buf, sizeof(buf), PCIADDR_T_FMT, a);
+ else
+ {
+ if ((flg & PCI_BASE_ADDRESS_MEM_MASK) || z)
+ info_list_add_str(attrs_list, "<ignored>");
+ else
+ info_list_add_str(attrs_list, "<unassigned>");
+ }
+ }
+ info_list_add_str(attrs_list,
+ (t == PCI_BASE_ADDRESS_MEM_TYPE_32) ? "32-bit" :
+ (t == PCI_BASE_ADDRESS_MEM_TYPE_64) ? "64-bit" :
+ (t == PCI_BASE_ADDRESS_MEM_TYPE_1M) ? "low-1M" : "type 3");
+ info_list_add_str(attrs_list,
+ (flg & PCI_BASE_ADDRESS_MEM_PREFETCH) ? "prefetchable" : "non-prefetchable");
+ if (!virtual && !(cmd & PCI_COMMAND_MEMORY))
+ info_list_add_str(attrs_list, "[disabled]");
+ }
+ fill_size(buf, sizeof(buf), len);
+ info_obj_add_str(region_obj, "size", buf);
+ }
+}
+
+static void
+fill_info_htype0(struct info_obj *dev_obj, struct device *d)
+{
+ struct info_obj *htype0_obj = info_obj_create_in_obj(dev_obj, "htype0");
+
+ fill_info_bases(htype0_obj, d, 6);
+ fill_info_rom(htype0_obj, d, PCI_ROM_ADDRESS);
+}
+
+static void
+fill_info_htype1(struct info_obj *dev_obj, struct device *d)
+{
+ u32 io_base = get_conf_byte(d, PCI_IO_BASE);
+ u32 io_limit = get_conf_byte(d, PCI_IO_LIMIT);
+ u32 io_type = io_base & PCI_IO_RANGE_TYPE_MASK;
+ u32 mem_base = get_conf_word(d, PCI_MEMORY_BASE);
+ u32 mem_limit = get_conf_word(d, PCI_MEMORY_LIMIT);
+ u32 mem_type = mem_base & PCI_MEMORY_RANGE_TYPE_MASK;
+ u32 pref_base = get_conf_word(d, PCI_PREF_MEMORY_BASE);
+ u32 pref_limit = get_conf_word(d, PCI_PREF_MEMORY_LIMIT);
+ u32 pref_type = pref_base & PCI_PREF_RANGE_TYPE_MASK;
+ word sec_stat = get_conf_word(d, PCI_SEC_STATUS);
+ word brc = get_conf_word(d, PCI_BRIDGE_CONTROL);
+ struct info_obj *htype1_obj = info_obj_create_in_obj(dev_obj, "htype1");
+ struct info_obj *bus_obj;
+ char buf[64];
+
+ fill_info_bases(htype1_obj, d, 2);
+
+ bus_obj = info_obj_create_in_obj(htype1_obj, "Bus");
+ info_obj_add_fmt_buf_str(bus_obj, "primary", buf, sizeof(buf), "%02x", get_conf_byte(d, PCI_PRIMARY_BUS));
+ info_obj_add_fmt_buf_str(bus_obj, "secondary", buf, sizeof(buf), "%02x", get_conf_byte(d, PCI_SECONDARY_BUS));
+ info_obj_add_fmt_buf_str(bus_obj, "subordinate", buf, sizeof(buf), "%02x", get_conf_byte(d, PCI_SUBORDINATE_BUS));
+ info_obj_add_fmt_buf_str(bus_obj, "sec-latency", buf, sizeof(buf), "%d", get_conf_byte(d, PCI_SEC_LATENCY_TIMER));
+
+ if (io_type != (io_limit & PCI_IO_RANGE_TYPE_MASK) ||
+ (io_type != PCI_IO_RANGE_TYPE_16 && io_type != PCI_IO_RANGE_TYPE_32))
+ fprintf(stderr, "\t!!! Unknown I/O range types %x/%x\n", io_base, io_limit);
+ else
+ {
+ io_base = (io_base & PCI_IO_RANGE_MASK) << 8;
+ io_limit = (io_limit & PCI_IO_RANGE_MASK) << 8;
+ if (io_type == PCI_IO_RANGE_TYPE_32)
+ {
+ io_base |= (get_conf_word(d, PCI_IO_BASE_UPPER16) << 16);
+ io_limit |= (get_conf_word(d, PCI_IO_LIMIT_UPPER16) << 16);
+ }
+ fill_range(buf, sizeof(buf), io_base, io_limit + 0xfff, 0);
+ info_obj_add_str(htype1_obj, "io-behind-bridge", buf);
+ }
+
+ if (mem_type != (mem_limit & PCI_MEMORY_RANGE_TYPE_MASK) ||
+ mem_type)
+ fprintf(stderr, "\t!!! Unknown memory range types %x/%x\n", mem_base, mem_limit);
+ else
+ {
+ mem_base = (mem_base & PCI_MEMORY_RANGE_MASK) << 16;
+ mem_limit = (mem_limit & PCI_MEMORY_RANGE_MASK) << 16;
+ fill_range(buf, sizeof(buf), mem_base, mem_limit + 0xfffff, 0);
+ info_obj_add_str(htype1_obj, "memory-behind-bridge", buf);
+ }
+
+ if (pref_type != (pref_limit & PCI_PREF_RANGE_TYPE_MASK) ||
+ (pref_type != PCI_PREF_RANGE_TYPE_32 && pref_type != PCI_PREF_RANGE_TYPE_64))
+ fprintf(stderr, "\t!!! Unknown prefetchable memory range types %x/%x\n", pref_base, pref_limit);
+ else
+ {
+ u64 pref_base_64 = (pref_base & PCI_PREF_RANGE_MASK) << 16;
+ u64 pref_limit_64 = (pref_limit & PCI_PREF_RANGE_MASK) << 16;
+ if (pref_type == PCI_PREF_RANGE_TYPE_64)
+ {
+ pref_base_64 |= (u64) get_conf_long(d, PCI_PREF_BASE_UPPER32) << 32;
+ pref_limit_64 |= (u64) get_conf_long(d, PCI_PREF_LIMIT_UPPER32) << 32;
+ }
+ fill_range(buf, sizeof(buf), pref_base_64, pref_limit_64 + 0xfffff, (pref_type == PCI_PREF_RANGE_TYPE_64));
+ info_obj_add_str(htype1_obj, "prefetchable-memory-behind-bridge", buf);
+ }
+
+ if (verbose > 1)
+ {
+ struct info_obj *sec_status_obj = info_obj_create();
+
+ sec_status_obj = info_obj_create_in_obj(htype1_obj, "SecondaryStatus");
+ info_obj_add_flag(sec_status_obj, "66MHz", FLAG(sec_stat, PCI_STATUS_66MHZ));
+ info_obj_add_flag(sec_status_obj, "FastB2B", FLAG(sec_stat, PCI_STATUS_FAST_BACK));
+ info_obj_add_flag(sec_status_obj, "ParErr", FLAG(sec_stat, PCI_STATUS_PARITY));
+ info_obj_add_str(sec_status_obj, "DEVSEL=", ((sec_stat & PCI_STATUS_DEVSEL_MASK) == PCI_STATUS_DEVSEL_SLOW) ? "slow" :
+ ((sec_stat & PCI_STATUS_DEVSEL_MASK) == PCI_STATUS_DEVSEL_MEDIUM) ? "medium" :
+ ((sec_stat & PCI_STATUS_DEVSEL_MASK) == PCI_STATUS_DEVSEL_FAST) ? "fast" : "??");
+ info_obj_add_flag(sec_status_obj, ">TAbort", FLAG(sec_stat, PCI_STATUS_SIG_TARGET_ABORT));
+ info_obj_add_flag(sec_status_obj, "<TAbort", FLAG(sec_stat, PCI_STATUS_REC_TARGET_ABORT));
+ info_obj_add_flag(sec_status_obj, "<MAbort", FLAG(sec_stat, PCI_STATUS_REC_MASTER_ABORT));
+ info_obj_add_flag(sec_status_obj, "<SERR", FLAG(sec_stat, PCI_STATUS_SIG_SYSTEM_ERROR));
+ info_obj_add_flag(sec_status_obj, "<PERR", FLAG(sec_stat, PCI_STATUS_DETECTED_PARITY));
+ }
+
+ fill_info_rom(htype1_obj, d, PCI_ROM_ADDRESS1);
+
+ if (verbose > 1)
+ {
+ struct info_obj *bridgectl_obj;
+
+ bridgectl_obj = info_obj_create_in_obj(htype1_obj, "BridgeCtl");
+ info_obj_add_flag(bridgectl_obj, "Parity", FLAG(brc, PCI_BRIDGE_CTL_PARITY));
+ info_obj_add_flag(bridgectl_obj, "SERR", FLAG(brc, PCI_BRIDGE_CTL_SERR));
+ info_obj_add_flag(bridgectl_obj, "NoISA", FLAG(brc, PCI_BRIDGE_CTL_NO_ISA));
+ info_obj_add_flag(bridgectl_obj, "VGA", FLAG(brc, PCI_BRIDGE_CTL_VGA));
+ info_obj_add_flag(bridgectl_obj, "VGA16", FLAG(brc, PCI_BRIDGE_CTL_VGA_16BIT));
+ info_obj_add_flag(bridgectl_obj, "MAbort", FLAG(brc, PCI_BRIDGE_CTL_MASTER_ABORT));
+ info_obj_add_flag(bridgectl_obj, ">Reset", FLAG(brc, PCI_BRIDGE_CTL_BUS_RESET));
+ info_obj_add_flag(bridgectl_obj, "FastB2B", FLAG(brc, PCI_BRIDGE_CTL_FAST_BACK));
+ info_obj_add_flag(bridgectl_obj, "PriDiscTmr", FLAG(brc, PCI_BRIDGE_CTL_PRI_DISCARD_TIMER));
+ info_obj_add_flag(bridgectl_obj, "SecDiscTmr", FLAG(brc, PCI_BRIDGE_CTL_SEC_DISCARD_TIMER));
+ info_obj_add_flag(bridgectl_obj, "DiscTmrStat", FLAG(brc, PCI_BRIDGE_CTL_DISCARD_TIMER_STATUS));
+ info_obj_add_flag(bridgectl_obj, "DiscTmrSERREn", FLAG(brc, PCI_BRIDGE_CTL_DISCARD_TIMER_SERR_EN));
+ }
+}
+
+static void
+fill_info_htype2(struct info_obj *dev_obj, struct device *d)
+{
+ int i;
+ word cmd = get_conf_word(d, PCI_COMMAND);
+ word brc = get_conf_word(d, PCI_CB_BRIDGE_CONTROL);
+ word exca;
+ int verb = verbose > 2;
+ struct info_obj *htype2_obj = info_obj_create_in_obj(dev_obj, "htype2");
+ struct info_obj *bus_obj;
+ char buf[64];
+
+ fill_info_bases(htype2_obj, d, 1);
+
+ bus_obj = info_obj_create_in_obj(htype2_obj, "Bus");
+ info_obj_add_fmt_buf_str(bus_obj, "primary", buf, sizeof(buf), "%02x", get_conf_byte(d, PCI_CB_PRIMARY_BUS));
+ info_obj_add_fmt_buf_str(bus_obj, "secondary", buf, sizeof(buf), "%02x", get_conf_byte(d, PCI_CB_CARD_BUS));
+ info_obj_add_fmt_buf_str(bus_obj, "subordinate", buf, sizeof(buf), "%02x", get_conf_byte(d, PCI_CB_SUBORDINATE_BUS));
+ info_obj_add_fmt_buf_str(bus_obj, "sec-latency", buf, sizeof(buf), "%d", get_conf_byte(d, PCI_CB_LATENCY_TIMER));
+
+ for (i=0; i<2; i++)
+ {
+ int p = 8*i;
+ struct info_obj *mem_win_obj;
+
+ u32 base = get_conf_long(d, PCI_CB_MEMORY_BASE_0 + p);
+ u32 limit = get_conf_long(d, PCI_CB_MEMORY_LIMIT_0 + p);
+ limit = limit + 0xfff;
+ if (base <= limit || verb)
+ {
+ snprintf(buf, sizeof(buf), "memory-window-%d", i);
+ mem_win_obj = info_obj_create_in_obj(htype2_obj, buf);
+ info_obj_add_fmt_buf_str(mem_win_obj, "base", buf, sizeof(buf), "%08x", base);
+ info_obj_add_fmt_buf_str(mem_win_obj, "limit", buf, sizeof(buf), "%08x", limit);
+ info_obj_add_flag(mem_win_obj, "disabled", (cmd & PCI_COMMAND_MEMORY) ? '-' : '+');
+ info_obj_add_flag(mem_win_obj, "prefetchable", (brc & (PCI_CB_BRIDGE_CTL_PREFETCH_MEM0 << i)) ? '-' : '+');
+ }
+ }
+ for (i=0; i<2; i++)
+ {
+ int p = 8*i;
+ struct info_obj *io_win_obj;
+
+ u32 base = get_conf_long(d, PCI_CB_IO_BASE_0 + p);
+ u32 limit = get_conf_long(d, PCI_CB_IO_LIMIT_0 + p);
+ if (!(base & PCI_IO_RANGE_TYPE_32))
+ {
+ base &= 0xffff;
+ limit &= 0xffff;
+ }
+ base &= PCI_CB_IO_RANGE_MASK;
+ limit = (limit & PCI_CB_IO_RANGE_MASK) + 3;
+ if (base <= limit || verb)
+ {
+ snprintf(buf, sizeof(buf), "io-window-%d", i);
+ io_win_obj = info_obj_create_in_obj(htype2_obj, buf);
+ info_obj_add_fmt_buf_str(io_win_obj, "base", buf, sizeof(buf), "%08x", base);
+ info_obj_add_fmt_buf_str(io_win_obj, "limit", buf, sizeof(buf), "%08x", limit);
+ info_obj_add_flag(io_win_obj, "disabled", (cmd & PCI_COMMAND_IO) ? '-' : '+');
+ }
+ }
+
+ if (get_conf_word(d, PCI_CB_SEC_STATUS) & PCI_STATUS_SIG_SYSTEM_ERROR)
+ info_obj_add_str(htype2_obj, "SecondaryStatus", "SERR");
+ if (verbose > 1)
+ {
+ struct info_obj *bridgectl_obj;
+
+ bridgectl_obj = info_obj_create_in_obj(htype2_obj, "BridgeCtl");
+ info_obj_add_flag(bridgectl_obj, "Parity", FLAG(brc, PCI_CB_BRIDGE_CTL_PARITY));
+ info_obj_add_flag(bridgectl_obj, "SERR", FLAG(brc, PCI_CB_BRIDGE_CTL_SERR));
+ info_obj_add_flag(bridgectl_obj, "ISA", FLAG(brc, PCI_CB_BRIDGE_CTL_ISA));
+ info_obj_add_flag(bridgectl_obj, "VGA", FLAG(brc, PCI_CB_BRIDGE_CTL_VGA));
+ info_obj_add_flag(bridgectl_obj, "MAbort", FLAG(brc, PCI_CB_BRIDGE_CTL_MASTER_ABORT));
+ info_obj_add_flag(bridgectl_obj, ">Reset", FLAG(brc, PCI_CB_BRIDGE_CTL_CB_RESET));
+ info_obj_add_flag(bridgectl_obj, "16bInt", FLAG(brc, PCI_CB_BRIDGE_CTL_16BIT_INT));
+ info_obj_add_flag(bridgectl_obj, "PostWrite", FLAG(brc, PCI_CB_BRIDGE_CTL_POST_WRITES));
+ }
+
+ if (d->config_cached < 128)
+ return;
+
+ exca = get_conf_word(d, PCI_CB_LEGACY_MODE_BASE);
+ if (exca)
+ info_obj_add_fmt_buf_str(htype2_obj, "exca", buf, sizeof(buf), "%04x", exca);
+}
+
+static void
+fill_info_verbose(struct info_obj *dev_obj, struct device *d)
+{
+ struct pci_dev *p = d->dev;
+ word status = get_conf_word(d, PCI_STATUS);
+ word cmd = get_conf_word(d, PCI_COMMAND);
+ word class = p->device_class;
+ byte bist = get_conf_byte(d, PCI_BIST);
+ byte htype = get_conf_byte(d, PCI_HEADER_TYPE) & 0x7f;
+ byte latency = get_conf_byte(d, PCI_LATENCY_TIMER);
+ byte cache_line = get_conf_byte(d, PCI_CACHE_LINE_SIZE);
+ byte max_lat, min_gnt;
+ byte int_pin = get_conf_byte(d, PCI_INTERRUPT_PIN);
+ unsigned int irq;
+
+ fill_info_terse(dev_obj, d);
+ pci_fill_info(p, PCI_FILL_IRQ | PCI_FILL_BASES | PCI_FILL_ROM_BASE | PCI_FILL_SIZES |
+ PCI_FILL_PHYS_SLOT | PCI_FILL_LABEL | PCI_FILL_NUMA_NODE);
+ irq = p->irq;
+
+ switch (htype)
+ {
+ case PCI_HEADER_TYPE_NORMAL:
+ if (class == PCI_CLASS_BRIDGE_PCI)
+ fprintf(stderr, "\t!!! Invalid class %04x for header type %02x\n", class, htype);
+ max_lat = get_conf_byte(d, PCI_MAX_LAT);
+ min_gnt = get_conf_byte(d, PCI_MIN_GNT);
+ break;
+ case PCI_HEADER_TYPE_BRIDGE:
+ if ((class >> 8) != PCI_BASE_CLASS_BRIDGE)
+ fprintf(stderr, "\t!!! Invalid class %04x for header type %02x\n", class, htype);
+ min_gnt = max_lat = 0;
+ break;
+ case PCI_HEADER_TYPE_CARDBUS:
+ if ((class >> 8) != PCI_BASE_CLASS_BRIDGE)
+ fprintf(stderr, "\t!!! Invalid class %04x for header type %02x\n", class, htype);
+ min_gnt = max_lat = 0;
+ break;
+ default:
+ fprintf(stderr, "\t!!! Unknown header type %02x\n", htype);
+ return;
+ }
+
+ if (p->phy_slot)
+ info_obj_add_str(dev_obj, "PhySlot", p->phy_slot);
+
+ if (verbose > 1)
+ {
+ struct info_obj *control_obj;
+ struct info_obj *status_obj;
+
+ control_obj = info_obj_create_in_obj(dev_obj, "Control");
+ info_obj_add_flag(control_obj, "I/O", FLAG(cmd, PCI_COMMAND_IO));
+ info_obj_add_flag(control_obj, "Mem", FLAG(cmd, PCI_COMMAND_MEMORY));
+ info_obj_add_flag(control_obj, "BusMaster", FLAG(cmd, PCI_COMMAND_MASTER));
+ info_obj_add_flag(control_obj, "SpecCycle", FLAG(cmd, PCI_COMMAND_SPECIAL));
+ info_obj_add_flag(control_obj, "MemWINV", FLAG(cmd, PCI_COMMAND_INVALIDATE));
+ info_obj_add_flag(control_obj, "VGASnoop", FLAG(cmd, PCI_COMMAND_VGA_PALETTE));
+ info_obj_add_flag(control_obj, "ParErr", FLAG(cmd, PCI_COMMAND_PARITY));
+ info_obj_add_flag(control_obj, "Stepping", FLAG(cmd, PCI_COMMAND_WAIT));
+ info_obj_add_flag(control_obj, "SERR", FLAG(cmd, PCI_COMMAND_SERR));
+ info_obj_add_flag(control_obj, "FastB2B", FLAG(cmd, PCI_COMMAND_FAST_BACK));
+ info_obj_add_flag(control_obj, "DisINTx", FLAG(cmd, PCI_COMMAND_DISABLE_INTx));
+
+ status_obj = info_obj_create_in_obj(dev_obj, "Status");
+ info_obj_add_flag(status_obj, "Cap", FLAG(status, PCI_STATUS_CAP_LIST));
+ info_obj_add_flag(status_obj, "66MHz", FLAG(status, PCI_STATUS_66MHZ));
+ info_obj_add_flag(status_obj, "UDF", FLAG(status, PCI_STATUS_UDF));
+ info_obj_add_flag(status_obj, "FastB2B", FLAG(status, PCI_STATUS_FAST_BACK));
+ info_obj_add_flag(status_obj, "ParErr", FLAG(status, PCI_STATUS_PARITY));
+ info_obj_add_str(status_obj, "DEVSEL=",
+ ((status & PCI_STATUS_DEVSEL_MASK) == PCI_STATUS_DEVSEL_SLOW) ? "slow" :
+ ((status & PCI_STATUS_DEVSEL_MASK) == PCI_STATUS_DEVSEL_MEDIUM) ? "medium" :
+ ((status & PCI_STATUS_DEVSEL_MASK) == PCI_STATUS_DEVSEL_FAST) ? "fast" : "??");
+ info_obj_add_flag(status_obj, ">TAbort", FLAG(status, PCI_STATUS_SIG_TARGET_ABORT));
+ info_obj_add_flag(status_obj, "<TAbort", FLAG(status, PCI_STATUS_REC_TARGET_ABORT));
+ info_obj_add_flag(status_obj, "<MAbort", FLAG(status, PCI_STATUS_REC_MASTER_ABORT));
+ info_obj_add_flag(status_obj, ">SERR", FLAG(status, PCI_STATUS_SIG_SYSTEM_ERROR));
+ info_obj_add_flag(status_obj, "<PERR", FLAG(status, PCI_STATUS_DETECTED_PARITY));
+ info_obj_add_flag(status_obj, "INTx", FLAG(status, PCI_STATUS_INTx));
+
+ if (cmd & PCI_COMMAND_MASTER)
+ {
+ info_obj_add_fmt_str(dev_obj, "Latency", 16, "%d", latency);
+ if (min_gnt)
+ info_obj_add_fmt_str(dev_obj, "min-gnt", 16, "%d", min_gnt*250);
+ if (max_lat)
+ info_obj_add_fmt_str(dev_obj, "max-lat", 16, "%d", max_lat*250);
+ if (cache_line)
+ info_obj_add_fmt_str(dev_obj, "CacheLineSize", 16, "%d", cache_line * 4);
+ }
+ if (int_pin || irq)
+ {
+ info_obj_add_fmt_str(dev_obj, "int-pin", 2, "%c", (int_pin ? 'A' + int_pin - 1 : '?'));
+ info_obj_add_fmt_str(dev_obj, "IRQ", 16, PCIIRQ_FMT, irq);
+ }
+ }
+ else
+ {
+ struct info_list *flags_list = info_list_create(INFO_VAL_STRING);
+
+ info_obj_add_list(dev_obj, "Flags", flags_list);
+ if (cmd & PCI_COMMAND_MASTER)
+ info_list_add_str(flags_list, "bus master");
+ if (cmd & PCI_COMMAND_VGA_PALETTE)
+ info_list_add_str(flags_list, "VGA palette snoop");
+ if (cmd & PCI_COMMAND_WAIT)
+ info_list_add_str(flags_list, "stepping");
+ if (cmd & PCI_COMMAND_FAST_BACK)
+ info_list_add_str(flags_list, "fast Back2Back");
+ if (status & PCI_STATUS_66MHZ)
+ info_list_add_str(flags_list, "66MHz");
+ if (status & PCI_STATUS_UDF)
+ info_list_add_str(flags_list, "user-definable features");
+
+ info_obj_add_str(dev_obj, "devsel",
+ ((status & PCI_STATUS_DEVSEL_MASK) == PCI_STATUS_DEVSEL_SLOW) ? "slow" :
+ ((status & PCI_STATUS_DEVSEL_MASK) == PCI_STATUS_DEVSEL_MEDIUM) ? "medium" :
+ ((status & PCI_STATUS_DEVSEL_MASK) == PCI_STATUS_DEVSEL_FAST) ? "fast" : "??");
+ if (cmd & PCI_COMMAND_MASTER)
+ info_obj_add_fmt_str(dev_obj, "Latency", 16, "%d", latency);
+ if (irq)
+ info_obj_add_fmt_str(dev_obj, "IRQ", 16, PCIIRQ_FMT, irq);
+ }
+
+ if (bist & PCI_BIST_CAPABLE)
+ {
+ if (bist & PCI_BIST_START)
+ info_obj_add_str(dev_obj, "BIST", "running");
+ else
+ info_obj_add_fmt_str(dev_obj, "BIST", 3, "%02x", bist & PCI_BIST_CODE_MASK);
+ }
+
+ switch (htype)
+ {
+ case PCI_HEADER_TYPE_NORMAL:
+ fill_info_htype0(dev_obj, d);
+ break;
+ case PCI_HEADER_TYPE_BRIDGE:
+ fill_info_htype1(dev_obj, d);
+ break;
+ case PCI_HEADER_TYPE_CARDBUS:
+ fill_info_htype2(dev_obj, d);
+ break;
+ }
+}
+
+static void
+fill_info_device(struct info_obj *dev_obj, struct device *d)
+{
+ if (opt_machine)
+ fill_info_machine(dev_obj, d);
+ else
+ {
+ if (verbose)
+ fill_info_verbose(dev_obj, d);
+ else
+ fill_info_terse(dev_obj, d);
+ if (!opt_kernel && verbose)
+ fill_info_kernel(dev_obj, d);
+ }
+ if (opt_hex)
+ fill_info_hex_dump(dev_obj, d);
+}
+
+static void
+fill_info(struct info_obj *root)
+{
+ struct device *d;
+ struct info_list *dev_list = info_list_create(INFO_VAL_OBJECT);
+
+ for (d=first_dev; d; d=d->next)
+ {
+ struct info_obj *dev_obj = info_obj_create();
+ fill_info_device(dev_obj, d);
+ info_list_add_obj(dev_list, dev_obj);
+ }
+
+ info_obj_add_list(root, "pcidevices", dev_list);
+}
+
+static void
+show_json(void)
+{
+ struct info_obj *root = info_obj_create();
+
+ fill_info(root);
+ info_obj_print_json(root);
+ info_obj_delete(root);
+}
+
/* Main */
int
@@ -1009,6 +1703,9 @@ main(int argc, char **argv)
case 'D':
opt_domains = 2;
break;
+ case 'J':
+ opt_json = 1;
+ break;
#ifdef PCI_USE_DNS
case 'q':
opt_query_dns++;
@@ -1049,6 +1746,8 @@ main(int argc, char **argv)
sort_them();
if (opt_tree)
show_forest();
+ else if (opt_json)
+ show_json();
else
show();
}
diff --git a/lspci.h b/lspci.h
index ba5a56b..fc23d32 100644
--- a/lspci.h
+++ b/lspci.h
@@ -133,6 +133,7 @@ void info_list_add_obj(struct info_list *list, struct info_obj *obj);
void show_kernel_machine(struct device *d UNUSED);
void show_kernel(struct device *d UNUSED);
void show_kernel_cleanup(void);
+void fill_info_kernel(struct info_obj *dev_obj UNUSED, struct device *d UNUSED);
/* ls-tree.c */
diff --git a/lspci.man b/lspci.man
index 9348cfc..3a126a4 100644
--- a/lspci.man
+++ b/lspci.man
@@ -51,6 +51,9 @@ See below for details.
.B -t
Show a tree-like diagram containing all buses, bridges, devices and connections
between them.
+.TP
+.B -J
+Use JSON output format.
.SS Display options
.TP
--
2.14.3
^ permalink raw reply related [flat|nested] 5+ messages in thread* [PATCH v3 3/3] lspci: Add JSON PCI Express capabilities
2018-02-18 23:12 [PATCH v3 0/3] lspci: Add support of JSON output format Viktor Prutyanov
2018-02-18 23:12 ` [PATCH v3 1/3] lspci: Add printing info in JSON format Viktor Prutyanov
2018-02-18 23:12 ` [PATCH v3 2/3] lspci: Add PCI info output " Viktor Prutyanov
@ 2018-02-18 23:12 ` Viktor Prutyanov
2018-03-11 15:03 ` [PATCH v3 0/3] lspci: Add support of JSON output format viktor.prutyanov
3 siblings, 0 replies; 5+ messages in thread
From: Viktor Prutyanov @ 2018-02-18 23:12 UTC (permalink / raw)
To: linux-pci, mj; +Cc: Viktor Prutyanov
This patch adds JSON format of PCI Express capabilities
Signed-off-by: Viktor Prutyanov <viktor.prutyanov@virtuozzo.com>
---
ls-caps.c | 417 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
lspci.c | 5 +
lspci.h | 33 ++---
3 files changed, 439 insertions(+), 16 deletions(-)
diff --git a/ls-caps.c b/ls-caps.c
index d4aebc8..9fe4b82 100644
--- a/ls-caps.c
+++ b/ls-caps.c
@@ -716,6 +716,73 @@ static void cap_express_dev(struct device *d, int where, int type)
FLAG(w, PCI_EXP_DEVSTA_TRPND));
}
+static void fill_info_express_dev(struct info_obj *exp_obj, struct device *d, int where, int type)
+{
+ u32 t;
+ u16 w;
+ struct info_obj *dev_cap_obj, *dev_ctl_obj, *dev_sta_obj, *dev_ctl_re_obj;
+ char buf[64];
+
+ t = get_conf_long(d, where + PCI_EXP_DEVCAP);
+ dev_cap_obj = info_obj_create_in_obj(exp_obj, "DevCap");
+ info_obj_add_fmt_buf_str(dev_cap_obj, "MaxPayload", buf, sizeof(buf), "%d",
+ 128 << (t & PCI_EXP_DEVCAP_PAYLOAD));
+ info_obj_add_fmt_buf_str(dev_cap_obj, "PhantFunc", buf, sizeof(buf), "%d",
+ (1 << ((t & PCI_EXP_DEVCAP_PHANTOM) >> 3)) - 1);
+ if ((type == PCI_EXP_TYPE_ENDPOINT) || (type == PCI_EXP_TYPE_LEG_END))
+ {
+ info_obj_add_str(dev_cap_obj, "L0s", latency_l0s((t & PCI_EXP_DEVCAP_L0S) >> 6));
+ info_obj_add_str(dev_cap_obj, "L1", latency_l1((t & PCI_EXP_DEVCAP_L1) >> 9));
+ }
+ info_obj_add_flag(dev_cap_obj, "ExtTag", FLAG(t, PCI_EXP_DEVCAP_EXT_TAG));
+ if ((type == PCI_EXP_TYPE_ENDPOINT) || (type == PCI_EXP_TYPE_LEG_END) ||
+ (type == PCI_EXP_TYPE_UPSTREAM) || (type == PCI_EXP_TYPE_PCI_BRIDGE))
+ {
+ info_obj_add_flag(dev_cap_obj, "AttnBtn", FLAG(t, PCI_EXP_DEVCAP_ATN_BUT));
+ info_obj_add_flag(dev_cap_obj, "AttnInd", FLAG(t, PCI_EXP_DEVCAP_ATN_IND));
+ info_obj_add_flag(dev_cap_obj, "PwrInd", FLAG(t, PCI_EXP_DEVCAP_PWR_IND));
+ }
+ info_obj_add_flag(dev_cap_obj, "RBE", FLAG(t, PCI_EXP_DEVCAP_RBE));
+ if ((type == PCI_EXP_TYPE_ENDPOINT) || (type == PCI_EXP_TYPE_LEG_END))
+ info_obj_add_flag(dev_cap_obj, "FLReset", FLAG(t, PCI_EXP_DEVCAP_FLRESET));
+ if ((type == PCI_EXP_TYPE_ENDPOINT) || (type == PCI_EXP_TYPE_UPSTREAM) ||
+ (type == PCI_EXP_TYPE_PCI_BRIDGE))
+ info_obj_add_fmt_buf_str(dev_cap_obj, "SlotPowerLimit", buf, sizeof(buf), "%.3f",
+ power_limit((t & PCI_EXP_DEVCAP_PWR_VAL) >> 18,
+ (t & PCI_EXP_DEVCAP_PWR_SCL) >> 26));
+
+ w = get_conf_word(d, where + PCI_EXP_DEVCTL);
+ dev_ctl_obj = info_obj_create_in_obj(exp_obj, "DevCtl");
+ dev_ctl_re_obj = info_obj_create_in_obj(dev_ctl_obj, "Report-errors");
+ info_obj_add_flag(dev_ctl_re_obj, "Correctable", FLAG(w, PCI_EXP_DEVCTL_CERE));
+ info_obj_add_flag(dev_ctl_re_obj, "Non-Fatal", FLAG(w, PCI_EXP_DEVCTL_NFERE));
+ info_obj_add_flag(dev_ctl_re_obj, "Fatal", FLAG(w, PCI_EXP_DEVCTL_FERE));
+ info_obj_add_flag(dev_ctl_re_obj, "Unsupported", FLAG(w, PCI_EXP_DEVCTL_URRE));
+ info_obj_add_flag(dev_ctl_obj, "RlxdOrd", FLAG(w, PCI_EXP_DEVCTL_RELAXED));
+ info_obj_add_flag(dev_ctl_obj, "ExtTag", FLAG(w, PCI_EXP_DEVCTL_EXT_TAG));
+ info_obj_add_flag(dev_ctl_obj, "PhantFunc", FLAG(w, PCI_EXP_DEVCTL_PHANTOM));
+ info_obj_add_flag(dev_ctl_obj, "AuxPwr", FLAG(w, PCI_EXP_DEVCTL_AUX_PME));
+ info_obj_add_flag(dev_ctl_obj, "NoSnoop", FLAG(w, PCI_EXP_DEVCTL_NOSNOOP));
+ if (type == PCI_EXP_TYPE_PCI_BRIDGE)
+ info_obj_add_flag(dev_ctl_obj, "BrConfRtry", FLAG(w, PCI_EXP_DEVCTL_BCRE));
+ if (((type == PCI_EXP_TYPE_ENDPOINT) || (type == PCI_EXP_TYPE_LEG_END)) &&
+ (t & PCI_EXP_DEVCAP_FLRESET))
+ info_obj_add_flag(dev_ctl_obj, "FLReset", FLAG(w, PCI_EXP_DEVCTL_FLRESET));
+ info_obj_add_fmt_buf_str(dev_ctl_obj, "MaxPayload", buf, sizeof(buf), "%d",
+ 128 << ((w & PCI_EXP_DEVCTL_PAYLOAD) >> 5));
+ info_obj_add_fmt_buf_str(dev_ctl_obj, "MaxReadReq", buf, sizeof(buf), "%d",
+ 128 << ((w & PCI_EXP_DEVCTL_READRQ) >> 12));
+
+ w = get_conf_word(d, where + PCI_EXP_DEVSTA);
+ dev_sta_obj = info_obj_create_in_obj(exp_obj, "DevSta");
+ info_obj_add_flag(dev_sta_obj, "CorrErr", FLAG(w, PCI_EXP_DEVSTA_CED));
+ info_obj_add_flag(dev_sta_obj, "UncorrErr", FLAG(w, PCI_EXP_DEVSTA_NFED));
+ info_obj_add_flag(dev_sta_obj, "FatalErr", FLAG(w, PCI_EXP_DEVSTA_FED));
+ info_obj_add_flag(dev_sta_obj, "UnsuppReq", FLAG(w, PCI_EXP_DEVSTA_URD));
+ info_obj_add_flag(dev_sta_obj, "AuxPwr", FLAG(w, PCI_EXP_DEVSTA_AUXPD));
+ info_obj_add_flag(dev_sta_obj, "TransPend", FLAG(w, PCI_EXP_DEVSTA_TRPND));
+}
+
static char *link_speed(int speed)
{
switch (speed)
@@ -810,6 +877,59 @@ static void cap_express_link(struct device *d, int where, int type)
FLAG(w, PCI_EXP_LNKSTA_AUTBW));
}
+static void fill_info_express_link(struct info_obj *exp_obj, struct device *d, int where, int type)
+{
+ u32 t, aspm;
+ u16 w;
+ struct info_obj *lnk_cap_obj, *lnk_ctl_obj, *lnk_sta_obj;
+ char buf[64];
+
+ t = get_conf_long(d, where + PCI_EXP_LNKCAP);
+ aspm = (t & PCI_EXP_LNKCAP_ASPM) >> 10;
+ lnk_cap_obj = info_obj_create_in_obj(exp_obj, "LnkCap");
+ info_obj_add_fmt_buf_str(lnk_cap_obj, "Port", buf, sizeof(buf), "%d", t >> 24);
+ info_obj_add_str(lnk_cap_obj, "Speed", link_speed(t & PCI_EXP_LNKCAP_SPEED));
+ info_obj_add_fmt_buf_str(lnk_cap_obj, "Width", buf, sizeof(buf), "x%d", (t & PCI_EXP_LNKCAP_WIDTH) >> 4);
+ info_obj_add_str(lnk_cap_obj, "ASPM", aspm_support(aspm));
+ if (aspm)
+ {
+ if (aspm & 1)
+ info_obj_add_str(lnk_cap_obj, "L0s", latency_l0s((t & PCI_EXP_LNKCAP_L0S) >> 12));
+ if (aspm & 2)
+ info_obj_add_str(lnk_cap_obj, "L1", latency_l1((t & PCI_EXP_LNKCAP_L1) >> 15));
+ }
+ info_obj_add_flag(lnk_cap_obj, "ClockPM", FLAG(t, PCI_EXP_LNKCAP_CLOCKPM));
+ info_obj_add_flag(lnk_cap_obj, "Surprise", FLAG(t, PCI_EXP_LNKCAP_SURPRISE));
+ info_obj_add_flag(lnk_cap_obj, "LLActRep", FLAG(t, PCI_EXP_LNKCAP_DLLA));
+ info_obj_add_flag(lnk_cap_obj, "BwNot", FLAG(t, PCI_EXP_LNKCAP_LBNC));
+ info_obj_add_flag(lnk_cap_obj, "ASPMOptComp", FLAG(t, PCI_EXP_LNKCAP_AOC));
+
+ w = get_conf_word(d, where + PCI_EXP_LNKCTL);
+ lnk_ctl_obj = info_obj_create_in_obj(exp_obj, "LnkCtl");
+ info_obj_add_str(lnk_ctl_obj, "ASPM", aspm_enabled(w & PCI_EXP_LNKCTL_ASPM));
+ if ((type == PCI_EXP_TYPE_ROOT_PORT) || (type == PCI_EXP_TYPE_ENDPOINT) ||
+ (type == PCI_EXP_TYPE_LEG_END) || (type == PCI_EXP_TYPE_PCI_BRIDGE))
+ info_obj_add_fmt_buf_str(lnk_ctl_obj, "RCB", buf, sizeof(buf), "%d", w & PCI_EXP_LNKCTL_RCB ? 128 : 64);
+ info_obj_add_flag(lnk_ctl_obj, "Disabled", FLAG(w, PCI_EXP_LNKCTL_DISABLE));
+ info_obj_add_flag(lnk_ctl_obj, "CommClk", FLAG(w, PCI_EXP_LNKCTL_CLOCK));
+ info_obj_add_flag(lnk_ctl_obj, "ExtSynch", FLAG(w, PCI_EXP_LNKCTL_XSYNCH));
+ info_obj_add_flag(lnk_ctl_obj, "ClockPM", FLAG(w, PCI_EXP_LNKCTL_CLOCKPM));
+ info_obj_add_flag(lnk_ctl_obj, "AutWidDis", FLAG(w, PCI_EXP_LNKCTL_HWAUTWD));
+ info_obj_add_flag(lnk_ctl_obj, "BWInt", FLAG(w, PCI_EXP_LNKCTL_BWMIE));
+ info_obj_add_flag(lnk_ctl_obj, "AutBWInt", FLAG(w, PCI_EXP_LNKCTL_AUTBWIE));
+
+ w = get_conf_word(d, where + PCI_EXP_LNKSTA);
+ lnk_sta_obj = info_obj_create_in_obj(exp_obj, "LnkSta");
+ info_obj_add_str(lnk_sta_obj, "Speed", link_speed(w & PCI_EXP_LNKSTA_SPEED));
+ info_obj_add_fmt_buf_str(lnk_sta_obj, "Width", buf, sizeof(buf), "x%d", (w & PCI_EXP_LNKSTA_WIDTH) >> 4);
+ info_obj_add_flag(lnk_sta_obj, "TrErr", FLAG(w, PCI_EXP_LNKSTA_TR_ERR));
+ info_obj_add_flag(lnk_sta_obj, "Train", FLAG(w, PCI_EXP_LNKSTA_TRAIN));
+ info_obj_add_flag(lnk_sta_obj, "SlotClk", FLAG(w, PCI_EXP_LNKSTA_SL_CLK));
+ info_obj_add_flag(lnk_sta_obj, "DLActive", FLAG(w, PCI_EXP_LNKSTA_DL_ACT));
+ info_obj_add_flag(lnk_sta_obj, "BWMgmt", FLAG(w, PCI_EXP_LNKSTA_BWMGMT));
+ info_obj_add_flag(lnk_sta_obj, "ABWMgmt", FLAG(w, PCI_EXP_LNKSTA_AUTBW));
+}
+
static const char *indicator(int code)
{
static const char *names[] = { "Unknown", "On", "Blink", "Off" };
@@ -865,6 +985,61 @@ static void cap_express_slot(struct device *d, int where)
FLAG(w, PCI_EXP_SLTSTA_LLCHG));
}
+static void fill_info_express_slot(struct info_obj *exp_obj, struct device *d, int where)
+{
+ u32 t;
+ u16 w;
+ struct info_obj *slt_cap_obj, *slt_ctl_obj, *slt_sta_obj;
+ struct info_obj *slt_ctl_enable_obj, *slt_ctl_control_obj;
+ struct info_obj *slt_sta_status_obj, *slt_sta_changed_obj;
+ char buf[128];
+
+ t = get_conf_long(d, where + PCI_EXP_SLTCAP);
+ slt_cap_obj = info_obj_create_in_obj(exp_obj, "SltCap");
+ info_obj_add_flag(slt_cap_obj, "AttnBtn", FLAG(t, PCI_EXP_SLTCAP_ATNB));
+ info_obj_add_flag(slt_cap_obj, "PwrCtrl", FLAG(t, PCI_EXP_SLTCAP_PWRC));
+ info_obj_add_flag(slt_cap_obj, "MRL", FLAG(t, PCI_EXP_SLTCAP_MRL));
+ info_obj_add_flag(slt_cap_obj, "AttnInd", FLAG(t, PCI_EXP_SLTCAP_ATNI));
+ info_obj_add_flag(slt_cap_obj, "PwrInd", FLAG(t, PCI_EXP_SLTCAP_PWRI));
+ info_obj_add_flag(slt_cap_obj, "HotPlug", FLAG(t, PCI_EXP_SLTCAP_HPC));
+ info_obj_add_flag(slt_cap_obj, "Surprise", FLAG(t, PCI_EXP_SLTCAP_HPS));
+ info_obj_add_fmt_buf_str(slt_cap_obj, "Slot", buf, sizeof(buf), "%d", (t & PCI_EXP_SLTCAP_PSN) >> 19);
+ info_obj_add_fmt_buf_str(slt_cap_obj, "PowerLimit", buf, sizeof(buf), "%.3f",
+ power_limit((t & PCI_EXP_SLTCAP_PWR_VAL) >> 7, (t & PCI_EXP_SLTCAP_PWR_SCL) >> 15));
+ info_obj_add_flag(slt_cap_obj, "Interlock", FLAG(t, PCI_EXP_SLTCAP_INTERLOCK));
+ info_obj_add_flag(slt_cap_obj, "NoCompl", FLAG(t, PCI_EXP_SLTCAP_NOCMDCOMP));
+
+ w = get_conf_word(d, where + PCI_EXP_SLTCTL);
+ slt_ctl_obj = info_obj_create_in_obj(exp_obj, "SltCtl");
+ slt_ctl_enable_obj = info_obj_create_in_obj(slt_ctl_obj, "Enable");
+ info_obj_add_flag(slt_ctl_enable_obj, "AttnBtn", FLAG(w, PCI_EXP_SLTCTL_ATNB));
+ info_obj_add_flag(slt_ctl_enable_obj, "PwrFlt", FLAG(w, PCI_EXP_SLTCTL_PWRF));
+ info_obj_add_flag(slt_ctl_enable_obj, "MRL", FLAG(w, PCI_EXP_SLTCTL_MRLS));
+ info_obj_add_flag(slt_ctl_enable_obj, "PresDet", FLAG(w, PCI_EXP_SLTCTL_PRSD));
+ info_obj_add_flag(slt_ctl_enable_obj, "CmdCplt", FLAG(w, PCI_EXP_SLTCTL_CMDC));
+ info_obj_add_flag(slt_ctl_enable_obj, "HPIrq", FLAG(w, PCI_EXP_SLTCTL_HPIE));
+ info_obj_add_flag(slt_ctl_enable_obj, "LinkChg", FLAG(w, PCI_EXP_SLTCTL_LLCHG));
+ slt_ctl_control_obj = info_obj_create_in_obj(slt_ctl_obj, "Control");
+ info_obj_add_str(slt_ctl_control_obj, "AttnInd", indicator((w & PCI_EXP_SLTCTL_ATNI) >> 6));
+ info_obj_add_str(slt_ctl_control_obj, "PwrInd", indicator((w & PCI_EXP_SLTCTL_PWRI) >> 8));
+ info_obj_add_flag(slt_ctl_control_obj, "Power", FLAG(w, PCI_EXP_SLTCTL_PWRC));
+ info_obj_add_flag(slt_ctl_control_obj, "Inrelock", FLAG(w, PCI_EXP_SLTCTL_INTERLOCK));
+
+ w = get_conf_word(d, where + PCI_EXP_SLTSTA);
+ slt_sta_obj = info_obj_create_in_obj(exp_obj, "SltSta");
+ slt_sta_status_obj = info_obj_create_in_obj(slt_sta_obj, "Status");
+ info_obj_add_flag(slt_sta_status_obj, "AttnBtn", FLAG(w, PCI_EXP_SLTSTA_ATNB));
+ info_obj_add_flag(slt_sta_status_obj, "PowerFlt", FLAG(w, PCI_EXP_SLTSTA_PWRF));
+ info_obj_add_flag(slt_sta_status_obj, "MRL", FLAG(w, PCI_EXP_SLTSTA_MRL_ST));
+ info_obj_add_flag(slt_sta_status_obj, "CmdCplt", FLAG(w, PCI_EXP_SLTSTA_CMDC));
+ info_obj_add_flag(slt_sta_status_obj, "PresDet", FLAG(w, PCI_EXP_SLTSTA_PRES));
+ info_obj_add_flag(slt_sta_status_obj, "Interlock", FLAG(w, PCI_EXP_SLTSTA_INTERLOCK));
+ slt_sta_changed_obj = info_obj_create_in_obj(slt_sta_obj, "Changed");
+ info_obj_add_flag(slt_sta_changed_obj, "MRL", FLAG(w, PCI_EXP_SLTSTA_MRLS));
+ info_obj_add_flag(slt_sta_changed_obj, "PresDet", FLAG(w, PCI_EXP_SLTSTA_PRSD));
+ info_obj_add_flag(slt_sta_changed_obj, "LinkState", FLAG(w, PCI_EXP_SLTSTA_LLCHG));
+}
+
static void cap_express_root(struct device *d, int where)
{
u32 w = get_conf_word(d, where + PCI_EXP_RTCTL);
@@ -886,6 +1061,29 @@ static void cap_express_root(struct device *d, int where)
FLAG(w, PCI_EXP_RTSTA_PME_PENDING));
}
+static void fill_info_express_root(struct info_obj *exp_obj, struct device *d, int where)
+{
+ struct info_obj *root_cap_obj, *root_ctl_obj, *root_sta_obj;
+
+ u32 w = get_conf_word(d, where + PCI_EXP_RTCTL);
+ root_ctl_obj = info_obj_create_in_obj(exp_obj, "RootCtl");
+ info_obj_add_flag(root_ctl_obj, "ErrCorrectable", FLAG(w, PCI_EXP_RTCTL_SECEE));
+ info_obj_add_flag(root_ctl_obj, "ErrNon-Fatal", FLAG(w, PCI_EXP_RTCTL_SENFEE));
+ info_obj_add_flag(root_ctl_obj, "ErrFatal", FLAG(w, PCI_EXP_RTCTL_SEFEE));
+ info_obj_add_flag(root_ctl_obj, "PMEIntEna", FLAG(w, PCI_EXP_RTCTL_PMEIE));
+ info_obj_add_flag(root_ctl_obj, "CRSVisible", FLAG(w, PCI_EXP_RTCTL_CRSVIS));
+
+ w = get_conf_word(d, where + PCI_EXP_RTCAP);
+ root_cap_obj = info_obj_create_in_obj(exp_obj, "RootCap");
+ info_obj_add_flag(root_cap_obj, "CRSVisible", FLAG(w, PCI_EXP_RTCAP_CRSVIS));
+
+ w = get_conf_long(d, where + PCI_EXP_RTSTA);
+ root_sta_obj = info_obj_create_in_obj(exp_obj, "RootSta");
+ info_obj_add_fmt_str(root_sta_obj, "PME-ReqID", 5, "%04x", w & PCI_EXP_RTSTA_PME_REQID);
+ info_obj_add_flag(root_sta_obj, "PMEStatus", FLAG(w, PCI_EXP_RTSTA_PME_STATUS));
+ info_obj_add_flag(root_sta_obj, "PMEPending", FLAG(w, PCI_EXP_RTSTA_PME_PENDING));
+}
+
static const char *cap_express_dev2_timeout_range(int type)
{
/* Decode Completion Timeout Ranges. */
@@ -1046,6 +1244,61 @@ static void cap_express_dev2(struct device *d, int where, int type)
}
}
+static void fill_info_express_dev2(struct info_obj *exp_obj, struct device *d, int where, int type)
+{
+ u32 l;
+ u16 w;
+ int has_mem_bar = device_has_memory_space_bar(d);
+ struct info_obj *dev_cap2_obj, *dev_ctl2_obj;
+
+ l = get_conf_long(d, where + PCI_EXP_DEVCAP2);
+ dev_cap2_obj = info_obj_create_in_obj(exp_obj, "DevCap2");
+ info_obj_add_str(dev_cap2_obj, "Completion-Timeout", cap_express_dev2_timeout_range(PCI_EXP_DEV2_TIMEOUT_RANGE(l)));
+ info_obj_add_flag(dev_cap2_obj, "TimeoutDis", FLAG(l, PCI_EXP_DEV2_TIMEOUT_DIS));
+ info_obj_add_flag(dev_cap2_obj, "LTR", FLAG(l, PCI_EXP_DEVCAP2_LTR));
+ info_obj_add_str(dev_cap2_obj, "OBFF", cap_express_devcap2_obff(PCI_EXP_DEVCAP2_OBFF(l)));
+ if (type == PCI_EXP_TYPE_ROOT_PORT || type == PCI_EXP_TYPE_DOWNSTREAM)
+ info_obj_add_flag(dev_cap2_obj, "ARIFwd", FLAG(l, PCI_EXP_DEV2_ARI));
+ if (type == PCI_EXP_TYPE_ROOT_PORT || type == PCI_EXP_TYPE_UPSTREAM ||
+ type == PCI_EXP_TYPE_DOWNSTREAM || has_mem_bar)
+ {
+ struct info_obj *dev_cap2_aoc_obj = info_obj_create_in_obj(dev_cap2_obj, "AtomicOpsCap");
+
+ if (type == PCI_EXP_TYPE_ROOT_PORT || type == PCI_EXP_TYPE_UPSTREAM ||
+ type == PCI_EXP_TYPE_DOWNSTREAM)
+ info_obj_add_flag(dev_cap2_aoc_obj, "Routing", FLAG(l, PCI_EXP_DEVCAP2_ATOMICOP_ROUTING));
+ if (type == PCI_EXP_TYPE_ROOT_PORT || has_mem_bar)
+ {
+ info_obj_add_flag(dev_cap2_aoc_obj, "32bit", FLAG(l, PCI_EXP_DEVCAP2_32BIT_ATOMICOP_COMP));
+ info_obj_add_flag(dev_cap2_aoc_obj, "64bit", FLAG(l, PCI_EXP_DEVCAP2_64BIT_ATOMICOP_COMP));
+ info_obj_add_flag(dev_cap2_aoc_obj, "128bitCAS", FLAG(l, PCI_EXP_DEVCAP2_128BIT_CAS_COMP));
+ }
+ }
+
+ w = get_conf_word(d, where + PCI_EXP_DEVCTL2);
+ dev_ctl2_obj = info_obj_create_in_obj(exp_obj, "DevCtl2");
+ info_obj_add_str(dev_ctl2_obj, "Completion-Timeout", cap_express_dev2_timeout_value(PCI_EXP_DEV2_TIMEOUT_VALUE(w)));
+ info_obj_add_flag(dev_ctl2_obj, "TimeoutDis", FLAG(w, PCI_EXP_DEV2_TIMEOUT_DIS));
+ info_obj_add_flag(dev_ctl2_obj, "LTR", FLAG(w, PCI_EXP_DEV2_LTR));
+ info_obj_add_str(dev_ctl2_obj, "OBFF", cap_express_devctl2_obff(PCI_EXP_DEV2_OBFF(w)));
+ if (type == PCI_EXP_TYPE_ROOT_PORT || type == PCI_EXP_TYPE_DOWNSTREAM)
+ info_obj_add_flag(dev_ctl2_obj, "ARIFwd", FLAG(w, PCI_EXP_DEV2_ARI));
+ if (type == PCI_EXP_TYPE_ROOT_PORT || type == PCI_EXP_TYPE_UPSTREAM ||
+ type == PCI_EXP_TYPE_DOWNSTREAM || type == PCI_EXP_TYPE_ENDPOINT ||
+ type == PCI_EXP_TYPE_ROOT_INT_EP || type == PCI_EXP_TYPE_LEG_END)
+ {
+ struct info_obj *dev_ctl2_aoc_obj = info_obj_create_in_obj(dev_ctl2_obj, "AtomicOpsCtl");
+
+ if (type == PCI_EXP_TYPE_ROOT_PORT || type == PCI_EXP_TYPE_ENDPOINT ||
+ type == PCI_EXP_TYPE_ROOT_INT_EP || type == PCI_EXP_TYPE_LEG_END)
+ info_obj_add_flag(dev_ctl2_aoc_obj, "ReqEn", FLAG(w, PCI_EXP_DEV2_ATOMICOP_REQUESTER_EN));
+ if (type == PCI_EXP_TYPE_ROOT_PORT || type == PCI_EXP_TYPE_UPSTREAM ||
+ type == PCI_EXP_TYPE_DOWNSTREAM)
+ info_obj_add_flag(dev_ctl2_aoc_obj, "EgressBlck", FLAG(w, PCI_EXP_DEV2_ATOMICOP_EGRESS_BLOCK));
+ }
+}
+
+
static const char *cap_express_link2_speed(int type)
{
switch (type)
@@ -1129,6 +1382,38 @@ static void cap_express_link2(struct device *d, int where, int type)
FLAG(w, PCI_EXP_LINKSTA2_EQU_REQ));
}
+static void fill_info_express_link2(struct info_obj *exp_obj, struct device *d, int where, int type)
+{
+ u16 w;
+ struct info_obj *lnk_sta2_obj;
+
+ if (!((type == PCI_EXP_TYPE_ENDPOINT || type == PCI_EXP_TYPE_LEG_END) &&
+ (d->dev->dev != 0 || d->dev->func != 0))) {
+ struct info_obj *lnk_ctl2_obj;
+
+ w = get_conf_word(d, where + PCI_EXP_LNKCTL2);
+ lnk_ctl2_obj = info_obj_create_in_obj(exp_obj, "LnkCtl2");
+ info_obj_add_str(lnk_ctl2_obj, "Target-Link-Speed", cap_express_link2_speed(PCI_EXP_LNKCTL2_SPEED(w)));
+ info_obj_add_flag(lnk_ctl2_obj, "EnterCompliance", FLAG(w, PCI_EXP_LNKCTL2_CMPLNC));
+ info_obj_add_flag(lnk_ctl2_obj, "SpeedDis", FLAG(w, PCI_EXP_LNKCTL2_SPEED_DIS));
+ if (type == PCI_EXP_TYPE_DOWNSTREAM)
+ info_obj_add_str(lnk_ctl2_obj, "Selectable-De-emphasis", cap_express_link2_deemphasis(PCI_EXP_LNKCTL2_DEEMPHASIS(w)));
+ info_obj_add_str(lnk_ctl2_obj, "Transmit-Margin", cap_express_link2_transmargin(PCI_EXP_LNKCTL2_MARGIN(w)));
+ info_obj_add_flag(lnk_ctl2_obj, "EnterModifiedCompliance", FLAG(w, PCI_EXP_LNKCTL2_MOD_CMPLNC));
+ info_obj_add_flag(lnk_ctl2_obj, "ComplianceSOS", FLAG(w, PCI_EXP_LNKCTL2_CMPLNC_SOS));
+ info_obj_add_str(lnk_ctl2_obj, "Compliance-De-emphasis", cap_express_link2_deemphasis(PCI_EXP_LNKCTL2_COM_DEEMPHASIS(w)));
+ }
+
+ w = get_conf_word(d, where + PCI_EXP_LNKSTA2);
+ lnk_sta2_obj = info_obj_create_in_obj(exp_obj, "LnkSta2");
+ info_obj_add_str(lnk_sta2_obj, "Current-De-emphasis-Level", cap_express_link2_deemphasis(PCI_EXP_LINKSTA2_DEEMPHASIS(w)));
+ info_obj_add_flag(lnk_sta2_obj, "EqualizationComplete", FLAG(w, PCI_EXP_LINKSTA2_EQU_COMP));
+ info_obj_add_flag(lnk_sta2_obj, "EqualizationPhase1", FLAG(w, PCI_EXP_LINKSTA2_EQU_PHASE1));
+ info_obj_add_flag(lnk_sta2_obj, "EqualizationPhase2", FLAG(w, PCI_EXP_LINKSTA2_EQU_PHASE2));
+ info_obj_add_flag(lnk_sta2_obj, "EqualizationPhase3", FLAG(w, PCI_EXP_LINKSTA2_EQU_PHASE3));
+ info_obj_add_flag(lnk_sta2_obj, "LinkEqualizationRequest", FLAG(w, PCI_EXP_LINKSTA2_EQU_REQ));
+}
+
static void cap_express_slot2(struct device *d UNUSED, int where UNUSED)
{
/* No capabilities that require this field in PCIe rev2.0 spec. */
@@ -1220,6 +1505,94 @@ cap_express(struct device *d, int where, int cap)
return type;
}
+static int
+fill_info_cap_express(struct info_obj *caps_obj, struct device *d, int where, int cap)
+{
+ int type = (cap & PCI_EXP_FLAGS_TYPE) >> 4;
+ int size;
+ int slot = 0;
+ int link = 1;
+ struct info_obj *express_obj = info_obj_create_in_obj(caps_obj, "express");
+ char buf[128];
+
+ if (verbose >= 2)
+ info_obj_add_fmt_buf_str(express_obj, "ver", buf, sizeof(buf), "%d", cap & PCI_EXP_FLAGS_VERS);
+ switch (type)
+ {
+ case PCI_EXP_TYPE_ENDPOINT:
+ snprintf(buf, sizeof(buf), "Endpoint");
+ break;
+ case PCI_EXP_TYPE_LEG_END:
+ snprintf(buf, sizeof(buf), "Legacy Endpoint");
+ break;
+ case PCI_EXP_TYPE_ROOT_PORT:
+ slot = cap & PCI_EXP_FLAGS_SLOT;
+ snprintf(buf, sizeof(buf), "Root Port (Slot%c)", FLAG(cap, PCI_EXP_FLAGS_SLOT));
+ break;
+ case PCI_EXP_TYPE_UPSTREAM:
+ snprintf(buf, sizeof(buf), "Upstream Port");
+ break;
+ case PCI_EXP_TYPE_DOWNSTREAM:
+ slot = cap & PCI_EXP_FLAGS_SLOT;
+ snprintf(buf, sizeof(buf), "Downstream Port (Slot%c)", FLAG(cap, PCI_EXP_FLAGS_SLOT));
+ break;
+ case PCI_EXP_TYPE_PCI_BRIDGE:
+ snprintf(buf, sizeof(buf), "PCI-Express to PCI/PCI-X Bridge");
+ break;
+ case PCI_EXP_TYPE_PCIE_BRIDGE:
+ slot = cap & PCI_EXP_FLAGS_SLOT;
+ snprintf(buf, sizeof(buf), "PCI/PCI-X to PCI-Express Bridge (Slot%c)",
+ FLAG(cap, PCI_EXP_FLAGS_SLOT));
+ break;
+ case PCI_EXP_TYPE_ROOT_INT_EP:
+ link = 0;
+ snprintf(buf, sizeof(buf), "Root Complex Integrated Endpoint");
+ break;
+ case PCI_EXP_TYPE_ROOT_EC:
+ link = 0;
+ snprintf(buf, sizeof(buf), "Root Complex Event Collector");
+ break;
+ default:
+ snprintf(buf, sizeof(buf), "Unknown type %d", type);
+ }
+ info_obj_add_str(express_obj, "type", buf);
+ info_obj_add_fmt_buf_str(express_obj, "MSI", buf, sizeof(buf), "%02x", (cap & PCI_EXP_FLAGS_IRQ) >> 9);
+
+ if (verbose < 2)
+ return type;
+
+ size = 16;
+ if (slot)
+ size = 24;
+ if (type == PCI_EXP_TYPE_ROOT_PORT)
+ size = 32;
+ if (!config_fetch(d, where + PCI_EXP_DEVCAP, size))
+ return type;
+
+ fill_info_express_dev(express_obj, d, where, type);
+ if (link)
+ fill_info_express_link(express_obj, d, where, type);
+ if (slot)
+ fill_info_express_slot(express_obj, d, where);
+ if (type == PCI_EXP_TYPE_ROOT_PORT)
+ fill_info_express_root(express_obj, d, where);
+
+ if ((cap & PCI_EXP_FLAGS_VERS) < 2)
+ return type;
+
+ size = 16;
+ if (slot)
+ size = 24;
+ if (!config_fetch(d, where + PCI_EXP_DEVCAP2, size))
+ return type;
+
+ fill_info_express_dev2(express_obj, d, where, type);
+ if (link)
+ fill_info_express_link2(express_obj, d, where, type);
+
+ return type;
+}
+
static void
cap_msix(struct device *d, int where, int cap)
{
@@ -1577,3 +1950,47 @@ show_caps(struct device *d, int where)
if (can_have_ext_caps)
show_ext_caps(d, type);
}
+
+void
+fill_info_caps(struct info_obj *dev_obj, struct device *d, int where)
+{
+ struct info_obj *caps_obj = info_obj_create_in_obj(dev_obj, "capabilities");
+
+ if (get_conf_word(d, PCI_STATUS) & PCI_STATUS_CAP_LIST)
+ {
+ byte been_there[256];
+ where = get_conf_byte(d, where) & ~3;
+ memset(been_there, 0, 256);
+ while (where)
+ {
+ int id, next, cap;
+ if (!config_fetch(d, where, 4))
+ {
+ fputs("<access denied>", stderr);
+ break;
+ }
+ id = get_conf_byte(d, where + PCI_CAP_LIST_ID);
+ next = get_conf_byte(d, where + PCI_CAP_LIST_NEXT) & ~3;
+ cap = get_conf_word(d, where + PCI_CAP_FLAGS);
+ if (been_there[where]++)
+ {
+ fprintf(stderr, "<chain looped>\n");
+ break;
+ }
+ if (id == 0xff)
+ {
+ fprintf(stderr, "<chain broken>\n");
+ break;
+ }
+ switch (id)
+ {
+ case PCI_CAP_ID_EXP:
+ fill_info_cap_express(caps_obj, d, where, cap);
+ break;
+ default:
+ break;
+ }
+ where = next;
+ }
+ }
+}
diff --git a/lspci.c b/lspci.c
index ae50a85..d5a02e8 100644
--- a/lspci.c
+++ b/lspci.c
@@ -1250,6 +1250,7 @@ fill_info_htype0(struct info_obj *dev_obj, struct device *d)
fill_info_bases(htype0_obj, d, 6);
fill_info_rom(htype0_obj, d, PCI_ROM_ADDRESS);
+ fill_info_caps(dev_obj, d, PCI_CAPABILITY_LIST);
}
static void
@@ -1359,6 +1360,8 @@ fill_info_htype1(struct info_obj *dev_obj, struct device *d)
info_obj_add_flag(bridgectl_obj, "DiscTmrStat", FLAG(brc, PCI_BRIDGE_CTL_DISCARD_TIMER_STATUS));
info_obj_add_flag(bridgectl_obj, "DiscTmrSERREn", FLAG(brc, PCI_BRIDGE_CTL_DISCARD_TIMER_SERR_EN));
}
+
+ fill_info_caps(dev_obj, d, PCI_CAPABILITY_LIST);
}
static void
@@ -1446,6 +1449,8 @@ fill_info_htype2(struct info_obj *dev_obj, struct device *d)
exca = get_conf_word(d, PCI_CB_LEGACY_MODE_BASE);
if (exca)
info_obj_add_fmt_buf_str(htype2_obj, "exca", buf, sizeof(buf), "%04x", exca);
+
+ fill_info_caps(dev_obj, d, PCI_CB_CAPABILITY_LIST);
}
static void
diff --git a/lspci.h b/lspci.h
index fc23d32..1646f99 100644
--- a/lspci.h
+++ b/lspci.h
@@ -58,22 +58,6 @@ void get_subid(struct device *d, word *subvp, word *subdp);
#define BITS(x,at,width) (((x) >> (at)) & ((1 << (width)) - 1))
#define TABLE(tab,x,buf) ((x) < sizeof(tab)/sizeof((tab)[0]) ? (tab)[x] : (sprintf((buf), "??%d", (x)), (buf)))
-/* ls-vpd.c */
-
-void cap_vpd(struct device *d);
-
-/* ls-caps.c */
-
-void show_caps(struct device *d, int where);
-
-/* ls-ecaps.c */
-
-void show_ext_caps(struct device *d, int type);
-
-/* ls-caps-vendor.c */
-
-void show_vendor_caps(struct device *d, int where, int cap);
-
/* ls-info.c */
enum info_val_type {
@@ -128,6 +112,23 @@ struct info_list *info_list_create_in_obj(struct info_obj *parent_obj, char *key
void info_list_add_str(struct info_list *list, const char *str);
void info_list_add_obj(struct info_list *list, struct info_obj *obj);
+/* ls-vpd.c */
+
+void cap_vpd(struct device *d);
+
+/* ls-caps.c */
+
+void show_caps(struct device *d, int where);
+void fill_info_caps(struct info_obj *dev_obj, struct device *d, int where);
+
+/* ls-ecaps.c */
+
+void show_ext_caps(struct device *d, int type);
+
+/* ls-caps-vendor.c */
+
+void show_vendor_caps(struct device *d, int where, int cap);
+
/* ls-kernel.c */
void show_kernel_machine(struct device *d UNUSED);
--
2.14.3
^ permalink raw reply related [flat|nested] 5+ messages in thread* Re: [PATCH v3 0/3] lspci: Add support of JSON output format
2018-02-18 23:12 [PATCH v3 0/3] lspci: Add support of JSON output format Viktor Prutyanov
` (2 preceding siblings ...)
2018-02-18 23:12 ` [PATCH v3 3/3] lspci: Add JSON PCI Express capabilities Viktor Prutyanov
@ 2018-03-11 15:03 ` viktor.prutyanov
3 siblings, 0 replies; 5+ messages in thread
From: viktor.prutyanov @ 2018-03-11 15:03 UTC (permalink / raw)
To: linux-pci, mj
=D0=92 Mon, 19 Feb 2018 02:12:48 +0300
Viktor Prutyanov <viktor.prutyanov@virtuozzo.com> =D0=BF=D0=B8=D1=88=D0=B5=
=D1=82:
ping
> This collection of patches adds support of printing PCI info in JSON
> format.
>=20
> 1st patch adds skeleton for creating structured object which can be
> printed in JSON or another structured format.
> 2nd patch adds output of general PCI info which prints with -m, -k,
> -[v]+ options, but without capabilities.
> 3rd patch adds PCI-E capability.
>=20
> For example, we can easily, using jq utility, check speed of a device:
> lspci -Jvv | jq -r '.[][] | select(.Device =3D=3D "GK107M [GeForce GT
> 750M]") | .capabilities.express | {s1:.LnkCap.Speed,
> s2:.LnkSta.Speed, w1 :.LnkCap.Width, w2:.LnkSta.Width }'
>=20
> {
> "s1": "8GT/s",
> "s2": "5GT/s",
> "w1": "x16",
> "w2": "x4"
> }
>=20
> Viktor Prutyanov (3):
> lspci: Add printing info in JSON format
> lspci: Add PCI info output in JSON format
> lspci: Add JSON PCI Express capabilities
>=20
> Makefile | 3 +-
> common.c | 2 +-
> ls-caps.c | 417 +++++++++++++++++++++++++++++++++++
> ls-info.c | 328 ++++++++++++++++++++++++++++
> ls-kernel.c | 24 +++
> lspci.c | 706
> +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
> lspci.h | 56 +++++ lspci.man | 3 +
> pciutils.h | 2 +-
> 9 files changed, 1537 insertions(+), 4 deletions(-)
> create mode 100644 ls-info.c
>=20
^ permalink raw reply [flat|nested] 5+ messages in thread