From ce6a90027c10f970f872de5db0294f9e3e969f1c Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Fri, 8 Sep 2017 10:23:11 -0500 Subject: platform/x86: Add driver to force WMI Thunderbolt controller power status Current implementations of Intel Thunderbolt controllers will go into a low power mode when not in use. Many machines containing these controllers also have a GPIO wired up that can force the controller awake. This is offered via a ACPI-WMI interface intended to be manipulated by a userspace utility. This mechanism is provided by Intel to OEMs to include in BIOS. It uses an industry wide GUID that is populated in a separate _WDG entry with no binary MOF. This interface allows software such as fwupd to wake up thunderbolt controllers to query the firmware version or flash new firmware. Signed-off-by: Mario Limonciello Reviewed-by: Mika Westerberg Reviewed-by: Yehezkel Bernat Signed-off-by: Darren Hart (VMware) [andy fixed merge conflicts and bump kernel version for ABI] Signed-off-by: Andy Shevchenko --- .../ABI/testing/sysfs-platform-intel-wmi-thunderbolt | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 Documentation/ABI/testing/sysfs-platform-intel-wmi-thunderbolt (limited to 'Documentation/ABI/testing') diff --git a/Documentation/ABI/testing/sysfs-platform-intel-wmi-thunderbolt b/Documentation/ABI/testing/sysfs-platform-intel-wmi-thunderbolt new file mode 100644 index 000000000000..8af65059d519 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-platform-intel-wmi-thunderbolt @@ -0,0 +1,11 @@ +What: /sys/devices/platform//force_power +Date: September 2017 +KernelVersion: 4.15 +Contact: "Mario Limonciello" +Description: + Modify the platform force power state, influencing + Thunderbolt controllers to turn on or off when no + devices are connected (write-only) + There are two available states: + * 0 -> Force power disabled + * 1 -> Force power enabled -- cgit v1.2.3-59-g8ed1b From d1ff70241a275133e1a0258b7c23588b122276c8 Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Mon, 2 Oct 2017 13:38:34 +0300 Subject: thunderbolt: Add support for XDomain discovery protocol When two hosts are connected over a Thunderbolt cable, there is a protocol they can use to communicate capabilities supported by the host. The discovery protocol uses automatically configured control channel (ring 0) and is build on top of request/response transactions using special XDomain primitives provided by the Thunderbolt base protocol. The capabilities consists of a root directory block of basic properties used for identification of the host, and then there can be zero or more directories each describing a Thunderbolt service and its capabilities. Once both sides have discovered what is supported the two hosts can setup high-speed DMA paths and transfer data to the other side using whatever protocol was agreed based on the properties. The software protocol used to communicate which DMA paths to enable is service specific. This patch adds support for the XDomain discovery protocol to the Thunderbolt bus. We model each remote host connection as a Linux XDomain device. For each Thunderbolt service found supported on the XDomain device, we create Linux Thunderbolt service device which Thunderbolt service drivers can then bind to based on the protocol identification information retrieved from the property directory describing the service. This code is based on the work done by Amir Levy and Michael Jamet. Signed-off-by: Michael Jamet Signed-off-by: Mika Westerberg Reviewed-by: Yehezkel Bernat Reviewed-by: Andy Shevchenko Signed-off-by: David S. Miller --- Documentation/ABI/testing/sysfs-bus-thunderbolt | 48 + drivers/thunderbolt/Makefile | 2 +- drivers/thunderbolt/ctl.c | 11 +- drivers/thunderbolt/ctl.h | 2 +- drivers/thunderbolt/domain.c | 197 ++- drivers/thunderbolt/icm.c | 218 +++- drivers/thunderbolt/nhi.h | 2 + drivers/thunderbolt/switch.c | 7 +- drivers/thunderbolt/tb.h | 39 +- drivers/thunderbolt/tb_msgs.h | 123 ++ drivers/thunderbolt/xdomain.c | 1576 +++++++++++++++++++++++ include/linux/mod_devicetable.h | 26 + include/linux/thunderbolt.h | 242 ++++ scripts/mod/devicetable-offsets.c | 7 + scripts/mod/file2alias.c | 25 + 15 files changed, 2507 insertions(+), 18 deletions(-) create mode 100644 drivers/thunderbolt/xdomain.c (limited to 'Documentation/ABI/testing') diff --git a/Documentation/ABI/testing/sysfs-bus-thunderbolt b/Documentation/ABI/testing/sysfs-bus-thunderbolt index 392bef5bd399..93798c02e28b 100644 --- a/Documentation/ABI/testing/sysfs-bus-thunderbolt +++ b/Documentation/ABI/testing/sysfs-bus-thunderbolt @@ -110,3 +110,51 @@ Description: When new NVM image is written to the non-active NVM is directly the status value from the DMA configuration based mailbox before the device is power cycled. Writing 0 here clears the status. + +What: /sys/bus/thunderbolt/devices/./key +Date: Jan 2018 +KernelVersion: 4.15 +Contact: thunderbolt-software@lists.01.org +Description: This contains name of the property directory the XDomain + service exposes. This entry describes the protocol in + question. Following directories are already reserved by + the Apple XDomain specification: + + network: IP/ethernet over Thunderbolt + targetdm: Target disk mode protocol over Thunderbolt + extdisp: External display mode protocol over Thunderbolt + +What: /sys/bus/thunderbolt/devices/./modalias +Date: Jan 2018 +KernelVersion: 4.15 +Contact: thunderbolt-software@lists.01.org +Description: Stores the same MODALIAS value emitted by uevent for + the XDomain service. Format: tbtsvc:kSpNvNrN + +What: /sys/bus/thunderbolt/devices/./prtcid +Date: Jan 2018 +KernelVersion: 4.15 +Contact: thunderbolt-software@lists.01.org +Description: This contains XDomain protocol identifier the XDomain + service supports. + +What: /sys/bus/thunderbolt/devices/./prtcvers +Date: Jan 2018 +KernelVersion: 4.15 +Contact: thunderbolt-software@lists.01.org +Description: This contains XDomain protocol version the XDomain + service supports. + +What: /sys/bus/thunderbolt/devices/./prtcrevs +Date: Jan 2018 +KernelVersion: 4.15 +Contact: thunderbolt-software@lists.01.org +Description: This contains XDomain software version the XDomain + service supports. + +What: /sys/bus/thunderbolt/devices/./prtcstns +Date: Jan 2018 +KernelVersion: 4.15 +Contact: thunderbolt-software@lists.01.org +Description: This contains XDomain service specific settings as + bitmask. Format: %x diff --git a/drivers/thunderbolt/Makefile b/drivers/thunderbolt/Makefile index 7afd21f5383a..f2f0de27252b 100644 --- a/drivers/thunderbolt/Makefile +++ b/drivers/thunderbolt/Makefile @@ -1,3 +1,3 @@ obj-${CONFIG_THUNDERBOLT} := thunderbolt.o thunderbolt-objs := nhi.o ctl.o tb.o switch.o cap.o path.o tunnel_pci.o eeprom.o -thunderbolt-objs += domain.o dma_port.o icm.o property.o +thunderbolt-objs += domain.o dma_port.o icm.o property.o xdomain.o diff --git a/drivers/thunderbolt/ctl.c b/drivers/thunderbolt/ctl.c index e6a4c9458c76..46e393c5fd1d 100644 --- a/drivers/thunderbolt/ctl.c +++ b/drivers/thunderbolt/ctl.c @@ -368,10 +368,10 @@ static int tb_ctl_tx(struct tb_ctl *ctl, const void *data, size_t len, /** * tb_ctl_handle_event() - acknowledge a plug event, invoke ctl->callback */ -static void tb_ctl_handle_event(struct tb_ctl *ctl, enum tb_cfg_pkg_type type, +static bool tb_ctl_handle_event(struct tb_ctl *ctl, enum tb_cfg_pkg_type type, struct ctl_pkg *pkg, size_t size) { - ctl->callback(ctl->callback_data, type, pkg->buffer, size); + return ctl->callback(ctl->callback_data, type, pkg->buffer, size); } static void tb_ctl_rx_submit(struct ctl_pkg *pkg) @@ -444,6 +444,8 @@ static void tb_ctl_rx_callback(struct tb_ring *ring, struct ring_frame *frame, break; case TB_CFG_PKG_EVENT: + case TB_CFG_PKG_XDOMAIN_RESP: + case TB_CFG_PKG_XDOMAIN_REQ: if (*(__be32 *)(pkg->buffer + frame->size) != crc32) { tb_ctl_err(pkg->ctl, "RX: checksum mismatch, dropping packet\n"); @@ -451,8 +453,9 @@ static void tb_ctl_rx_callback(struct tb_ring *ring, struct ring_frame *frame, } /* Fall through */ case TB_CFG_PKG_ICM_EVENT: - tb_ctl_handle_event(pkg->ctl, frame->eof, pkg, frame->size); - goto rx; + if (tb_ctl_handle_event(pkg->ctl, frame->eof, pkg, frame->size)) + goto rx; + break; default: break; diff --git a/drivers/thunderbolt/ctl.h b/drivers/thunderbolt/ctl.h index d0f21e1e0b8b..85c49dd301ea 100644 --- a/drivers/thunderbolt/ctl.h +++ b/drivers/thunderbolt/ctl.h @@ -16,7 +16,7 @@ /* control channel */ struct tb_ctl; -typedef void (*event_cb)(void *data, enum tb_cfg_pkg_type type, +typedef bool (*event_cb)(void *data, enum tb_cfg_pkg_type type, const void *buf, size_t size); struct tb_ctl *tb_ctl_alloc(struct tb_nhi *nhi, event_cb cb, void *cb_data); diff --git a/drivers/thunderbolt/domain.c b/drivers/thunderbolt/domain.c index 9f2dcd48974d..9b90115319ce 100644 --- a/drivers/thunderbolt/domain.c +++ b/drivers/thunderbolt/domain.c @@ -20,6 +20,98 @@ static DEFINE_IDA(tb_domain_ida); +static bool match_service_id(const struct tb_service_id *id, + const struct tb_service *svc) +{ + if (id->match_flags & TBSVC_MATCH_PROTOCOL_KEY) { + if (strcmp(id->protocol_key, svc->key)) + return false; + } + + if (id->match_flags & TBSVC_MATCH_PROTOCOL_ID) { + if (id->protocol_id != svc->prtcid) + return false; + } + + if (id->match_flags & TBSVC_MATCH_PROTOCOL_VERSION) { + if (id->protocol_version != svc->prtcvers) + return false; + } + + if (id->match_flags & TBSVC_MATCH_PROTOCOL_VERSION) { + if (id->protocol_revision != svc->prtcrevs) + return false; + } + + return true; +} + +static const struct tb_service_id *__tb_service_match(struct device *dev, + struct device_driver *drv) +{ + struct tb_service_driver *driver; + const struct tb_service_id *ids; + struct tb_service *svc; + + svc = tb_to_service(dev); + if (!svc) + return NULL; + + driver = container_of(drv, struct tb_service_driver, driver); + if (!driver->id_table) + return NULL; + + for (ids = driver->id_table; ids->match_flags != 0; ids++) { + if (match_service_id(ids, svc)) + return ids; + } + + return NULL; +} + +static int tb_service_match(struct device *dev, struct device_driver *drv) +{ + return !!__tb_service_match(dev, drv); +} + +static int tb_service_probe(struct device *dev) +{ + struct tb_service *svc = tb_to_service(dev); + struct tb_service_driver *driver; + const struct tb_service_id *id; + + driver = container_of(dev->driver, struct tb_service_driver, driver); + id = __tb_service_match(dev, &driver->driver); + + return driver->probe(svc, id); +} + +static int tb_service_remove(struct device *dev) +{ + struct tb_service *svc = tb_to_service(dev); + struct tb_service_driver *driver; + + driver = container_of(dev->driver, struct tb_service_driver, driver); + if (driver->remove) + driver->remove(svc); + + return 0; +} + +static void tb_service_shutdown(struct device *dev) +{ + struct tb_service_driver *driver; + struct tb_service *svc; + + svc = tb_to_service(dev); + if (!svc || !dev->driver) + return; + + driver = container_of(dev->driver, struct tb_service_driver, driver); + if (driver->shutdown) + driver->shutdown(svc); +} + static const char * const tb_security_names[] = { [TB_SECURITY_NONE] = "none", [TB_SECURITY_USER] = "user", @@ -52,6 +144,10 @@ static const struct attribute_group *domain_attr_groups[] = { struct bus_type tb_bus_type = { .name = "thunderbolt", + .match = tb_service_match, + .probe = tb_service_probe, + .remove = tb_service_remove, + .shutdown = tb_service_shutdown, }; static void tb_domain_release(struct device *dev) @@ -128,17 +224,26 @@ err_free: return NULL; } -static void tb_domain_event_cb(void *data, enum tb_cfg_pkg_type type, +static bool tb_domain_event_cb(void *data, enum tb_cfg_pkg_type type, const void *buf, size_t size) { struct tb *tb = data; if (!tb->cm_ops->handle_event) { tb_warn(tb, "domain does not have event handler\n"); - return; + return true; } - tb->cm_ops->handle_event(tb, type, buf, size); + switch (type) { + case TB_CFG_PKG_XDOMAIN_REQ: + case TB_CFG_PKG_XDOMAIN_RESP: + return tb_xdomain_handle_request(tb, type, buf, size); + + default: + tb->cm_ops->handle_event(tb, type, buf, size); + } + + return true; } /** @@ -443,9 +548,92 @@ int tb_domain_disconnect_pcie_paths(struct tb *tb) return tb->cm_ops->disconnect_pcie_paths(tb); } +/** + * tb_domain_approve_xdomain_paths() - Enable DMA paths for XDomain + * @tb: Domain enabling the DMA paths + * @xd: XDomain DMA paths are created to + * + * Calls connection manager specific method to enable DMA paths to the + * XDomain in question. + * + * Return: 0% in case of success and negative errno otherwise. In + * particular returns %-ENOTSUPP if the connection manager + * implementation does not support XDomains. + */ +int tb_domain_approve_xdomain_paths(struct tb *tb, struct tb_xdomain *xd) +{ + if (!tb->cm_ops->approve_xdomain_paths) + return -ENOTSUPP; + + return tb->cm_ops->approve_xdomain_paths(tb, xd); +} + +/** + * tb_domain_disconnect_xdomain_paths() - Disable DMA paths for XDomain + * @tb: Domain disabling the DMA paths + * @xd: XDomain whose DMA paths are disconnected + * + * Calls connection manager specific method to disconnect DMA paths to + * the XDomain in question. + * + * Return: 0% in case of success and negative errno otherwise. In + * particular returns %-ENOTSUPP if the connection manager + * implementation does not support XDomains. + */ +int tb_domain_disconnect_xdomain_paths(struct tb *tb, struct tb_xdomain *xd) +{ + if (!tb->cm_ops->disconnect_xdomain_paths) + return -ENOTSUPP; + + return tb->cm_ops->disconnect_xdomain_paths(tb, xd); +} + +static int disconnect_xdomain(struct device *dev, void *data) +{ + struct tb_xdomain *xd; + struct tb *tb = data; + int ret = 0; + + xd = tb_to_xdomain(dev); + if (xd && xd->tb == tb) + ret = tb_xdomain_disable_paths(xd); + + return ret; +} + +/** + * tb_domain_disconnect_all_paths() - Disconnect all paths for the domain + * @tb: Domain whose paths are disconnected + * + * This function can be used to disconnect all paths (PCIe, XDomain) for + * example in preparation for host NVM firmware upgrade. After this is + * called the paths cannot be established without resetting the switch. + * + * Return: %0 in case of success and negative errno otherwise. + */ +int tb_domain_disconnect_all_paths(struct tb *tb) +{ + int ret; + + ret = tb_domain_disconnect_pcie_paths(tb); + if (ret) + return ret; + + return bus_for_each_dev(&tb_bus_type, NULL, tb, disconnect_xdomain); +} + int tb_domain_init(void) { - return bus_register(&tb_bus_type); + int ret; + + ret = tb_xdomain_init(); + if (ret) + return ret; + ret = bus_register(&tb_bus_type); + if (ret) + tb_xdomain_exit(); + + return ret; } void tb_domain_exit(void) @@ -453,4 +641,5 @@ void tb_domain_exit(void) bus_unregister(&tb_bus_type); ida_destroy(&tb_domain_ida); tb_switch_exit(); + tb_xdomain_exit(); } diff --git a/drivers/thunderbolt/icm.c b/drivers/thunderbolt/icm.c index 8c22b91ed040..ab02d13f40b7 100644 --- a/drivers/thunderbolt/icm.c +++ b/drivers/thunderbolt/icm.c @@ -60,6 +60,8 @@ * @get_route: Find a route string for given switch * @device_connected: Handle device connected ICM message * @device_disconnected: Handle device disconnected ICM message + * @xdomain_connected - Handle XDomain connected ICM message + * @xdomain_disconnected - Handle XDomain disconnected ICM message */ struct icm { struct mutex request_lock; @@ -74,6 +76,10 @@ struct icm { const struct icm_pkg_header *hdr); void (*device_disconnected)(struct tb *tb, const struct icm_pkg_header *hdr); + void (*xdomain_connected)(struct tb *tb, + const struct icm_pkg_header *hdr); + void (*xdomain_disconnected)(struct tb *tb, + const struct icm_pkg_header *hdr); }; struct icm_notification { @@ -89,7 +95,10 @@ static inline struct tb *icm_to_tb(struct icm *icm) static inline u8 phy_port_from_route(u64 route, u8 depth) { - return tb_phy_port_from_link(route >> ((depth - 1) * 8)); + u8 link; + + link = depth ? route >> ((depth - 1) * 8) : route; + return tb_phy_port_from_link(link); } static inline u8 dual_link_from_link(u8 link) @@ -320,6 +329,51 @@ static int icm_fr_challenge_switch_key(struct tb *tb, struct tb_switch *sw, return 0; } +static int icm_fr_approve_xdomain_paths(struct tb *tb, struct tb_xdomain *xd) +{ + struct icm_fr_pkg_approve_xdomain_response reply; + struct icm_fr_pkg_approve_xdomain request; + int ret; + + memset(&request, 0, sizeof(request)); + request.hdr.code = ICM_APPROVE_XDOMAIN; + request.link_info = xd->depth << ICM_LINK_INFO_DEPTH_SHIFT | xd->link; + memcpy(&request.remote_uuid, xd->remote_uuid, sizeof(*xd->remote_uuid)); + + request.transmit_path = xd->transmit_path; + request.transmit_ring = xd->transmit_ring; + request.receive_path = xd->receive_path; + request.receive_ring = xd->receive_ring; + + memset(&reply, 0, sizeof(reply)); + ret = icm_request(tb, &request, sizeof(request), &reply, sizeof(reply), + 1, ICM_TIMEOUT); + if (ret) + return ret; + + if (reply.hdr.flags & ICM_FLAGS_ERROR) + return -EIO; + + return 0; +} + +static int icm_fr_disconnect_xdomain_paths(struct tb *tb, struct tb_xdomain *xd) +{ + u8 phy_port; + u8 cmd; + + phy_port = tb_phy_port_from_link(xd->link); + if (phy_port == 0) + cmd = NHI_MAILBOX_DISCONNECT_PA; + else + cmd = NHI_MAILBOX_DISCONNECT_PB; + + nhi_mailbox_cmd(tb->nhi, cmd, 1); + usleep_range(10, 50); + nhi_mailbox_cmd(tb->nhi, cmd, 2); + return 0; +} + static void remove_switch(struct tb_switch *sw) { struct tb_switch *parent_sw; @@ -475,6 +529,141 @@ icm_fr_device_disconnected(struct tb *tb, const struct icm_pkg_header *hdr) tb_switch_put(sw); } +static void remove_xdomain(struct tb_xdomain *xd) +{ + struct tb_switch *sw; + + sw = tb_to_switch(xd->dev.parent); + tb_port_at(xd->route, sw)->xdomain = NULL; + tb_xdomain_remove(xd); +} + +static void +icm_fr_xdomain_connected(struct tb *tb, const struct icm_pkg_header *hdr) +{ + const struct icm_fr_event_xdomain_connected *pkg = + (const struct icm_fr_event_xdomain_connected *)hdr; + struct tb_xdomain *xd; + struct tb_switch *sw; + u8 link, depth; + bool approved; + u64 route; + + /* + * After NVM upgrade adding root switch device fails because we + * initiated reset. During that time ICM might still send + * XDomain connected message which we ignore here. + */ + if (!tb->root_switch) + return; + + link = pkg->link_info & ICM_LINK_INFO_LINK_MASK; + depth = (pkg->link_info & ICM_LINK_INFO_DEPTH_MASK) >> + ICM_LINK_INFO_DEPTH_SHIFT; + approved = pkg->link_info & ICM_LINK_INFO_APPROVED; + + if (link > ICM_MAX_LINK || depth > ICM_MAX_DEPTH) { + tb_warn(tb, "invalid topology %u.%u, ignoring\n", link, depth); + return; + } + + route = get_route(pkg->local_route_hi, pkg->local_route_lo); + + xd = tb_xdomain_find_by_uuid(tb, &pkg->remote_uuid); + if (xd) { + u8 xd_phy_port, phy_port; + + xd_phy_port = phy_port_from_route(xd->route, xd->depth); + phy_port = phy_port_from_route(route, depth); + + if (xd->depth == depth && xd_phy_port == phy_port) { + xd->link = link; + xd->route = route; + xd->is_unplugged = false; + tb_xdomain_put(xd); + return; + } + + /* + * If we find an existing XDomain connection remove it + * now. We need to go through login handshake and + * everything anyway to be able to re-establish the + * connection. + */ + remove_xdomain(xd); + tb_xdomain_put(xd); + } + + /* + * Look if there already exists an XDomain in the same place + * than the new one and in that case remove it because it is + * most likely another host that got disconnected. + */ + xd = tb_xdomain_find_by_link_depth(tb, link, depth); + if (!xd) { + u8 dual_link; + + dual_link = dual_link_from_link(link); + if (dual_link) + xd = tb_xdomain_find_by_link_depth(tb, dual_link, + depth); + } + if (xd) { + remove_xdomain(xd); + tb_xdomain_put(xd); + } + + /* + * If the user disconnected a switch during suspend and + * connected another host to the same port, remove the switch + * first. + */ + sw = get_switch_at_route(tb->root_switch, route); + if (sw) + remove_switch(sw); + + sw = tb_switch_find_by_link_depth(tb, link, depth); + if (!sw) { + tb_warn(tb, "no switch exists at %u.%u, ignoring\n", link, + depth); + return; + } + + xd = tb_xdomain_alloc(sw->tb, &sw->dev, route, + &pkg->local_uuid, &pkg->remote_uuid); + if (!xd) { + tb_switch_put(sw); + return; + } + + xd->link = link; + xd->depth = depth; + + tb_port_at(route, sw)->xdomain = xd; + + tb_xdomain_add(xd); + tb_switch_put(sw); +} + +static void +icm_fr_xdomain_disconnected(struct tb *tb, const struct icm_pkg_header *hdr) +{ + const struct icm_fr_event_xdomain_disconnected *pkg = + (const struct icm_fr_event_xdomain_disconnected *)hdr; + struct tb_xdomain *xd; + + /* + * If the connection is through one or multiple devices, the + * XDomain device is removed along with them so it is fine if we + * cannot find it here. + */ + xd = tb_xdomain_find_by_uuid(tb, &pkg->remote_uuid); + if (xd) { + remove_xdomain(xd); + tb_xdomain_put(xd); + } +} + static struct pci_dev *get_upstream_port(struct pci_dev *pdev) { struct pci_dev *parent; @@ -594,6 +783,12 @@ static void icm_handle_notification(struct work_struct *work) case ICM_EVENT_DEVICE_DISCONNECTED: icm->device_disconnected(tb, n->pkg); break; + case ICM_EVENT_XDOMAIN_CONNECTED: + icm->xdomain_connected(tb, n->pkg); + break; + case ICM_EVENT_XDOMAIN_DISCONNECTED: + icm->xdomain_disconnected(tb, n->pkg); + break; } mutex_unlock(&tb->lock); @@ -927,6 +1122,10 @@ static void icm_unplug_children(struct tb_switch *sw) if (tb_is_upstream_port(port)) continue; + if (port->xdomain) { + port->xdomain->is_unplugged = true; + continue; + } if (!port->remote) continue; @@ -943,6 +1142,13 @@ static void icm_free_unplugged_children(struct tb_switch *sw) if (tb_is_upstream_port(port)) continue; + + if (port->xdomain && port->xdomain->is_unplugged) { + tb_xdomain_remove(port->xdomain); + port->xdomain = NULL; + continue; + } + if (!port->remote) continue; @@ -1009,8 +1215,10 @@ static int icm_start(struct tb *tb) tb->root_switch->no_nvm_upgrade = x86_apple_machine; ret = tb_switch_add(tb->root_switch); - if (ret) + if (ret) { tb_switch_put(tb->root_switch); + tb->root_switch = NULL; + } return ret; } @@ -1042,6 +1250,8 @@ static const struct tb_cm_ops icm_fr_ops = { .add_switch_key = icm_fr_add_switch_key, .challenge_switch_key = icm_fr_challenge_switch_key, .disconnect_pcie_paths = icm_disconnect_pcie_paths, + .approve_xdomain_paths = icm_fr_approve_xdomain_paths, + .disconnect_xdomain_paths = icm_fr_disconnect_xdomain_paths, }; struct tb *icm_probe(struct tb_nhi *nhi) @@ -1064,6 +1274,8 @@ struct tb *icm_probe(struct tb_nhi *nhi) icm->get_route = icm_fr_get_route; icm->device_connected = icm_fr_device_connected; icm->device_disconnected = icm_fr_device_disconnected; + icm->xdomain_connected = icm_fr_xdomain_connected; + icm->xdomain_disconnected = icm_fr_xdomain_disconnected; tb->cm_ops = &icm_fr_ops; break; @@ -1077,6 +1289,8 @@ struct tb *icm_probe(struct tb_nhi *nhi) icm->get_route = icm_ar_get_route; icm->device_connected = icm_fr_device_connected; icm->device_disconnected = icm_fr_device_disconnected; + icm->xdomain_connected = icm_fr_xdomain_connected; + icm->xdomain_disconnected = icm_fr_xdomain_disconnected; tb->cm_ops = &icm_fr_ops; break; } diff --git a/drivers/thunderbolt/nhi.h b/drivers/thunderbolt/nhi.h index 5b5bb2c436be..0e05828983db 100644 --- a/drivers/thunderbolt/nhi.h +++ b/drivers/thunderbolt/nhi.h @@ -157,6 +157,8 @@ enum nhi_mailbox_cmd { NHI_MAILBOX_SAVE_DEVS = 0x05, NHI_MAILBOX_DISCONNECT_PCIE_PATHS = 0x06, NHI_MAILBOX_DRV_UNLOADS = 0x07, + NHI_MAILBOX_DISCONNECT_PA = 0x10, + NHI_MAILBOX_DISCONNECT_PB = 0x11, NHI_MAILBOX_ALLOW_ALL_DEVS = 0x23, }; diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c index 53f40c57df59..dfc357d33e1e 100644 --- a/drivers/thunderbolt/switch.c +++ b/drivers/thunderbolt/switch.c @@ -171,11 +171,11 @@ static int nvm_authenticate_host(struct tb_switch *sw) /* * Root switch NVM upgrade requires that we disconnect the - * existing PCIe paths first (in case it is not in safe mode + * existing paths first (in case it is not in safe mode * already). */ if (!sw->safe_mode) { - ret = tb_domain_disconnect_pcie_paths(sw->tb); + ret = tb_domain_disconnect_all_paths(sw->tb); if (ret) return ret; /* @@ -1363,6 +1363,9 @@ void tb_switch_remove(struct tb_switch *sw) if (sw->ports[i].remote) tb_switch_remove(sw->ports[i].remote->sw); sw->ports[i].remote = NULL; + if (sw->ports[i].xdomain) + tb_xdomain_remove(sw->ports[i].xdomain); + sw->ports[i].xdomain = NULL; } if (!sw->is_unplugged) diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h index ea21d927bd09..74af9d4929ab 100644 --- a/drivers/thunderbolt/tb.h +++ b/drivers/thunderbolt/tb.h @@ -9,6 +9,7 @@ #include #include +#include #include #include "tb_regs.h" @@ -109,14 +110,25 @@ struct tb_switch { /** * struct tb_port - a thunderbolt port, part of a tb_switch + * @config: Cached port configuration read from registers + * @sw: Switch the port belongs to + * @remote: Remote port (%NULL if not connected) + * @xdomain: Remote host (%NULL if not connected) + * @cap_phy: Offset, zero if not found + * @port: Port number on switch + * @disabled: Disabled by eeprom + * @dual_link_port: If the switch is connected using two ports, points + * to the other port. + * @link_nr: Is this primary or secondary port on the dual_link. */ struct tb_port { struct tb_regs_port_header config; struct tb_switch *sw; - struct tb_port *remote; /* remote port, NULL if not connected */ - int cap_phy; /* offset, zero if not found */ - u8 port; /* port number on switch */ - bool disabled; /* disabled by eeprom */ + struct tb_port *remote; + struct tb_xdomain *xdomain; + int cap_phy; + u8 port; + bool disabled; struct tb_port *dual_link_port; u8 link_nr:1; }; @@ -189,6 +201,8 @@ struct tb_path { * @add_switch_key: Add key to switch * @challenge_switch_key: Challenge switch using key * @disconnect_pcie_paths: Disconnects PCIe paths before NVM update + * @approve_xdomain_paths: Approve (establish) XDomain DMA paths + * @disconnect_xdomain_paths: Disconnect XDomain DMA paths */ struct tb_cm_ops { int (*driver_ready)(struct tb *tb); @@ -205,6 +219,8 @@ struct tb_cm_ops { int (*challenge_switch_key)(struct tb *tb, struct tb_switch *sw, const u8 *challenge, u8 *response); int (*disconnect_pcie_paths)(struct tb *tb); + int (*approve_xdomain_paths)(struct tb *tb, struct tb_xdomain *xd); + int (*disconnect_xdomain_paths)(struct tb *tb, struct tb_xdomain *xd); }; static inline void *tb_priv(struct tb *tb) @@ -331,6 +347,8 @@ extern struct device_type tb_switch_type; int tb_domain_init(void); void tb_domain_exit(void); void tb_switch_exit(void); +int tb_xdomain_init(void); +void tb_xdomain_exit(void); struct tb *tb_domain_alloc(struct tb_nhi *nhi, size_t privsize); int tb_domain_add(struct tb *tb); @@ -343,6 +361,9 @@ int tb_domain_approve_switch(struct tb *tb, struct tb_switch *sw); int tb_domain_approve_switch_key(struct tb *tb, struct tb_switch *sw); int tb_domain_challenge_switch_key(struct tb *tb, struct tb_switch *sw); int tb_domain_disconnect_pcie_paths(struct tb *tb); +int tb_domain_approve_xdomain_paths(struct tb *tb, struct tb_xdomain *xd); +int tb_domain_disconnect_xdomain_paths(struct tb *tb, struct tb_xdomain *xd); +int tb_domain_disconnect_all_paths(struct tb *tb); static inline void tb_domain_put(struct tb *tb) { @@ -422,4 +443,14 @@ static inline u64 tb_downstream_route(struct tb_port *port) | ((u64) port->port << (port->sw->config.depth * 8)); } +bool tb_xdomain_handle_request(struct tb *tb, enum tb_cfg_pkg_type type, + const void *buf, size_t size); +struct tb_xdomain *tb_xdomain_alloc(struct tb *tb, struct device *parent, + u64 route, const uuid_t *local_uuid, + const uuid_t *remote_uuid); +void tb_xdomain_add(struct tb_xdomain *xd); +void tb_xdomain_remove(struct tb_xdomain *xd); +struct tb_xdomain *tb_xdomain_find_by_link_depth(struct tb *tb, u8 link, + u8 depth); + #endif diff --git a/drivers/thunderbolt/tb_msgs.h b/drivers/thunderbolt/tb_msgs.h index f2b2550cd97c..b0a092baa605 100644 --- a/drivers/thunderbolt/tb_msgs.h +++ b/drivers/thunderbolt/tb_msgs.h @@ -101,11 +101,14 @@ enum icm_pkg_code { ICM_CHALLENGE_DEVICE = 0x5, ICM_ADD_DEVICE_KEY = 0x6, ICM_GET_ROUTE = 0xa, + ICM_APPROVE_XDOMAIN = 0x10, }; enum icm_event_code { ICM_EVENT_DEVICE_CONNECTED = 3, ICM_EVENT_DEVICE_DISCONNECTED = 4, + ICM_EVENT_XDOMAIN_CONNECTED = 6, + ICM_EVENT_XDOMAIN_DISCONNECTED = 7, }; struct icm_pkg_header { @@ -188,6 +191,25 @@ struct icm_fr_event_device_disconnected { u16 link_info; }; +struct icm_fr_event_xdomain_connected { + struct icm_pkg_header hdr; + u16 reserved; + u16 link_info; + uuid_t remote_uuid; + uuid_t local_uuid; + u32 local_route_hi; + u32 local_route_lo; + u32 remote_route_hi; + u32 remote_route_lo; +}; + +struct icm_fr_event_xdomain_disconnected { + struct icm_pkg_header hdr; + u16 reserved; + u16 link_info; + uuid_t remote_uuid; +}; + struct icm_fr_pkg_add_device_key { struct icm_pkg_header hdr; uuid_t ep_uuid; @@ -224,6 +246,28 @@ struct icm_fr_pkg_challenge_device_response { u32 response[8]; }; +struct icm_fr_pkg_approve_xdomain { + struct icm_pkg_header hdr; + u16 reserved; + u16 link_info; + uuid_t remote_uuid; + u16 transmit_path; + u16 transmit_ring; + u16 receive_path; + u16 receive_ring; +}; + +struct icm_fr_pkg_approve_xdomain_response { + struct icm_pkg_header hdr; + u16 reserved; + u16 link_info; + uuid_t remote_uuid; + u16 transmit_path; + u16 transmit_ring; + u16 receive_path; + u16 receive_ring; +}; + /* Alpine Ridge only messages */ struct icm_ar_pkg_get_route { @@ -240,4 +284,83 @@ struct icm_ar_pkg_get_route_response { u32 route_lo; }; +/* XDomain messages */ + +struct tb_xdomain_header { + u32 route_hi; + u32 route_lo; + u32 length_sn; +}; + +#define TB_XDOMAIN_LENGTH_MASK GENMASK(5, 0) +#define TB_XDOMAIN_SN_MASK GENMASK(28, 27) +#define TB_XDOMAIN_SN_SHIFT 27 + +enum tb_xdp_type { + UUID_REQUEST_OLD = 1, + UUID_RESPONSE = 2, + PROPERTIES_REQUEST, + PROPERTIES_RESPONSE, + PROPERTIES_CHANGED_REQUEST, + PROPERTIES_CHANGED_RESPONSE, + ERROR_RESPONSE, + UUID_REQUEST = 12, +}; + +struct tb_xdp_header { + struct tb_xdomain_header xd_hdr; + uuid_t uuid; + u32 type; +}; + +struct tb_xdp_properties { + struct tb_xdp_header hdr; + uuid_t src_uuid; + uuid_t dst_uuid; + u16 offset; + u16 reserved; +}; + +struct tb_xdp_properties_response { + struct tb_xdp_header hdr; + uuid_t src_uuid; + uuid_t dst_uuid; + u16 offset; + u16 data_length; + u32 generation; + u32 data[0]; +}; + +/* + * Max length of data array single XDomain property response is allowed + * to carry. + */ +#define TB_XDP_PROPERTIES_MAX_DATA_LENGTH \ + (((256 - 4 - sizeof(struct tb_xdp_properties_response))) / 4) + +/* Maximum size of the total property block in dwords we allow */ +#define TB_XDP_PROPERTIES_MAX_LENGTH 500 + +struct tb_xdp_properties_changed { + struct tb_xdp_header hdr; + uuid_t src_uuid; +}; + +struct tb_xdp_properties_changed_response { + struct tb_xdp_header hdr; +}; + +enum tb_xdp_error { + ERROR_SUCCESS, + ERROR_UNKNOWN_PACKET, + ERROR_UNKNOWN_DOMAIN, + ERROR_NOT_SUPPORTED, + ERROR_NOT_READY, +}; + +struct tb_xdp_error_response { + struct tb_xdp_header hdr; + u32 error; +}; + #endif diff --git a/drivers/thunderbolt/xdomain.c b/drivers/thunderbolt/xdomain.c new file mode 100644 index 000000000000..f2d06f6f7be9 --- /dev/null +++ b/drivers/thunderbolt/xdomain.c @@ -0,0 +1,1576 @@ +/* + * Thunderbolt XDomain discovery protocol support + * + * Copyright (C) 2017, Intel Corporation + * Authors: Michael Jamet + * Mika Westerberg + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include + +#include "tb.h" + +#define XDOMAIN_DEFAULT_TIMEOUT 5000 /* ms */ +#define XDOMAIN_PROPERTIES_RETRIES 60 +#define XDOMAIN_PROPERTIES_CHANGED_RETRIES 10 + +struct xdomain_request_work { + struct work_struct work; + struct tb_xdp_header *pkg; + struct tb *tb; +}; + +/* Serializes access to the properties and protocol handlers below */ +static DEFINE_MUTEX(xdomain_lock); + +/* Properties exposed to the remote domains */ +static struct tb_property_dir *xdomain_property_dir; +static u32 *xdomain_property_block; +static u32 xdomain_property_block_len; +static u32 xdomain_property_block_gen; + +/* Additional protocol handlers */ +static LIST_HEAD(protocol_handlers); + +/* UUID for XDomain discovery protocol: b638d70e-42ff-40bb-97c2-90e2c0b2ff07 */ +static const uuid_t tb_xdp_uuid = + UUID_INIT(0xb638d70e, 0x42ff, 0x40bb, + 0x97, 0xc2, 0x90, 0xe2, 0xc0, 0xb2, 0xff, 0x07); + +static bool tb_xdomain_match(const struct tb_cfg_request *req, + const struct ctl_pkg *pkg) +{ + switch (pkg->frame.eof) { + case TB_CFG_PKG_ERROR: + return true; + + case TB_CFG_PKG_XDOMAIN_RESP: { + const struct tb_xdp_header *res_hdr = pkg->buffer; + const struct tb_xdp_header *req_hdr = req->request; + u8 req_seq, res_seq; + + if (pkg->frame.size < req->response_size / 4) + return false; + + /* Make sure route matches */ + if ((res_hdr->xd_hdr.route_hi & ~BIT(31)) != + req_hdr->xd_hdr.route_hi) + return false; + if ((res_hdr->xd_hdr.route_lo) != req_hdr->xd_hdr.route_lo) + return false; + + /* Then check that the sequence number matches */ + res_seq = res_hdr->xd_hdr.length_sn & TB_XDOMAIN_SN_MASK; + res_seq >>= TB_XDOMAIN_SN_SHIFT; + req_seq = req_hdr->xd_hdr.length_sn & TB_XDOMAIN_SN_MASK; + req_seq >>= TB_XDOMAIN_SN_SHIFT; + if (res_seq != req_seq) + return false; + + /* Check that the XDomain protocol matches */ + if (!uuid_equal(&res_hdr->uuid, &req_hdr->uuid)) + return false; + + return true; + } + + default: + return false; + } +} + +static bool tb_xdomain_copy(struct tb_cfg_request *req, + const struct ctl_pkg *pkg) +{ + memcpy(req->response, pkg->buffer, req->response_size); + req->result.err = 0; + return true; +} + +static void response_ready(void *data) +{ + tb_cfg_request_put(data); +} + +static int __tb_xdomain_response(struct tb_ctl *ctl, const void *response, + size_t size, enum tb_cfg_pkg_type type) +{ + struct tb_cfg_request *req; + + req = tb_cfg_request_alloc(); + if (!req) + return -ENOMEM; + + req->match = tb_xdomain_match; + req->copy = tb_xdomain_copy; + req->request = response; + req->request_size = size; + req->request_type = type; + + return tb_cfg_request(ctl, req, response_ready, req); +} + +/** + * tb_xdomain_response() - Send a XDomain response message + * @xd: XDomain to send the message + * @response: Response to send + * @size: Size of the response + * @type: PDF type of the response + * + * This can be used to send a XDomain response message to the other + * domain. No response for the message is expected. + * + * Return: %0 in case of success and negative errno in case of failure + */ +int tb_xdomain_response(struct tb_xdomain *xd, const void *response, + size_t size, enum tb_cfg_pkg_type type) +{ + return __tb_xdomain_response(xd->tb->ctl, response, size, type); +} +EXPORT_SYMBOL_GPL(tb_xdomain_response); + +static int __tb_xdomain_request(struct tb_ctl *ctl, const void *request, + size_t request_size, enum tb_cfg_pkg_type request_type, void *response, + size_t response_size, enum tb_cfg_pkg_type response_type, + unsigned int timeout_msec) +{ + struct tb_cfg_request *req; + struct tb_cfg_result res; + + req = tb_cfg_request_alloc(); + if (!req) + return -ENOMEM; + + req->match = tb_xdomain_match; + req->copy = tb_xdomain_copy; + req->request = request; + req->request_size = request_size; + req->request_type = request_type; + req->response = response; + req->response_size = response_size; + req->response_type = response_type; + + res = tb_cfg_request_sync(ctl, req, timeout_msec); + + tb_cfg_request_put(req); + + return res.err == 1 ? -EIO : res.err; +} + +/** + * tb_xdomain_request() - Send a XDomain request + * @xd: XDomain to send the request + * @request: Request to send + * @request_size: Size of the request in bytes + * @request_type: PDF type of the request + * @response: Response is copied here + * @response_size: Expected size of the response in bytes + * @response_type: Expected PDF type of the response + * @timeout_msec: Timeout in milliseconds to wait for the response + * + * This function can be used to send XDomain control channel messages to + * the other domain. The function waits until the response is received + * or when timeout triggers. Whichever comes first. + * + * Return: %0 in case of success and negative errno in case of failure + */ +int tb_xdomain_request(struct tb_xdomain *xd, const void *request, + size_t request_size, enum tb_cfg_pkg_type request_type, + void *response, size_t response_size, + enum tb_cfg_pkg_type response_type, unsigned int timeout_msec) +{ + return __tb_xdomain_request(xd->tb->ctl, request, request_size, + request_type, response, response_size, + response_type, timeout_msec); +} +EXPORT_SYMBOL_GPL(tb_xdomain_request); + +static inline void tb_xdp_fill_header(struct tb_xdp_header *hdr, u64 route, + u8 sequence, enum tb_xdp_type type, size_t size) +{ + u32 length_sn; + + length_sn = (size - sizeof(hdr->xd_hdr)) / 4; + length_sn |= (sequence << TB_XDOMAIN_SN_SHIFT) & TB_XDOMAIN_SN_MASK; + + hdr->xd_hdr.route_hi = upper_32_bits(route); + hdr->xd_hdr.route_lo = lower_32_bits(route); + hdr->xd_hdr.length_sn = length_sn; + hdr->type = type; + memcpy(&hdr->uuid, &tb_xdp_uuid, sizeof(tb_xdp_uuid)); +} + +static int tb_xdp_handle_error(const struct tb_xdp_header *hdr) +{ + const struct tb_xdp_error_response *error; + + if (hdr->type != ERROR_RESPONSE) + return 0; + + error = (const struct tb_xdp_error_response *)hdr; + + switch (error->error) { + case ERROR_UNKNOWN_PACKET: + case ERROR_UNKNOWN_DOMAIN: + return -EIO; + case ERROR_NOT_SUPPORTED: + return -ENOTSUPP; + case ERROR_NOT_READY: + return -EAGAIN; + default: + break; + } + + return 0; +} + +static int tb_xdp_error_response(struct tb_ctl *ctl, u64 route, u8 sequence, + enum tb_xdp_error error) +{ + struct tb_xdp_error_response res; + + memset(&res, 0, sizeof(res)); + tb_xdp_fill_header(&res.hdr, route, sequence, ERROR_RESPONSE, + sizeof(res)); + res.error = error; + + return __tb_xdomain_response(ctl, &res, sizeof(res), + TB_CFG_PKG_XDOMAIN_RESP); +} + +static int tb_xdp_properties_request(struct tb_ctl *ctl, u64 route, + const uuid_t *src_uuid, const uuid_t *dst_uuid, int retry, + u32 **block, u32 *generation) +{ + struct tb_xdp_properties_response *res; + struct tb_xdp_properties req; + u16 data_len, len; + size_t total_size; + u32 *data = NULL; + int ret; + + total_size = sizeof(*res) + TB_XDP_PROPERTIES_MAX_DATA_LENGTH * 4; + res = kzalloc(total_size, GFP_KERNEL); + if (!res) + return -ENOMEM; + + memset(&req, 0, sizeof(req)); + tb_xdp_fill_header(&req.hdr, route, retry % 4, PROPERTIES_REQUEST, + sizeof(req)); + memcpy(&req.src_uuid, src_uuid, sizeof(*src_uuid)); + memcpy(&req.dst_uuid, dst_uuid, sizeof(*dst_uuid)); + + len = 0; + data_len = 0; + + do { + ret = __tb_xdomain_request(ctl, &req, sizeof(req), + TB_CFG_PKG_XDOMAIN_REQ, res, + total_size, TB_CFG_PKG_XDOMAIN_RESP, + XDOMAIN_DEFAULT_TIMEOUT); + if (ret) + goto err; + + ret = tb_xdp_handle_error(&res->hdr); + if (ret) + goto err; + + /* + * Package length includes the whole payload without the + * XDomain header. Validate first that the package is at + * least size of the response structure. + */ + len = res->hdr.xd_hdr.length_sn & TB_XDOMAIN_LENGTH_MASK; + if (len < sizeof(*res) / 4) { + ret = -EINVAL; + goto err; + } + + len += sizeof(res->hdr.xd_hdr) / 4; + len -= sizeof(*res) / 4; + + if (res->offset != req.offset) { + ret = -EINVAL; + goto err; + } + + /* + * First time allocate block that has enough space for + * the whole properties block. + */ + if (!data) { + data_len = res->data_length; + if (data_len > TB_XDP_PROPERTIES_MAX_LENGTH) { + ret = -E2BIG; + goto err; + } + + data = kcalloc(data_len, sizeof(u32), GFP_KERNEL); + if (!data) { + ret = -ENOMEM; + goto err; + } + } + + memcpy(data + req.offset, res->data, len * 4); + req.offset += len; + } while (!data_len || req.offset < data_len); + + *block = data; + *generation = res->generation; + + kfree(res); + + return data_len; + +err: + kfree(data); + kfree(res); + + return ret; +} + +static int tb_xdp_properties_response(struct tb *tb, struct tb_ctl *ctl, + u64 route, u8 sequence, const uuid_t *src_uuid, + const struct tb_xdp_properties *req) +{ + struct tb_xdp_properties_response *res; + size_t total_size; + u16 len; + int ret; + + /* + * Currently we expect all requests to be directed to us. The + * protocol supports forwarding, though which we might add + * support later on. + */ + if (!uuid_equal(src_uuid, &req->dst_uuid)) { + tb_xdp_error_response(ctl, route, sequence, + ERROR_UNKNOWN_DOMAIN); + return 0; + } + + mutex_lock(&xdomain_lock); + + if (req->offset >= xdomain_property_block_len) { + mutex_unlock(&xdomain_lock); + return -EINVAL; + } + + len = xdomain_property_block_len - req->offset; + len = min_t(u16, len, TB_XDP_PROPERTIES_MAX_DATA_LENGTH); + total_size = sizeof(*res) + len * 4; + + res = kzalloc(total_size, GFP_KERNEL); + if (!res) { + mutex_unlock(&xdomain_lock); + return -ENOMEM; + } + + tb_xdp_fill_header(&res->hdr, route, sequence, PROPERTIES_RESPONSE, + total_size); + res->generation = xdomain_property_block_gen; + res->data_length = xdomain_property_block_len; + res->offset = req->offset; + uuid_copy(&res->src_uuid, src_uuid); + uuid_copy(&res->dst_uuid, &req->src_uuid); + memcpy(res->data, &xdomain_property_block[req->offset], len * 4); + + mutex_unlock(&xdomain_lock); + + ret = __tb_xdomain_response(ctl, res, total_size, + TB_CFG_PKG_XDOMAIN_RESP); + + kfree(res); + return ret; +} + +static int tb_xdp_properties_changed_request(struct tb_ctl *ctl, u64 route, + int retry, const uuid_t *uuid) +{ + struct tb_xdp_properties_changed_response res; + struct tb_xdp_properties_changed req; + int ret; + + memset(&req, 0, sizeof(req)); + tb_xdp_fill_header(&req.hdr, route, retry % 4, + PROPERTIES_CHANGED_REQUEST, sizeof(req)); + uuid_copy(&req.src_uuid, uuid); + + memset(&res, 0, sizeof(res)); + ret = __tb_xdomain_request(ctl, &req, sizeof(req), + TB_CFG_PKG_XDOMAIN_REQ, &res, sizeof(res), + TB_CFG_PKG_XDOMAIN_RESP, + XDOMAIN_DEFAULT_TIMEOUT); + if (ret) + return ret; + + return tb_xdp_handle_error(&res.hdr); +} + +static int +tb_xdp_properties_changed_response(struct tb_ctl *ctl, u64 route, u8 sequence) +{ + struct tb_xdp_properties_changed_response res; + + memset(&res, 0, sizeof(res)); + tb_xdp_fill_header(&res.hdr, route, sequence, + PROPERTIES_CHANGED_RESPONSE, sizeof(res)); + return __tb_xdomain_response(ctl, &res, sizeof(res), + TB_CFG_PKG_XDOMAIN_RESP); +} + +/** + * tb_register_protocol_handler() - Register protocol handler + * @handler: Handler to register + * + * This allows XDomain service drivers to hook into incoming XDomain + * messages. After this function is called the service driver needs to + * be able to handle calls to callback whenever a package with the + * registered protocol is received. + */ +int tb_register_protocol_handler(struct tb_protocol_handler *handler) +{ + if (!handler->uuid || !handler->callback) + return -EINVAL; + if (uuid_equal(handler->uuid, &tb_xdp_uuid)) + return -EINVAL; + + mutex_lock(&xdomain_lock); + list_add_tail(&handler->list, &protocol_handlers); + mutex_unlock(&xdomain_lock); + + return 0; +} +EXPORT_SYMBOL_GPL(tb_register_protocol_handler); + +/** + * tb_unregister_protocol_handler() - Unregister protocol handler + * @handler: Handler to unregister + * + * Removes the previously registered protocol handler. + */ +void tb_unregister_protocol_handler(struct tb_protocol_handler *handler) +{ + mutex_lock(&xdomain_lock); + list_del_init(&handler->list); + mutex_unlock(&xdomain_lock); +} +EXPORT_SYMBOL_GPL(tb_unregister_protocol_handler); + +static void tb_xdp_handle_request(struct work_struct *work) +{ + struct xdomain_request_work *xw = container_of(work, typeof(*xw), work); + const struct tb_xdp_header *pkg = xw->pkg; + const struct tb_xdomain_header *xhdr = &pkg->xd_hdr; + struct tb *tb = xw->tb; + struct tb_ctl *ctl = tb->ctl; + const uuid_t *uuid; + int ret = 0; + u8 sequence; + u64 route; + + route = ((u64)xhdr->route_hi << 32 | xhdr->route_lo) & ~BIT_ULL(63); + sequence = xhdr->length_sn & TB_XDOMAIN_SN_MASK; + sequence >>= TB_XDOMAIN_SN_SHIFT; + + mutex_lock(&tb->lock); + if (tb->root_switch) + uuid = tb->root_switch->uuid; + else + uuid = NULL; + mutex_unlock(&tb->lock); + + if (!uuid) { + tb_xdp_error_response(ctl, route, sequence, ERROR_NOT_READY); + goto out; + } + + switch (pkg->type) { + case PROPERTIES_REQUEST: + ret = tb_xdp_properties_response(tb, ctl, route, sequence, uuid, + (const struct tb_xdp_properties *)pkg); + break; + + case PROPERTIES_CHANGED_REQUEST: { + const struct tb_xdp_properties_changed *xchg = + (const struct tb_xdp_properties_changed *)pkg; + struct tb_xdomain *xd; + + ret = tb_xdp_properties_changed_response(ctl, route, sequence); + + /* + * Since the properties have been changed, let's update + * the xdomain related to this connection as well in + * case there is a change in services it offers. + */ + xd = tb_xdomain_find_by_uuid_locked(tb, &xchg->src_uuid); + if (xd) { + queue_delayed_work(tb->wq, &xd->get_properties_work, + msecs_to_jiffies(50)); + tb_xdomain_put(xd); + } + + break; + } + + default: + break; + } + + if (ret) { + tb_warn(tb, "failed to send XDomain response for %#x\n", + pkg->type); + } + +out: + kfree(xw->pkg); + kfree(xw); +} + +static void +tb_xdp_schedule_request(struct tb *tb, const struct tb_xdp_header *hdr, + size_t size) +{ + struct xdomain_request_work *xw; + + xw = kmalloc(sizeof(*xw), GFP_KERNEL); + if (!xw) + return; + + INIT_WORK(&xw->work, tb_xdp_handle_request); + xw->pkg = kmemdup(hdr, size, GFP_KERNEL); + xw->tb = tb; + + queue_work(tb->wq, &xw->work); +} + +/** + * tb_register_service_driver() - Register XDomain service driver + * @drv: Driver to register + * + * Registers new service driver from @drv to the bus. + */ +int tb_register_service_driver(struct tb_service_driver *drv) +{ + drv->driver.bus = &tb_bus_type; + return driver_register(&drv->driver); +} +EXPORT_SYMBOL_GPL(tb_register_service_driver); + +/** + * tb_unregister_service_driver() - Unregister XDomain service driver + * @xdrv: Driver to unregister + * + * Unregisters XDomain service driver from the bus. + */ +void tb_unregister_service_driver(struct tb_service_driver *drv) +{ + driver_unregister(&drv->driver); +} +EXPORT_SYMBOL_GPL(tb_unregister_service_driver); + +static ssize_t key_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct tb_service *svc = container_of(dev, struct tb_service, dev); + + /* + * It should be null terminated but anything else is pretty much + * allowed. + */ + return sprintf(buf, "%*pEp\n", (int)strlen(svc->key), svc->key); +} +static DEVICE_ATTR_RO(key); + +static int get_modalias(struct tb_service *svc, char *buf, size_t size) +{ + return snprintf(buf, size, "tbsvc:k%sp%08Xv%08Xr%08X", svc->key, + svc->prtcid, svc->prtcvers, svc->prtcrevs); +} + +static ssize_t modalias_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct tb_service *svc = container_of(dev, struct tb_service, dev); + + /* Full buffer size except new line and null termination */ + get_modalias(svc, buf, PAGE_SIZE - 2); + return sprintf(buf, "%s\n", buf); +} +static DEVICE_ATTR_RO(modalias); + +static ssize_t prtcid_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct tb_service *svc = container_of(dev, struct tb_service, dev); + + return sprintf(buf, "%u\n", svc->prtcid); +} +static DEVICE_ATTR_RO(prtcid); + +static ssize_t prtcvers_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct tb_service *svc = container_of(dev, struct tb_service, dev); + + return sprintf(buf, "%u\n", svc->prtcvers); +} +static DEVICE_ATTR_RO(prtcvers); + +static ssize_t prtcrevs_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct tb_service *svc = container_of(dev, struct tb_service, dev); + + return sprintf(buf, "%u\n", svc->prtcrevs); +} +static DEVICE_ATTR_RO(prtcrevs); + +static ssize_t prtcstns_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct tb_service *svc = container_of(dev, struct tb_service, dev); + + return sprintf(buf, "0x%08x\n", svc->prtcstns); +} +static DEVICE_ATTR_RO(prtcstns); + +static struct attribute *tb_service_attrs[] = { + &dev_attr_key.attr, + &dev_attr_modalias.attr, + &dev_attr_prtcid.attr, + &dev_attr_prtcvers.attr, + &dev_attr_prtcrevs.attr, + &dev_attr_prtcstns.attr, + NULL, +}; + +static struct attribute_group tb_service_attr_group = { + .attrs = tb_service_attrs, +}; + +static const struct attribute_group *tb_service_attr_groups[] = { + &tb_service_attr_group, + NULL, +}; + +static int tb_service_uevent(struct device *dev, struct kobj_uevent_env *env) +{ + struct tb_service *svc = container_of(dev, struct tb_service, dev); + char modalias[64]; + + get_modalias(svc, modalias, sizeof(modalias)); + return add_uevent_var(env, "MODALIAS=%s", modalias); +} + +static void tb_service_release(struct device *dev) +{ + struct tb_service *svc = container_of(dev, struct tb_service, dev); + struct tb_xdomain *xd = tb_service_parent(svc); + + ida_simple_remove(&xd->service_ids, svc->id); + kfree(svc->key); + kfree(svc); +} + +struct device_type tb_service_type = { + .name = "thunderbolt_service", + .groups = tb_service_attr_groups, + .uevent = tb_service_uevent, + .release = tb_service_release, +}; +EXPORT_SYMBOL_GPL(tb_service_type); + +static int remove_missing_service(struct device *dev, void *data) +{ + struct tb_xdomain *xd = data; + struct tb_service *svc; + + svc = tb_to_service(dev); + if (!svc) + return 0; + + if (!tb_property_find(xd->properties, svc->key, + TB_PROPERTY_TYPE_DIRECTORY)) + device_unregister(dev); + + return 0; +} + +static int find_service(struct device *dev, void *data) +{ + const struct tb_property *p = data; + struct tb_service *svc; + + svc = tb_to_service(dev); + if (!svc) + return 0; + + return !strcmp(svc->key, p->key); +} + +static int populate_service(struct tb_service *svc, + struct tb_property *property) +{ + struct tb_property_dir *dir = property->value.dir; + struct tb_property *p; + + /* Fill in standard properties */ + p = tb_property_find(dir, "prtcid", TB_PROPERTY_TYPE_VALUE); + if (p) + svc->prtcid = p->value.immediate; + p = tb_property_find(dir, "prtcvers", TB_PROPERTY_TYPE_VALUE); + if (p) + svc->prtcvers = p->value.immediate; + p = tb_property_find(dir, "prtcrevs", TB_PROPERTY_TYPE_VALUE); + if (p) + svc->prtcrevs = p->value.immediate; + p = tb_property_find(dir, "prtcstns", TB_PROPERTY_TYPE_VALUE); + if (p) + svc->prtcstns = p->value.immediate; + + svc->key = kstrdup(property->key, GFP_KERNEL); + if (!svc->key) + return -ENOMEM; + + return 0; +} + +static void enumerate_services(struct tb_xdomain *xd) +{ + struct tb_service *svc; + struct tb_property *p; + struct device *dev; + + /* + * First remove all services that are not available anymore in + * the updated property block. + */ + device_for_each_child_reverse(&xd->dev, xd, remove_missing_service); + + /* Then re-enumerate properties creating new services as we go */ + tb_property_for_each(xd->properties, p) { + if (p->type != TB_PROPERTY_TYPE_DIRECTORY) + continue; + + /* If the service exists already we are fine */ + dev = device_find_child(&xd->dev, p, find_service); + if (dev) { + put_device(dev); + continue; + } + + svc = kzalloc(sizeof(*svc), GFP_KERNEL); + if (!svc) + break; + + if (populate_service(svc, p)) { + kfree(svc); + break; + } + + svc->id = ida_simple_get(&xd->service_ids, 0, 0, GFP_KERNEL); + svc->dev.bus = &tb_bus_type; + svc->dev.type = &tb_service_type; + svc->dev.parent = &xd->dev; + dev_set_name(&svc->dev, "%s.%d", dev_name(&xd->dev), svc->id); + + if (device_register(&svc->dev)) { + put_device(&svc->dev); + break; + } + } +} + +static int populate_properties(struct tb_xdomain *xd, + struct tb_property_dir *dir) +{ + const struct tb_property *p; + + /* Required properties */ + p = tb_property_find(dir, "deviceid", TB_PROPERTY_TYPE_VALUE); + if (!p) + return -EINVAL; + xd->device = p->value.immediate; + + p = tb_property_find(dir, "vendorid", TB_PROPERTY_TYPE_VALUE); + if (!p) + return -EINVAL; + xd->vendor = p->value.immediate; + + kfree(xd->device_name); + xd->device_name = NULL; + kfree(xd->vendor_name); + xd->vendor_name = NULL; + + /* Optional properties */ + p = tb_property_find(dir, "deviceid", TB_PROPERTY_TYPE_TEXT); + if (p) + xd->device_name = kstrdup(p->value.text, GFP_KERNEL); + p = tb_property_find(dir, "vendorid", TB_PROPERTY_TYPE_TEXT); + if (p) + xd->vendor_name = kstrdup(p->value.text, GFP_KERNEL); + + return 0; +} + +/* Called with @xd->lock held */ +static void tb_xdomain_restore_paths(struct tb_xdomain *xd) +{ + if (!xd->resume) + return; + + xd->resume = false; + if (xd->transmit_path) { + dev_dbg(&xd->dev, "re-establishing DMA path\n"); + tb_domain_approve_xdomain_paths(xd->tb, xd); + } +} + +static void tb_xdomain_get_properties(struct work_struct *work) +{ + struct tb_xdomain *xd = container_of(work, typeof(*xd), + get_properties_work.work); + struct tb_property_dir *dir; + struct tb *tb = xd->tb; + bool update = false; + u32 *block = NULL; + u32 gen = 0; + int ret; + + ret = tb_xdp_properties_request(tb->ctl, xd->route, xd->local_uuid, + xd->remote_uuid, xd->properties_retries, + &block, &gen); + if (ret < 0) { + if (xd->properties_retries-- > 0) { + queue_delayed_work(xd->tb->wq, &xd->get_properties_work, + msecs_to_jiffies(1000)); + } else { + /* Give up now */ + dev_err(&xd->dev, + "failed read XDomain properties from %pUb\n", + xd->remote_uuid); + } + return; + } + + xd->properties_retries = XDOMAIN_PROPERTIES_RETRIES; + + mutex_lock(&xd->lock); + + /* Only accept newer generation properties */ + if (xd->properties && gen <= xd->property_block_gen) { + /* + * On resume it is likely that the properties block is + * not changed (unless the other end added or removed + * services). However, we need to make sure the existing + * DMA paths are restored properly. + */ + tb_xdomain_restore_paths(xd); + goto err_free_block; + } + + dir = tb_property_parse_dir(block, ret); + if (!dir) { + dev_err(&xd->dev, "failed to parse XDomain properties\n"); + goto err_free_block; + } + + ret = populate_properties(xd, dir); + if (ret) { + dev_err(&xd->dev, "missing XDomain properties in response\n"); + goto err_free_dir; + } + + /* Release the existing one */ + if (xd->properties) { + tb_property_free_dir(xd->properties); + update = true; + } + + xd->properties = dir; + xd->property_block_gen = gen; + + tb_xdomain_restore_paths(xd); + + mutex_unlock(&xd->lock); + + kfree(block); + + /* + * Now the device should be ready enough so we can add it to the + * bus and let userspace know about it. If the device is already + * registered, we notify the userspace that it has changed. + */ + if (!update) { + if (device_add(&xd->dev)) { + dev_err(&xd->dev, "failed to add XDomain device\n"); + return; + } + } else { + kobject_uevent(&xd->dev.kobj, KOBJ_CHANGE); + } + + enumerate_services(xd); + return; + +err_free_dir: + tb_property_free_dir(dir); +err_free_block: + kfree(block); + mutex_unlock(&xd->lock); +} + +static void tb_xdomain_properties_changed(struct work_struct *work) +{ + struct tb_xdomain *xd = container_of(work, typeof(*xd), + properties_changed_work.work); + int ret; + + ret = tb_xdp_properties_changed_request(xd->tb->ctl, xd->route, + xd->properties_changed_retries, xd->local_uuid); + if (ret) { + if (xd->properties_changed_retries-- > 0) + queue_delayed_work(xd->tb->wq, + &xd->properties_changed_work, + msecs_to_jiffies(1000)); + return; + } + + xd->properties_changed_retries = XDOMAIN_PROPERTIES_CHANGED_RETRIES; +} + +static ssize_t device_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct tb_xdomain *xd = container_of(dev, struct tb_xdomain, dev); + + return sprintf(buf, "%#x\n", xd->device); +} +static DEVICE_ATTR_RO(device); + +static ssize_t +device_name_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct tb_xdomain *xd = container_of(dev, struct tb_xdomain, dev); + int ret; + + if (mutex_lock_interruptible(&xd->lock)) + return -ERESTARTSYS; + ret = sprintf(buf, "%s\n", xd->device_name ? xd->device_name : ""); + mutex_unlock(&xd->lock); + + return ret; +} +static DEVICE_ATTR_RO(device_name); + +static ssize_t vendor_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct tb_xdomain *xd = container_of(dev, struct tb_xdomain, dev); + + return sprintf(buf, "%#x\n", xd->vendor); +} +static DEVICE_ATTR_RO(vendor); + +static ssize_t +vendor_name_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct tb_xdomain *xd = container_of(dev, struct tb_xdomain, dev); + int ret; + + if (mutex_lock_interruptible(&xd->lock)) + return -ERESTARTSYS; + ret = sprintf(buf, "%s\n", xd->vendor_name ? xd->vendor_name : ""); + mutex_unlock(&xd->lock); + + return ret; +} +static DEVICE_ATTR_RO(vendor_name); + +static ssize_t unique_id_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct tb_xdomain *xd = container_of(dev, struct tb_xdomain, dev); + + return sprintf(buf, "%pUb\n", xd->remote_uuid); +} +static DEVICE_ATTR_RO(unique_id); + +static struct attribute *xdomain_attrs[] = { + &dev_attr_device.attr, + &dev_attr_device_name.attr, + &dev_attr_unique_id.attr, + &dev_attr_vendor.attr, + &dev_attr_vendor_name.attr, + NULL, +}; + +static struct attribute_group xdomain_attr_group = { + .attrs = xdomain_attrs, +}; + +static const struct attribute_group *xdomain_attr_groups[] = { + &xdomain_attr_group, + NULL, +}; + +static void tb_xdomain_release(struct device *dev) +{ + struct tb_xdomain *xd = container_of(dev, struct tb_xdomain, dev); + + put_device(xd->dev.parent); + + tb_property_free_dir(xd->properties); + ida_destroy(&xd->service_ids); + + kfree(xd->local_uuid); + kfree(xd->remote_uuid); + kfree(xd->device_name); + kfree(xd->vendor_name); + kfree(xd); +} + +static void start_handshake(struct tb_xdomain *xd) +{ + xd->properties_retries = XDOMAIN_PROPERTIES_RETRIES; + xd->properties_changed_retries = XDOMAIN_PROPERTIES_CHANGED_RETRIES; + + /* Start exchanging properties with the other host */ + queue_delayed_work(xd->tb->wq, &xd->properties_changed_work, + msecs_to_jiffies(100)); + queue_delayed_work(xd->tb->wq, &xd->get_properties_work, + msecs_to_jiffies(1000)); +} + +static void stop_handshake(struct tb_xdomain *xd) +{ + xd->properties_retries = 0; + xd->properties_changed_retries = 0; + + cancel_delayed_work_sync(&xd->get_properties_work); + cancel_delayed_work_sync(&xd->properties_changed_work); +} + +static int __maybe_unused tb_xdomain_suspend(struct device *dev) +{ + stop_handshake(tb_to_xdomain(dev)); + return 0; +} + +static int __maybe_unused tb_xdomain_resume(struct device *dev) +{ + struct tb_xdomain *xd = tb_to_xdomain(dev); + + /* + * Ask tb_xdomain_get_properties() restore any existing DMA + * paths after properties are re-read. + */ + xd->resume = true; + start_handshake(xd); + + return 0; +} + +static const struct dev_pm_ops tb_xdomain_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(tb_xdomain_suspend, tb_xdomain_resume) +}; + +struct device_type tb_xdomain_type = { + .name = "thunderbolt_xdomain", + .release = tb_xdomain_release, + .pm = &tb_xdomain_pm_ops, +}; +EXPORT_SYMBOL_GPL(tb_xdomain_type); + +/** + * tb_xdomain_alloc() - Allocate new XDomain object + * @tb: Domain where the XDomain belongs + * @parent: Parent device (the switch through the connection to the + * other domain is reached). + * @route: Route string used to reach the other domain + * @local_uuid: Our local domain UUID + * @remote_uuid: UUID of the other domain + * + * Allocates new XDomain structure and returns pointer to that. The + * object must be released by calling tb_xdomain_put(). + */ +struct tb_xdomain *tb_xdomain_alloc(struct tb *tb, struct device *parent, + u64 route, const uuid_t *local_uuid, + const uuid_t *remote_uuid) +{ + struct tb_xdomain *xd; + + xd = kzalloc(sizeof(*xd), GFP_KERNEL); + if (!xd) + return NULL; + + xd->tb = tb; + xd->route = route; + ida_init(&xd->service_ids); + mutex_init(&xd->lock); + INIT_DELAYED_WORK(&xd->get_properties_work, tb_xdomain_get_properties); + INIT_DELAYED_WORK(&xd->properties_changed_work, + tb_xdomain_properties_changed); + + xd->local_uuid = kmemdup(local_uuid, sizeof(uuid_t), GFP_KERNEL); + if (!xd->local_uuid) + goto err_free; + + xd->remote_uuid = kmemdup(remote_uuid, sizeof(uuid_t), GFP_KERNEL); + if (!xd->remote_uuid) + goto err_free_local_uuid; + + device_initialize(&xd->dev); + xd->dev.parent = get_device(parent); + xd->dev.bus = &tb_bus_type; + xd->dev.type = &tb_xdomain_type; + xd->dev.groups = xdomain_attr_groups; + dev_set_name(&xd->dev, "%u-%llx", tb->index, route); + + return xd; + +err_free_local_uuid: + kfree(xd->local_uuid); +err_free: + kfree(xd); + + return NULL; +} + +/** + * tb_xdomain_add() - Add XDomain to the bus + * @xd: XDomain to add + * + * This function starts XDomain discovery protocol handshake and + * eventually adds the XDomain to the bus. After calling this function + * the caller needs to call tb_xdomain_remove() in order to remove and + * release the object regardless whether the handshake succeeded or not. + */ +void tb_xdomain_add(struct tb_xdomain *xd) +{ + /* Start exchanging properties with the other host */ + start_handshake(xd); +} + +static int unregister_service(struct device *dev, void *data) +{ + device_unregister(dev); + return 0; +} + +/** + * tb_xdomain_remove() - Remove XDomain from the bus + * @xd: XDomain to remove + * + * This will stop all ongoing configuration work and remove the XDomain + * along with any services from the bus. When the last reference to @xd + * is released the object will be released as well. + */ +void tb_xdomain_remove(struct tb_xdomain *xd) +{ + stop_handshake(xd); + + device_for_each_child_reverse(&xd->dev, xd, unregister_service); + + if (!device_is_registered(&xd->dev)) + put_device(&xd->dev); + else + device_unregister(&xd->dev); +} + +/** + * tb_xdomain_enable_paths() - Enable DMA paths for XDomain connection + * @xd: XDomain connection + * @transmit_path: HopID of the transmit path the other end is using to + * send packets + * @transmit_ring: DMA ring used to receive packets from the other end + * @receive_path: HopID of the receive path the other end is using to + * receive packets + * @receive_ring: DMA ring used to send packets to the other end + * + * The function enables DMA paths accordingly so that after successful + * return the caller can send and receive packets using high-speed DMA + * path. + * + * Return: %0 in case of success and negative errno in case of error + */ +int tb_xdomain_enable_paths(struct tb_xdomain *xd, u16 transmit_path, + u16 transmit_ring, u16 receive_path, + u16 receive_ring) +{ + int ret; + + mutex_lock(&xd->lock); + + if (xd->transmit_path) { + ret = xd->transmit_path == transmit_path ? 0 : -EBUSY; + goto exit_unlock; + } + + xd->transmit_path = transmit_path; + xd->transmit_ring = transmit_ring; + xd->receive_path = receive_path; + xd->receive_ring = receive_ring; + + ret = tb_domain_approve_xdomain_paths(xd->tb, xd); + +exit_unlock: + mutex_unlock(&xd->lock); + + return ret; +} +EXPORT_SYMBOL_GPL(tb_xdomain_enable_paths); + +/** + * tb_xdomain_disable_paths() - Disable DMA paths for XDomain connection + * @xd: XDomain connection + * + * This does the opposite of tb_xdomain_enable_paths(). After call to + * this the caller is not expected to use the rings anymore. + * + * Return: %0 in case of success and negative errno in case of error + */ +int tb_xdomain_disable_paths(struct tb_xdomain *xd) +{ + int ret = 0; + + mutex_lock(&xd->lock); + if (xd->transmit_path) { + xd->transmit_path = 0; + xd->transmit_ring = 0; + xd->receive_path = 0; + xd->receive_ring = 0; + + ret = tb_domain_disconnect_xdomain_paths(xd->tb, xd); + } + mutex_unlock(&xd->lock); + + return ret; +} +EXPORT_SYMBOL_GPL(tb_xdomain_disable_paths); + +struct tb_xdomain_lookup { + const uuid_t *uuid; + u8 link; + u8 depth; +}; + +static struct tb_xdomain *switch_find_xdomain(struct tb_switch *sw, + const struct tb_xdomain_lookup *lookup) +{ + int i; + + for (i = 1; i <= sw->config.max_port_number; i++) { + struct tb_port *port = &sw->ports[i]; + struct tb_xdomain *xd; + + if (tb_is_upstream_port(port)) + continue; + + if (port->xdomain) { + xd = port->xdomain; + + if (lookup->uuid) { + if (uuid_equal(xd->remote_uuid, lookup->uuid)) + return xd; + } else if (lookup->link == xd->link && + lookup->depth == xd->depth) { + return xd; + } + } else if (port->remote) { + xd = switch_find_xdomain(port->remote->sw, lookup); + if (xd) + return xd; + } + } + + return NULL; +} + +/** + * tb_xdomain_find_by_uuid() - Find an XDomain by UUID + * @tb: Domain where the XDomain belongs to + * @uuid: UUID to look for + * + * Finds XDomain by walking through the Thunderbolt topology below @tb. + * The returned XDomain will have its reference count increased so the + * caller needs to call tb_xdomain_put() when it is done with the + * object. + * + * This will find all XDomains including the ones that are not yet added + * to the bus (handshake is still in progress). + * + * The caller needs to hold @tb->lock. + */ +struct tb_xdomain *tb_xdomain_find_by_uuid(struct tb *tb, const uuid_t *uuid) +{ + struct tb_xdomain_lookup lookup; + struct tb_xdomain *xd; + + memset(&lookup, 0, sizeof(lookup)); + lookup.uuid = uuid; + + xd = switch_find_xdomain(tb->root_switch, &lookup); + if (xd) { + get_device(&xd->dev); + return xd; + } + + return NULL; +} +EXPORT_SYMBOL_GPL(tb_xdomain_find_by_uuid); + +/** + * tb_xdomain_find_by_link_depth() - Find an XDomain by link and depth + * @tb: Domain where the XDomain belongs to + * @link: Root switch link number + * @depth: Depth in the link + * + * Finds XDomain by walking through the Thunderbolt topology below @tb. + * The returned XDomain will have its reference count increased so the + * caller needs to call tb_xdomain_put() when it is done with the + * object. + * + * This will find all XDomains including the ones that are not yet added + * to the bus (handshake is still in progress). + * + * The caller needs to hold @tb->lock. + */ +struct tb_xdomain *tb_xdomain_find_by_link_depth(struct tb *tb, u8 link, + u8 depth) +{ + struct tb_xdomain_lookup lookup; + struct tb_xdomain *xd; + + memset(&lookup, 0, sizeof(lookup)); + lookup.link = link; + lookup.depth = depth; + + xd = switch_find_xdomain(tb->root_switch, &lookup); + if (xd) { + get_device(&xd->dev); + return xd; + } + + return NULL; +} + +bool tb_xdomain_handle_request(struct tb *tb, enum tb_cfg_pkg_type type, + const void *buf, size_t size) +{ + const struct tb_protocol_handler *handler, *tmp; + const struct tb_xdp_header *hdr = buf; + unsigned int length; + int ret = 0; + + /* We expect the packet is at least size of the header */ + length = hdr->xd_hdr.length_sn & TB_XDOMAIN_LENGTH_MASK; + if (length != size / 4 - sizeof(hdr->xd_hdr) / 4) + return true; + if (length < sizeof(*hdr) / 4 - sizeof(hdr->xd_hdr) / 4) + return true; + + /* + * Handle XDomain discovery protocol packets directly here. For + * other protocols (based on their UUID) we call registered + * handlers in turn. + */ + if (uuid_equal(&hdr->uuid, &tb_xdp_uuid)) { + if (type == TB_CFG_PKG_XDOMAIN_REQ) { + tb_xdp_schedule_request(tb, hdr, size); + return true; + } + return false; + } + + mutex_lock(&xdomain_lock); + list_for_each_entry_safe(handler, tmp, &protocol_handlers, list) { + if (!uuid_equal(&hdr->uuid, handler->uuid)) + continue; + + mutex_unlock(&xdomain_lock); + ret = handler->callback(buf, size, handler->data); + mutex_lock(&xdomain_lock); + + if (ret) + break; + } + mutex_unlock(&xdomain_lock); + + return ret > 0; +} + +static int rebuild_property_block(void) +{ + u32 *block, len; + int ret; + + ret = tb_property_format_dir(xdomain_property_dir, NULL, 0); + if (ret < 0) + return ret; + + len = ret; + + block = kcalloc(len, sizeof(u32), GFP_KERNEL); + if (!block) + return -ENOMEM; + + ret = tb_property_format_dir(xdomain_property_dir, block, len); + if (ret) { + kfree(block); + return ret; + } + + kfree(xdomain_property_block); + xdomain_property_block = block; + xdomain_property_block_len = len; + xdomain_property_block_gen++; + + return 0; +} + +static int update_xdomain(struct device *dev, void *data) +{ + struct tb_xdomain *xd; + + xd = tb_to_xdomain(dev); + if (xd) { + queue_delayed_work(xd->tb->wq, &xd->properties_changed_work, + msecs_to_jiffies(50)); + } + + return 0; +} + +static void update_all_xdomains(void) +{ + bus_for_each_dev(&tb_bus_type, NULL, NULL, update_xdomain); +} + +static bool remove_directory(const char *key, const struct tb_property_dir *dir) +{ + struct tb_property *p; + + p = tb_property_find(xdomain_property_dir, key, + TB_PROPERTY_TYPE_DIRECTORY); + if (p && p->value.dir == dir) { + tb_property_remove(p); + return true; + } + return false; +} + +/** + * tb_register_property_dir() - Register property directory to the host + * @key: Key (name) of the directory to add + * @dir: Directory to add + * + * Service drivers can use this function to add new property directory + * to the host available properties. The other connected hosts are + * notified so they can re-read properties of this host if they are + * interested. + * + * Return: %0 on success and negative errno on failure + */ +int tb_register_property_dir(const char *key, struct tb_property_dir *dir) +{ + int ret; + + if (!key || strlen(key) > 8) + return -EINVAL; + + mutex_lock(&xdomain_lock); + if (tb_property_find(xdomain_property_dir, key, + TB_PROPERTY_TYPE_DIRECTORY)) { + ret = -EEXIST; + goto err_unlock; + } + + ret = tb_property_add_dir(xdomain_property_dir, key, dir); + if (ret) + goto err_unlock; + + ret = rebuild_property_block(); + if (ret) { + remove_directory(key, dir); + goto err_unlock; + } + + mutex_unlock(&xdomain_lock); + update_all_xdomains(); + return 0; + +err_unlock: + mutex_unlock(&xdomain_lock); + return ret; +} +EXPORT_SYMBOL_GPL(tb_register_property_dir); + +/** + * tb_unregister_property_dir() - Removes property directory from host + * @key: Key (name) of the directory + * @dir: Directory to remove + * + * This will remove the existing directory from this host and notify the + * connected hosts about the change. + */ +void tb_unregister_property_dir(const char *key, struct tb_property_dir *dir) +{ + int ret = 0; + + mutex_lock(&xdomain_lock); + if (remove_directory(key, dir)) + ret = rebuild_property_block(); + mutex_unlock(&xdomain_lock); + + if (!ret) + update_all_xdomains(); +} +EXPORT_SYMBOL_GPL(tb_unregister_property_dir); + +int tb_xdomain_init(void) +{ + int ret; + + xdomain_property_dir = tb_property_create_dir(NULL); + if (!xdomain_property_dir) + return -ENOMEM; + + /* + * Initialize standard set of properties without any service + * directories. Those will be added by service drivers + * themselves when they are loaded. + */ + tb_property_add_immediate(xdomain_property_dir, "vendorid", + PCI_VENDOR_ID_INTEL); + tb_property_add_text(xdomain_property_dir, "vendorid", "Intel Corp."); + tb_property_add_immediate(xdomain_property_dir, "deviceid", 0x1); + tb_property_add_text(xdomain_property_dir, "deviceid", + utsname()->nodename); + tb_property_add_immediate(xdomain_property_dir, "devicerv", 0x80000100); + + ret = rebuild_property_block(); + if (ret) { + tb_property_free_dir(xdomain_property_dir); + xdomain_property_dir = NULL; + } + + return ret; +} + +void tb_xdomain_exit(void) +{ + kfree(xdomain_property_block); + tb_property_free_dir(xdomain_property_dir); +} diff --git a/include/linux/mod_devicetable.h b/include/linux/mod_devicetable.h index 694cebb50f72..7625c3b81f84 100644 --- a/include/linux/mod_devicetable.h +++ b/include/linux/mod_devicetable.h @@ -683,5 +683,31 @@ struct fsl_mc_device_id { const char obj_type[16]; }; +/** + * struct tb_service_id - Thunderbolt service identifiers + * @match_flags: Flags used to match the structure + * @protocol_key: Protocol key the service supports + * @protocol_id: Protocol id the service supports + * @protocol_version: Version of the protocol + * @protocol_revision: Revision of the protocol software + * @driver_data: Driver specific data + * + * Thunderbolt XDomain services are exposed as devices where each device + * carries the protocol information the service supports. Thunderbolt + * XDomain service drivers match against that information. + */ +struct tb_service_id { + __u32 match_flags; + char protocol_key[8 + 1]; + __u32 protocol_id; + __u32 protocol_version; + __u32 protocol_revision; + kernel_ulong_t driver_data; +}; + +#define TBSVC_MATCH_PROTOCOL_KEY 0x0001 +#define TBSVC_MATCH_PROTOCOL_ID 0x0002 +#define TBSVC_MATCH_PROTOCOL_VERSION 0x0004 +#define TBSVC_MATCH_PROTOCOL_REVISION 0x0008 #endif /* LINUX_MOD_DEVICETABLE_H */ diff --git a/include/linux/thunderbolt.h b/include/linux/thunderbolt.h index 43b8d1e09341..18c0e3d5e85c 100644 --- a/include/linux/thunderbolt.h +++ b/include/linux/thunderbolt.h @@ -17,6 +17,7 @@ #include #include #include +#include #include enum tb_cfg_pkg_type { @@ -77,6 +78,8 @@ struct tb { }; extern struct bus_type tb_bus_type; +extern struct device_type tb_service_type; +extern struct device_type tb_xdomain_type; #define TB_LINKS_PER_PHY_PORT 2 @@ -155,4 +158,243 @@ struct tb_property *tb_property_get_next(struct tb_property_dir *dir, property; \ property = tb_property_get_next(dir, property)) +int tb_register_property_dir(const char *key, struct tb_property_dir *dir); +void tb_unregister_property_dir(const char *key, struct tb_property_dir *dir); + +/** + * struct tb_xdomain - Cross-domain (XDomain) connection + * @dev: XDomain device + * @tb: Pointer to the domain + * @remote_uuid: UUID of the remote domain (host) + * @local_uuid: Cached local UUID + * @route: Route string the other domain can be reached + * @vendor: Vendor ID of the remote domain + * @device: Device ID of the demote domain + * @lock: Lock to serialize access to the following fields of this structure + * @vendor_name: Name of the vendor (or %NULL if not known) + * @device_name: Name of the device (or %NULL if not known) + * @is_unplugged: The XDomain is unplugged + * @resume: The XDomain is being resumed + * @transmit_path: HopID which the remote end expects us to transmit + * @transmit_ring: Local ring (hop) where outgoing packets are pushed + * @receive_path: HopID which we expect the remote end to transmit + * @receive_ring: Local ring (hop) where incoming packets arrive + * @service_ids: Used to generate IDs for the services + * @properties: Properties exported by the remote domain + * @property_block_gen: Generation of @properties + * @properties_lock: Lock protecting @properties. + * @get_properties_work: Work used to get remote domain properties + * @properties_retries: Number of times left to read properties + * @properties_changed_work: Work used to notify the remote domain that + * our properties have changed + * @properties_changed_retries: Number of times left to send properties + * changed notification + * @link: Root switch link the remote domain is connected (ICM only) + * @depth: Depth in the chain the remote domain is connected (ICM only) + * + * This structure represents connection across two domains (hosts). + * Each XDomain contains zero or more services which are exposed as + * &struct tb_service objects. + * + * Service drivers may access this structure if they need to enumerate + * non-standard properties but they need hold @lock when doing so + * because properties can be changed asynchronously in response to + * changes in the remote domain. + */ +struct tb_xdomain { + struct device dev; + struct tb *tb; + uuid_t *remote_uuid; + const uuid_t *local_uuid; + u64 route; + u16 vendor; + u16 device; + struct mutex lock; + const char *vendor_name; + const char *device_name; + bool is_unplugged; + bool resume; + u16 transmit_path; + u16 transmit_ring; + u16 receive_path; + u16 receive_ring; + struct ida service_ids; + struct tb_property_dir *properties; + u32 property_block_gen; + struct delayed_work get_properties_work; + int properties_retries; + struct delayed_work properties_changed_work; + int properties_changed_retries; + u8 link; + u8 depth; +}; + +int tb_xdomain_enable_paths(struct tb_xdomain *xd, u16 transmit_path, + u16 transmit_ring, u16 receive_path, + u16 receive_ring); +int tb_xdomain_disable_paths(struct tb_xdomain *xd); +struct tb_xdomain *tb_xdomain_find_by_uuid(struct tb *tb, const uuid_t *uuid); + +static inline struct tb_xdomain * +tb_xdomain_find_by_uuid_locked(struct tb *tb, const uuid_t *uuid) +{ + struct tb_xdomain *xd; + + mutex_lock(&tb->lock); + xd = tb_xdomain_find_by_uuid(tb, uuid); + mutex_unlock(&tb->lock); + + return xd; +} + +static inline struct tb_xdomain *tb_xdomain_get(struct tb_xdomain *xd) +{ + if (xd) + get_device(&xd->dev); + return xd; +} + +static inline void tb_xdomain_put(struct tb_xdomain *xd) +{ + if (xd) + put_device(&xd->dev); +} + +static inline bool tb_is_xdomain(const struct device *dev) +{ + return dev->type == &tb_xdomain_type; +} + +static inline struct tb_xdomain *tb_to_xdomain(struct device *dev) +{ + if (tb_is_xdomain(dev)) + return container_of(dev, struct tb_xdomain, dev); + return NULL; +} + +int tb_xdomain_response(struct tb_xdomain *xd, const void *response, + size_t size, enum tb_cfg_pkg_type type); +int tb_xdomain_request(struct tb_xdomain *xd, const void *request, + size_t request_size, enum tb_cfg_pkg_type request_type, + void *response, size_t response_size, + enum tb_cfg_pkg_type response_type, + unsigned int timeout_msec); + +/** + * tb_protocol_handler - Protocol specific handler + * @uuid: XDomain messages with this UUID are dispatched to this handler + * @callback: Callback called with the XDomain message. Returning %1 + * here tells the XDomain core that the message was handled + * by this handler and should not be forwared to other + * handlers. + * @data: Data passed with the callback + * @list: Handlers are linked using this + * + * Thunderbolt services can hook into incoming XDomain requests by + * registering protocol handler. Only limitation is that the XDomain + * discovery protocol UUID cannot be registered since it is handled by + * the core XDomain code. + * + * The @callback must check that the message is really directed to the + * service the driver implements. + */ +struct tb_protocol_handler { + const uuid_t *uuid; + int (*callback)(const void *buf, size_t size, void *data); + void *data; + struct list_head list; +}; + +int tb_register_protocol_handler(struct tb_protocol_handler *handler); +void tb_unregister_protocol_handler(struct tb_protocol_handler *handler); + +/** + * struct tb_service - Thunderbolt service + * @dev: XDomain device + * @id: ID of the service (shown in sysfs) + * @key: Protocol key from the properties directory + * @prtcid: Protocol ID from the properties directory + * @prtcvers: Protocol version from the properties directory + * @prtcrevs: Protocol software revision from the properties directory + * @prtcstns: Protocol settings mask from the properties directory + * + * Each domain exposes set of services it supports as collection of + * properties. For each service there will be one corresponding + * &struct tb_service. Service drivers are bound to these. + */ +struct tb_service { + struct device dev; + int id; + const char *key; + u32 prtcid; + u32 prtcvers; + u32 prtcrevs; + u32 prtcstns; +}; + +static inline struct tb_service *tb_service_get(struct tb_service *svc) +{ + if (svc) + get_device(&svc->dev); + return svc; +} + +static inline void tb_service_put(struct tb_service *svc) +{ + if (svc) + put_device(&svc->dev); +} + +static inline bool tb_is_service(const struct device *dev) +{ + return dev->type == &tb_service_type; +} + +static inline struct tb_service *tb_to_service(struct device *dev) +{ + if (tb_is_service(dev)) + return container_of(dev, struct tb_service, dev); + return NULL; +} + +/** + * tb_service_driver - Thunderbolt service driver + * @driver: Driver structure + * @probe: Called when the driver is probed + * @remove: Called when the driver is removed (optional) + * @shutdown: Called at shutdown time to stop the service (optional) + * @id_table: Table of service identifiers the driver supports + */ +struct tb_service_driver { + struct device_driver driver; + int (*probe)(struct tb_service *svc, const struct tb_service_id *id); + void (*remove)(struct tb_service *svc); + void (*shutdown)(struct tb_service *svc); + const struct tb_service_id *id_table; +}; + +#define TB_SERVICE(key, id) \ + .match_flags = TBSVC_MATCH_PROTOCOL_KEY | \ + TBSVC_MATCH_PROTOCOL_ID, \ + .protocol_key = (key), \ + .protocol_id = (id) + +int tb_register_service_driver(struct tb_service_driver *drv); +void tb_unregister_service_driver(struct tb_service_driver *drv); + +static inline void *tb_service_get_drvdata(const struct tb_service *svc) +{ + return dev_get_drvdata(&svc->dev); +} + +static inline void tb_service_set_drvdata(struct tb_service *svc, void *data) +{ + dev_set_drvdata(&svc->dev, data); +} + +static inline struct tb_xdomain *tb_service_parent(struct tb_service *svc) +{ + return tb_to_xdomain(svc->dev.parent); +} + #endif /* THUNDERBOLT_H_ */ diff --git a/scripts/mod/devicetable-offsets.c b/scripts/mod/devicetable-offsets.c index e4d90e50f6fe..57263f2f8f2f 100644 --- a/scripts/mod/devicetable-offsets.c +++ b/scripts/mod/devicetable-offsets.c @@ -206,5 +206,12 @@ int main(void) DEVID_FIELD(fsl_mc_device_id, vendor); DEVID_FIELD(fsl_mc_device_id, obj_type); + DEVID(tb_service_id); + DEVID_FIELD(tb_service_id, match_flags); + DEVID_FIELD(tb_service_id, protocol_key); + DEVID_FIELD(tb_service_id, protocol_id); + DEVID_FIELD(tb_service_id, protocol_version); + DEVID_FIELD(tb_service_id, protocol_revision); + return 0; } diff --git a/scripts/mod/file2alias.c b/scripts/mod/file2alias.c index 29d6699d5a06..6ef6e63f96fd 100644 --- a/scripts/mod/file2alias.c +++ b/scripts/mod/file2alias.c @@ -1301,6 +1301,31 @@ static int do_fsl_mc_entry(const char *filename, void *symval, } ADD_TO_DEVTABLE("fslmc", fsl_mc_device_id, do_fsl_mc_entry); +/* Looks like: tbsvc:kSpNvNrN */ +static int do_tbsvc_entry(const char *filename, void *symval, char *alias) +{ + DEF_FIELD(symval, tb_service_id, match_flags); + DEF_FIELD_ADDR(symval, tb_service_id, protocol_key); + DEF_FIELD(symval, tb_service_id, protocol_id); + DEF_FIELD(symval, tb_service_id, protocol_version); + DEF_FIELD(symval, tb_service_id, protocol_revision); + + strcpy(alias, "tbsvc:"); + if (match_flags & TBSVC_MATCH_PROTOCOL_KEY) + sprintf(alias + strlen(alias), "k%s", *protocol_key); + else + strcat(alias + strlen(alias), "k*"); + ADD(alias, "p", match_flags & TBSVC_MATCH_PROTOCOL_ID, protocol_id); + ADD(alias, "v", match_flags & TBSVC_MATCH_PROTOCOL_VERSION, + protocol_version); + ADD(alias, "r", match_flags & TBSVC_MATCH_PROTOCOL_REVISION, + protocol_revision); + + add_wildcard(alias); + return 1; +} +ADD_TO_DEVTABLE("tbsvc", tb_service_id, do_tbsvc_entry); + /* Does namelen bytes of name exactly match the symbol? */ static bool sym_is(const char *name, unsigned namelen, const char *symbol) { -- cgit v1.2.3-59-g8ed1b From ebc4768ac4971eab4b570e733e47ac9dfd0e4175 Mon Sep 17 00:00:00 2001 From: Jan Kandziora Date: Wed, 20 Sep 2017 23:52:46 +0200 Subject: add w1_ds28e17 driver for the DS28E17 Onewire to I2C master bridge This subpatch adds a driver for the DS28E17 Onewire to I2C master bridge. Signed-off-by: Jan Kandziora Acked-by: Evgeniy Polyakov Signed-off-by: Greg Kroah-Hartman --- Documentation/ABI/testing/sysfs-driver-w1_ds28e17 | 21 + Documentation/w1/slaves/00-INDEX | 2 + Documentation/w1/slaves/w1_ds28e17 | 68 ++ drivers/w1/slaves/Kconfig | 15 + drivers/w1/slaves/Makefile | 1 + drivers/w1/slaves/w1_ds28e17.c | 771 ++++++++++++++++++++++ 6 files changed, 878 insertions(+) create mode 100644 Documentation/ABI/testing/sysfs-driver-w1_ds28e17 create mode 100644 Documentation/w1/slaves/w1_ds28e17 create mode 100644 drivers/w1/slaves/w1_ds28e17.c (limited to 'Documentation/ABI/testing') diff --git a/Documentation/ABI/testing/sysfs-driver-w1_ds28e17 b/Documentation/ABI/testing/sysfs-driver-w1_ds28e17 new file mode 100644 index 000000000000..d301e7017afe --- /dev/null +++ b/Documentation/ABI/testing/sysfs-driver-w1_ds28e17 @@ -0,0 +1,21 @@ +What: /sys/bus/w1/devices/19-/speed +Date: Sep 2017 +KernelVersion: 4.14 +Contact: Jan Kandziora +Description: When written, this file sets the I2C speed on the connected + DS28E17 chip. When read, it reads the current setting from + the DS28E17 chip. + Valid values: 100, 400, 900 [kBaud]. + Default 100, can be set by w1_ds28e17.speed= module parameter. +Users: w1_ds28e17 driver + +What: /sys/bus/w1/devices/19-/stretch +Date: Sep 2017 +KernelVersion: 4.14 +Contact: Jan Kandziora +Description: When written, this file sets the multiplier used to calculate + the busy timeout for I2C operations on the connected DS28E17 + chip. When read, returns the current setting. + Valid values: 1 to 9. + Default 1, can be set by w1_ds28e17.stretch= module parameter. +Users: w1_ds28e17 driver diff --git a/Documentation/w1/slaves/00-INDEX b/Documentation/w1/slaves/00-INDEX index 8d76718e1ea2..68946f83e579 100644 --- a/Documentation/w1/slaves/00-INDEX +++ b/Documentation/w1/slaves/00-INDEX @@ -10,3 +10,5 @@ w1_ds2438 - The Maxim/Dallas Semiconductor ds2438 smart battery monitor. w1_ds28e04 - The Maxim/Dallas Semiconductor ds28e04 eeprom. +w1_ds28e17 + - The Maxim/Dallas Semiconductor ds28e17 1-Wire-to-I2C Master Bridge. diff --git a/Documentation/w1/slaves/w1_ds28e17 b/Documentation/w1/slaves/w1_ds28e17 new file mode 100644 index 000000000000..7fcfad5b4a37 --- /dev/null +++ b/Documentation/w1/slaves/w1_ds28e17 @@ -0,0 +1,68 @@ +Kernel driver w1_ds28e17 +======================== + +Supported chips: + * Maxim DS28E17 1-Wire-to-I2C Master Bridge + +supported family codes: + W1_FAMILY_DS28E17 0x19 + +Author: Jan Kandziora + + +Description +----------- +The DS28E17 is a Onewire slave device which acts as an I2C bus master. + +This driver creates a new I2C bus for any DS28E17 device detected. I2C buses +come and go as the DS28E17 devices come and go. I2C slave devices connected to +a DS28E17 can be accessed by the kernel or userspace tools as if they were +connected to a "native" I2C bus master. + + +An udev rule like the following +------------------------------------------------------------------------------- +SUBSYSTEM=="i2c-dev", KERNEL=="i2c-[0-9]*", ATTRS{name}=="w1-19-*", \ + SYMLINK+="i2c-$attr{name}" +------------------------------------------------------------------------------- +may be used to create stable /dev/i2c- entries based on the unique id of the +DS28E17 chip. + + +Driver parameters are: + +speed: + This sets up the default I2C speed a DS28E17 get configured for as soon + it is connected. The power-on default of the DS28E17 is 400kBaud, but + chips may come and go on the Onewire bus without being de-powered and + as soon the "w1_ds28e17" driver notices a freshly connected, or + reconnected DS28E17 device on the Onewire bus, it will re-apply this + setting. + + Valid values are 100, 400, 900 [kBaud]. Any other value means to leave + alone the current DS28E17 setting on detect. The default value is 100. + +stretch: + This sets up the default stretch value used for freshly connected + DS28E17 devices. It is a multiplier used on the calculation of the busy + wait time for an I2C transfer. This is to account for I2C slave devices + which make heavy use of the I2C clock stretching feature and thus, the + needed timeout cannot be pre-calculated correctly. As the w1_ds28e17 + driver checks the DS28E17's busy flag in a loop after the precalculated + wait time, it should be hardly needed to tweak this setting. + + Leave it at 1 unless you get ETIMEDOUT errors and a "w1_slave_driver + 19-00000002dbd8: busy timeout" in the kernel log. + + Valid values are 1 to 9. The default is 1. + + +The driver creates sysfs files /sys/bus/w1/devices/19-/speed and +/sys/bus/w1/devices/19-/stretch for each device, preloaded with the default +settings from the driver parameters. They may be changed anytime. In addition a +directory /sys/bus/w1/devices/19-/i2c- for the I2C bus master sysfs +structure is created. + + +See https://github.com/ianka/w1_ds28e17 for even more information. + diff --git a/drivers/w1/slaves/Kconfig b/drivers/w1/slaves/Kconfig index 3c945f9f5f0f..7931231d8e80 100644 --- a/drivers/w1/slaves/Kconfig +++ b/drivers/w1/slaves/Kconfig @@ -148,4 +148,19 @@ config W1_SLAVE_DS28E04 If you are unsure, say N. +config W1_SLAVE_DS28E17 + tristate "1-wire-to-I2C master bridge (DS28E17)" + select CRC16 + depends on I2C + help + Say Y here if you want to use the DS28E17 1-wire-to-I2C master bridge. + For each DS28E17 detected, a new I2C adapter is created within the + kernel. I2C devices on that bus can be configured to be used by the + kernel and userspace tools as on any other "native" I2C bus. + + This driver is also available as a module. If so, the module + will be called w1_ds28e17. + + If you are unsure, say N. + endmenu diff --git a/drivers/w1/slaves/Makefile b/drivers/w1/slaves/Makefile index 36b22fb2d3a1..855371a99e6a 100644 --- a/drivers/w1/slaves/Makefile +++ b/drivers/w1/slaves/Makefile @@ -17,3 +17,4 @@ obj-$(CONFIG_W1_SLAVE_DS2760) += w1_ds2760.o obj-$(CONFIG_W1_SLAVE_DS2780) += w1_ds2780.o obj-$(CONFIG_W1_SLAVE_DS2781) += w1_ds2781.o obj-$(CONFIG_W1_SLAVE_DS28E04) += w1_ds28e04.o +obj-$(CONFIG_W1_SLAVE_DS28E17) += w1_ds28e17.o diff --git a/drivers/w1/slaves/w1_ds28e17.c b/drivers/w1/slaves/w1_ds28e17.c new file mode 100644 index 000000000000..e78b63ea4daf --- /dev/null +++ b/drivers/w1/slaves/w1_ds28e17.c @@ -0,0 +1,771 @@ +/* + * w1_ds28e17.c - w1 family 19 (DS28E17) driver + * + * Copyright (c) 2016 Jan Kandziora + * + * This source code is licensed under the GNU General Public License, + * Version 2. See the file COPYING for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define CRC16_INIT 0 + +#include + +#define W1_FAMILY_DS28E17 0x19 + +/* Module setup. */ +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Jan Kandziora "); +MODULE_DESCRIPTION("w1 family 19 driver for DS28E17, 1-wire to I2C master bridge"); +MODULE_ALIAS("w1-family-" __stringify(W1_FAMILY_DS28E17)); + + +/* Default I2C speed to be set when a DS28E17 is detected. */ +static int i2c_speed = 100; +module_param_named(speed, i2c_speed, int, (S_IRUSR | S_IWUSR)); +MODULE_PARM_DESC(speed, "Default I2C speed to be set when a DS28E17 is detected"); + +/* Default I2C stretch value to be set when a DS28E17 is detected. */ +static char i2c_stretch = 1; +module_param_named(stretch, i2c_stretch, byte, (S_IRUSR | S_IWUSR)); +MODULE_PARM_DESC(stretch, "Default I2C stretch value to be set when a DS28E17 is detected"); + +/* DS28E17 device command codes. */ +#define W1_F19_WRITE_DATA_WITH_STOP 0x4B +#define W1_F19_WRITE_DATA_NO_STOP 0x5A +#define W1_F19_WRITE_DATA_ONLY 0x69 +#define W1_F19_WRITE_DATA_ONLY_WITH_STOP 0x78 +#define W1_F19_READ_DATA_WITH_STOP 0x87 +#define W1_F19_WRITE_READ_DATA_WITH_STOP 0x2D +#define W1_F19_WRITE_CONFIGURATION 0xD2 +#define W1_F19_READ_CONFIGURATION 0xE1 +#define W1_F19_ENABLE_SLEEP_MODE 0x1E +#define W1_F19_READ_DEVICE_REVISION 0xC4 + +/* DS28E17 status bits */ +#define W1_F19_STATUS_CRC 0x01 +#define W1_F19_STATUS_ADDRESS 0x02 +#define W1_F19_STATUS_START 0x08 + +/* + * Maximum number of I2C bytes to transfer within one CRC16 protected onewire + * command. + * */ +#define W1_F19_WRITE_DATA_LIMIT 255 + +/* Maximum number of I2C bytes to read with one onewire command. */ +#define W1_F19_READ_DATA_LIMIT 255 + +/* Constants for calculating the busy sleep. */ +#define W1_F19_BUSY_TIMEBASES { 90, 23, 10 } +#define W1_F19_BUSY_GRATUITY 1000 + +/* Number of checks for the busy flag before timeout. */ +#define W1_F19_BUSY_CHECKS 1000 + + +/* Slave specific data. */ +struct w1_f19_data { + u8 speed; + u8 stretch; + struct i2c_adapter adapter; +}; + + +/* Wait a while until the busy flag clears. */ +static int w1_f19_i2c_busy_wait(struct w1_slave *sl, size_t count) +{ + const unsigned long timebases[3] = W1_F19_BUSY_TIMEBASES; + struct w1_f19_data *data = sl->family_data; + unsigned int checks; + + /* Check the busy flag first in any case.*/ + if (w1_touch_bit(sl->master, 1) == 0) + return 0; + + /* + * Do a generously long sleep in the beginning, + * as we have to wait at least this time for all + * the I2C bytes at the given speed to be transferred. + */ + usleep_range(timebases[data->speed] * (data->stretch) * count, + timebases[data->speed] * (data->stretch) * count + + W1_F19_BUSY_GRATUITY); + + /* Now continusly check the busy flag sent by the DS28E17. */ + checks = W1_F19_BUSY_CHECKS; + while ((checks--) > 0) { + /* Return success if the busy flag is cleared. */ + if (w1_touch_bit(sl->master, 1) == 0) + return 0; + + /* Wait one non-streched byte timeslot. */ + udelay(timebases[data->speed]); + } + + /* Timeout. */ + dev_warn(&sl->dev, "busy timeout\n"); + return -ETIMEDOUT; +} + + +/* Utility function: result. */ +static size_t w1_f19_error(struct w1_slave *sl, u8 w1_buf[]) +{ + /* Warnings. */ + if (w1_buf[0] & W1_F19_STATUS_CRC) + dev_warn(&sl->dev, "crc16 mismatch\n"); + if (w1_buf[0] & W1_F19_STATUS_ADDRESS) + dev_warn(&sl->dev, "i2c device not responding\n"); + if ((w1_buf[0] & (W1_F19_STATUS_CRC | W1_F19_STATUS_ADDRESS)) == 0 + && w1_buf[1] != 0) { + dev_warn(&sl->dev, "i2c short write, %d bytes not acknowledged\n", + w1_buf[1]); + } + + /* Check error conditions. */ + if (w1_buf[0] & W1_F19_STATUS_ADDRESS) + return -ENXIO; + if (w1_buf[0] & W1_F19_STATUS_START) + return -EAGAIN; + if (w1_buf[0] != 0 || w1_buf[1] != 0) + return -EIO; + + /* All ok. */ + return 0; +} + + +/* Utility function: write data to I2C slave, single chunk. */ +static int __w1_f19_i2c_write(struct w1_slave *sl, + const u8 *command, size_t command_count, + const u8 *buffer, size_t count) +{ + u16 crc; + int error; + u8 w1_buf[2]; + + /* Send command and I2C data to DS28E17. */ + crc = crc16(CRC16_INIT, command, command_count); + w1_write_block(sl->master, command, command_count); + + w1_buf[0] = count; + crc = crc16(crc, w1_buf, 1); + w1_write_8(sl->master, w1_buf[0]); + + crc = crc16(crc, buffer, count); + w1_write_block(sl->master, buffer, count); + + w1_buf[0] = ~(crc & 0xFF); + w1_buf[1] = ~((crc >> 8) & 0xFF); + w1_write_block(sl->master, w1_buf, 2); + + /* Wait until busy flag clears (or timeout). */ + if (w1_f19_i2c_busy_wait(sl, count + 1) < 0) + return -ETIMEDOUT; + + /* Read status from DS28E17. */ + w1_read_block(sl->master, w1_buf, 2); + + /* Check error conditions. */ + error = w1_f19_error(sl, w1_buf); + if (error < 0) + return error; + + /* Return number of bytes written. */ + return count; +} + + +/* Write data to I2C slave. */ +static int w1_f19_i2c_write(struct w1_slave *sl, u16 i2c_address, + const u8 *buffer, size_t count, bool stop) +{ + int result; + int remaining = count; + const u8 *p; + u8 command[2]; + + /* Check input. */ + if (count == 0) + return -EOPNOTSUPP; + + /* Check whether we need multiple commands. */ + if (count <= W1_F19_WRITE_DATA_LIMIT) { + /* + * Small data amount. Data can be sent with + * a single onewire command. + */ + + /* Send all data to DS28E17. */ + command[0] = (stop ? W1_F19_WRITE_DATA_WITH_STOP + : W1_F19_WRITE_DATA_NO_STOP); + command[1] = i2c_address << 1; + result = __w1_f19_i2c_write(sl, command, 2, buffer, count); + } else { + /* Large data amount. Data has to be sent in multiple chunks. */ + + /* Send first chunk to DS28E17. */ + p = buffer; + command[0] = W1_F19_WRITE_DATA_NO_STOP; + command[1] = i2c_address << 1; + result = __w1_f19_i2c_write(sl, command, 2, p, + W1_F19_WRITE_DATA_LIMIT); + if (result < 0) + return result; + + /* Resume to same DS28E17. */ + if (w1_reset_resume_command(sl->master)) + return -EIO; + + /* Next data chunk. */ + p += W1_F19_WRITE_DATA_LIMIT; + remaining -= W1_F19_WRITE_DATA_LIMIT; + + while (remaining > W1_F19_WRITE_DATA_LIMIT) { + /* Send intermediate chunk to DS28E17. */ + command[0] = W1_F19_WRITE_DATA_ONLY; + result = __w1_f19_i2c_write(sl, command, 1, p, + W1_F19_WRITE_DATA_LIMIT); + if (result < 0) + return result; + + /* Resume to same DS28E17. */ + if (w1_reset_resume_command(sl->master)) + return -EIO; + + /* Next data chunk. */ + p += W1_F19_WRITE_DATA_LIMIT; + remaining -= W1_F19_WRITE_DATA_LIMIT; + } + + /* Send final chunk to DS28E17. */ + command[0] = (stop ? W1_F19_WRITE_DATA_ONLY_WITH_STOP + : W1_F19_WRITE_DATA_ONLY); + result = __w1_f19_i2c_write(sl, command, 1, p, remaining); + } + + return result; +} + + +/* Read data from I2C slave. */ +static int w1_f19_i2c_read(struct w1_slave *sl, u16 i2c_address, + u8 *buffer, size_t count) +{ + u16 crc; + int error; + u8 w1_buf[5]; + + /* Check input. */ + if (count == 0) + return -EOPNOTSUPP; + + /* Send command to DS28E17. */ + w1_buf[0] = W1_F19_READ_DATA_WITH_STOP; + w1_buf[1] = i2c_address << 1 | 0x01; + w1_buf[2] = count; + crc = crc16(CRC16_INIT, w1_buf, 3); + w1_buf[3] = ~(crc & 0xFF); + w1_buf[4] = ~((crc >> 8) & 0xFF); + w1_write_block(sl->master, w1_buf, 5); + + /* Wait until busy flag clears (or timeout). */ + if (w1_f19_i2c_busy_wait(sl, count + 1) < 0) + return -ETIMEDOUT; + + /* Read status from DS28E17. */ + w1_buf[0] = w1_read_8(sl->master); + w1_buf[1] = 0; + + /* Check error conditions. */ + error = w1_f19_error(sl, w1_buf); + if (error < 0) + return error; + + /* Read received I2C data from DS28E17. */ + return w1_read_block(sl->master, buffer, count); +} + + +/* Write to, then read data from I2C slave. */ +static int w1_f19_i2c_write_read(struct w1_slave *sl, u16 i2c_address, + const u8 *wbuffer, size_t wcount, u8 *rbuffer, size_t rcount) +{ + u16 crc; + int error; + u8 w1_buf[3]; + + /* Check input. */ + if (wcount == 0 || rcount == 0) + return -EOPNOTSUPP; + + /* Send command and I2C data to DS28E17. */ + w1_buf[0] = W1_F19_WRITE_READ_DATA_WITH_STOP; + w1_buf[1] = i2c_address << 1; + w1_buf[2] = wcount; + crc = crc16(CRC16_INIT, w1_buf, 3); + w1_write_block(sl->master, w1_buf, 3); + + crc = crc16(crc, wbuffer, wcount); + w1_write_block(sl->master, wbuffer, wcount); + + w1_buf[0] = rcount; + crc = crc16(crc, w1_buf, 1); + w1_buf[1] = ~(crc & 0xFF); + w1_buf[2] = ~((crc >> 8) & 0xFF); + w1_write_block(sl->master, w1_buf, 3); + + /* Wait until busy flag clears (or timeout). */ + if (w1_f19_i2c_busy_wait(sl, wcount + rcount + 2) < 0) + return -ETIMEDOUT; + + /* Read status from DS28E17. */ + w1_read_block(sl->master, w1_buf, 2); + + /* Check error conditions. */ + error = w1_f19_error(sl, w1_buf); + if (error < 0) + return error; + + /* Read received I2C data from DS28E17. */ + return w1_read_block(sl->master, rbuffer, rcount); +} + + +/* Do an I2C master transfer. */ +static int w1_f19_i2c_master_transfer(struct i2c_adapter *adapter, + struct i2c_msg *msgs, int num) +{ + struct w1_slave *sl = (struct w1_slave *) adapter->algo_data; + int i = 0; + int result = 0; + + /* Start onewire transaction. */ + mutex_lock(&sl->master->bus_mutex); + + /* Select DS28E17. */ + if (w1_reset_select_slave(sl)) { + i = -EIO; + goto error; + } + + /* Loop while there are still messages to transfer. */ + while (i < num) { + /* + * Check for special case: Small write followed + * by read to same I2C device. + */ + if (i < (num-1) + && msgs[i].addr == msgs[i+1].addr + && !(msgs[i].flags & I2C_M_RD) + && (msgs[i+1].flags & I2C_M_RD) + && (msgs[i].len <= W1_F19_WRITE_DATA_LIMIT)) { + /* + * The DS28E17 has a combined transfer + * for small write+read. + */ + result = w1_f19_i2c_write_read(sl, msgs[i].addr, + msgs[i].buf, msgs[i].len, + msgs[i+1].buf, msgs[i+1].len); + if (result < 0) { + i = result; + goto error; + } + + /* + * Check if we should interpret the read data + * as a length byte. The DS28E17 unfortunately + * has no read without stop, so we can just do + * another simple read in that case. + */ + if (msgs[i+1].flags & I2C_M_RECV_LEN) { + result = w1_f19_i2c_read(sl, msgs[i+1].addr, + &(msgs[i+1].buf[1]), msgs[i+1].buf[0]); + if (result < 0) { + i = result; + goto error; + } + } + + /* Eat up read message, too. */ + i++; + } else if (msgs[i].flags & I2C_M_RD) { + /* Read transfer. */ + result = w1_f19_i2c_read(sl, msgs[i].addr, + msgs[i].buf, msgs[i].len); + if (result < 0) { + i = result; + goto error; + } + + /* + * Check if we should interpret the read data + * as a length byte. The DS28E17 unfortunately + * has no read without stop, so we can just do + * another simple read in that case. + */ + if (msgs[i].flags & I2C_M_RECV_LEN) { + result = w1_f19_i2c_read(sl, + msgs[i].addr, + &(msgs[i].buf[1]), + msgs[i].buf[0]); + if (result < 0) { + i = result; + goto error; + } + } + } else { + /* + * Write transfer. + * Stop condition only for last + * transfer. + */ + result = w1_f19_i2c_write(sl, + msgs[i].addr, + msgs[i].buf, + msgs[i].len, + i == (num-1)); + if (result < 0) { + i = result; + goto error; + } + } + + /* Next message. */ + i++; + + /* Are there still messages to send/receive? */ + if (i < num) { + /* Yes. Resume to same DS28E17. */ + if (w1_reset_resume_command(sl->master)) { + i = -EIO; + goto error; + } + } + } + +error: + /* End onewire transaction. */ + mutex_unlock(&sl->master->bus_mutex); + + /* Return number of messages processed or error. */ + return i; +} + + +/* Get I2C adapter functionality. */ +static u32 w1_f19_i2c_functionality(struct i2c_adapter *adapter) +{ + /* + * Plain I2C functions only. + * SMBus is emulated by the kernel's I2C layer. + * No "I2C_FUNC_SMBUS_QUICK" + * No "I2C_FUNC_SMBUS_READ_BLOCK_DATA" + * No "I2C_FUNC_SMBUS_BLOCK_PROC_CALL" + */ + return I2C_FUNC_I2C | + I2C_FUNC_SMBUS_BYTE | + I2C_FUNC_SMBUS_BYTE_DATA | + I2C_FUNC_SMBUS_WORD_DATA | + I2C_FUNC_SMBUS_PROC_CALL | + I2C_FUNC_SMBUS_WRITE_BLOCK_DATA | + I2C_FUNC_SMBUS_I2C_BLOCK | + I2C_FUNC_SMBUS_PEC; +} + + +/* I2C adapter quirks. */ +static const struct i2c_adapter_quirks w1_f19_i2c_adapter_quirks = { + .max_read_len = W1_F19_READ_DATA_LIMIT, +}; + +/* I2C algorithm. */ +static const struct i2c_algorithm w1_f19_i2c_algorithm = { + .master_xfer = w1_f19_i2c_master_transfer, + .functionality = w1_f19_i2c_functionality, +}; + + +/* Read I2C speed from DS28E17. */ +static int w1_f19_get_i2c_speed(struct w1_slave *sl) +{ + struct w1_f19_data *data = sl->family_data; + int result = -EIO; + + /* Start onewire transaction. */ + mutex_lock(&sl->master->bus_mutex); + + /* Select slave. */ + if (w1_reset_select_slave(sl)) + goto error; + + /* Read slave configuration byte. */ + w1_write_8(sl->master, W1_F19_READ_CONFIGURATION); + result = w1_read_8(sl->master); + if (result < 0 || result > 2) { + result = -EIO; + goto error; + } + + /* Update speed in slave specific data. */ + data->speed = result; + +error: + /* End onewire transaction. */ + mutex_unlock(&sl->master->bus_mutex); + + return result; +} + + +/* Set I2C speed on DS28E17. */ +static int __w1_f19_set_i2c_speed(struct w1_slave *sl, u8 speed) +{ + struct w1_f19_data *data = sl->family_data; + const int i2c_speeds[3] = { 100, 400, 900 }; + u8 w1_buf[2]; + + /* Select slave. */ + if (w1_reset_select_slave(sl)) + return -EIO; + + w1_buf[0] = W1_F19_WRITE_CONFIGURATION; + w1_buf[1] = speed; + w1_write_block(sl->master, w1_buf, 2); + + /* Update speed in slave specific data. */ + data->speed = speed; + + dev_info(&sl->dev, "i2c speed set to %d kBaud\n", i2c_speeds[speed]); + + return 0; +} + +static int w1_f19_set_i2c_speed(struct w1_slave *sl, u8 speed) +{ + int result; + + /* Start onewire transaction. */ + mutex_lock(&sl->master->bus_mutex); + + /* Set I2C speed on DS28E17. */ + result = __w1_f19_set_i2c_speed(sl, speed); + + /* End onewire transaction. */ + mutex_unlock(&sl->master->bus_mutex); + + return result; +} + + +/* Sysfs attributes. */ + +/* I2C speed attribute for a single chip. */ +static ssize_t speed_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct w1_slave *sl = dev_to_w1_slave(dev); + int result; + + /* Read current speed from slave. Updates data->speed. */ + result = w1_f19_get_i2c_speed(sl); + if (result < 0) + return result; + + /* Return current speed value. */ + return sprintf(buf, "%d\n", result); +} + +static ssize_t speed_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct w1_slave *sl = dev_to_w1_slave(dev); + int error; + + /* Valid values are: "100", "400", "900" */ + if (count < 3 || count > 4 || !buf) + return -EINVAL; + if (count == 4 && buf[3] != '\n') + return -EINVAL; + if (buf[1] != '0' || buf[2] != '0') + return -EINVAL; + + /* Set speed on slave. */ + switch (buf[0]) { + case '1': + error = w1_f19_set_i2c_speed(sl, 0); + break; + case '4': + error = w1_f19_set_i2c_speed(sl, 1); + break; + case '9': + error = w1_f19_set_i2c_speed(sl, 2); + break; + default: + return -EINVAL; + } + + if (error < 0) + return error; + + /* Return bytes written. */ + return count; +} + +static DEVICE_ATTR_RW(speed); + + +/* Busy stretch attribute for a single chip. */ +static ssize_t stretch_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct w1_slave *sl = dev_to_w1_slave(dev); + struct w1_f19_data *data = sl->family_data; + + /* Return current stretch value. */ + return sprintf(buf, "%d\n", data->stretch); +} + +static ssize_t stretch_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct w1_slave *sl = dev_to_w1_slave(dev); + struct w1_f19_data *data = sl->family_data; + + /* Valid values are '1' to '9' */ + if (count < 1 || count > 2 || !buf) + return -EINVAL; + if (count == 2 && buf[1] != '\n') + return -EINVAL; + if (buf[0] < '1' || buf[0] > '9') + return -EINVAL; + + /* Set busy stretch value. */ + data->stretch = buf[0] & 0x0F; + + /* Return bytes written. */ + return count; +} + +static DEVICE_ATTR_RW(stretch); + + +/* All attributes. */ +static struct attribute *w1_f19_attrs[] = { + &dev_attr_speed.attr, + &dev_attr_stretch.attr, + NULL, +}; + +static const struct attribute_group w1_f19_group = { + .attrs = w1_f19_attrs, +}; + +static const struct attribute_group *w1_f19_groups[] = { + &w1_f19_group, + NULL, +}; + + +/* Slave add and remove functions. */ +static int w1_f19_add_slave(struct w1_slave *sl) +{ + struct w1_f19_data *data = NULL; + + /* Allocate memory for slave specific data. */ + data = devm_kzalloc(&sl->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + sl->family_data = data; + + /* Setup default I2C speed on slave. */ + switch (i2c_speed) { + case 100: + __w1_f19_set_i2c_speed(sl, 0); + break; + case 400: + __w1_f19_set_i2c_speed(sl, 1); + break; + case 900: + __w1_f19_set_i2c_speed(sl, 2); + break; + default: + /* + * A i2c_speed module parameter of anything else + * than 100, 400, 900 means not to touch the + * speed of the DS28E17. + * We assume 400kBaud, the power-on value. + */ + data->speed = 1; + } + + /* + * Setup default busy stretch + * configuration for the DS28E17. + */ + data->stretch = i2c_stretch; + + /* Setup I2C adapter. */ + data->adapter.owner = THIS_MODULE; + data->adapter.algo = &w1_f19_i2c_algorithm; + data->adapter.algo_data = sl; + strcpy(data->adapter.name, "w1-"); + strcat(data->adapter.name, sl->name); + data->adapter.dev.parent = &sl->dev; + data->adapter.quirks = &w1_f19_i2c_adapter_quirks; + + return i2c_add_adapter(&data->adapter); +} + +static void w1_f19_remove_slave(struct w1_slave *sl) +{ + struct w1_f19_data *family_data = sl->family_data; + + /* Delete I2C adapter. */ + i2c_del_adapter(&family_data->adapter); + + /* Free slave specific data. */ + devm_kfree(&sl->dev, family_data); + sl->family_data = NULL; +} + + +/* Declarations within the w1 subsystem. */ +static struct w1_family_ops w1_f19_fops = { + .add_slave = w1_f19_add_slave, + .remove_slave = w1_f19_remove_slave, + .groups = w1_f19_groups, +}; + +static struct w1_family w1_family_19 = { + .fid = W1_FAMILY_DS28E17, + .fops = &w1_f19_fops, +}; + + +/* Module init and remove functions. */ +static int __init w1_f19_init(void) +{ + return w1_register_family(&w1_family_19); +} + +static void __exit w1_f19_fini(void) +{ + w1_unregister_family(&w1_family_19); +} + +module_init(w1_f19_init); +module_exit(w1_f19_fini); + -- cgit v1.2.3-59-g8ed1b From 3ba9b1b814fe37ba3267b58917a73dc69eebde40 Mon Sep 17 00:00:00 2001 From: Tom Saeger Date: Tue, 10 Oct 2017 12:36:16 -0500 Subject: Documentation: fix admin-guide doc refs Make admin-guide document refs valid. Signed-off-by: Tom Saeger Acked-by: Rafael J. Wysocki Signed-off-by: Jonathan Corbet --- Documentation/ABI/stable/sysfs-devices | 2 +- Documentation/ABI/testing/sysfs-devices-system-cpu | 6 ++++-- Documentation/ABI/testing/sysfs-power | 6 ++++-- Documentation/admin-guide/README.rst | 2 +- Documentation/admin-guide/kernel-parameters.txt | 2 +- Documentation/admin-guide/reporting-bugs.rst | 4 ++-- Documentation/laptops/laptop-mode.txt | 6 +++--- Documentation/media/v4l-drivers/bttv.rst | 2 +- Documentation/power/interface.txt | 3 ++- 9 files changed, 19 insertions(+), 14 deletions(-) (limited to 'Documentation/ABI/testing') diff --git a/Documentation/ABI/stable/sysfs-devices b/Documentation/ABI/stable/sysfs-devices index 35c457f8ce73..4404bd9b96c1 100644 --- a/Documentation/ABI/stable/sysfs-devices +++ b/Documentation/ABI/stable/sysfs-devices @@ -1,5 +1,5 @@ # Note: This documents additional properties of any device beyond what -# is documented in Documentation/sysfs-rules.txt +# is documented in Documentation/admin-guide/sysfs-rules.rst What: /sys/devices/*/of_node Date: February 2015 diff --git a/Documentation/ABI/testing/sysfs-devices-system-cpu b/Documentation/ABI/testing/sysfs-devices-system-cpu index f3d5817c4ef0..d6d862db3b5d 100644 --- a/Documentation/ABI/testing/sysfs-devices-system-cpu +++ b/Documentation/ABI/testing/sysfs-devices-system-cpu @@ -187,7 +187,8 @@ Description: Processor frequency boosting control This switch controls the boost setting for the whole system. Boosting allows the CPU and the firmware to run at a frequency beyound it's nominal limit. - More details can be found in Documentation/cpu-freq/boost.txt + More details can be found in + Documentation/admin-guide/pm/cpufreq.rst What: /sys/devices/system/cpu/cpu#/crash_notes @@ -223,7 +224,8 @@ Description: Parameters for the Intel P-state driver no_turbo: limits the driver to selecting P states below the turbo frequency range. - More details can be found in Documentation/cpu-freq/intel-pstate.txt + More details can be found in + Documentation/admin-guide/pm/intel_pstate.rst What: /sys/devices/system/cpu/cpu*/cache/index*/ Date: July 2014(documented, existed before August 2008) diff --git a/Documentation/ABI/testing/sysfs-power b/Documentation/ABI/testing/sysfs-power index 713cab1d5f12..4b2e13e5f020 100644 --- a/Documentation/ABI/testing/sysfs-power +++ b/Documentation/ABI/testing/sysfs-power @@ -18,7 +18,8 @@ Description: Writing one of the above strings to this file causes the system to transition into the corresponding state, if available. - See Documentation/power/states.txt for more information. + See Documentation/admin-guide/pm/sleep-states.rst for more + information. What: /sys/power/mem_sleep Date: November 2016 @@ -35,7 +36,8 @@ Description: represented by it to be used on subsequent attempts to suspend the system. - See Documentation/power/states.txt for more information. + See Documentation/admin-guide/pm/sleep-states.rst for more + information. What: /sys/power/disk Date: September 2006 diff --git a/Documentation/admin-guide/README.rst b/Documentation/admin-guide/README.rst index b5343c5aa224..63066db39910 100644 --- a/Documentation/admin-guide/README.rst +++ b/Documentation/admin-guide/README.rst @@ -350,7 +350,7 @@ If something goes wrong help debugging the problem. The text above the dump is also important: it tells something about why the kernel dumped code (in the above example, it's due to a bad kernel pointer). More information - on making sense of the dump is in Documentation/admin-guide/oops-tracing.rst + on making sense of the dump is in Documentation/admin-guide/bug-hunting.rst - If you compiled the kernel with CONFIG_KALLSYMS you can send the dump as is, otherwise you will have to use the ``ksymoops`` program to make diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt index a3427399fd07..957b931cc84c 100644 --- a/Documentation/admin-guide/kernel-parameters.txt +++ b/Documentation/admin-guide/kernel-parameters.txt @@ -3134,7 +3134,7 @@ plip= [PPT,NET] Parallel port network link Format: { parport | timid | 0 } - See also Documentation/parport.txt. + See also Documentation/admin-guide/parport.rst. pmtmr= [X86] Manual setup of pmtmr I/O Port. Override pmtimer IOPort with a hex value. diff --git a/Documentation/admin-guide/reporting-bugs.rst b/Documentation/admin-guide/reporting-bugs.rst index 26b60b419652..4650edb8840a 100644 --- a/Documentation/admin-guide/reporting-bugs.rst +++ b/Documentation/admin-guide/reporting-bugs.rst @@ -94,7 +94,7 @@ step-by-step instructions for how a user can trigger the bug. If the failure includes an "OOPS:", take a picture of the screen, capture a netconsole trace, or type the message from your screen into the bug -report. Please read "Documentation/admin-guide/oops-tracing.rst" before posting your +report. Please read "Documentation/admin-guide/bug-hunting.rst" before posting your bug report. This explains what you should do with the "Oops" information to make it useful to the recipient. @@ -120,7 +120,7 @@ summary from [1.]>" for easy identification by the developers:: [4.2.] Kernel .config file: [5.] Most recent kernel version which did not have the bug: [6.] Output of Oops.. message (if applicable) with symbolic information - resolved (see Documentation/admin-guide/oops-tracing.rst) + resolved (see Documentation/admin-guide/bug-hunting.rst) [7.] A small shell script or example program which triggers the problem (if possible) [8.] Environment diff --git a/Documentation/laptops/laptop-mode.txt b/Documentation/laptops/laptop-mode.txt index 19276f5d195c..1c707fc9b141 100644 --- a/Documentation/laptops/laptop-mode.txt +++ b/Documentation/laptops/laptop-mode.txt @@ -184,7 +184,7 @@ is done when dirty_ratio is reached. DO_CPU: Enable CPU frequency scaling when in laptop mode. (Requires CPUFreq to be setup. -See Documentation/cpu-freq/user-guide.txt for more info. Disabled by default.) +See Documentation/admin-guide/pm/cpufreq.rst for more info. Disabled by default.) CPU_MAXFREQ: @@ -287,7 +287,7 @@ MINIMUM_BATTERY_MINUTES=10 # Should the maximum CPU frequency be adjusted down while on battery? # Requires CPUFreq to be setup. -# See Documentation/cpu-freq/user-guide.txt for more info +# See Documentation/admin-guide/pm/cpufreq.rst for more info #DO_CPU=0 # When on battery what is the maximum CPU speed that the system should @@ -378,7 +378,7 @@ BATT_HD=${BATT_HD:-'4'} DIRTY_RATIO=${DIRTY_RATIO:-'40'} # cpu frequency scaling -# See Documentation/cpu-freq/user-guide.txt for more info +# See Documentation/admin-guide/pm/cpufreq.rst for more info DO_CPU=${CPU_MANAGE:-'0'} CPU_MAXFREQ=${CPU_MAXFREQ:-'slowest'} diff --git a/Documentation/media/v4l-drivers/bttv.rst b/Documentation/media/v4l-drivers/bttv.rst index 195ccaac2816..5f35e2fb5afa 100644 --- a/Documentation/media/v4l-drivers/bttv.rst +++ b/Documentation/media/v4l-drivers/bttv.rst @@ -307,7 +307,7 @@ console and let some terminal application log the messages. /me uses screen. See Documentation/admin-guide/serial-console.rst for details on setting up a serial console. -Read Documentation/admin-guide/oops-tracing.rst to learn how to get any useful +Read Documentation/admin-guide/bug-hunting.rst to learn how to get any useful information out of a register+stack dump printed by the kernel on protection faults (so-called "kernel oops"). diff --git a/Documentation/power/interface.txt b/Documentation/power/interface.txt index 974916ff6608..7dc75f48e8bd 100644 --- a/Documentation/power/interface.txt +++ b/Documentation/power/interface.txt @@ -24,7 +24,8 @@ platform. If one of the strings listed in /sys/power/state is written to it, the system will attempt to transition into the corresponding sleep state. Refer to -Documentation/power/states.txt for a description of each of those states. +Documentation/admin-guide/pm/sleep-states.rst for a description of each of +those states. /sys/power/disk controls the operating mode of hibernation (Suspend-to-Disk). Specifically, it tells the kernel what to do after creating a hibernation image. -- cgit v1.2.3-59-g8ed1b From c7f66400f504fd54bda6ec644853c07333e8cb87 Mon Sep 17 00:00:00 2001 From: Tom Saeger Date: Tue, 10 Oct 2017 12:36:30 -0500 Subject: Documentation: fix security related doc refs Make security document refs valid. Signed-off-by: Tom Saeger Signed-off-by: Jonathan Corbet --- Documentation/ABI/testing/evm | 4 ++-- Documentation/security/LSM.rst | 2 +- Documentation/security/credentials.rst | 2 +- Documentation/security/keys/request-key.rst | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) (limited to 'Documentation/ABI/testing') diff --git a/Documentation/ABI/testing/evm b/Documentation/ABI/testing/evm index 8374d4557e5d..ca622c9aa24c 100644 --- a/Documentation/ABI/testing/evm +++ b/Documentation/ABI/testing/evm @@ -18,6 +18,6 @@ Description: in the initramfs, which has already been measured as part of the trusted boot. For more information on creating and loading existing trusted/encrypted keys, refer to: - Documentation/keys-trusted-encrypted.txt. (A sample dracut - patch, which loads the trusted/encrypted key and enables + Documentation/security/keys/trusted-encrypted.rst. (A sample + dracut patch, which loads the trusted/encrypted key and enables EVM, is available from http://linux-ima.sourceforge.net/#EVM.) diff --git a/Documentation/security/LSM.rst b/Documentation/security/LSM.rst index d75778b0fa10..98522e0e1ee2 100644 --- a/Documentation/security/LSM.rst +++ b/Documentation/security/LSM.rst @@ -5,7 +5,7 @@ Linux Security Module Development Based on https://lkml.org/lkml/2007/10/26/215, a new LSM is accepted into the kernel when its intent (a description of what it tries to protect against and in what cases one would expect to -use it) has been appropriately documented in ``Documentation/security/LSM``. +use it) has been appropriately documented in ``Documentation/security/LSM.rst``. This allows an LSM's code to be easily compared to its goals, and so that end users and distros can make a more informed decision about which LSMs suit their requirements. diff --git a/Documentation/security/credentials.rst b/Documentation/security/credentials.rst index 038a7e19eff9..66a2e24939d8 100644 --- a/Documentation/security/credentials.rst +++ b/Documentation/security/credentials.rst @@ -196,7 +196,7 @@ The Linux kernel supports the following types of credentials: When a process accesses a key, if not already present, it will normally be cached on one of these keyrings for future accesses to find. - For more information on using keys, see Documentation/security/keys.txt. + For more information on using keys, see ``Documentation/security/keys/*``. 5. LSM diff --git a/Documentation/security/keys/request-key.rst b/Documentation/security/keys/request-key.rst index b2d16abaa9e9..21e27238cec6 100644 --- a/Documentation/security/keys/request-key.rst +++ b/Documentation/security/keys/request-key.rst @@ -3,7 +3,7 @@ Key Request Service =================== The key request service is part of the key retention service (refer to -Documentation/security/core.rst). This document explains more fully how +Documentation/security/keys/core.rst). This document explains more fully how the requesting algorithm works. The process starts by either the kernel requesting a service by calling -- cgit v1.2.3-59-g8ed1b From 20f97caf1120bd02e8ff4adbad3b44b63626feb5 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Fri, 13 Oct 2017 15:27:24 +0200 Subject: PM / QoS: Drop PM_QOS_FLAG_REMOTE_WAKEUP The PM QoS flag PM_QOS_FLAG_REMOTE_WAKEUP is not used consistently and the vast majority of code simply assumes that remote wakeup should be enabled for devices in runtime suspend if they can generate wakeup signals, so drop it. Signed-off-by: Rafael J. Wysocki Acked-by: Ulf Hansson Reviewed-by: Mika Westerberg --- Documentation/ABI/testing/sysfs-devices-power | 16 --------------- Documentation/power/pm_qos_interface.txt | 13 ++++++------- drivers/acpi/device_pm.c | 6 ++---- drivers/base/power/domain.c | 4 +--- drivers/base/power/sysfs.c | 28 --------------------------- include/linux/pm_qos.h | 1 - 6 files changed, 9 insertions(+), 59 deletions(-) (limited to 'Documentation/ABI/testing') diff --git a/Documentation/ABI/testing/sysfs-devices-power b/Documentation/ABI/testing/sysfs-devices-power index 676fdf5f2a99..f4b24c327665 100644 --- a/Documentation/ABI/testing/sysfs-devices-power +++ b/Documentation/ABI/testing/sysfs-devices-power @@ -258,19 +258,3 @@ Description: This attribute has no effect on system-wide suspend/resume and hibernation. - -What: /sys/devices/.../power/pm_qos_remote_wakeup -Date: September 2012 -Contact: Rafael J. Wysocki -Description: - The /sys/devices/.../power/pm_qos_remote_wakeup attribute - is used for manipulating the PM QoS "remote wakeup required" - flag. If set, this flag indicates to the kernel that the - device is a source of user events that have to be signaled from - its low-power states. - - Not all drivers support this attribute. If it isn't supported, - it is not present. - - This attribute has no effect on system-wide suspend/resume and - hibernation. diff --git a/Documentation/power/pm_qos_interface.txt b/Documentation/power/pm_qos_interface.txt index 21d2d48f87a2..19c5f7b1a7ba 100644 --- a/Documentation/power/pm_qos_interface.txt +++ b/Documentation/power/pm_qos_interface.txt @@ -98,8 +98,7 @@ Values are updated in response to changes of the request list. The target values of resume latency and active state latency tolerance are simply the minimum of the request values held in the parameter list elements. The PM QoS flags aggregate value is a gather (bitwise OR) of all list elements' -values. Two device PM QoS flags are defined currently: PM_QOS_FLAG_NO_POWER_OFF -and PM_QOS_FLAG_REMOTE_WAKEUP. +values. One device PM QoS flag is defined currently: PM_QOS_FLAG_NO_POWER_OFF. Note: The aggregated target values are implemented in such a way that reading the aggregated value does not require any locking mechanism. @@ -153,14 +152,14 @@ PM QoS list of resume latency constraints and remove sysfs attribute pm_qos_resume_latency_us from the device's power directory. int dev_pm_qos_expose_flags(device, value) -Add a request to the device's PM QoS list of flags and create sysfs attributes -pm_qos_no_power_off and pm_qos_remote_wakeup under the device's power directory -allowing user space to change these flags' value. +Add a request to the device's PM QoS list of flags and create sysfs attribute +pm_qos_no_power_off under the device's power directory allowing user space to +change the value of the PM_QOS_FLAG_NO_POWER_OFF flag. void dev_pm_qos_hide_flags(device) Drop the request added by dev_pm_qos_expose_flags() from the device's PM QoS list -of flags and remove sysfs attributes pm_qos_no_power_off and pm_qos_remote_wakeup -under the device's power directory. +of flags and remove sysfs attribute pm_qos_no_power_off from the device's power +directory. Notification mechanisms: The per-device PM QoS framework has a per-device notification tree. diff --git a/drivers/acpi/device_pm.c b/drivers/acpi/device_pm.c index fbcc73f7a099..e8c820129797 100644 --- a/drivers/acpi/device_pm.c +++ b/drivers/acpi/device_pm.c @@ -581,8 +581,7 @@ static int acpi_dev_pm_get_state(struct device *dev, struct acpi_device *adev, d_min = ret; wakeup = device_may_wakeup(dev) && adev->wakeup.flags.valid && adev->wakeup.sleep_state >= target_state; - } else if (dev_pm_qos_flags(dev, PM_QOS_FLAG_REMOTE_WAKEUP) != - PM_QOS_FLAGS_NONE) { + } else { wakeup = adev->wakeup.flags.valid; } @@ -865,8 +864,7 @@ int acpi_dev_runtime_suspend(struct device *dev) if (!adev) return 0; - remote_wakeup = dev_pm_qos_flags(dev, PM_QOS_FLAG_REMOTE_WAKEUP) > - PM_QOS_FLAGS_NONE; + remote_wakeup = acpi_device_can_wakeup(adev); if (remote_wakeup) { error = acpi_device_wakeup_enable(adev, ACPI_STATE_S0); if (error) diff --git a/drivers/base/power/domain.c b/drivers/base/power/domain.c index e8ca5e2cf1e5..e6414e9998bb 100644 --- a/drivers/base/power/domain.c +++ b/drivers/base/power/domain.c @@ -346,9 +346,7 @@ static int genpd_power_off(struct generic_pm_domain *genpd, bool one_dev_on, list_for_each_entry(pdd, &genpd->dev_list, list_node) { enum pm_qos_flags_status stat; - stat = dev_pm_qos_flags(pdd->dev, - PM_QOS_FLAG_NO_POWER_OFF - | PM_QOS_FLAG_REMOTE_WAKEUP); + stat = dev_pm_qos_flags(pdd->dev, PM_QOS_FLAG_NO_POWER_OFF); if (stat > PM_QOS_FLAGS_NONE) return -EBUSY; diff --git a/drivers/base/power/sysfs.c b/drivers/base/power/sysfs.c index 156ab57bca77..29bf28fef136 100644 --- a/drivers/base/power/sysfs.c +++ b/drivers/base/power/sysfs.c @@ -309,33 +309,6 @@ static ssize_t pm_qos_no_power_off_store(struct device *dev, static DEVICE_ATTR(pm_qos_no_power_off, 0644, pm_qos_no_power_off_show, pm_qos_no_power_off_store); -static ssize_t pm_qos_remote_wakeup_show(struct device *dev, - struct device_attribute *attr, - char *buf) -{ - return sprintf(buf, "%d\n", !!(dev_pm_qos_requested_flags(dev) - & PM_QOS_FLAG_REMOTE_WAKEUP)); -} - -static ssize_t pm_qos_remote_wakeup_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t n) -{ - int ret; - - if (kstrtoint(buf, 0, &ret)) - return -EINVAL; - - if (ret != 0 && ret != 1) - return -EINVAL; - - ret = dev_pm_qos_update_flags(dev, PM_QOS_FLAG_REMOTE_WAKEUP, ret); - return ret < 0 ? ret : n; -} - -static DEVICE_ATTR(pm_qos_remote_wakeup, 0644, - pm_qos_remote_wakeup_show, pm_qos_remote_wakeup_store); - #ifdef CONFIG_PM_SLEEP static const char _enabled[] = "enabled"; static const char _disabled[] = "disabled"; @@ -671,7 +644,6 @@ static const struct attribute_group pm_qos_latency_tolerance_attr_group = { static struct attribute *pm_qos_flags_attrs[] = { &dev_attr_pm_qos_no_power_off.attr, - &dev_attr_pm_qos_remote_wakeup.attr, NULL, }; static const struct attribute_group pm_qos_flags_attr_group = { diff --git a/include/linux/pm_qos.h b/include/linux/pm_qos.h index 032b55909145..51f0d7e0b15f 100644 --- a/include/linux/pm_qos.h +++ b/include/linux/pm_qos.h @@ -39,7 +39,6 @@ enum pm_qos_flags_status { #define PM_QOS_LATENCY_ANY ((s32)(~(__u32)0 >> 1)) #define PM_QOS_FLAG_NO_POWER_OFF (1 << 0) -#define PM_QOS_FLAG_REMOTE_WAKEUP (1 << 1) struct pm_qos_request { struct plist_node node; -- cgit v1.2.3-59-g8ed1b From d35d43d783bf770638098a8dd939e8fd270cafc9 Mon Sep 17 00:00:00 2001 From: Peter Meerwald-Stadler Date: Tue, 10 Oct 2017 15:48:46 +0200 Subject: Documentation: iio: Clarify meaning of IIO_DISTANCE channel type IIO_DISTANCE is used for two purposes: for pedometers to record the distance covered by a walker, and to measure the distance to an object IIO_DISTANCE is in meters while IIO_PROXIMITY is a unitless measure indirectly proportional to distance (higher value relates to a closer object) Signed-off-by: Peter Meerwald-Stadler Signed-off-by: Jonathan Cameron --- Documentation/ABI/testing/sysfs-bus-iio | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'Documentation/ABI/testing') diff --git a/Documentation/ABI/testing/sysfs-bus-iio b/Documentation/ABI/testing/sysfs-bus-iio index 7eead5f97e02..3fc79185cc56 100644 --- a/Documentation/ABI/testing/sysfs-bus-iio +++ b/Documentation/ABI/testing/sysfs-bus-iio @@ -1242,9 +1242,9 @@ What: /sys/.../iio:deviceX/in_distance_raw KernelVersion: 4.0 Contact: linux-iio@vger.kernel.org Description: - This attribute is used to read the distance covered by the user - since the last reboot while activated. Units after application - of scale are meters. + This attribute is used to read the measured distance to an object + or the distance covered by the user since the last reboot while + activated. Units after application of scale are meters. What: /sys/bus/iio/devices/iio:deviceX/store_eeprom KernelVersion: 3.4.0 -- cgit v1.2.3-59-g8ed1b From 11b86c7004ef14f9f8c1e2caf66bfaad6f3167a2 Mon Sep 17 00:00:00 2001 From: Gwendal Grignou Date: Thu, 12 Oct 2017 19:33:23 +0200 Subject: platform/chrome: Add cros_ec_accel_legacy driver Add driver to support older EC firmware that only support deprecated ec command. Rely on ACPI memory map register to access sensor information. Present same interface as the regular cros_ec sensor stack: - one iio device per accelerometer - use HTML5 axis definition - use iio abi units - accept calibration calls, but do nothing Chrome can use the same code than regular cros_ec sensor stack to calculate orientation and lid angle. Signed-off-by: Gwendal Grignou Signed-off-by: Thierry Escande Signed-off-by: Jonathan Cameron --- Documentation/ABI/testing/sysfs-bus-iio-cros-ec | 10 + drivers/iio/accel/Kconfig | 11 + drivers/iio/accel/Makefile | 2 + drivers/iio/accel/cros_ec_accel_legacy.c | 423 ++++++++++++++++++++++++ 4 files changed, 446 insertions(+) create mode 100644 drivers/iio/accel/cros_ec_accel_legacy.c (limited to 'Documentation/ABI/testing') diff --git a/Documentation/ABI/testing/sysfs-bus-iio-cros-ec b/Documentation/ABI/testing/sysfs-bus-iio-cros-ec index 297b9720f024..0e95c2ca105c 100644 --- a/Documentation/ABI/testing/sysfs-bus-iio-cros-ec +++ b/Documentation/ABI/testing/sysfs-bus-iio-cros-ec @@ -16,3 +16,13 @@ Description: the motion sensor is placed. For example, in a laptop a motion sensor can be located on the base or on the lid. Current valid values are 'base' and 'lid'. + +What: /sys/bus/iio/devices/iio:deviceX/id +Date: Septembre 2017 +KernelVersion: 4.14 +Contact: linux-iio@vger.kernel.org +Description: + This attribute is exposed by the CrOS EC legacy accelerometer + driver and represents the sensor ID as exposed by the EC. This + ID is used by the Android sensor service hardware abstraction + layer (sensor HAL) through the Android container on ChromeOS. diff --git a/drivers/iio/accel/Kconfig b/drivers/iio/accel/Kconfig index 0be352a7b6f4..c6d9517d7611 100644 --- a/drivers/iio/accel/Kconfig +++ b/drivers/iio/accel/Kconfig @@ -148,6 +148,17 @@ config HID_SENSOR_ACCEL_3D To compile this driver as a module, choose M here: the module will be called hid-sensor-accel-3d. +config IIO_CROS_EC_ACCEL_LEGACY + tristate "ChromeOS EC Legacy Accelerometer Sensor" + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + select CROS_EC_LPC_REGISTER_DEVICE + help + Say yes here to get support for accelerometers on Chromebook using + legacy EC firmware. + Sensor data is retrieved through IO memory. + Newer devices should use IIO_CROS_EC_SENSORS. + config IIO_ST_ACCEL_3AXIS tristate "STMicroelectronics accelerometers 3-Axis Driver" depends on (I2C || SPI_MASTER) && SYSFS diff --git a/drivers/iio/accel/Makefile b/drivers/iio/accel/Makefile index 31fba1974e95..fdd054af636c 100644 --- a/drivers/iio/accel/Makefile +++ b/drivers/iio/accel/Makefile @@ -43,6 +43,8 @@ obj-$(CONFIG_SCA3000) += sca3000.o obj-$(CONFIG_STK8312) += stk8312.o obj-$(CONFIG_STK8BA50) += stk8ba50.o +obj-$(CONFIG_IIO_CROS_EC_ACCEL_LEGACY) += cros_ec_accel_legacy.o + obj-$(CONFIG_IIO_SSP_SENSORS_COMMONS) += ssp_accel_sensor.o obj-$(CONFIG_IIO_ST_ACCEL_3AXIS) += st_accel.o diff --git a/drivers/iio/accel/cros_ec_accel_legacy.c b/drivers/iio/accel/cros_ec_accel_legacy.c new file mode 100644 index 000000000000..063e89eff791 --- /dev/null +++ b/drivers/iio/accel/cros_ec_accel_legacy.c @@ -0,0 +1,423 @@ +/* + * Driver for older Chrome OS EC accelerometer + * + * Copyright 2017 Google, Inc + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This driver uses the memory mapper cros-ec interface to communicate + * with the Chrome OS EC about accelerometer data. + * Accelerometer access is presented through iio sysfs. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRV_NAME "cros-ec-accel-legacy" + +/* + * Sensor scale hard coded at 10 bits per g, computed as: + * g / (2^10 - 1) = 0.009586168; with g = 9.80665 m.s^-2 + */ +#define ACCEL_LEGACY_NSCALE 9586168 + +/* Indices for EC sensor values. */ +enum { + X, + Y, + Z, + MAX_AXIS, +}; + +/* State data for cros_ec_accel_legacy iio driver. */ +struct cros_ec_accel_legacy_state { + struct cros_ec_device *ec; + + /* + * Array holding data from a single capture. 2 bytes per channel + * for the 3 channels plus the timestamp which is always last and + * 8-bytes aligned. + */ + s16 capture_data[8]; + s8 sign[MAX_AXIS]; + u8 sensor_num; +}; + +static int ec_cmd_read_u8(struct cros_ec_device *ec, unsigned int offset, + u8 *dest) +{ + return ec->cmd_readmem(ec, offset, 1, dest); +} + +static int ec_cmd_read_u16(struct cros_ec_device *ec, unsigned int offset, + u16 *dest) +{ + __le16 tmp; + int ret = ec->cmd_readmem(ec, offset, 2, &tmp); + + *dest = le16_to_cpu(tmp); + + return ret; +} + +/** + * read_ec_until_not_busy() - Read from EC status byte until it reads not busy. + * @st: Pointer to state information for device. + * + * This function reads EC status until its busy bit gets cleared. It does not + * wait indefinitely and returns -EIO if the EC status is still busy after a + * few hundreds milliseconds. + * + * Return: 8-bit status if ok, -EIO on error + */ +static int read_ec_until_not_busy(struct cros_ec_accel_legacy_state *st) +{ + struct cros_ec_device *ec = st->ec; + u8 status; + int attempts = 0; + + ec_cmd_read_u8(ec, EC_MEMMAP_ACC_STATUS, &status); + while (status & EC_MEMMAP_ACC_STATUS_BUSY_BIT) { + /* Give up after enough attempts, return error. */ + if (attempts++ >= 50) + return -EIO; + + /* Small delay every so often. */ + if (attempts % 5 == 0) + msleep(25); + + ec_cmd_read_u8(ec, EC_MEMMAP_ACC_STATUS, &status); + } + + return status; +} + +/** + * read_ec_accel_data_unsafe() - Read acceleration data from EC shared memory. + * @st: Pointer to state information for device. + * @scan_mask: Bitmap of the sensor indices to scan. + * @data: Location to store data. + * + * This is the unsafe function for reading the EC data. It does not guarantee + * that the EC will not modify the data as it is being read in. + */ +static void read_ec_accel_data_unsafe(struct cros_ec_accel_legacy_state *st, + unsigned long scan_mask, s16 *data) +{ + int i = 0; + int num_enabled = bitmap_weight(&scan_mask, MAX_AXIS); + + /* Read all sensors enabled in scan_mask. Each value is 2 bytes. */ + while (num_enabled--) { + i = find_next_bit(&scan_mask, MAX_AXIS, i); + ec_cmd_read_u16(st->ec, + EC_MEMMAP_ACC_DATA + + sizeof(s16) * + (1 + i + st->sensor_num * MAX_AXIS), + data); + *data *= st->sign[i]; + i++; + data++; + } +} + +/** + * read_ec_accel_data() - Read acceleration data from EC shared memory. + * @st: Pointer to state information for device. + * @scan_mask: Bitmap of the sensor indices to scan. + * @data: Location to store data. + * + * This is the safe function for reading the EC data. It guarantees that + * the data sampled was not modified by the EC while being read. + * + * Return: 0 if ok, -ve on error + */ +static int read_ec_accel_data(struct cros_ec_accel_legacy_state *st, + unsigned long scan_mask, s16 *data) +{ + u8 samp_id = 0xff; + u8 status = 0; + int ret; + int attempts = 0; + + /* + * Continually read all data from EC until the status byte after + * all reads reflects that the EC is not busy and the sample id + * matches the sample id from before all reads. This guarantees + * that data read in was not modified by the EC while reading. + */ + while ((status & (EC_MEMMAP_ACC_STATUS_BUSY_BIT | + EC_MEMMAP_ACC_STATUS_SAMPLE_ID_MASK)) != samp_id) { + /* If we have tried to read too many times, return error. */ + if (attempts++ >= 5) + return -EIO; + + /* Read status byte until EC is not busy. */ + ret = read_ec_until_not_busy(st); + if (ret < 0) + return ret; + status = ret; + + /* + * Store the current sample id so that we can compare to the + * sample id after reading the data. + */ + samp_id = status & EC_MEMMAP_ACC_STATUS_SAMPLE_ID_MASK; + + /* Read all EC data, format it, and store it into data. */ + read_ec_accel_data_unsafe(st, scan_mask, data); + + /* Read status byte. */ + ec_cmd_read_u8(st->ec, EC_MEMMAP_ACC_STATUS, &status); + } + + return 0; +} + +static int cros_ec_accel_legacy_read(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct cros_ec_accel_legacy_state *st = iio_priv(indio_dev); + s16 data = 0; + int ret = IIO_VAL_INT; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = read_ec_accel_data(st, (1 << chan->scan_index), &data); + if (ret) + return ret; + *val = data; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = 0; + *val2 = ACCEL_LEGACY_NSCALE; + return IIO_VAL_INT_PLUS_NANO; + case IIO_CHAN_INFO_CALIBBIAS: + /* Calibration not supported. */ + *val = 0; + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int cros_ec_accel_legacy_write(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + /* + * Do nothing but don't return an error code to allow calibration + * script to work. + */ + if (mask == IIO_CHAN_INFO_CALIBBIAS) + return 0; + + return -EINVAL; +} + +static const struct iio_info cros_ec_accel_legacy_info = { + .read_raw = &cros_ec_accel_legacy_read, + .write_raw = &cros_ec_accel_legacy_write, +}; + +/** + * cros_ec_accel_legacy_capture() - The trigger handler function + * @irq: The interrupt number. + * @p: Private data - always a pointer to the poll func. + * + * On a trigger event occurring, if the pollfunc is attached then this + * handler is called as a threaded interrupt (and hence may sleep). It + * is responsible for grabbing data from the device and pushing it into + * the associated buffer. + * + * Return: IRQ_HANDLED + */ +static irqreturn_t cros_ec_accel_legacy_capture(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct cros_ec_accel_legacy_state *st = iio_priv(indio_dev); + + /* Clear capture data. */ + memset(st->capture_data, 0, sizeof(st->capture_data)); + + /* + * Read data based on which channels are enabled in scan mask. Note + * that on a capture we are always reading the calibrated data. + */ + read_ec_accel_data(st, *indio_dev->active_scan_mask, st->capture_data); + + iio_push_to_buffers_with_timestamp(indio_dev, (void *)st->capture_data, + iio_get_time_ns(indio_dev)); + + /* + * Tell the core we are done with this trigger and ready for the + * next one. + */ + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static char *cros_ec_accel_legacy_loc_strings[] = { + [MOTIONSENSE_LOC_BASE] = "base", + [MOTIONSENSE_LOC_LID] = "lid", + [MOTIONSENSE_LOC_MAX] = "unknown", +}; + +static ssize_t cros_ec_accel_legacy_loc(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + char *buf) +{ + struct cros_ec_accel_legacy_state *st = iio_priv(indio_dev); + + return sprintf(buf, "%s\n", + cros_ec_accel_legacy_loc_strings[st->sensor_num + + MOTIONSENSE_LOC_BASE]); +} + +static ssize_t cros_ec_accel_legacy_id(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + char *buf) +{ + struct cros_ec_accel_legacy_state *st = iio_priv(indio_dev); + + return sprintf(buf, "%d\n", st->sensor_num); +} + +static const struct iio_chan_spec_ext_info cros_ec_accel_legacy_ext_info[] = { + { + .name = "id", + .shared = IIO_SHARED_BY_ALL, + .read = cros_ec_accel_legacy_id, + }, + { + .name = "location", + .shared = IIO_SHARED_BY_ALL, + .read = cros_ec_accel_legacy_loc, + }, + { } +}; + +#define CROS_EC_ACCEL_LEGACY_CHAN(_axis) \ + { \ + .type = IIO_ACCEL, \ + .channel2 = IIO_MOD_X + (_axis), \ + .modified = 1, \ + .info_mask_separate = \ + BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_CALIBBIAS), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SCALE), \ + .ext_info = cros_ec_accel_legacy_ext_info, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 16, \ + .storagebits = 16, \ + }, \ + } \ + +static struct iio_chan_spec ec_accel_channels[] = { + CROS_EC_ACCEL_LEGACY_CHAN(X), + CROS_EC_ACCEL_LEGACY_CHAN(Y), + CROS_EC_ACCEL_LEGACY_CHAN(Z), + IIO_CHAN_SOFT_TIMESTAMP(MAX_AXIS) +}; + +static int cros_ec_accel_legacy_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct cros_ec_dev *ec = dev_get_drvdata(dev->parent); + struct cros_ec_sensor_platform *sensor_platform = dev_get_platdata(dev); + struct iio_dev *indio_dev; + struct cros_ec_accel_legacy_state *state; + int ret, i; + + if (!ec || !ec->ec_dev) { + dev_warn(&pdev->dev, "No EC device found.\n"); + return -EINVAL; + } + + if (!ec->ec_dev->cmd_readmem) { + dev_warn(&pdev->dev, "EC does not support direct reads.\n"); + return -EINVAL; + } + + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*state)); + if (!indio_dev) + return -ENOMEM; + + platform_set_drvdata(pdev, indio_dev); + state = iio_priv(indio_dev); + state->ec = ec->ec_dev; + state->sensor_num = sensor_platform->sensor_num; + + indio_dev->dev.parent = dev; + indio_dev->name = pdev->name; + indio_dev->channels = ec_accel_channels; + /* + * Present the channel using HTML5 standard: + * need to invert X and Y and invert some lid axis. + */ + for (i = X ; i < MAX_AXIS; i++) { + switch (i) { + case X: + ec_accel_channels[X].scan_index = Y; + case Y: + ec_accel_channels[Y].scan_index = X; + case Z: + ec_accel_channels[Z].scan_index = Z; + } + if (state->sensor_num == MOTIONSENSE_LOC_LID && i != Y) + state->sign[i] = -1; + else + state->sign[i] = 1; + } + indio_dev->num_channels = ARRAY_SIZE(ec_accel_channels); + indio_dev->dev.parent = &pdev->dev; + indio_dev->info = &cros_ec_accel_legacy_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, NULL, + cros_ec_accel_legacy_capture, + NULL); + if (ret) + return ret; + + return devm_iio_device_register(dev, indio_dev); +} + +static struct platform_driver cros_ec_accel_platform_driver = { + .driver = { + .name = DRV_NAME, + }, + .probe = cros_ec_accel_legacy_probe, +}; +module_platform_driver(cros_ec_accel_platform_driver); + +MODULE_DESCRIPTION("ChromeOS EC legacy accelerometer driver"); +MODULE_AUTHOR("Gwendal Grignou "); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" DRV_NAME); -- cgit v1.2.3-59-g8ed1b From 61011264c1afd8c075fb9028ccc78e7f2e63ce48 Mon Sep 17 00:00:00 2001 From: Lukas Wunner Date: Tue, 17 Oct 2017 12:42:00 +0200 Subject: iio: dac: Add Texas Instruments 8/10/12-bit 2/4-channel DAC driver The DACrrcS085 (rr = 08/10/12, c = 2/4) family of SPI DACs was inherited by TI when they acquired National Semiconductor in 2011. This driver was developed for and tested with the DAC082S085 built into the Revolution Pi by KUNBUS, but should work with any of the other chips as they share the same programming interface. There is also a family of I2C DACs with just a single channel called DACrr1C08x (rr = 08/10/12, x = 1/5). Their programming interface is very similar and it should be possible to extend the driver for these chips with moderate effort. Alternatively they could be integrated into ad5446.c. (The AD5301/AD5311/AD5321 use different power-down modes but otherwise appear to be comparable.) Furthermore there is a family of 8-channel DACs called DACrr8S085 (rr = 08/10/12) as well as two 16-bit DACs called DAC161Sxxx (xxx = 055/997). These are more complicated devices with support for daisy-chaining and the ability to power down each channel separately. They could either be handled by a separate driver or integrated into the present driver with a larger effort. Cc: Mathias Duckeck Signed-off-by: Lukas Wunner Signed-off-by: Jonathan Cameron --- Documentation/ABI/testing/sysfs-bus-iio | 1 + drivers/iio/dac/Kconfig | 10 + drivers/iio/dac/Makefile | 1 + drivers/iio/dac/ti-dac082s085.c | 351 ++++++++++++++++++++++++++++++++ 4 files changed, 363 insertions(+) create mode 100644 drivers/iio/dac/ti-dac082s085.c (limited to 'Documentation/ABI/testing') diff --git a/Documentation/ABI/testing/sysfs-bus-iio b/Documentation/ABI/testing/sysfs-bus-iio index 3fc79185cc56..2e3f919485f4 100644 --- a/Documentation/ABI/testing/sysfs-bus-iio +++ b/Documentation/ABI/testing/sysfs-bus-iio @@ -522,6 +522,7 @@ Description: Specifies the output powerdown mode. DAC output stage is disconnected from the amplifier and 1kohm_to_gnd: connected to ground via an 1kOhm resistor, + 2.5kohm_to_gnd: connected to ground via a 2.5kOhm resistor, 6kohm_to_gnd: connected to ground via a 6kOhm resistor, 20kohm_to_gnd: connected to ground via a 20kOhm resistor, 90kohm_to_gnd: connected to ground via a 90kOhm resistor, diff --git a/drivers/iio/dac/Kconfig b/drivers/iio/dac/Kconfig index d187233dec3a..965d5c0d2468 100644 --- a/drivers/iio/dac/Kconfig +++ b/drivers/iio/dac/Kconfig @@ -310,6 +310,16 @@ config STM32_DAC config STM32_DAC_CORE tristate +config TI_DAC082S085 + tristate "Texas Instruments 8/10/12-bit 2/4-channel DAC driver" + depends on SPI_MASTER + help + Driver for the Texas Instruments (formerly National Semiconductor) + DAC082S085, DAC102S085, DAC122S085, DAC084S085, DAC104S085 and + DAC124S085. + + If compiled as a module, it will be called ti-dac082s085. + config VF610_DAC tristate "Vybrid vf610 DAC driver" depends on OF diff --git a/drivers/iio/dac/Makefile b/drivers/iio/dac/Makefile index 8fe5af231698..4785858e04cc 100644 --- a/drivers/iio/dac/Makefile +++ b/drivers/iio/dac/Makefile @@ -33,4 +33,5 @@ obj-$(CONFIG_MCP4725) += mcp4725.o obj-$(CONFIG_MCP4922) += mcp4922.o obj-$(CONFIG_STM32_DAC_CORE) += stm32-dac-core.o obj-$(CONFIG_STM32_DAC) += stm32-dac.o +obj-$(CONFIG_TI_DAC082S085) += ti-dac082s085.o obj-$(CONFIG_VF610_DAC) += vf610_dac.o diff --git a/drivers/iio/dac/ti-dac082s085.c b/drivers/iio/dac/ti-dac082s085.c new file mode 100644 index 000000000000..feac1db592af --- /dev/null +++ b/drivers/iio/dac/ti-dac082s085.c @@ -0,0 +1,351 @@ +/* + * ti-dac082s085.c - Texas Instruments 8/10/12-bit 2/4-channel DAC driver + * + * Copyright (C) 2017 KUNBUS GmbH + * + * http://www.ti.com/lit/ds/symlink/dac082s085.pdf + * http://www.ti.com/lit/ds/symlink/dac102s085.pdf + * http://www.ti.com/lit/ds/symlink/dac122s085.pdf + * http://www.ti.com/lit/ds/symlink/dac084s085.pdf + * http://www.ti.com/lit/ds/symlink/dac104s085.pdf + * http://www.ti.com/lit/ds/symlink/dac124s085.pdf + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License (version 2) as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include + +/** + * struct ti_dac_chip - TI DAC chip + * @lock: protects write sequences + * @vref: regulator generating Vref + * @mesg: SPI message to perform a write + * @xfer: SPI transfer used by @mesg + * @val: cached value of each output + * @powerdown: whether the chip is powered down + * @powerdown_mode: selected by the user + * @resolution: resolution of the chip + * @buf: buffer for @xfer + */ +struct ti_dac_chip { + struct mutex lock; + struct regulator *vref; + struct spi_message mesg; + struct spi_transfer xfer; + u16 val[4]; + bool powerdown; + u8 powerdown_mode; + u8 resolution; + u8 buf[2] ____cacheline_aligned; +}; + +#define WRITE_NOT_UPDATE(chan) (0x00 | (chan) << 6) +#define WRITE_AND_UPDATE(chan) (0x10 | (chan) << 6) +#define WRITE_ALL_UPDATE 0x20 +#define POWERDOWN(mode) (0x30 | ((mode) + 1) << 6) + +static int ti_dac_cmd(struct ti_dac_chip *ti_dac, u8 cmd, u16 val) +{ + u8 shift = 12 - ti_dac->resolution; + + ti_dac->buf[0] = cmd | (val >> (8 - shift)); + ti_dac->buf[1] = (val << shift) & 0xff; + return spi_sync(ti_dac->mesg.spi, &ti_dac->mesg); +} + +static const char * const ti_dac_powerdown_modes[] = { + "2.5kohm_to_gnd", "100kohm_to_gnd", "three_state", +}; + +static int ti_dac_get_powerdown_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct ti_dac_chip *ti_dac = iio_priv(indio_dev); + + return ti_dac->powerdown_mode; +} + +static int ti_dac_set_powerdown_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + unsigned int mode) +{ + struct ti_dac_chip *ti_dac = iio_priv(indio_dev); + int ret = 0; + + if (ti_dac->powerdown_mode == mode) + return 0; + + mutex_lock(&ti_dac->lock); + if (ti_dac->powerdown) { + ret = ti_dac_cmd(ti_dac, POWERDOWN(mode), 0); + if (ret) + goto out; + } + ti_dac->powerdown_mode = mode; + +out: + mutex_unlock(&ti_dac->lock); + return ret; +} + +static const struct iio_enum ti_dac_powerdown_mode = { + .items = ti_dac_powerdown_modes, + .num_items = ARRAY_SIZE(ti_dac_powerdown_modes), + .get = ti_dac_get_powerdown_mode, + .set = ti_dac_set_powerdown_mode, +}; + +static ssize_t ti_dac_read_powerdown(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + char *buf) +{ + struct ti_dac_chip *ti_dac = iio_priv(indio_dev); + + return sprintf(buf, "%d\n", ti_dac->powerdown); +} + +static ssize_t ti_dac_write_powerdown(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + const char *buf, size_t len) +{ + struct ti_dac_chip *ti_dac = iio_priv(indio_dev); + bool powerdown; + int ret; + + ret = strtobool(buf, &powerdown); + if (ret) + return ret; + + if (ti_dac->powerdown == powerdown) + return len; + + mutex_lock(&ti_dac->lock); + if (powerdown) + ret = ti_dac_cmd(ti_dac, POWERDOWN(ti_dac->powerdown_mode), 0); + else + ret = ti_dac_cmd(ti_dac, WRITE_AND_UPDATE(0), ti_dac->val[0]); + if (!ret) + ti_dac->powerdown = powerdown; + mutex_unlock(&ti_dac->lock); + + return ret ? ret : len; +} + +static const struct iio_chan_spec_ext_info ti_dac_ext_info[] = { + { + .name = "powerdown", + .read = ti_dac_read_powerdown, + .write = ti_dac_write_powerdown, + .shared = IIO_SHARED_BY_TYPE, + }, + IIO_ENUM("powerdown_mode", IIO_SHARED_BY_TYPE, &ti_dac_powerdown_mode), + IIO_ENUM_AVAILABLE("powerdown_mode", &ti_dac_powerdown_mode), + { }, +}; + +#define TI_DAC_CHANNEL(chan) { \ + .type = IIO_VOLTAGE, \ + .channel = (chan), \ + .address = (chan), \ + .indexed = true, \ + .output = true, \ + .datasheet_name = (const char[]){ 'A' + (chan), 0 }, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .ext_info = ti_dac_ext_info, \ +} + +static const struct iio_chan_spec ti_dac_channels[] = { + TI_DAC_CHANNEL(0), + TI_DAC_CHANNEL(1), + TI_DAC_CHANNEL(2), + TI_DAC_CHANNEL(3), +}; + +static int ti_dac_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct ti_dac_chip *ti_dac = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + *val = ti_dac->val[chan->channel]; + ret = IIO_VAL_INT; + break; + + case IIO_CHAN_INFO_SCALE: + ret = regulator_get_voltage(ti_dac->vref); + if (ret < 0) + return ret; + + *val = ret / 1000; + *val2 = ti_dac->resolution; + ret = IIO_VAL_FRACTIONAL_LOG2; + break; + + default: + ret = -EINVAL; + } + + return ret; +} + +static int ti_dac_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct ti_dac_chip *ti_dac = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (ti_dac->val[chan->channel] == val) + return 0; + + if (val >= (1 << ti_dac->resolution) || val < 0) + return -EINVAL; + + if (ti_dac->powerdown) + return -EBUSY; + + mutex_lock(&ti_dac->lock); + ret = ti_dac_cmd(ti_dac, WRITE_AND_UPDATE(chan->channel), val); + if (!ret) + ti_dac->val[chan->channel] = val; + mutex_unlock(&ti_dac->lock); + break; + + default: + ret = -EINVAL; + } + + return ret; +} + +static int ti_dac_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, long mask) +{ + return IIO_VAL_INT; +} + +static const struct iio_info ti_dac_info = { + .read_raw = ti_dac_read_raw, + .write_raw = ti_dac_write_raw, + .write_raw_get_fmt = ti_dac_write_raw_get_fmt, +}; + +static int ti_dac_probe(struct spi_device *spi) +{ + struct device *dev = &spi->dev; + struct ti_dac_chip *ti_dac; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*ti_dac)); + if (!indio_dev) + return -ENOMEM; + + indio_dev->dev.parent = dev; + indio_dev->info = &ti_dac_info; + indio_dev->name = spi->modalias; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = ti_dac_channels; + spi_set_drvdata(spi, indio_dev); + + ti_dac = iio_priv(indio_dev); + ti_dac->xfer.tx_buf = &ti_dac->buf; + ti_dac->xfer.len = sizeof(ti_dac->buf); + spi_message_init_with_transfers(&ti_dac->mesg, &ti_dac->xfer, 1); + ti_dac->mesg.spi = spi; + + ret = sscanf(spi->modalias, "dac%2hhu%1d", + &ti_dac->resolution, &indio_dev->num_channels); + WARN_ON(ret != 2); + + ti_dac->vref = devm_regulator_get(dev, "vref"); + if (IS_ERR(ti_dac->vref)) + return PTR_ERR(ti_dac->vref); + + ret = regulator_enable(ti_dac->vref); + if (ret < 0) + return ret; + + mutex_init(&ti_dac->lock); + + ret = ti_dac_cmd(ti_dac, WRITE_ALL_UPDATE, 0); + if (ret) { + dev_err(dev, "failed to initialize outputs to 0\n"); + goto err; + } + + ret = iio_device_register(indio_dev); + if (ret) + goto err; + + return 0; + +err: + mutex_destroy(&ti_dac->lock); + regulator_disable(ti_dac->vref); + return ret; +} + +static int ti_dac_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + struct ti_dac_chip *ti_dac = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + mutex_destroy(&ti_dac->lock); + regulator_disable(ti_dac->vref); + + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id ti_dac_of_id[] = { + { .compatible = "ti,dac082s085" }, + { .compatible = "ti,dac102s085" }, + { .compatible = "ti,dac122s085" }, + { .compatible = "ti,dac084s085" }, + { .compatible = "ti,dac104s085" }, + { .compatible = "ti,dac124s085" }, + { } +}; +MODULE_DEVICE_TABLE(of, ti_dac_of_id); +#endif + +static const struct spi_device_id ti_dac_spi_id[] = { + { "dac082s085" }, + { "dac102s085" }, + { "dac122s085" }, + { "dac084s085" }, + { "dac104s085" }, + { "dac124s085" }, + { } +}; +MODULE_DEVICE_TABLE(spi, ti_dac_spi_id); + +static struct spi_driver ti_dac_driver = { + .driver = { + .name = "ti-dac082s085", + .of_match_table = of_match_ptr(ti_dac_of_id), + }, + .probe = ti_dac_probe, + .remove = ti_dac_remove, + .id_table = ti_dac_spi_id, +}; +module_spi_driver(ti_dac_driver); + +MODULE_AUTHOR("Lukas Wunner "); +MODULE_DESCRIPTION("Texas Instruments 8/10/12-bit 2/4-channel DAC driver"); +MODULE_LICENSE("GPL v2"); -- cgit v1.2.3-59-g8ed1b From 04fa0540255ee5707a1addf4c7c2a5ba4ac2c407 Mon Sep 17 00:00:00 2001 From: Jin Qian Date: Thu, 12 Oct 2017 10:46:59 -0700 Subject: mmc: core: export emmc revision Expose emmc revision as part of device attributes. Signed-off-by: Jin Qian Signed-off-by: Ulf Hansson --- Documentation/ABI/testing/sysfs-bus-mmc | 4 ++++ drivers/mmc/core/mmc.c | 2 ++ 2 files changed, 6 insertions(+) create mode 100644 Documentation/ABI/testing/sysfs-bus-mmc (limited to 'Documentation/ABI/testing') diff --git a/Documentation/ABI/testing/sysfs-bus-mmc b/Documentation/ABI/testing/sysfs-bus-mmc new file mode 100644 index 000000000000..519f028d19cc --- /dev/null +++ b/Documentation/ABI/testing/sysfs-bus-mmc @@ -0,0 +1,4 @@ +What: /sys/bus/mmc/devices/.../rev +Date: October 2017 +Contact: Jin Qian +Description: Extended CSD revision number diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index 44d67ee8bf7b..066efbb89483 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -780,6 +780,7 @@ MMC_DEV_ATTR(manfid, "0x%06x\n", card->cid.manfid); MMC_DEV_ATTR(name, "%s\n", card->cid.prod_name); MMC_DEV_ATTR(oemid, "0x%04x\n", card->cid.oemid); MMC_DEV_ATTR(prv, "0x%x\n", card->cid.prv); +MMC_DEV_ATTR(rev, "0x%x\n", card->ext_csd.rev); MMC_DEV_ATTR(pre_eol_info, "%02x\n", card->ext_csd.pre_eol_info); MMC_DEV_ATTR(life_time, "0x%02x 0x%02x\n", card->ext_csd.device_life_time_est_typ_a, @@ -838,6 +839,7 @@ static struct attribute *mmc_std_attrs[] = { &dev_attr_name.attr, &dev_attr_oemid.attr, &dev_attr_prv.attr, + &dev_attr_rev.attr, &dev_attr_pre_eol_info.attr, &dev_attr_life_time.attr, &dev_attr_serial.attr, -- cgit v1.2.3-59-g8ed1b From 33b9ca1e53b45f7cacdba9d4fba5cb1387b26827 Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Wed, 1 Nov 2017 14:25:30 -0500 Subject: platform/x86: dell-smbios: Add a sysfs interface for SMBIOS tokens Currently userspace tools can access system tokens via the dcdbas kernel module and a SMI call that will cause the platform to execute SMM code. With a goal in mind of deprecating the dcdbas kernel module a different method for accessing these tokens from userspace needs to be created. This is intentionally marked to only be readable as a process with CAP_SYS_ADMIN as it can contain sensitive information about the platform's configuration. While adding this interface I found that some tokens are duplicated. These need to be ignored from sysfs to avoid duplicate files. MAINTAINERS was missing for this driver. Add myself and Pali to maintainers list for it. Signed-off-by: Mario Limonciello Reviewed-by: Edward O'Callaghan Signed-off-by: Darren Hart (VMware) --- .../ABI/testing/sysfs-platform-dell-smbios | 21 ++ MAINTAINERS | 7 + drivers/platform/x86/dell-smbios.c | 213 ++++++++++++++++++++- 3 files changed, 240 insertions(+), 1 deletion(-) create mode 100644 Documentation/ABI/testing/sysfs-platform-dell-smbios (limited to 'Documentation/ABI/testing') diff --git a/Documentation/ABI/testing/sysfs-platform-dell-smbios b/Documentation/ABI/testing/sysfs-platform-dell-smbios new file mode 100644 index 000000000000..205d3b6361e0 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-platform-dell-smbios @@ -0,0 +1,21 @@ +What: /sys/devices/platform//tokens/* +Date: November 2017 +KernelVersion: 4.15 +Contact: "Mario Limonciello" +Description: + A read-only description of Dell platform tokens + available on the machine. + + Each token attribute is available as a pair of + sysfs attributes readable by a process with + CAP_SYS_ADMIN. + + For example the token ID "5" would be available + as the following attributes: + + 0005_location + 0005_value + + Tokens will vary from machine to machine, and + only tokens available on that machine will be + displayed. diff --git a/MAINTAINERS b/MAINTAINERS index 69a81400cc65..cb4bc87874c0 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -3978,6 +3978,13 @@ M: "Maciej W. Rozycki" S: Maintained F: drivers/net/fddi/defxx.* +DELL SMBIOS DRIVER +M: Pali Rohár +M: Mario Limonciello +L: platform-driver-x86@vger.kernel.org +S: Maintained +F: drivers/platform/x86/dell-smbios.* + DELL LAPTOP DRIVER M: Matthew Garrett M: Pali Rohár diff --git a/drivers/platform/x86/dell-smbios.c b/drivers/platform/x86/dell-smbios.c index 12a3eb153911..ed4995fdcd46 100644 --- a/drivers/platform/x86/dell-smbios.c +++ b/drivers/platform/x86/dell-smbios.c @@ -16,10 +16,12 @@ #include #include +#include #include #include #include #include +#include #include #include #include "../../firmware/dcdbas.h" @@ -39,7 +41,11 @@ static DEFINE_MUTEX(buffer_mutex); static int da_command_address; static int da_command_code; static int da_num_tokens; +static struct platform_device *platform_device; static struct calling_interface_token *da_tokens; +static struct device_attribute *token_location_attrs; +static struct device_attribute *token_value_attrs; +static struct attribute **token_attrs; int dell_smbios_error(int value) { @@ -157,6 +163,27 @@ static void __init parse_da_table(const struct dmi_header *dm) da_num_tokens += tokens; } +static void zero_duplicates(struct device *dev) +{ + int i, j; + + for (i = 0; i < da_num_tokens; i++) { + if (da_tokens[i].tokenID == 0) + continue; + for (j = i+1; j < da_num_tokens; j++) { + if (da_tokens[j].tokenID == 0) + continue; + if (da_tokens[i].tokenID == da_tokens[j].tokenID) { + dev_dbg(dev, "Zeroing dup token ID %x(%x/%x)\n", + da_tokens[j].tokenID, + da_tokens[j].location, + da_tokens[j].value); + da_tokens[j].tokenID = 0; + } + } + } +} + static void __init find_tokens(const struct dmi_header *dm, void *dummy) { switch (dm->type) { @@ -170,6 +197,154 @@ static void __init find_tokens(const struct dmi_header *dm, void *dummy) } } +static int match_attribute(struct device *dev, + struct device_attribute *attr) +{ + int i; + + for (i = 0; i < da_num_tokens * 2; i++) { + if (!token_attrs[i]) + continue; + if (strcmp(token_attrs[i]->name, attr->attr.name) == 0) + return i/2; + } + dev_dbg(dev, "couldn't match: %s\n", attr->attr.name); + return -EINVAL; +} + +static ssize_t location_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int i; + + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + + i = match_attribute(dev, attr); + if (i > 0) + return scnprintf(buf, PAGE_SIZE, "%08x", da_tokens[i].location); + return 0; +} + +static ssize_t value_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int i; + + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + + i = match_attribute(dev, attr); + if (i > 0) + return scnprintf(buf, PAGE_SIZE, "%08x", da_tokens[i].value); + return 0; +} + +static struct attribute_group smbios_attribute_group = { + .name = "tokens" +}; + +static struct platform_driver platform_driver = { + .driver = { + .name = "dell-smbios", + }, +}; + +static int build_tokens_sysfs(struct platform_device *dev) +{ + char buffer_location[13]; + char buffer_value[10]; + char *location_name; + char *value_name; + size_t size; + int ret; + int i, j; + + /* (number of tokens + 1 for null terminated */ + size = sizeof(struct device_attribute) * (da_num_tokens + 1); + token_location_attrs = kzalloc(size, GFP_KERNEL); + if (!token_location_attrs) + return -ENOMEM; + token_value_attrs = kzalloc(size, GFP_KERNEL); + if (!token_value_attrs) + goto out_allocate_value; + + /* need to store both location and value + terminator*/ + size = sizeof(struct attribute *) * ((2 * da_num_tokens) + 1); + token_attrs = kzalloc(size, GFP_KERNEL); + if (!token_attrs) + goto out_allocate_attrs; + + for (i = 0, j = 0; i < da_num_tokens; i++) { + /* skip empty */ + if (da_tokens[i].tokenID == 0) + continue; + /* add location */ + sprintf(buffer_location, "%04x_location", + da_tokens[i].tokenID); + location_name = kstrdup(buffer_location, GFP_KERNEL); + if (location_name == NULL) + goto out_unwind_strings; + sysfs_attr_init(&token_location_attrs[i].attr); + token_location_attrs[i].attr.name = location_name; + token_location_attrs[i].attr.mode = 0444; + token_location_attrs[i].show = location_show; + token_attrs[j++] = &token_location_attrs[i].attr; + + /* add value */ + sprintf(buffer_value, "%04x_value", + da_tokens[i].tokenID); + value_name = kstrdup(buffer_value, GFP_KERNEL); + if (value_name == NULL) + goto loop_fail_create_value; + sysfs_attr_init(&token_value_attrs[i].attr); + token_value_attrs[i].attr.name = value_name; + token_value_attrs[i].attr.mode = 0444; + token_value_attrs[i].show = value_show; + token_attrs[j++] = &token_value_attrs[i].attr; + continue; + +loop_fail_create_value: + kfree(value_name); + goto out_unwind_strings; + } + smbios_attribute_group.attrs = token_attrs; + + ret = sysfs_create_group(&dev->dev.kobj, &smbios_attribute_group); + if (ret) + goto out_unwind_strings; + return 0; + +out_unwind_strings: + for (i = i-1; i > 0; i--) { + kfree(token_location_attrs[i].attr.name); + kfree(token_value_attrs[i].attr.name); + } + kfree(token_attrs); +out_allocate_attrs: + kfree(token_value_attrs); +out_allocate_value: + kfree(token_location_attrs); + + return -ENOMEM; +} + +static void free_group(struct platform_device *pdev) +{ + int i; + + sysfs_remove_group(&pdev->dev.kobj, + &smbios_attribute_group); + for (i = 0; i < da_num_tokens; i++) { + kfree(token_location_attrs[i].attr.name); + kfree(token_value_attrs[i].attr.name); + } + kfree(token_attrs); + kfree(token_value_attrs); + kfree(token_location_attrs); +} + + static int __init dell_smbios_init(void) { const struct dmi_device *valid; @@ -197,9 +372,40 @@ static int __init dell_smbios_init(void) ret = -ENOMEM; goto fail_buffer; } + ret = platform_driver_register(&platform_driver); + if (ret) + goto fail_platform_driver; + + platform_device = platform_device_alloc("dell-smbios", 0); + if (!platform_device) { + ret = -ENOMEM; + goto fail_platform_device_alloc; + } + ret = platform_device_add(platform_device); + if (ret) + goto fail_platform_device_add; + + /* duplicate tokens will cause problems building sysfs files */ + zero_duplicates(&platform_device->dev); + + ret = build_tokens_sysfs(platform_device); + if (ret) + goto fail_create_group; return 0; +fail_create_group: + platform_device_del(platform_device); + +fail_platform_device_add: + platform_device_put(platform_device); + +fail_platform_device_alloc: + platform_driver_unregister(&platform_driver); + +fail_platform_driver: + free_page((unsigned long)buffer); + fail_buffer: kfree(da_tokens); return ret; @@ -207,8 +413,13 @@ fail_buffer: static void __exit dell_smbios_exit(void) { - kfree(da_tokens); + if (platform_device) { + free_group(platform_device); + platform_device_unregister(platform_device); + platform_driver_unregister(&platform_driver); + } free_page((unsigned long)buffer); + kfree(da_tokens); } subsys_initcall(dell_smbios_init); -- cgit v1.2.3-59-g8ed1b From f2645fa317b8905b8934f06a0601d5b7fa66aba0 Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Wed, 1 Nov 2017 14:25:36 -0500 Subject: platform/x86: dell-smbios-wmi: introduce userspace interface It's important for the driver to provide a R/W ioctl to ensure that two competing userspace processes don't race to provide or read each others data. This userspace character device will be used to perform SMBIOS calls from any applications. It provides an ioctl that will allow passing the WMI calling interface buffer between userspace and kernel space. This character device is intended to deprecate the dcdbas kernel module and the interface that it provides to userspace. To perform an SMBIOS IOCTL call using the character device userspace will perform a read() on the the character device. The WMI bus will provide a u64 variable containing the necessary size of the IOCTL buffer. The API for interacting with this interface is defined in documentation as well as the WMI uapi header provides the format of the structures. Not all userspace requests will be accepted. The dell-smbios filtering functionality will be used to prevent access to certain tokens and calls. All whitelisted commands and tokens are now shared out to userspace so applications don't need to define them in their own headers. Signed-off-by: Mario Limonciello Reviewed-by: Edward O'Callaghan Signed-off-by: Darren Hart (VMware) --- Documentation/ABI/testing/dell-smbios-wmi | 41 +++++++++++++++++++++++ drivers/platform/x86/dell-smbios-wmi.c | 54 ++++++++++++++++++++++++------- drivers/platform/x86/dell-smbios.h | 32 ++---------------- include/uapi/linux/wmi.h | 47 +++++++++++++++++++++++++++ 4 files changed, 133 insertions(+), 41 deletions(-) create mode 100644 Documentation/ABI/testing/dell-smbios-wmi (limited to 'Documentation/ABI/testing') diff --git a/Documentation/ABI/testing/dell-smbios-wmi b/Documentation/ABI/testing/dell-smbios-wmi new file mode 100644 index 000000000000..fc919ce16008 --- /dev/null +++ b/Documentation/ABI/testing/dell-smbios-wmi @@ -0,0 +1,41 @@ +What: /dev/wmi/dell-smbios +Date: November 2017 +KernelVersion: 4.15 +Contact: "Mario Limonciello" +Description: + Perform SMBIOS calls on supported Dell machines. + through the Dell ACPI-WMI interface. + + IOCTL's and buffer formats are defined in: + + + 1) To perform an SMBIOS call from userspace, you'll need to + first determine the minimum size of the calling interface + buffer for your machine. + Platforms that contain larger buffers can return larger + objects from the system firmware. + Commonly this size is either 4k or 32k. + + To determine the size of the buffer read() a u64 dword from + the WMI character device /dev/wmi/dell-smbios. + + 2) After you've determined the minimum size of the calling + interface buffer, you can allocate a structure that represents + the structure documented above. + + 3) In the 'length' object store the size of the buffer you + determined above and allocated. + + 4) In this buffer object, prepare as necessary for the SMBIOS + call you're interested in. Typically SMBIOS buffers have + "class", "select", and "input" defined to values that coincide + with the data you are interested in. + Documenting class/select/input values is outside of the scope + of this documentation. Check with the libsmbios project for + further documentation on these values. + + 6) Run the call by using ioctl() as described in the header. + + 7) The output will be returned in the buffer object. + + 8) Be sure to free up your allocated object. diff --git a/drivers/platform/x86/dell-smbios-wmi.c b/drivers/platform/x86/dell-smbios-wmi.c index b31f457e58c3..35c13815b24c 100644 --- a/drivers/platform/x86/dell-smbios-wmi.c +++ b/drivers/platform/x86/dell-smbios-wmi.c @@ -30,17 +30,6 @@ struct misc_bios_flags_structure { #define DELL_WMI_SMBIOS_GUID "A80593CE-A997-11DA-B012-B622A1EF5492" -struct dell_wmi_extensions { - __u32 argattrib; - __u32 blength; - __u8 data[]; -} __packed; - -struct dell_wmi_smbios_buffer { - struct calling_interface_buffer std; - struct dell_wmi_extensions ext; -} __packed; - struct wmi_smbios_priv { struct dell_wmi_smbios_buffer *buf; struct list_head list; @@ -117,6 +106,42 @@ int dell_smbios_wmi_call(struct calling_interface_buffer *buffer) return ret; } +static long dell_smbios_wmi_filter(struct wmi_device *wdev, unsigned int cmd, + struct wmi_ioctl_buffer *arg) +{ + struct wmi_smbios_priv *priv; + int ret = 0; + + switch (cmd) { + case DELL_WMI_SMBIOS_CMD: + mutex_lock(&call_mutex); + priv = dev_get_drvdata(&wdev->dev); + if (!priv) { + ret = -ENODEV; + goto fail_smbios_cmd; + } + memcpy(priv->buf, arg, priv->req_buf_size); + if (dell_smbios_call_filter(&wdev->dev, &priv->buf->std)) { + dev_err(&wdev->dev, "Invalid call %d/%d:%8x\n", + priv->buf->std.cmd_class, + priv->buf->std.cmd_select, + priv->buf->std.input[0]); + ret = -EFAULT; + goto fail_smbios_cmd; + } + ret = run_smbios_call(priv->wdev); + if (ret) + goto fail_smbios_cmd; + memcpy(arg, priv->buf, priv->req_buf_size); +fail_smbios_cmd: + mutex_unlock(&call_mutex); + break; + default: + ret = -ENOIOCTLCMD; + } + return ret; +} + static int dell_smbios_wmi_probe(struct wmi_device *wdev) { struct wmi_smbios_priv *priv; @@ -135,6 +160,12 @@ static int dell_smbios_wmi_probe(struct wmi_device *wdev) if (!dell_wmi_get_size(&priv->req_buf_size)) return -EPROBE_DEFER; + /* add in the length object we will use internally with ioctl */ + priv->req_buf_size += sizeof(u64); + ret = set_required_buffer_size(wdev, priv->req_buf_size); + if (ret) + return ret; + count = get_order(priv->req_buf_size); priv->buf = (void *)__get_free_pages(GFP_KERNEL, count); if (!priv->buf) @@ -210,6 +241,7 @@ static struct wmi_driver dell_smbios_wmi_driver = { .probe = dell_smbios_wmi_probe, .remove = dell_smbios_wmi_remove, .id_table = dell_smbios_wmi_id_table, + .filter_callback = dell_smbios_wmi_filter, }; static int __init init_dell_smbios_wmi(void) diff --git a/drivers/platform/x86/dell-smbios.h b/drivers/platform/x86/dell-smbios.h index 91e8004d48ba..138d478d9adc 100644 --- a/drivers/platform/x86/dell-smbios.h +++ b/drivers/platform/x86/dell-smbios.h @@ -17,23 +17,11 @@ #define _DELL_SMBIOS_H_ #include +#include -/* Classes and selects used in kernel drivers */ -#define CLASS_TOKEN_READ 0 -#define CLASS_TOKEN_WRITE 1 -#define SELECT_TOKEN_STD 0 -#define SELECT_TOKEN_BAT 1 -#define SELECT_TOKEN_AC 2 +/* Classes and selects used only in kernel drivers */ #define CLASS_KBD_BACKLIGHT 4 #define SELECT_KBD_BACKLIGHT 11 -#define CLASS_FLASH_INTERFACE 7 -#define SELECT_FLASH_INTERFACE 3 -#define CLASS_ADMIN_PROP 10 -#define SELECT_ADMIN_PROP 3 -#define CLASS_INFO 17 -#define SELECT_RFKILL 11 -#define SELECT_APP_REGISTRATION 3 -#define SELECT_DOCK 22 /* Tokens used in kernel drivers, any of these * should be filtered from userspace access @@ -50,24 +38,8 @@ #define GLOBAL_MIC_MUTE_ENABLE 0x0364 #define GLOBAL_MIC_MUTE_DISABLE 0x0365 -/* tokens whitelisted to userspace use */ -#define CAPSULE_EN_TOKEN 0x0461 -#define CAPSULE_DIS_TOKEN 0x0462 -#define WSMT_EN_TOKEN 0x04EC -#define WSMT_DIS_TOKEN 0x04ED - struct notifier_block; -/* This structure will be modified by the firmware when we enter - * system management mode, hence the volatiles */ - -struct calling_interface_buffer { - u16 cmd_class; - u16 cmd_select; - volatile u32 input[4]; - volatile u32 output[4]; -} __packed; - struct calling_interface_token { u16 tokenID; u16 location; diff --git a/include/uapi/linux/wmi.h b/include/uapi/linux/wmi.h index 7e52350ac9b3..7a92e9e3d1c0 100644 --- a/include/uapi/linux/wmi.h +++ b/include/uapi/linux/wmi.h @@ -10,6 +10,7 @@ #ifndef _UAPI_LINUX_WMI_H #define _UAPI_LINUX_WMI_H +#include #include /* WMI bus will filter all WMI vendor driver requests through this IOC */ @@ -23,4 +24,50 @@ struct wmi_ioctl_buffer { __u8 data[]; }; +/* This structure may be modified by the firmware when we enter + * system management mode through SMM, hence the volatiles + */ +struct calling_interface_buffer { + __u16 cmd_class; + __u16 cmd_select; + volatile __u32 input[4]; + volatile __u32 output[4]; +} __packed; + +struct dell_wmi_extensions { + __u32 argattrib; + __u32 blength; + __u8 data[]; +} __packed; + +struct dell_wmi_smbios_buffer { + __u64 length; + struct calling_interface_buffer std; + struct dell_wmi_extensions ext; +} __packed; + +/* Whitelisted smbios class/select commands */ +#define CLASS_TOKEN_READ 0 +#define CLASS_TOKEN_WRITE 1 +#define SELECT_TOKEN_STD 0 +#define SELECT_TOKEN_BAT 1 +#define SELECT_TOKEN_AC 2 +#define CLASS_FLASH_INTERFACE 7 +#define SELECT_FLASH_INTERFACE 3 +#define CLASS_ADMIN_PROP 10 +#define SELECT_ADMIN_PROP 3 +#define CLASS_INFO 17 +#define SELECT_RFKILL 11 +#define SELECT_APP_REGISTRATION 3 +#define SELECT_DOCK 22 + +/* whitelisted tokens */ +#define CAPSULE_EN_TOKEN 0x0461 +#define CAPSULE_DIS_TOKEN 0x0462 +#define WSMT_EN_TOKEN 0x04EC +#define WSMT_DIS_TOKEN 0x04ED + +/* Dell SMBIOS calling IOCTL command used by dell-smbios-wmi */ +#define DELL_WMI_SMBIOS_CMD _IOWR(WMI_IOC, 0, struct dell_wmi_smbios_buffer) + #endif -- cgit v1.2.3-59-g8ed1b From 80d421450187e894af841849d66614a0456912f9 Mon Sep 17 00:00:00 2001 From: Yunlong Song Date: Fri, 27 Oct 2017 20:45:05 +0800 Subject: f2fs: support soft block reservation It supports to extend reserved_blocks sysfs interface to be soft threshold, which allows user configure it exceeding current available user space. This patch also introduces a new sysfs interface called current_reserved_blocks, which shows the current blocks that have already been reserved. Signed-off-by: Yunlong Song Signed-off-by: Chao Yu Signed-off-by: Jaegeuk Kim --- Documentation/ABI/testing/sysfs-fs-f2fs | 13 ++++++++++++- fs/f2fs/f2fs.h | 13 +++++++++++-- fs/f2fs/super.c | 3 ++- fs/f2fs/sysfs.c | 15 ++++++++++++--- 4 files changed, 37 insertions(+), 7 deletions(-) (limited to 'Documentation/ABI/testing') diff --git a/Documentation/ABI/testing/sysfs-fs-f2fs b/Documentation/ABI/testing/sysfs-fs-f2fs index 11b7f4ebea7c..7562b0d42105 100644 --- a/Documentation/ABI/testing/sysfs-fs-f2fs +++ b/Documentation/ABI/testing/sysfs-fs-f2fs @@ -138,7 +138,18 @@ What: /sys/fs/f2fs//reserved_blocks Date: June 2017 Contact: "Chao Yu" Description: - Controls current reserved blocks in system. + Controls target reserved blocks in system, the threshold + is soft, it could exceed current available user space. + +What: /sys/fs/f2fs//current_reserved_blocks +Date: October 2017 +Contact: "Yunlong Song" +Contact: "Chao Yu" +Description: + Shows current reserved blocks in system, it may be temporarily + smaller than target_reserved_blocks, but will gradually + increase to target_reserved_blocks when more free blocks are + freed by user later. What: /sys/fs/f2fs//gc_urgent Date: August 2017 diff --git a/fs/f2fs/f2fs.h b/fs/f2fs/f2fs.h index 13a96b8aa2d7..5ed40fbd1a36 100644 --- a/fs/f2fs/f2fs.h +++ b/fs/f2fs/f2fs.h @@ -1082,6 +1082,7 @@ struct f2fs_sb_info { block_t discard_blks; /* discard command candidats */ block_t last_valid_block_count; /* for recovery */ block_t reserved_blocks; /* configurable reserved blocks */ + block_t current_reserved_blocks; /* current reserved blocks */ u32 s_next_generation; /* for NFS support */ @@ -1557,7 +1558,8 @@ static inline int inc_valid_block_count(struct f2fs_sb_info *sbi, spin_lock(&sbi->stat_lock); sbi->total_valid_block_count += (block_t)(*count); - avail_user_block_count = sbi->user_block_count - sbi->reserved_blocks; + avail_user_block_count = sbi->user_block_count - + sbi->current_reserved_blocks; if (unlikely(sbi->total_valid_block_count > avail_user_block_count)) { diff = sbi->total_valid_block_count - avail_user_block_count; *count -= diff; @@ -1591,6 +1593,10 @@ static inline void dec_valid_block_count(struct f2fs_sb_info *sbi, f2fs_bug_on(sbi, sbi->total_valid_block_count < (block_t) count); f2fs_bug_on(sbi, inode->i_blocks < sectors); sbi->total_valid_block_count -= (block_t)count; + if (sbi->reserved_blocks && + sbi->current_reserved_blocks < sbi->reserved_blocks) + sbi->current_reserved_blocks = min(sbi->reserved_blocks, + sbi->current_reserved_blocks + count); spin_unlock(&sbi->stat_lock); f2fs_i_blocks_write(inode, count, false, true); } @@ -1737,7 +1743,7 @@ static inline int inc_valid_node_count(struct f2fs_sb_info *sbi, spin_lock(&sbi->stat_lock); valid_block_count = sbi->total_valid_block_count + 1; - if (unlikely(valid_block_count + sbi->reserved_blocks > + if (unlikely(valid_block_count + sbi->current_reserved_blocks > sbi->user_block_count)) { spin_unlock(&sbi->stat_lock); goto enospc; @@ -1780,6 +1786,9 @@ static inline void dec_valid_node_count(struct f2fs_sb_info *sbi, sbi->total_valid_node_count--; sbi->total_valid_block_count--; + if (sbi->reserved_blocks && + sbi->current_reserved_blocks < sbi->reserved_blocks) + sbi->current_reserved_blocks++; spin_unlock(&sbi->stat_lock); diff --git a/fs/f2fs/super.c b/fs/f2fs/super.c index 213d2c1e5759..dab1fedf7148 100644 --- a/fs/f2fs/super.c +++ b/fs/f2fs/super.c @@ -987,7 +987,7 @@ static int f2fs_statfs(struct dentry *dentry, struct kstatfs *buf) buf->f_blocks = total_count - start_count; buf->f_bfree = user_block_count - valid_user_blocks(sbi) + ovp_count; buf->f_bavail = user_block_count - valid_user_blocks(sbi) - - sbi->reserved_blocks; + sbi->current_reserved_blocks; avail_node_count = sbi->total_node_count - F2FS_RESERVED_NODE_NUM; @@ -2458,6 +2458,7 @@ try_onemore: le64_to_cpu(sbi->ckpt->valid_block_count); sbi->last_valid_block_count = sbi->total_valid_block_count; sbi->reserved_blocks = 0; + sbi->current_reserved_blocks = 0; for (i = 0; i < NR_INODE_TYPE; i++) { INIT_LIST_HEAD(&sbi->inode_list[i]); diff --git a/fs/f2fs/sysfs.c b/fs/f2fs/sysfs.c index e09e59cc678a..4166ac74e837 100644 --- a/fs/f2fs/sysfs.c +++ b/fs/f2fs/sysfs.c @@ -30,7 +30,7 @@ enum { FAULT_INFO_RATE, /* struct f2fs_fault_info */ FAULT_INFO_TYPE, /* struct f2fs_fault_info */ #endif - RESERVED_BLOCKS, + RESERVED_BLOCKS, /* struct f2fs_sb_info */ }; struct f2fs_attr { @@ -114,6 +114,12 @@ static ssize_t features_show(struct f2fs_attr *a, return len; } +static ssize_t current_reserved_blocks_show(struct f2fs_attr *a, + struct f2fs_sb_info *sbi, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%u\n", sbi->current_reserved_blocks); +} + static ssize_t f2fs_sbi_show(struct f2fs_attr *a, struct f2fs_sb_info *sbi, char *buf) { @@ -153,12 +159,13 @@ static ssize_t f2fs_sbi_store(struct f2fs_attr *a, #endif if (a->struct_type == RESERVED_BLOCKS) { spin_lock(&sbi->stat_lock); - if ((unsigned long)sbi->total_valid_block_count + t > - (unsigned long)sbi->user_block_count) { + if (t > (unsigned long)sbi->user_block_count) { spin_unlock(&sbi->stat_lock); return -EINVAL; } *ui = t; + sbi->current_reserved_blocks = min(sbi->reserved_blocks, + sbi->user_block_count - valid_user_blocks(sbi)); spin_unlock(&sbi->stat_lock); return count; } @@ -293,6 +300,7 @@ F2FS_RW_ATTR(FAULT_INFO_TYPE, f2fs_fault_info, inject_type, inject_type); F2FS_GENERAL_RO_ATTR(dirty_segments); F2FS_GENERAL_RO_ATTR(lifetime_write_kbytes); F2FS_GENERAL_RO_ATTR(features); +F2FS_GENERAL_RO_ATTR(current_reserved_blocks); #ifdef CONFIG_F2FS_FS_ENCRYPTION F2FS_FEATURE_RO_ATTR(encryption, FEAT_CRYPTO); @@ -338,6 +346,7 @@ static struct attribute *f2fs_attrs[] = { ATTR_LIST(lifetime_write_kbytes), ATTR_LIST(features), ATTR_LIST(reserved_blocks), + ATTR_LIST(current_reserved_blocks), NULL, }; -- cgit v1.2.3-59-g8ed1b From b32d73abc6f0a63503f68a7227b1d46aff1f46af Mon Sep 17 00:00:00 2001 From: Chao Yu Date: Sat, 28 Oct 2017 16:52:29 +0800 Subject: f2fs: add missing sysfs description There are some missing sysfs entries' description in document, add them. Signed-off-by: Chao Yu Signed-off-by: Jaegeuk Kim --- Documentation/ABI/testing/sysfs-fs-f2fs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) (limited to 'Documentation/ABI/testing') diff --git a/Documentation/ABI/testing/sysfs-fs-f2fs b/Documentation/ABI/testing/sysfs-fs-f2fs index 7562b0d42105..fc1dff659df4 100644 --- a/Documentation/ABI/testing/sysfs-fs-f2fs +++ b/Documentation/ABI/testing/sysfs-fs-f2fs @@ -51,6 +51,12 @@ Description: Controls the dirty page count condition for the in-place-update policies. +What: /sys/fs/f2fs//min_hot_blocks +Date: March 2017 +Contact: "Jaegeuk Kim" +Description: + Controls the dirty page count condition for redefining hot data. + What: /sys/fs/f2fs//max_small_discards Date: November 2013 Contact: "Jaegeuk Kim" @@ -102,6 +108,12 @@ Contact: "Jaegeuk Kim" Description: Controls the idle timing. +What: /sys/fs/f2fs//iostat_enable +Date: August 2017 +Contact: "Chao Yu" +Description: + Controls to enable/disable IO stat. + What: /sys/fs/f2fs//ra_nid_pages Date: October 2015 Contact: "Chao Yu" @@ -122,6 +134,12 @@ Contact: "Shuoran Liu" Description: Shows total written kbytes issued to disk. +What: /sys/fs/f2fs//feature +Date: July 2017 +Contact: "Jaegeuk Kim" +Description: + Shows all enabled features in current device. + What: /sys/fs/f2fs//inject_rate Date: May 2016 Contact: "Sheng Yong" -- cgit v1.2.3-59-g8ed1b From a2a12b679f367014fa86d2cf3309e37c59e155c2 Mon Sep 17 00:00:00 2001 From: Chao Yu Date: Sat, 28 Oct 2017 16:52:33 +0800 Subject: f2fs: export SSR allocation threshold This patch exports min_ssr_segments threshold in sysfs to let user control triggering SSR allocation flexibly. Signed-off-by: Chao Yu Signed-off-by: Jaegeuk Kim --- Documentation/ABI/testing/sysfs-fs-f2fs | 6 ++++++ fs/f2fs/f2fs.h | 2 ++ fs/f2fs/segment.c | 3 ++- fs/f2fs/sysfs.c | 2 ++ 4 files changed, 12 insertions(+), 1 deletion(-) (limited to 'Documentation/ABI/testing') diff --git a/Documentation/ABI/testing/sysfs-fs-f2fs b/Documentation/ABI/testing/sysfs-fs-f2fs index fc1dff659df4..a7799c2fca28 100644 --- a/Documentation/ABI/testing/sysfs-fs-f2fs +++ b/Documentation/ABI/testing/sysfs-fs-f2fs @@ -57,6 +57,12 @@ Contact: "Jaegeuk Kim" Description: Controls the dirty page count condition for redefining hot data. +What: /sys/fs/f2fs//min_ssr_sections +Date: October 2017 +Contact: "Chao Yu" +Description: + Controls the fee section threshold to trigger SSR allocation. + What: /sys/fs/f2fs//max_small_discards Date: November 2013 Contact: "Jaegeuk Kim" diff --git a/fs/f2fs/f2fs.h b/fs/f2fs/f2fs.h index 2b9b1d8b3870..ee10c5206b2f 100644 --- a/fs/f2fs/f2fs.h +++ b/fs/f2fs/f2fs.h @@ -840,6 +840,7 @@ struct f2fs_sm_info { unsigned int min_ipu_util; /* in-place-update threshold */ unsigned int min_fsync_blocks; /* threshold for fsync */ unsigned int min_hot_blocks; /* threshold for hot block allocation */ + unsigned int min_ssr_sections; /* threshold to trigger SSR allocation */ /* for flush command control */ struct flush_cmd_control *fcc_info; @@ -1077,6 +1078,7 @@ struct f2fs_sb_info { int active_logs; /* # of active logs */ int dir_level; /* directory level */ int inline_xattr_size; /* inline xattr size */ + unsigned int trigger_ssr_threshold; /* threshold to trigger ssr */ block_t user_block_count; /* # of user blocks */ block_t total_valid_block_count; /* # of valid blocks */ diff --git a/fs/f2fs/segment.c b/fs/f2fs/segment.c index d2b2351e7d9c..0de1761928d3 100644 --- a/fs/f2fs/segment.c +++ b/fs/f2fs/segment.c @@ -181,7 +181,7 @@ bool need_SSR(struct f2fs_sb_info *sbi) return true; return free_sections(sbi) <= (node_secs + 2 * dent_secs + imeta_secs + - 2 * reserved_sections(sbi)); + SM_I(sbi)->min_ssr_sections + reserved_sections(sbi)); } void register_inmem_page(struct inode *inode, struct page *page) @@ -3671,6 +3671,7 @@ int build_segment_manager(struct f2fs_sb_info *sbi) sm_info->min_ipu_util = DEF_MIN_IPU_UTIL; sm_info->min_fsync_blocks = DEF_MIN_FSYNC_BLOCKS; sm_info->min_hot_blocks = DEF_MIN_HOT_BLOCKS; + sm_info->min_ssr_sections = reserved_sections(sbi); sm_info->trim_sections = DEF_BATCHED_TRIM_SECTIONS; diff --git a/fs/f2fs/sysfs.c b/fs/f2fs/sysfs.c index 4166ac74e837..f0fdc89ce82f 100644 --- a/fs/f2fs/sysfs.c +++ b/fs/f2fs/sysfs.c @@ -285,6 +285,7 @@ F2FS_RW_ATTR(SM_INFO, f2fs_sm_info, ipu_policy, ipu_policy); F2FS_RW_ATTR(SM_INFO, f2fs_sm_info, min_ipu_util, min_ipu_util); F2FS_RW_ATTR(SM_INFO, f2fs_sm_info, min_fsync_blocks, min_fsync_blocks); F2FS_RW_ATTR(SM_INFO, f2fs_sm_info, min_hot_blocks, min_hot_blocks); +F2FS_RW_ATTR(SM_INFO, f2fs_sm_info, min_ssr_sections, min_ssr_sections); F2FS_RW_ATTR(NM_INFO, f2fs_nm_info, ram_thresh, ram_thresh); F2FS_RW_ATTR(NM_INFO, f2fs_nm_info, ra_nid_pages, ra_nid_pages); F2FS_RW_ATTR(NM_INFO, f2fs_nm_info, dirty_nats_ratio, dirty_nats_ratio); @@ -330,6 +331,7 @@ static struct attribute *f2fs_attrs[] = { ATTR_LIST(min_ipu_util), ATTR_LIST(min_fsync_blocks), ATTR_LIST(min_hot_blocks), + ATTR_LIST(min_ssr_sections), ATTR_LIST(max_victim_search), ATTR_LIST(dir_level), ATTR_LIST(ram_thresh), -- cgit v1.2.3-59-g8ed1b From 0759e80b84e34a84e7e46e2b1adb528c83d84a47 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 7 Nov 2017 11:33:49 +0100 Subject: PM / QoS: Fix device resume latency framework The special value of 0 for device resume latency PM QoS means "no restriction", but there are two problems with that. First, device resume latency PM QoS requests with 0 as the value are always put in front of requests with positive values in the priority lists used internally by the PM QoS framework, causing 0 to be chosen as an effective constraint value. However, that 0 is then interpreted as "no restriction" effectively overriding the other requests with specific restrictions which is incorrect. Second, the users of device resume latency PM QoS have no way to specify that *any* resume latency at all should be avoided, which is an artificial limitation in general. To address these issues, modify device resume latency PM QoS to use S32_MAX as the "no constraint" value and 0 as the "no latency at all" one and rework its users (the cpuidle menu governor, the genpd QoS governor and the runtime PM framework) to follow these changes. Also add a special "n/a" value to the corresponding user space I/F to allow user space to indicate that it cannot accept any resume latencies at all for the given device. Fixes: 85dc0b8a4019 (PM / QoS: Make it possible to expose PM QoS latency constraints) Link: https://bugzilla.kernel.org/show_bug.cgi?id=197323 Reported-by: Reinette Chatre Signed-off-by: Rafael J. Wysocki Tested-by: Reinette Chatre Tested-by: Geert Uytterhoeven Tested-by: Tero Kristo Reviewed-by: Ramesh Thomas --- Documentation/ABI/testing/sysfs-devices-power | 4 ++- drivers/base/cpu.c | 3 +- drivers/base/power/domain.c | 2 +- drivers/base/power/domain_governor.c | 40 +++++++++++---------------- drivers/base/power/qos.c | 5 +++- drivers/base/power/runtime.c | 2 +- drivers/base/power/sysfs.c | 25 ++++++++++++++--- drivers/cpuidle/governors/menu.c | 4 +-- include/linux/pm_qos.h | 26 +++++++++++------ 9 files changed, 68 insertions(+), 43 deletions(-) (limited to 'Documentation/ABI/testing') diff --git a/Documentation/ABI/testing/sysfs-devices-power b/Documentation/ABI/testing/sysfs-devices-power index f4b24c327665..80a00f7b6667 100644 --- a/Documentation/ABI/testing/sysfs-devices-power +++ b/Documentation/ABI/testing/sysfs-devices-power @@ -211,7 +211,9 @@ Description: device, after it has been suspended at run time, from a resume request to the moment the device will be ready to process I/O, in microseconds. If it is equal to 0, however, this means that - the PM QoS resume latency may be arbitrary. + the PM QoS resume latency may be arbitrary and the special value + "n/a" means that user space cannot accept any resume latency at + all for the given device. Not all drivers support this attribute. If it isn't supported, it is not present. diff --git a/drivers/base/cpu.c b/drivers/base/cpu.c index 321cd7b4d817..227bac5f1191 100644 --- a/drivers/base/cpu.c +++ b/drivers/base/cpu.c @@ -377,7 +377,8 @@ int register_cpu(struct cpu *cpu, int num) per_cpu(cpu_sys_devices, num) = &cpu->dev; register_cpu_under_node(num, cpu_to_node(num)); - dev_pm_qos_expose_latency_limit(&cpu->dev, 0); + dev_pm_qos_expose_latency_limit(&cpu->dev, + PM_QOS_RESUME_LATENCY_NO_CONSTRAINT); return 0; } diff --git a/drivers/base/power/domain.c b/drivers/base/power/domain.c index 679c79545e42..24e39ce27bd8 100644 --- a/drivers/base/power/domain.c +++ b/drivers/base/power/domain.c @@ -1326,7 +1326,7 @@ static struct generic_pm_domain_data *genpd_alloc_dev_data(struct device *dev, gpd_data->base.dev = dev; gpd_data->td.constraint_changed = true; - gpd_data->td.effective_constraint_ns = 0; + gpd_data->td.effective_constraint_ns = PM_QOS_RESUME_LATENCY_NO_CONSTRAINT_NS; gpd_data->nb.notifier_call = genpd_dev_pm_qos_notifier; spin_lock_irq(&dev->power.lock); diff --git a/drivers/base/power/domain_governor.c b/drivers/base/power/domain_governor.c index e4cca8adab32..99896fbf18e4 100644 --- a/drivers/base/power/domain_governor.c +++ b/drivers/base/power/domain_governor.c @@ -33,15 +33,10 @@ static int dev_update_qos_constraint(struct device *dev, void *data) * known at this point anyway). */ constraint_ns = dev_pm_qos_read_value(dev); - if (constraint_ns > 0) - constraint_ns *= NSEC_PER_USEC; + constraint_ns *= NSEC_PER_USEC; } - /* 0 means "no constraint" */ - if (constraint_ns == 0) - return 0; - - if (constraint_ns < *constraint_ns_p || *constraint_ns_p == 0) + if (constraint_ns < *constraint_ns_p) *constraint_ns_p = constraint_ns; return 0; @@ -69,12 +64,12 @@ static bool default_suspend_ok(struct device *dev) } td->constraint_changed = false; td->cached_suspend_ok = false; - td->effective_constraint_ns = -1; + td->effective_constraint_ns = 0; constraint_ns = __dev_pm_qos_read_value(dev); spin_unlock_irqrestore(&dev->power.lock, flags); - if (constraint_ns < 0) + if (constraint_ns == 0) return false; constraint_ns *= NSEC_PER_USEC; @@ -87,25 +82,25 @@ static bool default_suspend_ok(struct device *dev) device_for_each_child(dev, &constraint_ns, dev_update_qos_constraint); - if (constraint_ns == 0) { + if (constraint_ns == PM_QOS_RESUME_LATENCY_NO_CONSTRAINT_NS) { /* "No restriction", so the device is allowed to suspend. */ - td->effective_constraint_ns = 0; + td->effective_constraint_ns = PM_QOS_RESUME_LATENCY_NO_CONSTRAINT_NS; td->cached_suspend_ok = true; - } else if (constraint_ns < 0) { + } else if (constraint_ns == 0) { /* * This triggers if one of the children that don't belong to a - * domain has a negative PM QoS constraint and it's better not - * to suspend then. effective_constraint_ns is negative already - * and cached_suspend_ok is false, so bail out. + * domain has a zero PM QoS constraint and it's better not to + * suspend then. effective_constraint_ns is zero already and + * cached_suspend_ok is false, so bail out. */ return false; } else { constraint_ns -= td->suspend_latency_ns + td->resume_latency_ns; /* - * effective_constraint_ns is negative already and - * cached_suspend_ok is false, so if the computed value is not - * positive, return right away. + * effective_constraint_ns is zero already and cached_suspend_ok + * is false, so if the computed value is not positive, return + * right away. */ if (constraint_ns <= 0) return false; @@ -174,13 +169,10 @@ static bool __default_power_down_ok(struct dev_pm_domain *pd, td = &to_gpd_data(pdd)->td; constraint_ns = td->effective_constraint_ns; /* - * Negative values mean "no suspend at all" and this runs only - * when all devices in the domain are suspended, so it must be - * 0 at least. - * - * 0 means "no constraint" + * Zero means "no suspend at all" and this runs only when all + * devices in the domain are suspended, so it must be positive. */ - if (constraint_ns == 0) + if (constraint_ns == PM_QOS_RESUME_LATENCY_NO_CONSTRAINT_NS) continue; if (constraint_ns <= off_on_time_ns) diff --git a/drivers/base/power/qos.c b/drivers/base/power/qos.c index 277d43a83f53..3382542b39b7 100644 --- a/drivers/base/power/qos.c +++ b/drivers/base/power/qos.c @@ -139,6 +139,9 @@ static int apply_constraint(struct dev_pm_qos_request *req, switch(req->type) { case DEV_PM_QOS_RESUME_LATENCY: + if (WARN_ON(action != PM_QOS_REMOVE_REQ && value < 0)) + value = 0; + ret = pm_qos_update_target(&qos->resume_latency, &req->data.pnode, action, value); break; @@ -189,7 +192,7 @@ static int dev_pm_qos_constraints_allocate(struct device *dev) plist_head_init(&c->list); c->target_value = PM_QOS_RESUME_LATENCY_DEFAULT_VALUE; c->default_value = PM_QOS_RESUME_LATENCY_DEFAULT_VALUE; - c->no_constraint_value = PM_QOS_RESUME_LATENCY_DEFAULT_VALUE; + c->no_constraint_value = PM_QOS_RESUME_LATENCY_NO_CONSTRAINT; c->type = PM_QOS_MIN; c->notifiers = n; diff --git a/drivers/base/power/runtime.c b/drivers/base/power/runtime.c index 7bcf80fa9ada..13e015905543 100644 --- a/drivers/base/power/runtime.c +++ b/drivers/base/power/runtime.c @@ -253,7 +253,7 @@ static int rpm_check_suspend_allowed(struct device *dev) || (dev->power.request_pending && dev->power.request == RPM_REQ_RESUME)) retval = -EAGAIN; - else if (__dev_pm_qos_read_value(dev) < 0) + else if (__dev_pm_qos_read_value(dev) == 0) retval = -EPERM; else if (dev->power.runtime_status == RPM_SUSPENDED) retval = 1; diff --git a/drivers/base/power/sysfs.c b/drivers/base/power/sysfs.c index 29bf28fef136..e153e28b1857 100644 --- a/drivers/base/power/sysfs.c +++ b/drivers/base/power/sysfs.c @@ -218,7 +218,14 @@ static ssize_t pm_qos_resume_latency_show(struct device *dev, struct device_attribute *attr, char *buf) { - return sprintf(buf, "%d\n", dev_pm_qos_requested_resume_latency(dev)); + s32 value = dev_pm_qos_requested_resume_latency(dev); + + if (value == 0) + return sprintf(buf, "n/a\n"); + else if (value == PM_QOS_RESUME_LATENCY_NO_CONSTRAINT) + value = 0; + + return sprintf(buf, "%d\n", value); } static ssize_t pm_qos_resume_latency_store(struct device *dev, @@ -228,11 +235,21 @@ static ssize_t pm_qos_resume_latency_store(struct device *dev, s32 value; int ret; - if (kstrtos32(buf, 0, &value)) - return -EINVAL; + if (!kstrtos32(buf, 0, &value)) { + /* + * Prevent users from writing negative or "no constraint" values + * directly. + */ + if (value < 0 || value == PM_QOS_RESUME_LATENCY_NO_CONSTRAINT) + return -EINVAL; - if (value < 0) + if (value == 0) + value = PM_QOS_RESUME_LATENCY_NO_CONSTRAINT; + } else if (!strcmp(buf, "n/a") || !strcmp(buf, "n/a\n")) { + value = 0; + } else { return -EINVAL; + } ret = dev_pm_qos_update_request(dev->power.qos->resume_latency_req, value); diff --git a/drivers/cpuidle/governors/menu.c b/drivers/cpuidle/governors/menu.c index 48eaf2879228..aa390404e85f 100644 --- a/drivers/cpuidle/governors/menu.c +++ b/drivers/cpuidle/governors/menu.c @@ -298,8 +298,8 @@ static int menu_select(struct cpuidle_driver *drv, struct cpuidle_device *dev) data->needs_update = 0; } - /* resume_latency is 0 means no restriction */ - if (resume_latency && resume_latency < latency_req) + if (resume_latency < latency_req && + resume_latency != PM_QOS_RESUME_LATENCY_NO_CONSTRAINT) latency_req = resume_latency; /* Special case when user has set very strict latency requirement */ diff --git a/include/linux/pm_qos.h b/include/linux/pm_qos.h index 51f0d7e0b15f..2a3b36da61b1 100644 --- a/include/linux/pm_qos.h +++ b/include/linux/pm_qos.h @@ -27,16 +27,19 @@ enum pm_qos_flags_status { PM_QOS_FLAGS_ALL, }; -#define PM_QOS_DEFAULT_VALUE -1 +#define PM_QOS_DEFAULT_VALUE (-1) +#define PM_QOS_LATENCY_ANY S32_MAX +#define PM_QOS_LATENCY_ANY_NS ((s64)PM_QOS_LATENCY_ANY * NSEC_PER_USEC) #define PM_QOS_CPU_DMA_LAT_DEFAULT_VALUE (2000 * USEC_PER_SEC) #define PM_QOS_NETWORK_LAT_DEFAULT_VALUE (2000 * USEC_PER_SEC) #define PM_QOS_NETWORK_THROUGHPUT_DEFAULT_VALUE 0 #define PM_QOS_MEMORY_BANDWIDTH_DEFAULT_VALUE 0 -#define PM_QOS_RESUME_LATENCY_DEFAULT_VALUE 0 +#define PM_QOS_RESUME_LATENCY_DEFAULT_VALUE PM_QOS_LATENCY_ANY +#define PM_QOS_RESUME_LATENCY_NO_CONSTRAINT PM_QOS_LATENCY_ANY +#define PM_QOS_RESUME_LATENCY_NO_CONSTRAINT_NS PM_QOS_LATENCY_ANY_NS #define PM_QOS_LATENCY_TOLERANCE_DEFAULT_VALUE 0 #define PM_QOS_LATENCY_TOLERANCE_NO_CONSTRAINT (-1) -#define PM_QOS_LATENCY_ANY ((s32)(~(__u32)0 >> 1)) #define PM_QOS_FLAG_NO_POWER_OFF (1 << 0) @@ -173,7 +176,8 @@ static inline s32 dev_pm_qos_requested_flags(struct device *dev) static inline s32 dev_pm_qos_raw_read_value(struct device *dev) { return IS_ERR_OR_NULL(dev->power.qos) ? - 0 : pm_qos_read_value(&dev->power.qos->resume_latency); + PM_QOS_RESUME_LATENCY_NO_CONSTRAINT : + pm_qos_read_value(&dev->power.qos->resume_latency); } #else static inline enum pm_qos_flags_status __dev_pm_qos_flags(struct device *dev, @@ -183,9 +187,9 @@ static inline enum pm_qos_flags_status dev_pm_qos_flags(struct device *dev, s32 mask) { return PM_QOS_FLAGS_UNDEFINED; } static inline s32 __dev_pm_qos_read_value(struct device *dev) - { return 0; } + { return PM_QOS_RESUME_LATENCY_NO_CONSTRAINT; } static inline s32 dev_pm_qos_read_value(struct device *dev) - { return 0; } + { return PM_QOS_RESUME_LATENCY_NO_CONSTRAINT; } static inline int dev_pm_qos_add_request(struct device *dev, struct dev_pm_qos_request *req, enum dev_pm_qos_req_type type, @@ -231,9 +235,15 @@ static inline int dev_pm_qos_expose_latency_tolerance(struct device *dev) { return 0; } static inline void dev_pm_qos_hide_latency_tolerance(struct device *dev) {} -static inline s32 dev_pm_qos_requested_resume_latency(struct device *dev) { return 0; } +static inline s32 dev_pm_qos_requested_resume_latency(struct device *dev) +{ + return PM_QOS_RESUME_LATENCY_NO_CONSTRAINT; +} static inline s32 dev_pm_qos_requested_flags(struct device *dev) { return 0; } -static inline s32 dev_pm_qos_raw_read_value(struct device *dev) { return 0; } +static inline s32 dev_pm_qos_raw_read_value(struct device *dev) +{ + return PM_QOS_RESUME_LATENCY_NO_CONSTRAINT; +} #endif #endif -- cgit v1.2.3-59-g8ed1b From f00d79750712511d0a83c108eea0d44b680a915f Mon Sep 17 00:00:00 2001 From: Matthew Garrett Date: Wed, 11 Oct 2017 12:10:14 -0700 Subject: EVM: Allow userspace to signal an RSA key has been loaded EVM will only perform validation once a key has been loaded. This key may either be a symmetric trusted key (for HMAC validation and creation) or the public half of an asymmetric key (for digital signature validation). The /sys/kernel/security/evm interface allows userland to signal that a symmetric key has been loaded, but does not allow userland to signal that an asymmetric public key has been loaded. This patch extends the interface to permit userspace to pass a bitmask of loaded key types. It also allows userspace to block loading of a symmetric key in order to avoid a compromised system from being able to load an additional key type later. Signed-off-by: Matthew Garrett Signed-off-by: Mimi Zohar --- Documentation/ABI/testing/evm | 47 ++++++++++++++++++++++++++------------ security/integrity/evm/evm.h | 3 +++ security/integrity/evm/evm_secfs.c | 29 +++++++++++++---------- 3 files changed, 53 insertions(+), 26 deletions(-) (limited to 'Documentation/ABI/testing') diff --git a/Documentation/ABI/testing/evm b/Documentation/ABI/testing/evm index 8374d4557e5d..a0bbccb00736 100644 --- a/Documentation/ABI/testing/evm +++ b/Documentation/ABI/testing/evm @@ -7,17 +7,36 @@ Description: HMAC-sha1 value across the extended attributes, storing the value as the extended attribute 'security.evm'. - EVM depends on the Kernel Key Retention System to provide it - with a trusted/encrypted key for the HMAC-sha1 operation. - The key is loaded onto the root's keyring using keyctl. Until - EVM receives notification that the key has been successfully - loaded onto the keyring (echo 1 > /evm), EVM - can not create or validate the 'security.evm' xattr, but - returns INTEGRITY_UNKNOWN. Loading the key and signaling EVM - should be done as early as possible. Normally this is done - in the initramfs, which has already been measured as part - of the trusted boot. For more information on creating and - loading existing trusted/encrypted keys, refer to: - Documentation/keys-trusted-encrypted.txt. (A sample dracut - patch, which loads the trusted/encrypted key and enables - EVM, is available from http://linux-ima.sourceforge.net/#EVM.) + EVM supports two classes of security.evm. The first is + an HMAC-sha1 generated locally with a + trusted/encrypted key stored in the Kernel Key + Retention System. The second is a digital signature + generated either locally or remotely using an + asymmetric key. These keys are loaded onto root's + keyring using keyctl, and EVM is then enabled by + echoing a value to /evm: + + 1: enable HMAC validation and creation + 2: enable digital signature validation + 3: enable HMAC and digital signature validation and HMAC + creation + + Further writes will be blocked if HMAC support is enabled or + if bit 32 is set: + + echo 0x80000002 >/evm + + will enable digital signature validation and block + further writes to /evm. + + Until this is done, EVM can not create or validate the + 'security.evm' xattr, but returns INTEGRITY_UNKNOWN. + Loading keys and signaling EVM should be done as early + as possible. Normally this is done in the initramfs, + which has already been measured as part of the trusted + boot. For more information on creating and loading + existing trusted/encrypted keys, refer to: + Documentation/keys-trusted-encrypted.txt. Both dracut + (via 97masterkey and 98integrity) and systemd (via + core/ima-setup) have support for loading keys at boot + time. diff --git a/security/integrity/evm/evm.h b/security/integrity/evm/evm.h index f5f12727771a..241aca315b0c 100644 --- a/security/integrity/evm/evm.h +++ b/security/integrity/evm/evm.h @@ -23,6 +23,9 @@ #define EVM_INIT_HMAC 0x0001 #define EVM_INIT_X509 0x0002 +#define EVM_SETUP 0x80000000 /* userland has signaled key load */ + +#define EVM_INIT_MASK (EVM_INIT_HMAC | EVM_INIT_X509 | EVM_SETUP) extern int evm_initialized; extern char *evm_hmac; diff --git a/security/integrity/evm/evm_secfs.c b/security/integrity/evm/evm_secfs.c index c8dccd54d501..319cf16d6603 100644 --- a/security/integrity/evm/evm_secfs.c +++ b/security/integrity/evm/evm_secfs.c @@ -40,7 +40,7 @@ static ssize_t evm_read_key(struct file *filp, char __user *buf, if (*ppos != 0) return 0; - sprintf(temp, "%d", evm_initialized); + sprintf(temp, "%d", (evm_initialized & ~EVM_SETUP)); rc = simple_read_from_buffer(buf, count, ppos, temp, strlen(temp)); return rc; @@ -61,24 +61,29 @@ static ssize_t evm_read_key(struct file *filp, char __user *buf, static ssize_t evm_write_key(struct file *file, const char __user *buf, size_t count, loff_t *ppos) { - char temp[80]; - int i; + int i, ret; - if (!capable(CAP_SYS_ADMIN) || (evm_initialized & EVM_INIT_HMAC)) + if (!capable(CAP_SYS_ADMIN) || (evm_initialized & EVM_SETUP)) return -EPERM; - if (count >= sizeof(temp) || count == 0) - return -EINVAL; - - if (copy_from_user(temp, buf, count) != 0) - return -EFAULT; + ret = kstrtoint_from_user(buf, count, 0, &i); - temp[count] = '\0'; + if (ret) + return ret; - if ((sscanf(temp, "%d", &i) != 1) || (i != 1)) + /* Reject invalid values */ + if (!i || (i & ~EVM_INIT_MASK) != 0) return -EINVAL; - evm_init_key(); + if (i & EVM_INIT_HMAC) { + ret = evm_init_key(); + if (ret != 0) + return ret; + /* Forbid further writes after the symmetric key is loaded */ + i |= EVM_SETUP; + } + + evm_initialized |= i; return count; } -- cgit v1.2.3-59-g8ed1b From f44cd758121a341aa96ed89b88e3a08857b406ba Mon Sep 17 00:00:00 2001 From: Peter Meerwald-Stadler Date: Mon, 6 Nov 2017 00:25:00 +0100 Subject: Documentation: iio: Fix typo Signed-off-by: Peter Meerwald-Stadler Signed-off-by: Jonathan Cameron --- Documentation/ABI/testing/sysfs-bus-iio | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Documentation/ABI/testing') diff --git a/Documentation/ABI/testing/sysfs-bus-iio b/Documentation/ABI/testing/sysfs-bus-iio index 2e3f919485f4..a478740e2783 100644 --- a/Documentation/ABI/testing/sysfs-bus-iio +++ b/Documentation/ABI/testing/sysfs-bus-iio @@ -1290,7 +1290,7 @@ KernelVersion: 3.4 Contact: linux-iio@vger.kernel.org Description: Unit-less light intensity. Modifiers both and ir indicate - that measurements contains visible and infrared light + that measurements contain visible and infrared light components or just infrared light, respectively. Modifier uv indicates that measurements contain ultraviolet light components. -- cgit v1.2.3-59-g8ed1b From 43347d56c8d9dd732cee2f8efd384ad21dd1f6c4 Mon Sep 17 00:00:00 2001 From: Miroslav Benes Date: Wed, 15 Nov 2017 14:50:13 +0100 Subject: livepatch: send a fake signal to all blocking tasks Live patching consistency model is of LEAVE_PATCHED_SET and SWITCH_THREAD. This means that all tasks in the system have to be marked one by one as safe to call a new patched function. Safe means when a task is not (sleeping) in a set of patched functions. That is, no patched function is on the task's stack. Another clearly safe place is the boundary between kernel and userspace. The patching waits for all tasks to get outside of the patched set or to cross the boundary. The transition is completed afterwards. The problem is that a task can block the transition for quite a long time, if not forever. It could sleep in a set of patched functions, for example. Luckily we can force the task to leave the set by sending it a fake signal, that is a signal with no data in signal pending structures (no handler, no sign of proper signal delivered). Suspend/freezer use this to freeze the tasks as well. The task gets TIF_SIGPENDING set and is woken up (if it has been sleeping in the kernel before) or kicked by rescheduling IPI (if it was running on other CPU). This causes the task to go to kernel/userspace boundary where the signal would be handled and the task would be marked as safe in terms of live patching. There are tasks which are not affected by this technique though. The fake signal is not sent to kthreads. They should be handled differently. They can be woken up so they leave the patched set and their TIF_PATCH_PENDING can be cleared thanks to stack checking. For the sake of completeness, if the task is in TASK_RUNNING state but not currently running on some CPU it doesn't get the IPI, but it would eventually handle the signal anyway. Second, if the task runs in the kernel (in TASK_RUNNING state) it gets the IPI, but the signal is not handled on return from the interrupt. It would be handled on return to the userspace in the future when the fake signal is sent again. Stack checking deals with these cases in a better way. If the task was sleeping in a syscall it would be woken by our fake signal, it would check if TIF_SIGPENDING is set (by calling signal_pending() predicate) and return ERESTART* or EINTR. Syscalls with ERESTART* return values are restarted in case of the fake signal (see do_signal()). EINTR is propagated back to the userspace program. This could disturb the program, but... * each process dealing with signals should react accordingly to EINTR return values. * syscalls returning EINTR happen to be quite common situation in the system even if no fake signal is sent. * freezer sends the fake signal and does not deal with EINTR anyhow. Thus EINTR values are returned when the system is resumed. The very safe marking is done in architectures' "entry" on syscall and interrupt/exception exit paths, and in a stack checking functions of livepatch. TIF_PATCH_PENDING is cleared and the next recalc_sigpending() drops TIF_SIGPENDING. In connection with this, also call klp_update_patch_state() before do_signal(), so that recalc_sigpending() in dequeue_signal() can clear TIF_PATCH_PENDING immediately and thus prevent a double call of do_signal(). Note that the fake signal is not sent to stopped/traced tasks. Such task prevents the patching to finish till it continues again (is not traced anymore). Last, sending the fake signal is not automatic. It is done only when admin requests it by writing 1 to signal sysfs attribute in livepatch sysfs directory. Signed-off-by: Miroslav Benes Cc: Oleg Nesterov Cc: Michael Ellerman Cc: Thomas Gleixner Cc: Ingo Molnar Cc: "H. Peter Anvin" Cc: Andy Lutomirski Cc: linuxppc-dev@lists.ozlabs.org Cc: x86@kernel.org Acked-by: Michael Ellerman (powerpc) Signed-off-by: Jiri Kosina --- Documentation/ABI/testing/sysfs-kernel-livepatch | 12 +++++++ Documentation/livepatch/livepatch.txt | 11 +++++-- arch/powerpc/kernel/signal.c | 6 ++-- arch/x86/entry/common.c | 6 ++-- kernel/livepatch/core.c | 30 +++++++++++++++++ kernel/livepatch/transition.c | 41 ++++++++++++++++++++++++ kernel/livepatch/transition.h | 1 + kernel/signal.c | 4 ++- 8 files changed, 102 insertions(+), 9 deletions(-) (limited to 'Documentation/ABI/testing') diff --git a/Documentation/ABI/testing/sysfs-kernel-livepatch b/Documentation/ABI/testing/sysfs-kernel-livepatch index d5d39748382f..3bb9d5bc1ce3 100644 --- a/Documentation/ABI/testing/sysfs-kernel-livepatch +++ b/Documentation/ABI/testing/sysfs-kernel-livepatch @@ -33,6 +33,18 @@ Description: An attribute which indicates whether the patch is currently in transition. +What: /sys/kernel/livepatch//signal +Date: Nov 2017 +KernelVersion: 4.15.0 +Contact: live-patching@vger.kernel.org +Description: + A writable attribute that allows administrator to affect the + course of an existing transition. Writing 1 sends a fake + signal to all remaining blocking tasks. The fake signal + means that no proper signal is delivered (there is no data in + signal pending structures). Tasks are interrupted or woken up, + and forced to change their patched state. + What: /sys/kernel/livepatch// Date: Nov 2014 KernelVersion: 3.19.0 diff --git a/Documentation/livepatch/livepatch.txt b/Documentation/livepatch/livepatch.txt index ecdb18104ab0..9bcdef277a36 100644 --- a/Documentation/livepatch/livepatch.txt +++ b/Documentation/livepatch/livepatch.txt @@ -176,8 +176,12 @@ If a patch is in transition, this file shows 0 to indicate the task is unpatched and 1 to indicate it's patched. Otherwise, if no patch is in transition, it shows -1. Any tasks which are blocking the transition can be signaled with SIGSTOP and SIGCONT to force them to change their -patched state. - +patched state. This may be harmful to the system though. +/sys/kernel/livepatch//signal attribute provides a better alternative. +Writing 1 to the attribute sends a fake signal to all remaining blocking +tasks. No proper signal is actually delivered (there is no data in signal +pending structures). Tasks are interrupted or woken up, and forced to change +their patched state. 3.1 Adding consistency model support to new architectures --------------------------------------------------------- @@ -435,6 +439,9 @@ Information about the registered patches can be found under /sys/kernel/livepatch. The patches could be enabled and disabled by writing there. +/sys/kernel/livepatch//signal attribute allows administrator to affect a +patching operation. + See Documentation/ABI/testing/sysfs-kernel-livepatch for more details. diff --git a/arch/powerpc/kernel/signal.c b/arch/powerpc/kernel/signal.c index e9436c5e1e09..bf9c4e7792d1 100644 --- a/arch/powerpc/kernel/signal.c +++ b/arch/powerpc/kernel/signal.c @@ -153,6 +153,9 @@ void do_notify_resume(struct pt_regs *regs, unsigned long thread_info_flags) if (thread_info_flags & _TIF_UPROBE) uprobe_notify_resume(regs); + if (thread_info_flags & _TIF_PATCH_PENDING) + klp_update_patch_state(current); + if (thread_info_flags & _TIF_SIGPENDING) { BUG_ON(regs != current->thread.regs); do_signal(current); @@ -163,9 +166,6 @@ void do_notify_resume(struct pt_regs *regs, unsigned long thread_info_flags) tracehook_notify_resume(regs); } - if (thread_info_flags & _TIF_PATCH_PENDING) - klp_update_patch_state(current); - user_enter(); } diff --git a/arch/x86/entry/common.c b/arch/x86/entry/common.c index d7d3cc24baf4..1e3883e45687 100644 --- a/arch/x86/entry/common.c +++ b/arch/x86/entry/common.c @@ -153,6 +153,9 @@ static void exit_to_usermode_loop(struct pt_regs *regs, u32 cached_flags) if (cached_flags & _TIF_UPROBE) uprobe_notify_resume(regs); + if (cached_flags & _TIF_PATCH_PENDING) + klp_update_patch_state(current); + /* deal with pending signal delivery */ if (cached_flags & _TIF_SIGPENDING) do_signal(regs); @@ -165,9 +168,6 @@ static void exit_to_usermode_loop(struct pt_regs *regs, u32 cached_flags) if (cached_flags & _TIF_USER_RETURN_NOTIFY) fire_user_return_notifiers(); - if (cached_flags & _TIF_PATCH_PENDING) - klp_update_patch_state(current); - /* Disable IRQs and retry */ local_irq_disable(); diff --git a/kernel/livepatch/core.c b/kernel/livepatch/core.c index de9e45dca70f..88766bd91803 100644 --- a/kernel/livepatch/core.c +++ b/kernel/livepatch/core.c @@ -454,6 +454,7 @@ EXPORT_SYMBOL_GPL(klp_enable_patch); * /sys/kernel/livepatch/ * /sys/kernel/livepatch//enabled * /sys/kernel/livepatch//transition + * /sys/kernel/livepatch//signal * /sys/kernel/livepatch// * /sys/kernel/livepatch/// */ @@ -528,11 +529,40 @@ static ssize_t transition_show(struct kobject *kobj, patch == klp_transition_patch); } +static ssize_t signal_store(struct kobject *kobj, struct kobj_attribute *attr, + const char *buf, size_t count) +{ + struct klp_patch *patch; + int ret; + bool val; + + patch = container_of(kobj, struct klp_patch, kobj); + + /* + * klp_mutex lock is not grabbed here intentionally. It is not really + * needed. The race window is harmless and grabbing the lock would only + * hold the action back. + */ + if (patch != klp_transition_patch) + return -EINVAL; + + ret = kstrtobool(buf, &val); + if (ret) + return ret; + + if (val) + klp_send_signals(); + + return count; +} + static struct kobj_attribute enabled_kobj_attr = __ATTR_RW(enabled); static struct kobj_attribute transition_kobj_attr = __ATTR_RO(transition); +static struct kobj_attribute signal_kobj_attr = __ATTR_WO(signal); static struct attribute *klp_patch_attrs[] = { &enabled_kobj_attr.attr, &transition_kobj_attr.attr, + &signal_kobj_attr.attr, NULL }; diff --git a/kernel/livepatch/transition.c b/kernel/livepatch/transition.c index 56add6327736..edcfcb8ebb2d 100644 --- a/kernel/livepatch/transition.c +++ b/kernel/livepatch/transition.c @@ -608,3 +608,44 @@ void klp_copy_process(struct task_struct *child) /* TIF_PATCH_PENDING gets copied in setup_thread_stack() */ } + +/* + * Sends a fake signal to all non-kthread tasks with TIF_PATCH_PENDING set. + * Kthreads with TIF_PATCH_PENDING set are woken up. Only admin can request this + * action currently. + */ +void klp_send_signals(void) +{ + struct task_struct *g, *task; + + pr_notice("signaling remaining tasks\n"); + + read_lock(&tasklist_lock); + for_each_process_thread(g, task) { + if (!klp_patch_pending(task)) + continue; + + /* + * There is a small race here. We could see TIF_PATCH_PENDING + * set and decide to wake up a kthread or send a fake signal. + * Meanwhile the task could migrate itself and the action + * would be meaningless. It is not serious though. + */ + if (task->flags & PF_KTHREAD) { + /* + * Wake up a kthread which sleeps interruptedly and + * still has not been migrated. + */ + wake_up_state(task, TASK_INTERRUPTIBLE); + } else { + /* + * Send fake signal to all non-kthread tasks which are + * still not migrated. + */ + spin_lock_irq(&task->sighand->siglock); + signal_wake_up(task, 0); + spin_unlock_irq(&task->sighand->siglock); + } + } + read_unlock(&tasklist_lock); +} diff --git a/kernel/livepatch/transition.h b/kernel/livepatch/transition.h index 0f6e27c481f9..40522795a5f6 100644 --- a/kernel/livepatch/transition.h +++ b/kernel/livepatch/transition.h @@ -11,5 +11,6 @@ void klp_cancel_transition(void); void klp_start_transition(void); void klp_try_complete_transition(void); void klp_reverse_transition(void); +void klp_send_signals(void); #endif /* _LIVEPATCH_TRANSITION_H */ diff --git a/kernel/signal.c b/kernel/signal.c index 8dcd8825b2de..186143b06d00 100644 --- a/kernel/signal.c +++ b/kernel/signal.c @@ -40,6 +40,7 @@ #include #include #include +#include #define CREATE_TRACE_POINTS #include @@ -163,7 +164,8 @@ void recalc_sigpending_and_wake(struct task_struct *t) void recalc_sigpending(void) { - if (!recalc_sigpending_tsk(current) && !freezing(current)) + if (!recalc_sigpending_tsk(current) && !freezing(current) && + !klp_patch_pending(current)) clear_thread_flag(TIF_SIGPENDING); } -- cgit v1.2.3-59-g8ed1b From c99a2be790b07752d8cc694434d3450afd4c5a00 Mon Sep 17 00:00:00 2001 From: Miroslav Benes Date: Wed, 22 Nov 2017 11:29:21 +0100 Subject: livepatch: force transition to finish If a task sleeps in a set of patched functions uninterruptedly, it could block the whole transition indefinitely. Thus it may be useful to clear its TIF_PATCH_PENDING to allow the process to finish. Admin can do that now by writing to force sysfs attribute in livepatch sysfs directory. TIF_PATCH_PENDING is then cleared for all tasks and the transition can finish successfully. Important note! Administrator should not use this feature without a clearance from a patch distributor. It must be checked that by doing so the consistency model guarantees are not violated. Removal (rmmod) of patch modules is permanently disabled when the feature is used. It cannot be guaranteed there is no task sleeping in such module. Signed-off-by: Miroslav Benes Acked-by: Josh Poimboeuf Reviewed-by: Petr Mladek Signed-off-by: Jiri Kosina --- Documentation/ABI/testing/sysfs-kernel-livepatch | 14 +++++++++ Documentation/livepatch/livepatch.txt | 18 ++++++++++-- kernel/livepatch/core.c | 30 ++++++++++++++++++++ kernel/livepatch/transition.c | 36 ++++++++++++++++++++++-- kernel/livepatch/transition.h | 1 + 5 files changed, 95 insertions(+), 4 deletions(-) (limited to 'Documentation/ABI/testing') diff --git a/Documentation/ABI/testing/sysfs-kernel-livepatch b/Documentation/ABI/testing/sysfs-kernel-livepatch index 3bb9d5bc1ce3..dac7e1e62a8b 100644 --- a/Documentation/ABI/testing/sysfs-kernel-livepatch +++ b/Documentation/ABI/testing/sysfs-kernel-livepatch @@ -45,6 +45,20 @@ Description: signal pending structures). Tasks are interrupted or woken up, and forced to change their patched state. +What: /sys/kernel/livepatch//force +Date: Nov 2017 +KernelVersion: 4.15.0 +Contact: live-patching@vger.kernel.org +Description: + A writable attribute that allows administrator to affect the + course of an existing transition. Writing 1 clears + TIF_PATCH_PENDING flag of all tasks and thus forces the tasks to + the patched or unpatched state. Administrator should not + use this feature without a clearance from a patch + distributor. Removal (rmmod) of patch modules is permanently + disabled when the feature is used. See + Documentation/livepatch/livepatch.txt for more information. + What: /sys/kernel/livepatch// Date: Nov 2014 KernelVersion: 3.19.0 diff --git a/Documentation/livepatch/livepatch.txt b/Documentation/livepatch/livepatch.txt index 9bcdef277a36..896ba8941702 100644 --- a/Documentation/livepatch/livepatch.txt +++ b/Documentation/livepatch/livepatch.txt @@ -183,6 +183,20 @@ tasks. No proper signal is actually delivered (there is no data in signal pending structures). Tasks are interrupted or woken up, and forced to change their patched state. +Administrator can also affect a transition through +/sys/kernel/livepatch//force attribute. Writing 1 there clears +TIF_PATCH_PENDING flag of all tasks and thus forces the tasks to the patched +state. Important note! The force attribute is intended for cases when the +transition gets stuck for a long time because of a blocking task. Administrator +is expected to collect all necessary data (namely stack traces of such blocking +tasks) and request a clearance from a patch distributor to force the transition. +Unauthorized usage may cause harm to the system. It depends on the nature of the +patch, which functions are (un)patched, and which functions the blocking tasks +are sleeping in (/proc//stack may help here). Removal (rmmod) of patch +modules is permanently disabled when the force feature is used. It cannot be +guaranteed there is no task sleeping in such module. It implies unbounded +reference count if a patch module is disabled and enabled in a loop. + 3.1 Adding consistency model support to new architectures --------------------------------------------------------- @@ -439,8 +453,8 @@ Information about the registered patches can be found under /sys/kernel/livepatch. The patches could be enabled and disabled by writing there. -/sys/kernel/livepatch//signal attribute allows administrator to affect a -patching operation. +/sys/kernel/livepatch//signal and /sys/kernel/livepatch//force +attributes allow administrator to affect a patching operation. See Documentation/ABI/testing/sysfs-kernel-livepatch for more details. diff --git a/kernel/livepatch/core.c b/kernel/livepatch/core.c index 88766bd91803..1c3c9b27c916 100644 --- a/kernel/livepatch/core.c +++ b/kernel/livepatch/core.c @@ -455,6 +455,7 @@ EXPORT_SYMBOL_GPL(klp_enable_patch); * /sys/kernel/livepatch//enabled * /sys/kernel/livepatch//transition * /sys/kernel/livepatch//signal + * /sys/kernel/livepatch//force * /sys/kernel/livepatch// * /sys/kernel/livepatch/// */ @@ -556,13 +557,42 @@ static ssize_t signal_store(struct kobject *kobj, struct kobj_attribute *attr, return count; } +static ssize_t force_store(struct kobject *kobj, struct kobj_attribute *attr, + const char *buf, size_t count) +{ + struct klp_patch *patch; + int ret; + bool val; + + patch = container_of(kobj, struct klp_patch, kobj); + + /* + * klp_mutex lock is not grabbed here intentionally. It is not really + * needed. The race window is harmless and grabbing the lock would only + * hold the action back. + */ + if (patch != klp_transition_patch) + return -EINVAL; + + ret = kstrtobool(buf, &val); + if (ret) + return ret; + + if (val) + klp_force_transition(); + + return count; +} + static struct kobj_attribute enabled_kobj_attr = __ATTR_RW(enabled); static struct kobj_attribute transition_kobj_attr = __ATTR_RO(transition); static struct kobj_attribute signal_kobj_attr = __ATTR_WO(signal); +static struct kobj_attribute force_kobj_attr = __ATTR_WO(force); static struct attribute *klp_patch_attrs[] = { &enabled_kobj_attr.attr, &transition_kobj_attr.attr, &signal_kobj_attr.attr, + &force_kobj_attr.attr, NULL }; diff --git a/kernel/livepatch/transition.c b/kernel/livepatch/transition.c index edcfcb8ebb2d..be5bfa533ee8 100644 --- a/kernel/livepatch/transition.c +++ b/kernel/livepatch/transition.c @@ -33,6 +33,8 @@ struct klp_patch *klp_transition_patch; static int klp_target_state = KLP_UNDEFINED; +static bool klp_forced = false; + /* * This work can be performed periodically to finish patching or unpatching any * "straggler" tasks which failed to transition in the first attempt. @@ -146,9 +148,12 @@ done: /* * See complementary comment in __klp_enable_patch() for why we * keep the module reference for immediate patches. + * + * klp_forced or immediate_func set implies unbounded increase of + * module's ref count if the module is disabled/enabled in a loop. */ - if (!klp_transition_patch->immediate && !immediate_func && - klp_target_state == KLP_UNPATCHED) { + if (!klp_forced && !klp_transition_patch->immediate && + !immediate_func && klp_target_state == KLP_UNPATCHED) { module_put(klp_transition_patch->mod); } @@ -649,3 +654,30 @@ void klp_send_signals(void) } read_unlock(&tasklist_lock); } + +/* + * Drop TIF_PATCH_PENDING of all tasks on admin's request. This forces an + * existing transition to finish. + * + * NOTE: klp_update_patch_state(task) requires the task to be inactive or + * 'current'. This is not the case here and the consistency model could be + * broken. Administrator, who is the only one to execute the + * klp_force_transitions(), has to be aware of this. + */ +void klp_force_transition(void) +{ + struct task_struct *g, *task; + unsigned int cpu; + + pr_warn("forcing remaining tasks to the patched state\n"); + + read_lock(&tasklist_lock); + for_each_process_thread(g, task) + klp_update_patch_state(task); + read_unlock(&tasklist_lock); + + for_each_possible_cpu(cpu) + klp_update_patch_state(idle_task(cpu)); + + klp_forced = true; +} diff --git a/kernel/livepatch/transition.h b/kernel/livepatch/transition.h index 40522795a5f6..f9d0bc016067 100644 --- a/kernel/livepatch/transition.h +++ b/kernel/livepatch/transition.h @@ -12,5 +12,6 @@ void klp_start_transition(void); void klp_try_complete_transition(void); void klp_reverse_transition(void); void klp_send_signals(void); +void klp_force_transition(void); #endif /* _LIVEPATCH_TRANSITION_H */ -- cgit v1.2.3-59-g8ed1b From dfba2174dc421ecad8dc50741054a305cd3ba681 Mon Sep 17 00:00:00 2001 From: Lu Baolu Date: Fri, 8 Dec 2017 17:59:10 +0200 Subject: usb: xhci: Add DbC support in xHCI driver xHCI compatible USB host controllers(i.e. super-speed USB3 controllers) can be implemented with the Debug Capability(DbC). It presents a debug device which is fully compliant with the USB framework and provides the equivalent of a very high performance full-duplex serial link. The debug capability operation model and registers interface are defined in 7.6.8 of the xHCI specification, revision 1.1. The DbC debug device shares a root port with the xHCI host. By default, the debug capability is disabled and the root port is assigned to xHCI. When the DbC is enabled, the root port will be assigned to the DbC debug device, and the xHCI sees nothing on this port. This implementation uses a sysfs node named under the xHCI device to manage the enabling and disabling of the debug capability. When the debug capability is enabled, it will present a debug device through the debug port. This debug device is fully compliant with the USB3 framework, and it can be enumerated by a debug host on the other end of the USB link. As soon as the debug device is configured, a TTY serial device named /dev/ttyDBC0 will be created. Signed-off-by: Lu Baolu Signed-off-by: Mathias Nyman Signed-off-by: Greg Kroah-Hartman --- .../ABI/testing/sysfs-bus-pci-drivers-xhci_hcd | 25 + drivers/usb/host/Kconfig | 8 + drivers/usb/host/Makefile | 5 + drivers/usb/host/xhci-dbgcap.c | 996 +++++++++++++++++++++ drivers/usb/host/xhci-dbgcap.h | 229 +++++ drivers/usb/host/xhci-dbgtty.c | 497 ++++++++++ drivers/usb/host/xhci-trace.h | 59 ++ drivers/usb/host/xhci.c | 9 + drivers/usb/host/xhci.h | 1 + 9 files changed, 1829 insertions(+) create mode 100644 Documentation/ABI/testing/sysfs-bus-pci-drivers-xhci_hcd create mode 100644 drivers/usb/host/xhci-dbgcap.c create mode 100644 drivers/usb/host/xhci-dbgcap.h create mode 100644 drivers/usb/host/xhci-dbgtty.c (limited to 'Documentation/ABI/testing') diff --git a/Documentation/ABI/testing/sysfs-bus-pci-drivers-xhci_hcd b/Documentation/ABI/testing/sysfs-bus-pci-drivers-xhci_hcd new file mode 100644 index 000000000000..0088aba4caa8 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-bus-pci-drivers-xhci_hcd @@ -0,0 +1,25 @@ +What: /sys/bus/pci/drivers/xhci_hcd/.../dbc +Date: June 2017 +Contact: Lu Baolu +Description: + xHCI compatible USB host controllers (i.e. super-speed + USB3 controllers) are often implemented with the Debug + Capability (DbC). It can present a debug device which + is fully compliant with the USB framework and provides + the equivalent of a very high performance full-duplex + serial link for debug purpose. + + The DbC debug device shares a root port with xHCI host. + When the DbC is enabled, the root port will be assigned + to the Debug Capability. Otherwise, it will be assigned + to xHCI. + + Writing "enable" to this attribute will enable the DbC + functionality and the shared root port will be assigned + to the DbC device. Writing "disable" to this attribute + will disable the DbC functionality and the shared root + port will roll back to the xHCI. + + Reading this attribute gives the state of the DbC. It + can be one of the following states: disabled, enabled, + initialized, connected, configured and stalled. diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig index b80a94e632af..6150bed7cfa8 100644 --- a/drivers/usb/host/Kconfig +++ b/drivers/usb/host/Kconfig @@ -27,6 +27,14 @@ config USB_XHCI_HCD module will be called xhci-hcd. if USB_XHCI_HCD +config USB_XHCI_DBGCAP + bool "xHCI support for debug capability" + depends on TTY + ---help--- + Say 'Y' to enable the support for the xHCI debug capability. Make + sure that your xHCI host supports the extended debug capability and + you want a TTY serial device based on the xHCI debug capability + before enabling this option. If unsure, say 'N'. config USB_XHCI_PCI tristate diff --git a/drivers/usb/host/Makefile b/drivers/usb/host/Makefile index 32b036e2ffef..4ede4ce12366 100644 --- a/drivers/usb/host/Makefile +++ b/drivers/usb/host/Makefile @@ -14,6 +14,11 @@ fhci-$(CONFIG_FHCI_DEBUG) += fhci-dbg.o xhci-hcd-y := xhci.o xhci-mem.o xhci-hcd-y += xhci-ring.o xhci-hub.o xhci-dbg.o xhci-hcd-y += xhci-trace.o + +ifneq ($(CONFIG_USB_XHCI_DBGCAP), ) + xhci-hcd-y += xhci-dbgcap.o xhci-dbgtty.o +endif + ifneq ($(CONFIG_USB_XHCI_MTK), ) xhci-hcd-y += xhci-mtk-sch.o endif diff --git a/drivers/usb/host/xhci-dbgcap.c b/drivers/usb/host/xhci-dbgcap.c new file mode 100644 index 000000000000..671e5023e683 --- /dev/null +++ b/drivers/usb/host/xhci-dbgcap.c @@ -0,0 +1,996 @@ +/** + * xhci-dbgcap.c - xHCI debug capability support + * + * Copyright (C) 2017 Intel Corporation + * + * Author: Lu Baolu + */ +#include +#include +#include + +#include "xhci.h" +#include "xhci-trace.h" +#include "xhci-dbgcap.h" + +static inline void * +dbc_dma_alloc_coherent(struct xhci_hcd *xhci, size_t size, + dma_addr_t *dma_handle, gfp_t flags) +{ + void *vaddr; + + vaddr = dma_alloc_coherent(xhci_to_hcd(xhci)->self.sysdev, + size, dma_handle, flags); + memset(vaddr, 0, size); + return vaddr; +} + +static inline void +dbc_dma_free_coherent(struct xhci_hcd *xhci, size_t size, + void *cpu_addr, dma_addr_t dma_handle) +{ + if (cpu_addr) + dma_free_coherent(xhci_to_hcd(xhci)->self.sysdev, + size, cpu_addr, dma_handle); +} + +static u32 xhci_dbc_populate_strings(struct dbc_str_descs *strings) +{ + struct usb_string_descriptor *s_desc; + u32 string_length; + + /* Serial string: */ + s_desc = (struct usb_string_descriptor *)strings->serial; + utf8s_to_utf16s(DBC_STRING_SERIAL, strlen(DBC_STRING_SERIAL), + UTF16_LITTLE_ENDIAN, (wchar_t *)s_desc->wData, + DBC_MAX_STRING_LENGTH); + + s_desc->bLength = (strlen(DBC_STRING_SERIAL) + 1) * 2; + s_desc->bDescriptorType = USB_DT_STRING; + string_length = s_desc->bLength; + string_length <<= 8; + + /* Product string: */ + s_desc = (struct usb_string_descriptor *)strings->product; + utf8s_to_utf16s(DBC_STRING_PRODUCT, strlen(DBC_STRING_PRODUCT), + UTF16_LITTLE_ENDIAN, (wchar_t *)s_desc->wData, + DBC_MAX_STRING_LENGTH); + + s_desc->bLength = (strlen(DBC_STRING_PRODUCT) + 1) * 2; + s_desc->bDescriptorType = USB_DT_STRING; + string_length += s_desc->bLength; + string_length <<= 8; + + /* Manufacture string: */ + s_desc = (struct usb_string_descriptor *)strings->manufacturer; + utf8s_to_utf16s(DBC_STRING_MANUFACTURER, + strlen(DBC_STRING_MANUFACTURER), + UTF16_LITTLE_ENDIAN, (wchar_t *)s_desc->wData, + DBC_MAX_STRING_LENGTH); + + s_desc->bLength = (strlen(DBC_STRING_MANUFACTURER) + 1) * 2; + s_desc->bDescriptorType = USB_DT_STRING; + string_length += s_desc->bLength; + string_length <<= 8; + + /* String0: */ + strings->string0[0] = 4; + strings->string0[1] = USB_DT_STRING; + strings->string0[2] = 0x09; + strings->string0[3] = 0x04; + string_length += 4; + + return string_length; +} + +static void xhci_dbc_init_contexts(struct xhci_hcd *xhci, u32 string_length) +{ + struct xhci_dbc *dbc; + struct dbc_info_context *info; + struct xhci_ep_ctx *ep_ctx; + u32 dev_info; + dma_addr_t deq, dma; + unsigned int max_burst; + + dbc = xhci->dbc; + if (!dbc) + return; + + /* Populate info Context: */ + info = (struct dbc_info_context *)dbc->ctx->bytes; + dma = dbc->string_dma; + info->string0 = cpu_to_le64(dma); + info->manufacturer = cpu_to_le64(dma + DBC_MAX_STRING_LENGTH); + info->product = cpu_to_le64(dma + DBC_MAX_STRING_LENGTH * 2); + info->serial = cpu_to_le64(dma + DBC_MAX_STRING_LENGTH * 3); + info->length = cpu_to_le32(string_length); + + /* Populate bulk out endpoint context: */ + ep_ctx = dbc_bulkout_ctx(dbc); + max_burst = DBC_CTRL_MAXBURST(readl(&dbc->regs->control)); + deq = dbc_bulkout_enq(dbc); + ep_ctx->ep_info = 0; + ep_ctx->ep_info2 = dbc_epctx_info2(BULK_OUT_EP, 1024, max_burst); + ep_ctx->deq = cpu_to_le64(deq | dbc->ring_out->cycle_state); + + /* Populate bulk in endpoint context: */ + ep_ctx = dbc_bulkin_ctx(dbc); + deq = dbc_bulkin_enq(dbc); + ep_ctx->ep_info = 0; + ep_ctx->ep_info2 = dbc_epctx_info2(BULK_IN_EP, 1024, max_burst); + ep_ctx->deq = cpu_to_le64(deq | dbc->ring_in->cycle_state); + + /* Set DbC context and info registers: */ + xhci_write_64(xhci, dbc->ctx->dma, &dbc->regs->dccp); + + dev_info = cpu_to_le32((DBC_VENDOR_ID << 16) | DBC_PROTOCOL); + writel(dev_info, &dbc->regs->devinfo1); + + dev_info = cpu_to_le32((DBC_DEVICE_REV << 16) | DBC_PRODUCT_ID); + writel(dev_info, &dbc->regs->devinfo2); +} + +static void xhci_dbc_giveback(struct dbc_request *req, int status) + __releases(&dbc->lock) + __acquires(&dbc->lock) +{ + struct dbc_ep *dep = req->dep; + struct xhci_dbc *dbc = dep->dbc; + struct xhci_hcd *xhci = dbc->xhci; + struct device *dev = xhci_to_hcd(dbc->xhci)->self.sysdev; + + list_del_init(&req->list_pending); + req->trb_dma = 0; + req->trb = NULL; + + if (req->status == -EINPROGRESS) + req->status = status; + + trace_xhci_dbc_giveback_request(req); + + dma_unmap_single(dev, + req->dma, + req->length, + dbc_ep_dma_direction(dep)); + + /* Give back the transfer request: */ + spin_unlock(&dbc->lock); + req->complete(xhci, req); + spin_lock(&dbc->lock); +} + +static void xhci_dbc_flush_single_request(struct dbc_request *req) +{ + union xhci_trb *trb = req->trb; + + trb->generic.field[0] = 0; + trb->generic.field[1] = 0; + trb->generic.field[2] = 0; + trb->generic.field[3] &= cpu_to_le32(TRB_CYCLE); + trb->generic.field[3] |= cpu_to_le32(TRB_TYPE(TRB_TR_NOOP)); + + xhci_dbc_giveback(req, -ESHUTDOWN); +} + +static void xhci_dbc_flush_endpoint_requests(struct dbc_ep *dep) +{ + struct dbc_request *req, *tmp; + + list_for_each_entry_safe(req, tmp, &dep->list_pending, list_pending) + xhci_dbc_flush_single_request(req); +} + +static void xhci_dbc_flush_reqests(struct xhci_dbc *dbc) +{ + xhci_dbc_flush_endpoint_requests(&dbc->eps[BULK_OUT]); + xhci_dbc_flush_endpoint_requests(&dbc->eps[BULK_IN]); +} + +struct dbc_request * +dbc_alloc_request(struct dbc_ep *dep, gfp_t gfp_flags) +{ + struct dbc_request *req; + + req = kzalloc(sizeof(*req), gfp_flags); + if (!req) + return NULL; + + req->dep = dep; + INIT_LIST_HEAD(&req->list_pending); + INIT_LIST_HEAD(&req->list_pool); + req->direction = dep->direction; + + trace_xhci_dbc_alloc_request(req); + + return req; +} + +void +dbc_free_request(struct dbc_ep *dep, struct dbc_request *req) +{ + trace_xhci_dbc_free_request(req); + + kfree(req); +} + +static void +xhci_dbc_queue_trb(struct xhci_ring *ring, u32 field1, + u32 field2, u32 field3, u32 field4) +{ + union xhci_trb *trb, *next; + + trb = ring->enqueue; + trb->generic.field[0] = cpu_to_le32(field1); + trb->generic.field[1] = cpu_to_le32(field2); + trb->generic.field[2] = cpu_to_le32(field3); + trb->generic.field[3] = cpu_to_le32(field4); + + trace_xhci_dbc_gadget_ep_queue(ring, &trb->generic); + + ring->num_trbs_free--; + next = ++(ring->enqueue); + if (TRB_TYPE_LINK_LE32(next->link.control)) { + next->link.control ^= cpu_to_le32(TRB_CYCLE); + ring->enqueue = ring->enq_seg->trbs; + ring->cycle_state ^= 1; + } +} + +static int xhci_dbc_queue_bulk_tx(struct dbc_ep *dep, + struct dbc_request *req) +{ + u64 addr; + union xhci_trb *trb; + unsigned int num_trbs; + struct xhci_dbc *dbc = dep->dbc; + struct xhci_ring *ring = dep->ring; + u32 length, control, cycle; + + num_trbs = count_trbs(req->dma, req->length); + WARN_ON(num_trbs != 1); + if (ring->num_trbs_free < num_trbs) + return -EBUSY; + + addr = req->dma; + trb = ring->enqueue; + cycle = ring->cycle_state; + length = TRB_LEN(req->length); + control = TRB_TYPE(TRB_NORMAL) | TRB_IOC; + + if (cycle) + control &= cpu_to_le32(~TRB_CYCLE); + else + control |= cpu_to_le32(TRB_CYCLE); + + req->trb = ring->enqueue; + req->trb_dma = xhci_trb_virt_to_dma(ring->enq_seg, ring->enqueue); + xhci_dbc_queue_trb(ring, + lower_32_bits(addr), + upper_32_bits(addr), + length, control); + + /* + * Add a barrier between writes of trb fields and flipping + * the cycle bit: + */ + wmb(); + + if (cycle) + trb->generic.field[3] |= cpu_to_le32(TRB_CYCLE); + else + trb->generic.field[3] &= cpu_to_le32(~TRB_CYCLE); + + writel(DBC_DOOR_BELL_TARGET(dep->direction), &dbc->regs->doorbell); + + return 0; +} + +static int +dbc_ep_do_queue(struct dbc_ep *dep, struct dbc_request *req) +{ + int ret; + struct device *dev; + struct xhci_dbc *dbc = dep->dbc; + struct xhci_hcd *xhci = dbc->xhci; + + dev = xhci_to_hcd(xhci)->self.sysdev; + + if (!req->length || !req->buf) + return -EINVAL; + + req->actual = 0; + req->status = -EINPROGRESS; + + req->dma = dma_map_single(dev, + req->buf, + req->length, + dbc_ep_dma_direction(dep)); + if (dma_mapping_error(dev, req->dma)) { + xhci_err(xhci, "failed to map buffer\n"); + return -EFAULT; + } + + ret = xhci_dbc_queue_bulk_tx(dep, req); + if (ret) { + xhci_err(xhci, "failed to queue trbs\n"); + dma_unmap_single(dev, + req->dma, + req->length, + dbc_ep_dma_direction(dep)); + return -EFAULT; + } + + list_add_tail(&req->list_pending, &dep->list_pending); + + return 0; +} + +int dbc_ep_queue(struct dbc_ep *dep, struct dbc_request *req, + gfp_t gfp_flags) +{ + struct xhci_dbc *dbc = dep->dbc; + int ret = -ESHUTDOWN; + + spin_lock(&dbc->lock); + if (dbc->state == DS_CONFIGURED) + ret = dbc_ep_do_queue(dep, req); + spin_unlock(&dbc->lock); + + mod_delayed_work(system_wq, &dbc->event_work, 0); + + trace_xhci_dbc_queue_request(req); + + return ret; +} + +static inline void xhci_dbc_do_eps_init(struct xhci_hcd *xhci, bool direction) +{ + struct dbc_ep *dep; + struct xhci_dbc *dbc = xhci->dbc; + + dep = &dbc->eps[direction]; + dep->dbc = dbc; + dep->direction = direction; + dep->ring = direction ? dbc->ring_in : dbc->ring_out; + + INIT_LIST_HEAD(&dep->list_pending); +} + +static void xhci_dbc_eps_init(struct xhci_hcd *xhci) +{ + xhci_dbc_do_eps_init(xhci, BULK_OUT); + xhci_dbc_do_eps_init(xhci, BULK_IN); +} + +static void xhci_dbc_eps_exit(struct xhci_hcd *xhci) +{ + struct xhci_dbc *dbc = xhci->dbc; + + memset(dbc->eps, 0, ARRAY_SIZE(dbc->eps)); +} + +static int xhci_dbc_mem_init(struct xhci_hcd *xhci, gfp_t flags) +{ + int ret; + dma_addr_t deq; + u32 string_length; + struct xhci_dbc *dbc = xhci->dbc; + + /* Allocate various rings for events and transfers: */ + dbc->ring_evt = xhci_ring_alloc(xhci, 1, 1, TYPE_EVENT, 0, flags); + if (!dbc->ring_evt) + goto evt_fail; + + dbc->ring_in = xhci_ring_alloc(xhci, 1, 1, TYPE_BULK, 0, flags); + if (!dbc->ring_in) + goto in_fail; + + dbc->ring_out = xhci_ring_alloc(xhci, 1, 1, TYPE_BULK, 0, flags); + if (!dbc->ring_out) + goto out_fail; + + /* Allocate and populate ERST: */ + ret = xhci_alloc_erst(xhci, dbc->ring_evt, &dbc->erst, flags); + if (ret) + goto erst_fail; + + /* Allocate context data structure: */ + dbc->ctx = xhci_alloc_container_ctx(xhci, XHCI_CTX_TYPE_DEVICE, flags); + if (!dbc->ctx) + goto ctx_fail; + + /* Allocate the string table: */ + dbc->string_size = sizeof(struct dbc_str_descs); + dbc->string = dbc_dma_alloc_coherent(xhci, + dbc->string_size, + &dbc->string_dma, + flags); + if (!dbc->string) + goto string_fail; + + /* Setup ERST register: */ + writel(dbc->erst.erst_size, &dbc->regs->ersts); + xhci_write_64(xhci, dbc->erst.erst_dma_addr, &dbc->regs->erstba); + deq = xhci_trb_virt_to_dma(dbc->ring_evt->deq_seg, + dbc->ring_evt->dequeue); + xhci_write_64(xhci, deq, &dbc->regs->erdp); + + /* Setup strings and contexts: */ + string_length = xhci_dbc_populate_strings(dbc->string); + xhci_dbc_init_contexts(xhci, string_length); + + mmiowb(); + + xhci_dbc_eps_init(xhci); + dbc->state = DS_INITIALIZED; + + return 0; + +string_fail: + xhci_free_container_ctx(xhci, dbc->ctx); + dbc->ctx = NULL; +ctx_fail: + xhci_free_erst(xhci, &dbc->erst); +erst_fail: + xhci_ring_free(xhci, dbc->ring_out); + dbc->ring_out = NULL; +out_fail: + xhci_ring_free(xhci, dbc->ring_in); + dbc->ring_in = NULL; +in_fail: + xhci_ring_free(xhci, dbc->ring_evt); + dbc->ring_evt = NULL; +evt_fail: + return -ENOMEM; +} + +static void xhci_dbc_mem_cleanup(struct xhci_hcd *xhci) +{ + struct xhci_dbc *dbc = xhci->dbc; + + if (!dbc) + return; + + xhci_dbc_eps_exit(xhci); + + if (dbc->string) { + dbc_dma_free_coherent(xhci, + dbc->string_size, + dbc->string, dbc->string_dma); + dbc->string = NULL; + } + + xhci_free_container_ctx(xhci, dbc->ctx); + dbc->ctx = NULL; + + xhci_free_erst(xhci, &dbc->erst); + xhci_ring_free(xhci, dbc->ring_out); + xhci_ring_free(xhci, dbc->ring_in); + xhci_ring_free(xhci, dbc->ring_evt); + dbc->ring_in = NULL; + dbc->ring_out = NULL; + dbc->ring_evt = NULL; +} + +static int xhci_do_dbc_start(struct xhci_hcd *xhci) +{ + int ret; + u32 ctrl; + struct xhci_dbc *dbc = xhci->dbc; + + if (dbc->state != DS_DISABLED) + return -EINVAL; + + writel(0, &dbc->regs->control); + ret = xhci_handshake(&dbc->regs->control, + DBC_CTRL_DBC_ENABLE, + 0, 1000); + if (ret) + return ret; + + ret = xhci_dbc_mem_init(xhci, GFP_ATOMIC); + if (ret) + return ret; + + ctrl = readl(&dbc->regs->control); + writel(ctrl | DBC_CTRL_DBC_ENABLE | DBC_CTRL_PORT_ENABLE, + &dbc->regs->control); + ret = xhci_handshake(&dbc->regs->control, + DBC_CTRL_DBC_ENABLE, + DBC_CTRL_DBC_ENABLE, 1000); + if (ret) + return ret; + + dbc->state = DS_ENABLED; + + return 0; +} + +static void xhci_do_dbc_stop(struct xhci_hcd *xhci) +{ + struct xhci_dbc *dbc = xhci->dbc; + + if (dbc->state == DS_DISABLED) + return; + + writel(0, &dbc->regs->control); + xhci_dbc_mem_cleanup(xhci); + dbc->state = DS_DISABLED; +} + +static int xhci_dbc_start(struct xhci_hcd *xhci) +{ + int ret; + struct xhci_dbc *dbc = xhci->dbc; + + WARN_ON(!dbc); + + pm_runtime_get_sync(xhci_to_hcd(xhci)->self.controller); + + spin_lock(&dbc->lock); + ret = xhci_do_dbc_start(xhci); + spin_unlock(&dbc->lock); + + if (ret) { + pm_runtime_put(xhci_to_hcd(xhci)->self.controller); + return ret; + } + + return mod_delayed_work(system_wq, &dbc->event_work, 1); +} + +static void xhci_dbc_stop(struct xhci_hcd *xhci) +{ + struct xhci_dbc *dbc = xhci->dbc; + struct dbc_port *port = &dbc->port; + + WARN_ON(!dbc); + + cancel_delayed_work_sync(&dbc->event_work); + + if (port->registered) + xhci_dbc_tty_unregister_device(xhci); + + spin_lock(&dbc->lock); + xhci_do_dbc_stop(xhci); + spin_unlock(&dbc->lock); + + pm_runtime_put_sync(xhci_to_hcd(xhci)->self.controller); +} + +static void +dbc_handle_port_status(struct xhci_hcd *xhci, union xhci_trb *event) +{ + u32 portsc; + struct xhci_dbc *dbc = xhci->dbc; + + portsc = readl(&dbc->regs->portsc); + if (portsc & DBC_PORTSC_CONN_CHANGE) + xhci_info(xhci, "DbC port connect change\n"); + + if (portsc & DBC_PORTSC_RESET_CHANGE) + xhci_info(xhci, "DbC port reset change\n"); + + if (portsc & DBC_PORTSC_LINK_CHANGE) + xhci_info(xhci, "DbC port link status change\n"); + + if (portsc & DBC_PORTSC_CONFIG_CHANGE) + xhci_info(xhci, "DbC config error change\n"); + + /* Port reset change bit will be cleared in other place: */ + writel(portsc & ~DBC_PORTSC_RESET_CHANGE, &dbc->regs->portsc); +} + +static void dbc_handle_xfer_event(struct xhci_hcd *xhci, union xhci_trb *event) +{ + struct dbc_ep *dep; + struct xhci_ring *ring; + int ep_id; + int status; + u32 comp_code; + size_t remain_length; + struct dbc_request *req = NULL, *r; + + comp_code = GET_COMP_CODE(le32_to_cpu(event->generic.field[2])); + remain_length = EVENT_TRB_LEN(le32_to_cpu(event->generic.field[2])); + ep_id = TRB_TO_EP_ID(le32_to_cpu(event->generic.field[3])); + dep = (ep_id == EPID_OUT) ? + get_out_ep(xhci) : get_in_ep(xhci); + ring = dep->ring; + + switch (comp_code) { + case COMP_SUCCESS: + remain_length = 0; + /* FALLTHROUGH */ + case COMP_SHORT_PACKET: + status = 0; + break; + case COMP_TRB_ERROR: + case COMP_BABBLE_DETECTED_ERROR: + case COMP_USB_TRANSACTION_ERROR: + case COMP_STALL_ERROR: + xhci_warn(xhci, "tx error %d detected\n", comp_code); + status = -comp_code; + break; + default: + xhci_err(xhci, "unknown tx error %d\n", comp_code); + status = -comp_code; + break; + } + + /* Match the pending request: */ + list_for_each_entry(r, &dep->list_pending, list_pending) { + if (r->trb_dma == event->trans_event.buffer) { + req = r; + break; + } + } + + if (!req) { + xhci_warn(xhci, "no matched request\n"); + return; + } + + trace_xhci_dbc_handle_transfer(ring, &req->trb->generic); + + ring->num_trbs_free++; + req->actual = req->length - remain_length; + xhci_dbc_giveback(req, status); +} + +static enum evtreturn xhci_dbc_do_handle_events(struct xhci_dbc *dbc) +{ + dma_addr_t deq; + struct dbc_ep *dep; + union xhci_trb *evt; + u32 ctrl, portsc; + struct xhci_hcd *xhci = dbc->xhci; + bool update_erdp = false; + + /* DbC state machine: */ + switch (dbc->state) { + case DS_DISABLED: + case DS_INITIALIZED: + + return EVT_ERR; + case DS_ENABLED: + portsc = readl(&dbc->regs->portsc); + if (portsc & DBC_PORTSC_CONN_STATUS) { + dbc->state = DS_CONNECTED; + xhci_info(xhci, "DbC connected\n"); + } + + return EVT_DONE; + case DS_CONNECTED: + ctrl = readl(&dbc->regs->control); + if (ctrl & DBC_CTRL_DBC_RUN) { + dbc->state = DS_CONFIGURED; + xhci_info(xhci, "DbC configured\n"); + portsc = readl(&dbc->regs->portsc); + writel(portsc, &dbc->regs->portsc); + return EVT_GSER; + } + + return EVT_DONE; + case DS_CONFIGURED: + /* Handle cable unplug event: */ + portsc = readl(&dbc->regs->portsc); + if (!(portsc & DBC_PORTSC_PORT_ENABLED) && + !(portsc & DBC_PORTSC_CONN_STATUS)) { + xhci_info(xhci, "DbC cable unplugged\n"); + dbc->state = DS_ENABLED; + xhci_dbc_flush_reqests(dbc); + + return EVT_DISC; + } + + /* Handle debug port reset event: */ + if (portsc & DBC_PORTSC_RESET_CHANGE) { + xhci_info(xhci, "DbC port reset\n"); + writel(portsc, &dbc->regs->portsc); + dbc->state = DS_ENABLED; + xhci_dbc_flush_reqests(dbc); + + return EVT_DISC; + } + + /* Handle endpoint stall event: */ + ctrl = readl(&dbc->regs->control); + if ((ctrl & DBC_CTRL_HALT_IN_TR) || + (ctrl & DBC_CTRL_HALT_OUT_TR)) { + xhci_info(xhci, "DbC Endpoint stall\n"); + dbc->state = DS_STALLED; + + if (ctrl & DBC_CTRL_HALT_IN_TR) { + dep = get_in_ep(xhci); + xhci_dbc_flush_endpoint_requests(dep); + } + + if (ctrl & DBC_CTRL_HALT_OUT_TR) { + dep = get_out_ep(xhci); + xhci_dbc_flush_endpoint_requests(dep); + } + + return EVT_DONE; + } + + /* Clear DbC run change bit: */ + if (ctrl & DBC_CTRL_DBC_RUN_CHANGE) { + writel(ctrl, &dbc->regs->control); + ctrl = readl(&dbc->regs->control); + } + + break; + case DS_STALLED: + ctrl = readl(&dbc->regs->control); + if (!(ctrl & DBC_CTRL_HALT_IN_TR) && + !(ctrl & DBC_CTRL_HALT_OUT_TR) && + (ctrl & DBC_CTRL_DBC_RUN)) { + dbc->state = DS_CONFIGURED; + break; + } + + return EVT_DONE; + default: + xhci_err(xhci, "Unknown DbC state %d\n", dbc->state); + break; + } + + /* Handle the events in the event ring: */ + evt = dbc->ring_evt->dequeue; + while ((le32_to_cpu(evt->event_cmd.flags) & TRB_CYCLE) == + dbc->ring_evt->cycle_state) { + /* + * Add a barrier between reading the cycle flag and any + * reads of the event's flags/data below: + */ + rmb(); + + trace_xhci_dbc_handle_event(dbc->ring_evt, &evt->generic); + + switch (le32_to_cpu(evt->event_cmd.flags) & TRB_TYPE_BITMASK) { + case TRB_TYPE(TRB_PORT_STATUS): + dbc_handle_port_status(xhci, evt); + break; + case TRB_TYPE(TRB_TRANSFER): + dbc_handle_xfer_event(xhci, evt); + break; + default: + break; + } + + inc_deq(xhci, dbc->ring_evt); + evt = dbc->ring_evt->dequeue; + update_erdp = true; + } + + /* Update event ring dequeue pointer: */ + if (update_erdp) { + deq = xhci_trb_virt_to_dma(dbc->ring_evt->deq_seg, + dbc->ring_evt->dequeue); + xhci_write_64(xhci, deq, &dbc->regs->erdp); + } + + return EVT_DONE; +} + +static void xhci_dbc_handle_events(struct work_struct *work) +{ + int ret; + enum evtreturn evtr; + struct xhci_dbc *dbc; + struct xhci_hcd *xhci; + + dbc = container_of(to_delayed_work(work), struct xhci_dbc, event_work); + xhci = dbc->xhci; + + spin_lock(&dbc->lock); + evtr = xhci_dbc_do_handle_events(dbc); + spin_unlock(&dbc->lock); + + switch (evtr) { + case EVT_GSER: + ret = xhci_dbc_tty_register_device(xhci); + if (ret) { + xhci_err(xhci, "failed to alloc tty device\n"); + break; + } + + xhci_info(xhci, "DbC now attached to /dev/ttyDBC0\n"); + break; + case EVT_DISC: + xhci_dbc_tty_unregister_device(xhci); + break; + case EVT_DONE: + break; + default: + xhci_info(xhci, "stop handling dbc events\n"); + return; + } + + mod_delayed_work(system_wq, &dbc->event_work, 1); +} + +static void xhci_do_dbc_exit(struct xhci_hcd *xhci) +{ + unsigned long flags; + + spin_lock_irqsave(&xhci->lock, flags); + kfree(xhci->dbc); + xhci->dbc = NULL; + spin_unlock_irqrestore(&xhci->lock, flags); +} + +static int xhci_do_dbc_init(struct xhci_hcd *xhci) +{ + u32 reg; + struct xhci_dbc *dbc; + unsigned long flags; + void __iomem *base; + int dbc_cap_offs; + + base = &xhci->cap_regs->hc_capbase; + dbc_cap_offs = xhci_find_next_ext_cap(base, 0, XHCI_EXT_CAPS_DEBUG); + if (!dbc_cap_offs) + return -ENODEV; + + dbc = kzalloc(sizeof(*dbc), GFP_KERNEL); + if (!dbc) + return -ENOMEM; + + dbc->regs = base + dbc_cap_offs; + + /* We will avoid using DbC in xhci driver if it's in use. */ + reg = readl(&dbc->regs->control); + if (reg & DBC_CTRL_DBC_ENABLE) { + kfree(dbc); + return -EBUSY; + } + + spin_lock_irqsave(&xhci->lock, flags); + if (xhci->dbc) { + spin_unlock_irqrestore(&xhci->lock, flags); + kfree(dbc); + return -EBUSY; + } + xhci->dbc = dbc; + spin_unlock_irqrestore(&xhci->lock, flags); + + dbc->xhci = xhci; + INIT_DELAYED_WORK(&dbc->event_work, xhci_dbc_handle_events); + spin_lock_init(&dbc->lock); + + return 0; +} + +static ssize_t dbc_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + const char *p; + struct xhci_dbc *dbc; + struct xhci_hcd *xhci; + + xhci = hcd_to_xhci(dev_get_drvdata(dev)); + dbc = xhci->dbc; + + switch (dbc->state) { + case DS_DISABLED: + p = "disabled"; + break; + case DS_INITIALIZED: + p = "initialized"; + break; + case DS_ENABLED: + p = "enabled"; + break; + case DS_CONNECTED: + p = "connected"; + break; + case DS_CONFIGURED: + p = "configured"; + break; + case DS_STALLED: + p = "stalled"; + break; + default: + p = "unknown"; + } + + return sprintf(buf, "%s\n", p); +} + +static ssize_t dbc_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct xhci_dbc *dbc; + struct xhci_hcd *xhci; + + xhci = hcd_to_xhci(dev_get_drvdata(dev)); + dbc = xhci->dbc; + + if (!strncmp(buf, "enable", 6)) + xhci_dbc_start(xhci); + else if (!strncmp(buf, "disable", 7)) + xhci_dbc_stop(xhci); + else + return -EINVAL; + + return count; +} + +static DEVICE_ATTR(dbc, 0644, dbc_show, dbc_store); + +int xhci_dbc_init(struct xhci_hcd *xhci) +{ + int ret; + struct device *dev = xhci_to_hcd(xhci)->self.controller; + + ret = xhci_do_dbc_init(xhci); + if (ret) + goto init_err3; + + ret = xhci_dbc_tty_register_driver(xhci); + if (ret) + goto init_err2; + + ret = device_create_file(dev, &dev_attr_dbc); + if (ret) + goto init_err1; + + return 0; + +init_err1: + xhci_dbc_tty_unregister_driver(); +init_err2: + xhci_do_dbc_exit(xhci); +init_err3: + return ret; +} + +void xhci_dbc_exit(struct xhci_hcd *xhci) +{ + struct device *dev = xhci_to_hcd(xhci)->self.controller; + + if (!xhci->dbc) + return; + + device_remove_file(dev, &dev_attr_dbc); + xhci_dbc_tty_unregister_driver(); + xhci_dbc_stop(xhci); + xhci_do_dbc_exit(xhci); +} + +#ifdef CONFIG_PM +int xhci_dbc_suspend(struct xhci_hcd *xhci) +{ + struct xhci_dbc *dbc = xhci->dbc; + + if (!dbc) + return 0; + + if (dbc->state == DS_CONFIGURED) + dbc->resume_required = 1; + + xhci_dbc_stop(xhci); + + return 0; +} + +int xhci_dbc_resume(struct xhci_hcd *xhci) +{ + int ret = 0; + struct xhci_dbc *dbc = xhci->dbc; + + if (!dbc) + return 0; + + if (dbc->resume_required) { + dbc->resume_required = 0; + xhci_dbc_start(xhci); + } + + return ret; +} +#endif /* CONFIG_PM */ diff --git a/drivers/usb/host/xhci-dbgcap.h b/drivers/usb/host/xhci-dbgcap.h new file mode 100644 index 000000000000..e66ea0748ba3 --- /dev/null +++ b/drivers/usb/host/xhci-dbgcap.h @@ -0,0 +1,229 @@ + +/** + * xhci-dbgcap.h - xHCI debug capability support + * + * Copyright (C) 2017 Intel Corporation + * + * Author: Lu Baolu + */ +#ifndef __LINUX_XHCI_DBGCAP_H +#define __LINUX_XHCI_DBGCAP_H + +#include +#include + +struct dbc_regs { + __le32 capability; + __le32 doorbell; + __le32 ersts; /* Event Ring Segment Table Size*/ + __le32 __reserved_0; /* 0c~0f reserved bits */ + __le64 erstba; /* Event Ring Segment Table Base Address */ + __le64 erdp; /* Event Ring Dequeue Pointer */ + __le32 control; + __le32 status; + __le32 portsc; /* Port status and control */ + __le32 __reserved_1; /* 2b~28 reserved bits */ + __le64 dccp; /* Debug Capability Context Pointer */ + __le32 devinfo1; /* Device Descriptor Info Register 1 */ + __le32 devinfo2; /* Device Descriptor Info Register 2 */ +}; + +struct dbc_info_context { + __le64 string0; + __le64 manufacturer; + __le64 product; + __le64 serial; + __le32 length; + __le32 __reserved_0[7]; +}; + +#define DBC_CTRL_DBC_RUN BIT(0) +#define DBC_CTRL_PORT_ENABLE BIT(1) +#define DBC_CTRL_HALT_OUT_TR BIT(2) +#define DBC_CTRL_HALT_IN_TR BIT(3) +#define DBC_CTRL_DBC_RUN_CHANGE BIT(4) +#define DBC_CTRL_DBC_ENABLE BIT(31) +#define DBC_CTRL_MAXBURST(p) (((p) >> 16) & 0xff) +#define DBC_DOOR_BELL_TARGET(p) (((p) & 0xff) << 8) + +#define DBC_MAX_PACKET 1024 +#define DBC_MAX_STRING_LENGTH 64 +#define DBC_STRING_MANUFACTURER "Linux Foundation" +#define DBC_STRING_PRODUCT "Linux USB Debug Target" +#define DBC_STRING_SERIAL "0001" +#define DBC_CONTEXT_SIZE 64 + +/* + * Port status: + */ +#define DBC_PORTSC_CONN_STATUS BIT(0) +#define DBC_PORTSC_PORT_ENABLED BIT(1) +#define DBC_PORTSC_CONN_CHANGE BIT(17) +#define DBC_PORTSC_RESET_CHANGE BIT(21) +#define DBC_PORTSC_LINK_CHANGE BIT(22) +#define DBC_PORTSC_CONFIG_CHANGE BIT(23) + +struct dbc_str_descs { + char string0[DBC_MAX_STRING_LENGTH]; + char manufacturer[DBC_MAX_STRING_LENGTH]; + char product[DBC_MAX_STRING_LENGTH]; + char serial[DBC_MAX_STRING_LENGTH]; +}; + +#define DBC_PROTOCOL 1 /* GNU Remote Debug Command */ +#define DBC_VENDOR_ID 0x1d6b /* Linux Foundation 0x1d6b */ +#define DBC_PRODUCT_ID 0x0010 /* device 0010 */ +#define DBC_DEVICE_REV 0x0010 /* 0.10 */ + +enum dbc_state { + DS_DISABLED = 0, + DS_INITIALIZED, + DS_ENABLED, + DS_CONNECTED, + DS_CONFIGURED, + DS_STALLED, +}; + +struct dbc_request { + void *buf; + unsigned int length; + dma_addr_t dma; + void (*complete)(struct xhci_hcd *xhci, + struct dbc_request *req); + struct list_head list_pool; + int status; + unsigned int actual; + + struct dbc_ep *dep; + struct list_head list_pending; + dma_addr_t trb_dma; + union xhci_trb *trb; + unsigned direction:1; +}; + +struct dbc_ep { + struct xhci_dbc *dbc; + struct list_head list_pending; + struct xhci_ring *ring; + unsigned direction:1; +}; + +#define DBC_QUEUE_SIZE 16 +#define DBC_WRITE_BUF_SIZE 8192 + +/* + * Private structure for DbC hardware state: + */ +struct dbc_port { + struct tty_port port; + spinlock_t port_lock; /* port access */ + + struct list_head read_pool; + struct list_head read_queue; + unsigned int n_read; + struct tasklet_struct push; + + struct list_head write_pool; + struct kfifo write_fifo; + + bool registered; + struct dbc_ep *in; + struct dbc_ep *out; +}; + +struct xhci_dbc { + spinlock_t lock; /* device access */ + struct xhci_hcd *xhci; + struct dbc_regs __iomem *regs; + struct xhci_ring *ring_evt; + struct xhci_ring *ring_in; + struct xhci_ring *ring_out; + struct xhci_erst erst; + struct xhci_container_ctx *ctx; + + struct dbc_str_descs *string; + dma_addr_t string_dma; + size_t string_size; + + enum dbc_state state; + struct delayed_work event_work; + unsigned resume_required:1; + struct dbc_ep eps[2]; + + struct dbc_port port; +}; + +#define dbc_bulkout_ctx(d) \ + ((struct xhci_ep_ctx *)((d)->ctx->bytes + DBC_CONTEXT_SIZE)) +#define dbc_bulkin_ctx(d) \ + ((struct xhci_ep_ctx *)((d)->ctx->bytes + DBC_CONTEXT_SIZE * 2)) +#define dbc_bulkout_enq(d) \ + xhci_trb_virt_to_dma((d)->ring_out->enq_seg, (d)->ring_out->enqueue) +#define dbc_bulkin_enq(d) \ + xhci_trb_virt_to_dma((d)->ring_in->enq_seg, (d)->ring_in->enqueue) +#define dbc_epctx_info2(t, p, b) \ + cpu_to_le32(EP_TYPE(t) | MAX_PACKET(p) | MAX_BURST(b)) +#define dbc_ep_dma_direction(d) \ + ((d)->direction ? DMA_FROM_DEVICE : DMA_TO_DEVICE) + +#define BULK_OUT 0 +#define BULK_IN 1 +#define EPID_OUT 2 +#define EPID_IN 3 + +enum evtreturn { + EVT_ERR = -1, + EVT_DONE, + EVT_GSER, + EVT_DISC, +}; + +static inline struct dbc_ep *get_in_ep(struct xhci_hcd *xhci) +{ + struct xhci_dbc *dbc = xhci->dbc; + + return &dbc->eps[BULK_IN]; +} + +static inline struct dbc_ep *get_out_ep(struct xhci_hcd *xhci) +{ + struct xhci_dbc *dbc = xhci->dbc; + + return &dbc->eps[BULK_OUT]; +} + +#ifdef CONFIG_USB_XHCI_DBGCAP +int xhci_dbc_init(struct xhci_hcd *xhci); +void xhci_dbc_exit(struct xhci_hcd *xhci); +int xhci_dbc_tty_register_driver(struct xhci_hcd *xhci); +void xhci_dbc_tty_unregister_driver(void); +int xhci_dbc_tty_register_device(struct xhci_hcd *xhci); +void xhci_dbc_tty_unregister_device(struct xhci_hcd *xhci); +struct dbc_request *dbc_alloc_request(struct dbc_ep *dep, gfp_t gfp_flags); +void dbc_free_request(struct dbc_ep *dep, struct dbc_request *req); +int dbc_ep_queue(struct dbc_ep *dep, struct dbc_request *req, gfp_t gfp_flags); +#ifdef CONFIG_PM +int xhci_dbc_suspend(struct xhci_hcd *xhci); +int xhci_dbc_resume(struct xhci_hcd *xhci); +#endif /* CONFIG_PM */ +#else +static inline int xhci_dbc_init(struct xhci_hcd *xhci) +{ + return 0; +} + +static inline void xhci_dbc_exit(struct xhci_hcd *xhci) +{ +} + +static inline int xhci_dbc_suspend(struct xhci_hcd *xhci) +{ + return 0; +} + +static inline int xhci_dbc_resume(struct xhci_hcd *xhci) +{ + return 0; +} +#endif /* CONFIG_USB_XHCI_DBGCAP */ +#endif /* __LINUX_XHCI_DBGCAP_H */ diff --git a/drivers/usb/host/xhci-dbgtty.c b/drivers/usb/host/xhci-dbgtty.c new file mode 100644 index 000000000000..48811d72a94c --- /dev/null +++ b/drivers/usb/host/xhci-dbgtty.c @@ -0,0 +1,497 @@ +/** + * xhci-dbgtty.c - tty glue for xHCI debug capability + * + * Copyright (C) 2017 Intel Corporation + * + * Author: Lu Baolu + */ + +#include +#include +#include + +#include "xhci.h" +#include "xhci-dbgcap.h" + +static unsigned int +dbc_send_packet(struct dbc_port *port, char *packet, unsigned int size) +{ + unsigned int len; + + len = kfifo_len(&port->write_fifo); + if (len < size) + size = len; + if (size != 0) + size = kfifo_out(&port->write_fifo, packet, size); + return size; +} + +static int dbc_start_tx(struct dbc_port *port) + __releases(&port->port_lock) + __acquires(&port->port_lock) +{ + int len; + struct dbc_request *req; + int status = 0; + bool do_tty_wake = false; + struct list_head *pool = &port->write_pool; + + while (!list_empty(pool)) { + req = list_entry(pool->next, struct dbc_request, list_pool); + len = dbc_send_packet(port, req->buf, DBC_MAX_PACKET); + if (len == 0) + break; + do_tty_wake = true; + + req->length = len; + list_del(&req->list_pool); + + spin_unlock(&port->port_lock); + status = dbc_ep_queue(port->out, req, GFP_ATOMIC); + spin_lock(&port->port_lock); + + if (status) { + list_add(&req->list_pool, pool); + break; + } + } + + if (do_tty_wake && port->port.tty) + tty_wakeup(port->port.tty); + + return status; +} + +static void dbc_start_rx(struct dbc_port *port) + __releases(&port->port_lock) + __acquires(&port->port_lock) +{ + struct dbc_request *req; + int status; + struct list_head *pool = &port->read_pool; + + while (!list_empty(pool)) { + if (!port->port.tty) + break; + + req = list_entry(pool->next, struct dbc_request, list_pool); + list_del(&req->list_pool); + req->length = DBC_MAX_PACKET; + + spin_unlock(&port->port_lock); + status = dbc_ep_queue(port->in, req, GFP_ATOMIC); + spin_lock(&port->port_lock); + + if (status) { + list_add(&req->list_pool, pool); + break; + } + } +} + +static void +dbc_read_complete(struct xhci_hcd *xhci, struct dbc_request *req) +{ + struct xhci_dbc *dbc = xhci->dbc; + struct dbc_port *port = &dbc->port; + + spin_lock(&port->port_lock); + list_add_tail(&req->list_pool, &port->read_queue); + tasklet_schedule(&port->push); + spin_unlock(&port->port_lock); +} + +static void dbc_write_complete(struct xhci_hcd *xhci, struct dbc_request *req) +{ + struct xhci_dbc *dbc = xhci->dbc; + struct dbc_port *port = &dbc->port; + + spin_lock(&port->port_lock); + list_add(&req->list_pool, &port->write_pool); + switch (req->status) { + case 0: + dbc_start_tx(port); + break; + case -ESHUTDOWN: + break; + default: + xhci_warn(xhci, "unexpected write complete status %d\n", + req->status); + break; + } + spin_unlock(&port->port_lock); +} + +void xhci_dbc_free_req(struct dbc_ep *dep, struct dbc_request *req) +{ + kfree(req->buf); + dbc_free_request(dep, req); +} + +static int +xhci_dbc_alloc_requests(struct dbc_ep *dep, struct list_head *head, + void (*fn)(struct xhci_hcd *, struct dbc_request *)) +{ + int i; + struct dbc_request *req; + + for (i = 0; i < DBC_QUEUE_SIZE; i++) { + req = dbc_alloc_request(dep, GFP_ATOMIC); + if (!req) + break; + + req->length = DBC_MAX_PACKET; + req->buf = kmalloc(req->length, GFP_KERNEL); + if (!req->buf) { + xhci_dbc_free_req(dep, req); + break; + } + + req->complete = fn; + list_add_tail(&req->list_pool, head); + } + + return list_empty(head) ? -ENOMEM : 0; +} + +static void +xhci_dbc_free_requests(struct dbc_ep *dep, struct list_head *head) +{ + struct dbc_request *req; + + while (!list_empty(head)) { + req = list_entry(head->next, struct dbc_request, list_pool); + list_del(&req->list_pool); + xhci_dbc_free_req(dep, req); + } +} + +static int dbc_tty_install(struct tty_driver *driver, struct tty_struct *tty) +{ + struct dbc_port *port = driver->driver_state; + + tty->driver_data = port; + + return tty_port_install(&port->port, driver, tty); +} + +static int dbc_tty_open(struct tty_struct *tty, struct file *file) +{ + struct dbc_port *port = tty->driver_data; + + return tty_port_open(&port->port, tty, file); +} + +static void dbc_tty_close(struct tty_struct *tty, struct file *file) +{ + struct dbc_port *port = tty->driver_data; + + tty_port_close(&port->port, tty, file); +} + +static int dbc_tty_write(struct tty_struct *tty, + const unsigned char *buf, + int count) +{ + struct dbc_port *port = tty->driver_data; + unsigned long flags; + + spin_lock_irqsave(&port->port_lock, flags); + if (count) + count = kfifo_in(&port->write_fifo, buf, count); + dbc_start_tx(port); + spin_unlock_irqrestore(&port->port_lock, flags); + + return count; +} + +static int dbc_tty_put_char(struct tty_struct *tty, unsigned char ch) +{ + struct dbc_port *port = tty->driver_data; + unsigned long flags; + int status; + + spin_lock_irqsave(&port->port_lock, flags); + status = kfifo_put(&port->write_fifo, ch); + spin_unlock_irqrestore(&port->port_lock, flags); + + return status; +} + +static void dbc_tty_flush_chars(struct tty_struct *tty) +{ + struct dbc_port *port = tty->driver_data; + unsigned long flags; + + spin_lock_irqsave(&port->port_lock, flags); + dbc_start_tx(port); + spin_unlock_irqrestore(&port->port_lock, flags); +} + +static int dbc_tty_write_room(struct tty_struct *tty) +{ + struct dbc_port *port = tty->driver_data; + unsigned long flags; + int room = 0; + + spin_lock_irqsave(&port->port_lock, flags); + room = kfifo_avail(&port->write_fifo); + spin_unlock_irqrestore(&port->port_lock, flags); + + return room; +} + +static int dbc_tty_chars_in_buffer(struct tty_struct *tty) +{ + struct dbc_port *port = tty->driver_data; + unsigned long flags; + int chars = 0; + + spin_lock_irqsave(&port->port_lock, flags); + chars = kfifo_len(&port->write_fifo); + spin_unlock_irqrestore(&port->port_lock, flags); + + return chars; +} + +static void dbc_tty_unthrottle(struct tty_struct *tty) +{ + struct dbc_port *port = tty->driver_data; + unsigned long flags; + + spin_lock_irqsave(&port->port_lock, flags); + tasklet_schedule(&port->push); + spin_unlock_irqrestore(&port->port_lock, flags); +} + +static const struct tty_operations dbc_tty_ops = { + .install = dbc_tty_install, + .open = dbc_tty_open, + .close = dbc_tty_close, + .write = dbc_tty_write, + .put_char = dbc_tty_put_char, + .flush_chars = dbc_tty_flush_chars, + .write_room = dbc_tty_write_room, + .chars_in_buffer = dbc_tty_chars_in_buffer, + .unthrottle = dbc_tty_unthrottle, +}; + +static struct tty_driver *dbc_tty_driver; + +int xhci_dbc_tty_register_driver(struct xhci_hcd *xhci) +{ + int status; + struct xhci_dbc *dbc = xhci->dbc; + + dbc_tty_driver = tty_alloc_driver(1, TTY_DRIVER_REAL_RAW | + TTY_DRIVER_DYNAMIC_DEV); + if (IS_ERR(dbc_tty_driver)) { + status = PTR_ERR(dbc_tty_driver); + dbc_tty_driver = NULL; + return status; + } + + dbc_tty_driver->driver_name = "dbc_serial"; + dbc_tty_driver->name = "ttyDBC"; + + dbc_tty_driver->type = TTY_DRIVER_TYPE_SERIAL; + dbc_tty_driver->subtype = SERIAL_TYPE_NORMAL; + dbc_tty_driver->init_termios = tty_std_termios; + dbc_tty_driver->init_termios.c_cflag = + B9600 | CS8 | CREAD | HUPCL | CLOCAL; + dbc_tty_driver->init_termios.c_ispeed = 9600; + dbc_tty_driver->init_termios.c_ospeed = 9600; + dbc_tty_driver->driver_state = &dbc->port; + + tty_set_operations(dbc_tty_driver, &dbc_tty_ops); + + status = tty_register_driver(dbc_tty_driver); + if (status) { + xhci_err(xhci, + "can't register dbc tty driver, err %d\n", status); + put_tty_driver(dbc_tty_driver); + dbc_tty_driver = NULL; + } + + return status; +} + +void xhci_dbc_tty_unregister_driver(void) +{ + tty_unregister_driver(dbc_tty_driver); + put_tty_driver(dbc_tty_driver); + dbc_tty_driver = NULL; +} + +static void dbc_rx_push(unsigned long _port) +{ + struct dbc_request *req; + struct tty_struct *tty; + bool do_push = false; + bool disconnect = false; + struct dbc_port *port = (void *)_port; + struct list_head *queue = &port->read_queue; + + spin_lock_irq(&port->port_lock); + tty = port->port.tty; + while (!list_empty(queue)) { + req = list_first_entry(queue, struct dbc_request, list_pool); + + if (tty && tty_throttled(tty)) + break; + + switch (req->status) { + case 0: + break; + case -ESHUTDOWN: + disconnect = true; + break; + default: + pr_warn("ttyDBC0: unexpected RX status %d\n", + req->status); + break; + } + + if (req->actual) { + char *packet = req->buf; + unsigned int n, size = req->actual; + int count; + + n = port->n_read; + if (n) { + packet += n; + size -= n; + } + + count = tty_insert_flip_string(&port->port, packet, + size); + if (count) + do_push = true; + if (count != size) { + port->n_read += count; + break; + } + port->n_read = 0; + } + + list_move(&req->list_pool, &port->read_pool); + } + + if (do_push) + tty_flip_buffer_push(&port->port); + + if (!list_empty(queue) && tty) { + if (!tty_throttled(tty)) { + if (do_push) + tasklet_schedule(&port->push); + else + pr_warn("ttyDBC0: RX not scheduled?\n"); + } + } + + if (!disconnect) + dbc_start_rx(port); + + spin_unlock_irq(&port->port_lock); +} + +static int dbc_port_activate(struct tty_port *_port, struct tty_struct *tty) +{ + struct dbc_port *port = container_of(_port, struct dbc_port, port); + + spin_lock_irq(&port->port_lock); + dbc_start_rx(port); + spin_unlock_irq(&port->port_lock); + + return 0; +} + +static const struct tty_port_operations dbc_port_ops = { + .activate = dbc_port_activate, +}; + +static void +xhci_dbc_tty_init_port(struct xhci_hcd *xhci, struct dbc_port *port) +{ + tty_port_init(&port->port); + spin_lock_init(&port->port_lock); + tasklet_init(&port->push, dbc_rx_push, (unsigned long)port); + INIT_LIST_HEAD(&port->read_pool); + INIT_LIST_HEAD(&port->read_queue); + INIT_LIST_HEAD(&port->write_pool); + + port->in = get_in_ep(xhci); + port->out = get_out_ep(xhci); + port->port.ops = &dbc_port_ops; + port->n_read = 0; +} + +static void +xhci_dbc_tty_exit_port(struct dbc_port *port) +{ + tasklet_kill(&port->push); + tty_port_destroy(&port->port); +} + +int xhci_dbc_tty_register_device(struct xhci_hcd *xhci) +{ + int ret; + struct device *tty_dev; + struct xhci_dbc *dbc = xhci->dbc; + struct dbc_port *port = &dbc->port; + + xhci_dbc_tty_init_port(xhci, port); + tty_dev = tty_port_register_device(&port->port, + dbc_tty_driver, 0, NULL); + ret = IS_ERR_OR_NULL(tty_dev); + if (ret) + goto register_fail; + + ret = kfifo_alloc(&port->write_fifo, DBC_WRITE_BUF_SIZE, GFP_KERNEL); + if (ret) + goto buf_alloc_fail; + + ret = xhci_dbc_alloc_requests(port->in, &port->read_pool, + dbc_read_complete); + if (ret) + goto request_fail; + + ret = xhci_dbc_alloc_requests(port->out, &port->write_pool, + dbc_write_complete); + if (ret) + goto request_fail; + + port->registered = true; + + return 0; + +request_fail: + xhci_dbc_free_requests(port->in, &port->read_pool); + xhci_dbc_free_requests(port->out, &port->write_pool); + kfifo_free(&port->write_fifo); + +buf_alloc_fail: + tty_unregister_device(dbc_tty_driver, 0); + +register_fail: + xhci_dbc_tty_exit_port(port); + + xhci_err(xhci, "can't register tty port, err %d\n", ret); + + return ret; +} + +void xhci_dbc_tty_unregister_device(struct xhci_hcd *xhci) +{ + struct xhci_dbc *dbc = xhci->dbc; + struct dbc_port *port = &dbc->port; + + tty_unregister_device(dbc_tty_driver, 0); + xhci_dbc_tty_exit_port(port); + port->registered = false; + + kfifo_free(&port->write_fifo); + xhci_dbc_free_requests(get_out_ep(xhci), &port->read_pool); + xhci_dbc_free_requests(get_out_ep(xhci), &port->read_queue); + xhci_dbc_free_requests(get_in_ep(xhci), &port->write_pool); +} diff --git a/drivers/usb/host/xhci-trace.h b/drivers/usb/host/xhci-trace.h index a3b57c781db1..67ff314a6f41 100644 --- a/drivers/usb/host/xhci-trace.h +++ b/drivers/usb/host/xhci-trace.h @@ -23,6 +23,7 @@ #include #include "xhci.h" +#include "xhci-dbgcap.h" #define XHCI_MSG_MAX 500 @@ -155,6 +156,21 @@ DEFINE_EVENT(xhci_log_trb, xhci_queue_trb, TP_ARGS(ring, trb) ); +DEFINE_EVENT(xhci_log_trb, xhci_dbc_handle_event, + TP_PROTO(struct xhci_ring *ring, struct xhci_generic_trb *trb), + TP_ARGS(ring, trb) +); + +DEFINE_EVENT(xhci_log_trb, xhci_dbc_handle_transfer, + TP_PROTO(struct xhci_ring *ring, struct xhci_generic_trb *trb), + TP_ARGS(ring, trb) +); + +DEFINE_EVENT(xhci_log_trb, xhci_dbc_gadget_ep_queue, + TP_PROTO(struct xhci_ring *ring, struct xhci_generic_trb *trb), + TP_ARGS(ring, trb) +); + DECLARE_EVENT_CLASS(xhci_log_virt_dev, TP_PROTO(struct xhci_virt_device *vdev), TP_ARGS(vdev), @@ -478,6 +494,49 @@ DEFINE_EVENT(xhci_log_portsc, xhci_handle_port_status, TP_ARGS(portnum, portsc) ); +DECLARE_EVENT_CLASS(xhci_dbc_log_request, + TP_PROTO(struct dbc_request *req), + TP_ARGS(req), + TP_STRUCT__entry( + __field(struct dbc_request *, req) + __field(bool, dir) + __field(unsigned int, actual) + __field(unsigned int, length) + __field(int, status) + ), + TP_fast_assign( + __entry->req = req; + __entry->dir = req->direction; + __entry->actual = req->actual; + __entry->length = req->length; + __entry->status = req->status; + ), + TP_printk("%s: req %p length %u/%u ==> %d", + __entry->dir ? "bulk-in" : "bulk-out", + __entry->req, __entry->actual, + __entry->length, __entry->status + ) +); + +DEFINE_EVENT(xhci_dbc_log_request, xhci_dbc_alloc_request, + TP_PROTO(struct dbc_request *req), + TP_ARGS(req) +); + +DEFINE_EVENT(xhci_dbc_log_request, xhci_dbc_free_request, + TP_PROTO(struct dbc_request *req), + TP_ARGS(req) +); + +DEFINE_EVENT(xhci_dbc_log_request, xhci_dbc_queue_request, + TP_PROTO(struct dbc_request *req), + TP_ARGS(req) +); + +DEFINE_EVENT(xhci_dbc_log_request, xhci_dbc_giveback_request, + TP_PROTO(struct dbc_request *req), + TP_ARGS(req) +); #endif /* __XHCI_TRACE_H */ /* this part must be outside header guard */ diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index f25b4ce31965..76bb0cb4aba5 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -21,6 +21,7 @@ #include "xhci-trace.h" #include "xhci-mtk.h" #include "xhci-debugfs.h" +#include "xhci-dbgcap.h" #define DRIVER_AUTHOR "Sarah Sharp" #define DRIVER_DESC "'eXtensible' Host Controller (xHC) Driver" @@ -622,6 +623,8 @@ int xhci_run(struct usb_hcd *hcd) xhci_dbg_trace(xhci, trace_xhci_dbg_init, "Finished xhci_run for USB2 roothub"); + xhci_dbc_init(xhci); + xhci_debugfs_init(xhci); return 0; @@ -654,6 +657,8 @@ static void xhci_stop(struct usb_hcd *hcd) xhci_debugfs_exit(xhci); + xhci_dbc_exit(xhci); + spin_lock_irq(&xhci->lock); xhci->xhc_state |= XHCI_STATE_HALTED; xhci->cmd_ring_state = CMD_RING_STATE_STOPPED; @@ -870,6 +875,8 @@ int xhci_suspend(struct xhci_hcd *xhci, bool do_wakeup) xhci->shared_hcd->state != HC_STATE_SUSPENDED) return -EINVAL; + xhci_dbc_suspend(xhci); + /* Clear root port wake on bits if wakeup not allowed. */ if (!do_wakeup) xhci_disable_port_wake_on_bits(xhci); @@ -1065,6 +1072,8 @@ int xhci_resume(struct xhci_hcd *xhci, bool hibernated) spin_unlock_irq(&xhci->lock); + xhci_dbc_resume(xhci); + done: if (retval == 0) { /* Resume root hubs only when have pending events. */ diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index f1515500ed88..e613344f050a 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -1856,6 +1856,7 @@ struct xhci_hcd { struct dentry *debugfs_slots; struct list_head regset_list; + void *dbc; /* platform-specific data -- must come last */ unsigned long priv[0] __aligned(sizeof(s64)); }; -- cgit v1.2.3-59-g8ed1b From ae1ba1676b88e6c62368a433c7e2d0417e9879fd Mon Sep 17 00:00:00 2001 From: Matthew Garrett Date: Tue, 7 Nov 2017 07:18:35 -0800 Subject: EVM: Allow userland to permit modification of EVM-protected metadata When EVM is enabled it forbids modification of metadata protected by EVM unless there is already a valid EVM signature. If any modification is made, the kernel will then generate a new EVM HMAC. However, this does not map well on use cases which use only asymmetric EVM signatures, as in this scenario the kernel is unable to generate new signatures. This patch extends the /sys/kernel/security/evm interface to allow userland to request that modification of these xattrs be permitted. This is only permitted if no keys have already been loaded. In this configuration, modifying the metadata will invalidate the EVM appraisal on the file in question. This allows packaging systems to write out new files, set the relevant extended attributes and then move them into place. There's also some refactoring of the use of evm_initialized in order to avoid heading down codepaths that assume there's a key available. Signed-off-by: Matthew Garrett Signed-off-by: Mimi Zohar --- Documentation/ABI/testing/evm | 54 ++++++++++++++++++++++++-------------- security/integrity/evm/evm.h | 7 +++-- security/integrity/evm/evm_main.c | 38 ++++++++++++++++++++++----- security/integrity/evm/evm_secfs.c | 20 +++++++++++--- 4 files changed, 88 insertions(+), 31 deletions(-) (limited to 'Documentation/ABI/testing') diff --git a/Documentation/ABI/testing/evm b/Documentation/ABI/testing/evm index 9578247e1792..d12cb2eae9ee 100644 --- a/Documentation/ABI/testing/evm +++ b/Documentation/ABI/testing/evm @@ -14,30 +14,46 @@ Description: generated either locally or remotely using an asymmetric key. These keys are loaded onto root's keyring using keyctl, and EVM is then enabled by - echoing a value to /evm: + echoing a value to /evm made up of the + following bits: - 1: enable HMAC validation and creation - 2: enable digital signature validation - 3: enable HMAC and digital signature validation and HMAC - creation + Bit Effect + 0 Enable HMAC validation and creation + 1 Enable digital signature validation + 2 Permit modification of EVM-protected metadata at + runtime. Not supported if HMAC validation and + creation is enabled. + 31 Disable further runtime modification of EVM policy - Further writes will be blocked if HMAC support is enabled or - if bit 32 is set: + For example: - echo 0x80000002 >/evm + echo 1 >/evm - will enable digital signature validation and block - further writes to /evm. + will enable HMAC validation and creation - Until this is done, EVM can not create or validate the - 'security.evm' xattr, but returns INTEGRITY_UNKNOWN. - Loading keys and signaling EVM should be done as early - as possible. Normally this is done in the initramfs, - which has already been measured as part of the trusted - boot. For more information on creating and loading - existing trusted/encrypted keys, refer to: + echo 0x80000003 >/evm - Documentation/security/keys/trusted-encrypted.rst. Both dracut - (via 97masterkey and 98integrity) and systemd (via + will enable HMAC and digital signature validation and + HMAC creation and disable all further modification of policy. + + echo 0x80000006 >/evm + + will enable digital signature validation, permit + modification of EVM-protected metadata and + disable all further modification of policy + + Note that once a key has been loaded, it will no longer be + possible to enable metadata modification. + + Until key loading has been signaled EVM can not create + or validate the 'security.evm' xattr, but returns + INTEGRITY_UNKNOWN. Loading keys and signaling EVM + should be done as early as possible. Normally this is + done in the initramfs, which has already been measured + as part of the trusted boot. For more information on + creating and loading existing trusted/encrypted keys, + refer to: + Documentation/security/keys/trusted-encrypted.rst. Both + dracut (via 97masterkey and 98integrity) and systemd (via core/ima-setup) have support for loading keys at boot time. diff --git a/security/integrity/evm/evm.h b/security/integrity/evm/evm.h index 241aca315b0c..3d05250e8313 100644 --- a/security/integrity/evm/evm.h +++ b/security/integrity/evm/evm.h @@ -23,9 +23,12 @@ #define EVM_INIT_HMAC 0x0001 #define EVM_INIT_X509 0x0002 -#define EVM_SETUP 0x80000000 /* userland has signaled key load */ +#define EVM_ALLOW_METADATA_WRITES 0x0004 +#define EVM_SETUP_COMPLETE 0x80000000 /* userland has signaled key load */ -#define EVM_INIT_MASK (EVM_INIT_HMAC | EVM_INIT_X509 | EVM_SETUP) +#define EVM_KEY_MASK (EVM_INIT_HMAC | EVM_INIT_X509) +#define EVM_INIT_MASK (EVM_INIT_HMAC | EVM_INIT_X509 | EVM_SETUP_COMPLETE | \ + EVM_ALLOW_METADATA_WRITES) extern int evm_initialized; extern char *evm_hmac; diff --git a/security/integrity/evm/evm_main.c b/security/integrity/evm/evm_main.c index 9826c02e2db8..ba89c2468298 100644 --- a/security/integrity/evm/evm_main.c +++ b/security/integrity/evm/evm_main.c @@ -76,6 +76,11 @@ static void __init evm_init_config(void) pr_info("HMAC attrs: 0x%x\n", evm_hmac_attrs); } +static bool evm_key_loaded(void) +{ + return (bool)(evm_initialized & EVM_KEY_MASK); +} + static int evm_find_protected_xattrs(struct dentry *dentry) { struct inode *inode = d_backing_inode(dentry); @@ -241,7 +246,7 @@ enum integrity_status evm_verifyxattr(struct dentry *dentry, void *xattr_value, size_t xattr_value_len, struct integrity_iint_cache *iint) { - if (!evm_initialized || !evm_protected_xattr(xattr_name)) + if (!evm_key_loaded() || !evm_protected_xattr(xattr_name)) return INTEGRITY_UNKNOWN; if (!iint) { @@ -265,7 +270,7 @@ static enum integrity_status evm_verify_current_integrity(struct dentry *dentry) { struct inode *inode = d_backing_inode(dentry); - if (!evm_initialized || !S_ISREG(inode->i_mode) || evm_fixmode) + if (!evm_key_loaded() || !S_ISREG(inode->i_mode) || evm_fixmode) return 0; return evm_verify_hmac(dentry, NULL, NULL, 0, NULL); } @@ -299,6 +304,7 @@ static int evm_protect_xattr(struct dentry *dentry, const char *xattr_name, return 0; goto out; } + evm_status = evm_verify_current_integrity(dentry); if (evm_status == INTEGRITY_NOXATTRS) { struct integrity_iint_cache *iint; @@ -345,6 +351,12 @@ int evm_inode_setxattr(struct dentry *dentry, const char *xattr_name, { const struct evm_ima_xattr_data *xattr_data = xattr_value; + /* Policy permits modification of the protected xattrs even though + * there's no HMAC key loaded + */ + if (evm_initialized & EVM_ALLOW_METADATA_WRITES) + return 0; + if (strcmp(xattr_name, XATTR_NAME_EVM) == 0) { if (!xattr_value_len) return -EINVAL; @@ -365,6 +377,12 @@ int evm_inode_setxattr(struct dentry *dentry, const char *xattr_name, */ int evm_inode_removexattr(struct dentry *dentry, const char *xattr_name) { + /* Policy permits modification of the protected xattrs even though + * there's no HMAC key loaded + */ + if (evm_initialized & EVM_ALLOW_METADATA_WRITES) + return 0; + return evm_protect_xattr(dentry, xattr_name, NULL, 0); } @@ -393,8 +411,8 @@ static void evm_reset_status(struct inode *inode) void evm_inode_post_setxattr(struct dentry *dentry, const char *xattr_name, const void *xattr_value, size_t xattr_value_len) { - if (!evm_initialized || (!evm_protected_xattr(xattr_name) - && !posix_xattr_acl(xattr_name))) + if (!evm_key_loaded() || (!evm_protected_xattr(xattr_name) + && !posix_xattr_acl(xattr_name))) return; evm_reset_status(dentry->d_inode); @@ -414,7 +432,7 @@ void evm_inode_post_setxattr(struct dentry *dentry, const char *xattr_name, */ void evm_inode_post_removexattr(struct dentry *dentry, const char *xattr_name) { - if (!evm_initialized || !evm_protected_xattr(xattr_name)) + if (!evm_key_loaded() || !evm_protected_xattr(xattr_name)) return; evm_reset_status(dentry->d_inode); @@ -431,6 +449,12 @@ int evm_inode_setattr(struct dentry *dentry, struct iattr *attr) unsigned int ia_valid = attr->ia_valid; enum integrity_status evm_status; + /* Policy permits modification of the protected attrs even though + * there's no HMAC key loaded + */ + if (evm_initialized & EVM_ALLOW_METADATA_WRITES) + return 0; + if (!(ia_valid & (ATTR_MODE | ATTR_UID | ATTR_GID))) return 0; evm_status = evm_verify_current_integrity(dentry); @@ -456,7 +480,7 @@ int evm_inode_setattr(struct dentry *dentry, struct iattr *attr) */ void evm_inode_post_setattr(struct dentry *dentry, int ia_valid) { - if (!evm_initialized) + if (!evm_key_loaded()) return; if (ia_valid & (ATTR_MODE | ATTR_UID | ATTR_GID)) @@ -473,7 +497,7 @@ int evm_inode_init_security(struct inode *inode, struct evm_ima_xattr_data *xattr_data; int rc; - if (!evm_initialized || !evm_protected_xattr(lsm_xattr->name)) + if (!evm_key_loaded() || !evm_protected_xattr(lsm_xattr->name)) return 0; xattr_data = kzalloc(sizeof(*xattr_data), GFP_NOFS); diff --git a/security/integrity/evm/evm_secfs.c b/security/integrity/evm/evm_secfs.c index 319cf16d6603..feba03bbedae 100644 --- a/security/integrity/evm/evm_secfs.c +++ b/security/integrity/evm/evm_secfs.c @@ -40,7 +40,7 @@ static ssize_t evm_read_key(struct file *filp, char __user *buf, if (*ppos != 0) return 0; - sprintf(temp, "%d", (evm_initialized & ~EVM_SETUP)); + sprintf(temp, "%d", (evm_initialized & ~EVM_SETUP_COMPLETE)); rc = simple_read_from_buffer(buf, count, ppos, temp, strlen(temp)); return rc; @@ -63,7 +63,7 @@ static ssize_t evm_write_key(struct file *file, const char __user *buf, { int i, ret; - if (!capable(CAP_SYS_ADMIN) || (evm_initialized & EVM_SETUP)) + if (!capable(CAP_SYS_ADMIN) || (evm_initialized & EVM_SETUP_COMPLETE)) return -EPERM; ret = kstrtoint_from_user(buf, count, 0, &i); @@ -75,16 +75,30 @@ static ssize_t evm_write_key(struct file *file, const char __user *buf, if (!i || (i & ~EVM_INIT_MASK) != 0) return -EINVAL; + /* Don't allow a request to freshly enable metadata writes if + * keys are loaded. + */ + if ((i & EVM_ALLOW_METADATA_WRITES) && + ((evm_initialized & EVM_KEY_MASK) != 0) && + !(evm_initialized & EVM_ALLOW_METADATA_WRITES)) + return -EPERM; + if (i & EVM_INIT_HMAC) { ret = evm_init_key(); if (ret != 0) return ret; /* Forbid further writes after the symmetric key is loaded */ - i |= EVM_SETUP; + i |= EVM_SETUP_COMPLETE; } evm_initialized |= i; + /* Don't allow protected metadata modification if a symmetric key + * is loaded + */ + if (evm_initialized & EVM_INIT_HMAC) + evm_initialized &= ~(EVM_ALLOW_METADATA_WRITES); + return count; } -- cgit v1.2.3-59-g8ed1b From da1b0029f527a9b4204e90ba6f14ee139fd76f9e Mon Sep 17 00:00:00 2001 From: Mimi Zohar Date: Thu, 29 Sep 2016 10:04:52 -0400 Subject: ima: support new "hash" and "dont_hash" policy actions The builtin ima_appraise_tcb policy, which is specified on the boot command line, can be replaced with a custom policy, normally early in the boot process. Custom policies can be more restrictive in some ways, like requiring file signatures, but can be less restrictive in other ways, like not appraising mutable files. With a less restrictive policy in place, files in the builtin policy might not be hashed and labeled with a security.ima hash. On reboot, files which should be labeled in the ima_appraise_tcb are not labeled, possibly preventing the system from booting properly. To resolve this problem, this patch extends the existing IMA policy actions "measure", "dont_measure", "appraise", "dont_appraise", and "audit" with "hash" and "dont_hash". The new "hash" action will write the file hash as security.ima, but without requiring the file to be appraised as well. For example, the builtin ima_appraise_tcb policy includes the rule, "appraise fowner=0". Adding the "hash fowner=0" rule to a custom policy, will cause the needed file hashes to be calculated and written as security.ima xattrs. Signed-off-by: Mimi Zohar Signed-off-by: Stefan Berger --- Documentation/ABI/testing/ima_policy | 3 ++- security/integrity/ima/ima_api.c | 2 +- security/integrity/ima/ima_appraise.c | 16 +++++++++------- security/integrity/ima/ima_main.c | 12 ++++++++++++ security/integrity/ima/ima_policy.c | 30 ++++++++++++++++++++++++++++-- security/integrity/integrity.h | 23 +++++++++++++---------- 6 files changed, 65 insertions(+), 21 deletions(-) (limited to 'Documentation/ABI/testing') diff --git a/Documentation/ABI/testing/ima_policy b/Documentation/ABI/testing/ima_policy index e76432b9954d..2028f2d093b2 100644 --- a/Documentation/ABI/testing/ima_policy +++ b/Documentation/ABI/testing/ima_policy @@ -17,7 +17,8 @@ Description: rule format: action [condition ...] - action: measure | dont_measure | appraise | dont_appraise | audit + action: measure | dont_measure | appraise | dont_appraise | + audit | hash | dont_hash condition:= base | lsm [option] base: [[func=] [mask=] [fsmagic=] [fsuuid=] [uid=] [euid=] [fowner=]] diff --git a/security/integrity/ima/ima_api.c b/security/integrity/ima/ima_api.c index c7e8db0ea4c0..877f446fdca2 100644 --- a/security/integrity/ima/ima_api.c +++ b/security/integrity/ima/ima_api.c @@ -174,7 +174,7 @@ err_out: */ int ima_get_action(struct inode *inode, int mask, enum ima_hooks func, int *pcr) { - int flags = IMA_MEASURE | IMA_AUDIT | IMA_APPRAISE; + int flags = IMA_MEASURE | IMA_AUDIT | IMA_APPRAISE | IMA_HASH; flags &= ima_policy_flag; diff --git a/security/integrity/ima/ima_appraise.c b/security/integrity/ima/ima_appraise.c index ea1245606a14..f2803a40ff82 100644 --- a/security/integrity/ima/ima_appraise.c +++ b/security/integrity/ima/ima_appraise.c @@ -46,14 +46,15 @@ bool is_ima_appraise_enabled(void) /* * ima_must_appraise - set appraise flag * - * Return 1 to appraise + * Return 1 to appraise or hash */ int ima_must_appraise(struct inode *inode, int mask, enum ima_hooks func) { if (!ima_appraise) return 0; - return ima_match_policy(inode, func, mask, IMA_APPRAISE, NULL); + return ima_match_policy(inode, func, mask, IMA_APPRAISE | IMA_HASH, + NULL); } static int ima_fix_xattr(struct dentry *dentry, @@ -324,7 +325,8 @@ void ima_update_xattr(struct integrity_iint_cache *iint, struct file *file) if (test_bit(IMA_DIGSIG, &iint->atomic_flags)) return; - if (iint->ima_file_status != INTEGRITY_PASS) + if ((iint->ima_file_status != INTEGRITY_PASS) && + !(iint->flags & IMA_HASH)) return; rc = ima_collect_measurement(iint, file, NULL, 0, ima_hash_algo); @@ -349,19 +351,19 @@ void ima_inode_post_setattr(struct dentry *dentry) { struct inode *inode = d_backing_inode(dentry); struct integrity_iint_cache *iint; - int must_appraise; + int action; if (!(ima_policy_flag & IMA_APPRAISE) || !S_ISREG(inode->i_mode) || !(inode->i_opflags & IOP_XATTR)) return; - must_appraise = ima_must_appraise(inode, MAY_ACCESS, POST_SETATTR); - if (!must_appraise) + action = ima_must_appraise(inode, MAY_ACCESS, POST_SETATTR); + if (!action) __vfs_removexattr(dentry, XATTR_NAME_IMA); iint = integrity_iint_find(inode); if (iint) { set_bit(IMA_CHANGE_ATTR, &iint->atomic_flags); - if (!must_appraise) + if (!action) clear_bit(IMA_UPDATE_XATTR, &iint->atomic_flags); } } diff --git a/security/integrity/ima/ima_main.c b/security/integrity/ima/ima_main.c index edf4e0717494..be1987e13c43 100644 --- a/security/integrity/ima/ima_main.c +++ b/security/integrity/ima/ima_main.c @@ -243,6 +243,18 @@ static int process_measurement(struct file *file, char *buf, loff_t size, if ((action & IMA_MEASURE) && (iint->measured_pcrs & (0x1 << pcr))) action ^= IMA_MEASURE; + /* HASH sets the digital signature and update flags, nothing else */ + if ((action & IMA_HASH) && + !(test_bit(IMA_DIGSIG, &iint->atomic_flags))) { + xattr_len = ima_read_xattr(file_dentry(file), &xattr_value); + if ((xattr_value && xattr_len > 2) && + (xattr_value->type == EVM_IMA_XATTR_DIGSIG)) + set_bit(IMA_DIGSIG, &iint->atomic_flags); + iint->flags |= IMA_HASHED; + action ^= IMA_HASH; + set_bit(IMA_UPDATE_XATTR, &iint->atomic_flags); + } + /* Nothing to do, just return existing appraised status */ if (!action) { if (must_appraise) diff --git a/security/integrity/ima/ima_policy.c b/security/integrity/ima/ima_policy.c index ee4613fa5840..93dcf1bf92a8 100644 --- a/security/integrity/ima/ima_policy.c +++ b/security/integrity/ima/ima_policy.c @@ -40,6 +40,8 @@ #define APPRAISE 0x0004 /* same as IMA_APPRAISE */ #define DONT_APPRAISE 0x0008 #define AUDIT 0x0040 +#define HASH 0x0100 +#define DONT_HASH 0x0200 #define INVALID_PCR(a) (((a) < 0) || \ (a) >= (FIELD_SIZEOF(struct integrity_iint_cache, measured_pcrs) * 8)) @@ -380,8 +382,10 @@ int ima_match_policy(struct inode *inode, enum ima_hooks func, int mask, action |= entry->flags & IMA_ACTION_FLAGS; action |= entry->action & IMA_DO_MASK; - if (entry->action & IMA_APPRAISE) + if (entry->action & IMA_APPRAISE) { action |= get_subaction(entry, func); + action ^= IMA_HASH; + } if (entry->action & IMA_DO_MASK) actmask &= ~(entry->action | entry->action << 1); @@ -521,7 +525,7 @@ enum { Opt_err = -1, Opt_measure = 1, Opt_dont_measure, Opt_appraise, Opt_dont_appraise, - Opt_audit, + Opt_audit, Opt_hash, Opt_dont_hash, Opt_obj_user, Opt_obj_role, Opt_obj_type, Opt_subj_user, Opt_subj_role, Opt_subj_type, Opt_func, Opt_mask, Opt_fsmagic, @@ -538,6 +542,8 @@ static match_table_t policy_tokens = { {Opt_appraise, "appraise"}, {Opt_dont_appraise, "dont_appraise"}, {Opt_audit, "audit"}, + {Opt_hash, "hash"}, + {Opt_dont_hash, "dont_hash"}, {Opt_obj_user, "obj_user=%s"}, {Opt_obj_role, "obj_role=%s"}, {Opt_obj_type, "obj_type=%s"}, @@ -671,6 +677,22 @@ static int ima_parse_rule(char *rule, struct ima_rule_entry *entry) entry->action = AUDIT; break; + case Opt_hash: + ima_log_string(ab, "action", "hash"); + + if (entry->action != UNKNOWN) + result = -EINVAL; + + entry->action = HASH; + break; + case Opt_dont_hash: + ima_log_string(ab, "action", "dont_hash"); + + if (entry->action != UNKNOWN) + result = -EINVAL; + + entry->action = DONT_HASH; + break; case Opt_func: ima_log_string(ab, "func", args[0].from); @@ -1040,6 +1062,10 @@ int ima_policy_show(struct seq_file *m, void *v) seq_puts(m, pt(Opt_dont_appraise)); if (entry->action & AUDIT) seq_puts(m, pt(Opt_audit)); + if (entry->action & HASH) + seq_puts(m, pt(Opt_hash)); + if (entry->action & DONT_HASH) + seq_puts(m, pt(Opt_dont_hash)); seq_puts(m, " "); diff --git a/security/integrity/integrity.h b/security/integrity/integrity.h index c64ea8f88f66..50a8e3365df7 100644 --- a/security/integrity/integrity.h +++ b/security/integrity/integrity.h @@ -25,6 +25,8 @@ #define IMA_COLLECTED 0x00000020 #define IMA_AUDIT 0x00000040 #define IMA_AUDITED 0x00000080 +#define IMA_HASH 0x00000100 +#define IMA_HASHED 0x00000200 /* iint cache flags */ #define IMA_ACTION_FLAGS 0xff000000 @@ -35,19 +37,20 @@ #define EVM_IMMUTABLE_DIGSIG 0x08000000 #define IMA_DO_MASK (IMA_MEASURE | IMA_APPRAISE | IMA_AUDIT | \ - IMA_APPRAISE_SUBMASK) + IMA_HASH | IMA_APPRAISE_SUBMASK) #define IMA_DONE_MASK (IMA_MEASURED | IMA_APPRAISED | IMA_AUDITED | \ - IMA_COLLECTED | IMA_APPRAISED_SUBMASK) + IMA_HASHED | IMA_COLLECTED | \ + IMA_APPRAISED_SUBMASK) /* iint subaction appraise cache flags */ -#define IMA_FILE_APPRAISE 0x00000100 -#define IMA_FILE_APPRAISED 0x00000200 -#define IMA_MMAP_APPRAISE 0x00000400 -#define IMA_MMAP_APPRAISED 0x00000800 -#define IMA_BPRM_APPRAISE 0x00001000 -#define IMA_BPRM_APPRAISED 0x00002000 -#define IMA_READ_APPRAISE 0x00004000 -#define IMA_READ_APPRAISED 0x00008000 +#define IMA_FILE_APPRAISE 0x00001000 +#define IMA_FILE_APPRAISED 0x00002000 +#define IMA_MMAP_APPRAISE 0x00004000 +#define IMA_MMAP_APPRAISED 0x00008000 +#define IMA_BPRM_APPRAISE 0x00010000 +#define IMA_BPRM_APPRAISED 0x00020000 +#define IMA_READ_APPRAISE 0x00040000 +#define IMA_READ_APPRAISED 0x00080000 #define IMA_APPRAISE_SUBMASK (IMA_FILE_APPRAISE | IMA_MMAP_APPRAISE | \ IMA_BPRM_APPRAISE | IMA_READ_APPRAISE) #define IMA_APPRAISED_SUBMASK (IMA_FILE_APPRAISED | IMA_MMAP_APPRAISED | \ -- cgit v1.2.3-59-g8ed1b From bbecb07fa0af9a41507ce06d4631fdb3b5059417 Mon Sep 17 00:00:00 2001 From: Uwe Kleine-König Date: Mon, 18 Dec 2017 17:59:07 +0100 Subject: siox: new driver framework for eckelmann SIOX MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SIOX is a bus system invented at Eckelmann AG to control their building management and refrigeration systems. Traditionally the bus was implemented on custom microcontrollers, today Linux based machines are in use, too. The topology on a SIOX bus looks as follows: ,------->--DCLK-->---------------+----------------------. ^ v v ,--------. ,----------------------. ,------ | | | ,--------------. | | | |--->--DOUT-->---|->-|shift register|->-|--->---| | | | `--------------' | | | master | | device | | device | | | ,--------------. | | | |---<--DIN---<---|-<-|shift register|-<-|---<---| | | | `--------------' | | `--------' `----------------------' `------ v ^ ^ `----------DLD-------------------+----------------------' There are two control lines (DCLK and DLD) driven from the bus master to all devices in parallel and two daisy chained data lines, one for input and one for output. DCLK is the clock to shift both chains by a single bit. On an edge of DLD the devices latch both their input and output shift registers. This patch adds a framework for this bus type. Acked-by: Gavin Schenk Signed-off-by: Uwe Kleine-König Signed-off-by: Greg Kroah-Hartman --- Documentation/ABI/testing/sysfs-bus-siox | 87 +++ drivers/Kconfig | 2 + drivers/Makefile | 1 + drivers/siox/Kconfig | 9 + drivers/siox/Makefile | 1 + drivers/siox/siox-core.c | 922 +++++++++++++++++++++++++++++++ drivers/siox/siox.h | 49 ++ include/linux/siox.h | 77 +++ 8 files changed, 1148 insertions(+) create mode 100644 Documentation/ABI/testing/sysfs-bus-siox create mode 100644 drivers/siox/Kconfig create mode 100644 drivers/siox/Makefile create mode 100644 drivers/siox/siox-core.c create mode 100644 drivers/siox/siox.h create mode 100644 include/linux/siox.h (limited to 'Documentation/ABI/testing') diff --git a/Documentation/ABI/testing/sysfs-bus-siox b/Documentation/ABI/testing/sysfs-bus-siox new file mode 100644 index 000000000000..fed7c3765a4e --- /dev/null +++ b/Documentation/ABI/testing/sysfs-bus-siox @@ -0,0 +1,87 @@ +What: /sys/bus/siox/devices/siox-X/active +KernelVersion: 4.16 +Contact: Gavin Schenk , Uwe Kleine-König +Description: + On reading represents the current state of the bus. If it + contains a "0" the bus is stopped and connected devices are + expected to not do anything because their watchdog triggered. + When the file contains a "1" the bus is operated and periodically + does a push-pull cycle to write and read data from the + connected devices. + When writing a "0" or "1" the bus moves to the described state. + +What: /sys/bus/siox/devices/siox-X/device_add +KernelVersion: 4.16 +Contact: Gavin Schenk , Uwe Kleine-König +Description: + Write-only file. Write + + + + to add a new device dynamically. is the name that is used to match + to a driver (similar to the platform bus). and define + the length of the input and output shift register in bytes respectively. + defines the 4 bit device type that is check to identify connection + problems. + The new device is added to the end of the existing chain. + +What: /sys/bus/siox/devices/siox-X/device_remove +KernelVersion: 4.16 +Contact: Gavin Schenk , Uwe Kleine-König +Description: + Write-only file. A single write removes the last device in the siox chain. + +What: /sys/bus/siox/devices/siox-X/poll_interval_ns +KernelVersion: 4.16 +Contact: Gavin Schenk , Uwe Kleine-König +Description: + Defines the interval between two poll cycles in nano seconds. + Note this is rounded to jiffies on writing. On reading the current value + is returned. + +What: /sys/bus/siox/devices/siox-X-Y/connected +KernelVersion: 4.16 +Contact: Gavin Schenk , Uwe Kleine-König +Description: + Read-only value. "0" means the Yth device on siox bus X isn't "connected" i.e. + communication with it is not ensured. "1" signals a working connection. + +What: /sys/bus/siox/devices/siox-X-Y/inbytes +KernelVersion: 4.16 +Contact: Gavin Schenk , Uwe Kleine-König +Description: + Read-only value reporting the inbytes value provided to siox-X/device_add + +What: /sys/bus/siox/devices/siox-X-Y/status_errors +KernelVersion: 4.16 +Contact: Gavin Schenk , Uwe Kleine-König +Description: + Counts the number of time intervals when the read status byte doesn't yield the + expected value. + +What: /sys/bus/siox/devices/siox-X-Y/type +KernelVersion: 4.16 +Contact: Gavin Schenk , Uwe Kleine-König +Description: + Read-only value reporting the type value provided to siox-X/device_add. + +What: /sys/bus/siox/devices/siox-X-Y/watchdog +KernelVersion: 4.16 +Contact: Gavin Schenk , Uwe Kleine-König +Description: + Read-only value reporting if the watchdog of the siox device is + active. "0" means the watchdog is not active and the device is expected to + be operational. "1" means the watchdog keeps the device in reset. + +What: /sys/bus/siox/devices/siox-X-Y/watchdog_errors +KernelVersion: 4.16 +Contact: Gavin Schenk , Uwe Kleine-König +Description: + Read-only value reporting the number to time intervals when the + watchdog was active. + +What: /sys/bus/siox/devices/siox-X-Y/outbytes +KernelVersion: 4.16 +Contact: Gavin Schenk , Uwe Kleine-König +Description: + Read-only value reporting the outbytes value provided to siox-X/device_add. diff --git a/drivers/Kconfig b/drivers/Kconfig index 152744c5ef0f..5458b623c00c 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -211,4 +211,6 @@ source "drivers/mux/Kconfig" source "drivers/opp/Kconfig" +source "drivers/siox/Kconfig" + endmenu diff --git a/drivers/Makefile b/drivers/Makefile index e06f7f633f73..82b1e9a50989 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -184,3 +184,4 @@ obj-$(CONFIG_FPGA) += fpga/ obj-$(CONFIG_FSI) += fsi/ obj-$(CONFIG_TEE) += tee/ obj-$(CONFIG_MULTIPLEXER) += mux/ +obj-$(CONFIG_SIOX) += siox/ diff --git a/drivers/siox/Kconfig b/drivers/siox/Kconfig new file mode 100644 index 000000000000..bd24d9b50dc6 --- /dev/null +++ b/drivers/siox/Kconfig @@ -0,0 +1,9 @@ +menuconfig SIOX + tristate "Eckelmann SIOX Support" + help + SIOX stands for Serial Input Output eXtension and is a synchronous + bus system invented by Eckelmann AG. It is used in their control and + remote monitoring systems for commercial and industrial refrigeration + to drive additional I/O units. + + Unless you know better, it is probably safe to say "no" here. diff --git a/drivers/siox/Makefile b/drivers/siox/Makefile new file mode 100644 index 000000000000..d55cb5e08868 --- /dev/null +++ b/drivers/siox/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_SIOX) += siox-core.o diff --git a/drivers/siox/siox-core.c b/drivers/siox/siox-core.c new file mode 100644 index 000000000000..16585c1b2b9e --- /dev/null +++ b/drivers/siox/siox-core.c @@ -0,0 +1,922 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2015-2017 Pengutronix, Uwe Kleine-König + */ +#include +#include +#include +#include +#include + +#include "siox.h" + +/* + * The lowest bit in the SIOX status word signals if the in-device watchdog is + * ok. If the bit is set, the device is functional. + * + * On writing the watchdog timer is reset when this bit toggles. + */ +#define SIOX_STATUS_WDG 0x01 + +/* + * Bits 1 to 3 of the status word read as the bitwise negation of what was + * clocked in before. The value clocked in is changed in each cycle and so + * allows to detect transmit/receive problems. + */ +#define SIOX_STATUS_COUNTER 0x0e + +/* + * Each Siox-Device has a 4 bit type number that is neither 0 nor 15. This is + * available in the upper nibble of the read status. + * + * On write these bits are DC. + */ +#define SIOX_STATUS_TYPE 0xf0 + +static bool siox_is_registered; + +static void siox_master_lock(struct siox_master *smaster) +{ + mutex_lock(&smaster->lock); +} + +static void siox_master_unlock(struct siox_master *smaster) +{ + mutex_unlock(&smaster->lock); +} + +static inline u8 siox_status_clean(u8 status_read, u8 status_written) +{ + /* + * bits 3:1 of status sample the respective bit in the status + * byte written in the previous cycle but inverted. So if you wrote the + * status word as 0xa before (counter = 0b101), it is expected to get + * back the counter bits as 0b010. + * + * So given the last status written this function toggles the there + * unset counter bits in the read value such that the counter bits in + * the return value are all zero iff the bits were read as expected to + * simplify error detection. + */ + + return status_read ^ (~status_written & 0xe); +} + +static bool siox_device_counter_error(struct siox_device *sdevice, + u8 status_clean) +{ + return (status_clean & SIOX_STATUS_COUNTER) != 0; +} + +static bool siox_device_type_error(struct siox_device *sdevice, u8 status_clean) +{ + u8 statustype = (status_clean & SIOX_STATUS_TYPE) >> 4; + + /* + * If the device knows which value the type bits should have, check + * against this value otherwise just rule out the invalid values 0b0000 + * and 0b1111. + */ + if (sdevice->statustype) { + if (statustype != sdevice->statustype) + return true; + } else { + switch (statustype) { + case 0: + case 0xf: + return true; + } + } + + return false; +} + +static bool siox_device_wdg_error(struct siox_device *sdevice, u8 status_clean) +{ + return (status_clean & SIOX_STATUS_WDG) == 0; +} + +/* + * If there is a type or counter error the device is called "unsynced". + */ +bool siox_device_synced(struct siox_device *sdevice) +{ + if (siox_device_type_error(sdevice, sdevice->status_read_clean)) + return false; + + return !siox_device_counter_error(sdevice, sdevice->status_read_clean); + +} +EXPORT_SYMBOL_GPL(siox_device_synced); + +/* + * A device is called "connected" if it is synced and the watchdog is not + * asserted. + */ +bool siox_device_connected(struct siox_device *sdevice) +{ + if (!siox_device_synced(sdevice)) + return false; + + return !siox_device_wdg_error(sdevice, sdevice->status_read_clean); +} +EXPORT_SYMBOL_GPL(siox_device_connected); + +static void siox_poll(struct siox_master *smaster) +{ + struct siox_device *sdevice; + size_t i = smaster->setbuf_len; + int unsync_error = 0; + + smaster->last_poll = jiffies; + + /* + * The counter bits change in each second cycle, the watchdog bit + * toggles each time. + * The counter bits hold values from [0, 6]. 7 would be possible + * theoretically but the protocol designer considered that a bad idea + * for reasons unknown today. (Maybe that's because then the status read + * back has only zeros in the counter bits then which might be confused + * with a stuck-at-0 error. But for the same reason (with s/0/1/) 0 + * could be skipped.) + */ + if (++smaster->status > 0x0d) + smaster->status = 0; + + memset(smaster->buf, 0, smaster->setbuf_len); + + /* prepare data pushed out to devices in buf[0..setbuf_len) */ + list_for_each_entry(sdevice, &smaster->devices, node) { + struct siox_driver *sdriver = + to_siox_driver(sdevice->dev.driver); + sdevice->status_written = smaster->status; + + i -= sdevice->inbytes; + + /* + * If the device or a previous one is unsynced, don't pet the + * watchdog. This is done to ensure that the device is kept in + * reset when something is wrong. + */ + if (!siox_device_synced(sdevice)) + unsync_error = 1; + + if (sdriver && !unsync_error) + sdriver->set_data(sdevice, sdevice->status_written, + &smaster->buf[i + 1]); + else + /* + * Don't trigger watchdog if there is no driver or a + * sync problem + */ + sdevice->status_written &= ~SIOX_STATUS_WDG; + + smaster->buf[i] = sdevice->status_written; + } + + smaster->pushpull(smaster, smaster->setbuf_len, smaster->buf, + smaster->getbuf_len, + smaster->buf + smaster->setbuf_len); + + unsync_error = 0; + + /* interpret data pulled in from devices in buf[setbuf_len..] */ + i = smaster->setbuf_len; + list_for_each_entry(sdevice, &smaster->devices, node) { + struct siox_driver *sdriver = + to_siox_driver(sdevice->dev.driver); + u8 status = smaster->buf[i + sdevice->outbytes - 1]; + u8 status_clean; + u8 prev_status_clean = sdevice->status_read_clean; + bool synced = true; + bool connected = true; + + if (!siox_device_synced(sdevice)) + unsync_error = 1; + + /* + * If the watchdog bit wasn't toggled in this cycle, report the + * watchdog as active to give a consistent view for drivers and + * sysfs consumers. + */ + if (!sdriver || unsync_error) + status &= ~SIOX_STATUS_WDG; + + status_clean = + siox_status_clean(status, + sdevice->status_written_lastcycle); + + /* Check counter bits */ + if (siox_device_counter_error(sdevice, status_clean)) { + bool prev_counter_error; + + synced = false; + + /* only report a new error if the last cycle was ok */ + prev_counter_error = + siox_device_counter_error(sdevice, + prev_status_clean); + if (!prev_counter_error) { + sdevice->status_errors++; + sysfs_notify_dirent(sdevice->status_errors_kn); + } + } + + /* Check type bits */ + if (siox_device_type_error(sdevice, status_clean)) + synced = false; + + /* If the device is unsynced report the watchdog as active */ + if (!synced) { + status &= ~SIOX_STATUS_WDG; + status_clean &= ~SIOX_STATUS_WDG; + } + + if (siox_device_wdg_error(sdevice, status_clean)) + connected = false; + + /* The watchdog state changed just now */ + if ((status_clean ^ prev_status_clean) & SIOX_STATUS_WDG) { + sysfs_notify_dirent(sdevice->watchdog_kn); + + if (siox_device_wdg_error(sdevice, status_clean)) { + struct kernfs_node *wd_errs = + sdevice->watchdog_errors_kn; + + sdevice->watchdog_errors++; + sysfs_notify_dirent(wd_errs); + } + } + + if (connected != sdevice->connected) + sysfs_notify_dirent(sdevice->connected_kn); + + sdevice->status_read_clean = status_clean; + sdevice->status_written_lastcycle = sdevice->status_written; + sdevice->connected = connected; + + /* only give data read to driver if the device is connected */ + if (sdriver && connected) + sdriver->get_data(sdevice, &smaster->buf[i]); + + i += sdevice->outbytes; + } +} + +static int siox_poll_thread(void *data) +{ + struct siox_master *smaster = data; + signed long timeout = 0; + + get_device(&smaster->dev); + + for (;;) { + if (kthread_should_stop()) { + put_device(&smaster->dev); + return 0; + } + + siox_master_lock(smaster); + + if (smaster->active) { + unsigned long next_poll = + smaster->last_poll + smaster->poll_interval; + if (time_is_before_eq_jiffies(next_poll)) + siox_poll(smaster); + + timeout = smaster->poll_interval - + (jiffies - smaster->last_poll); + } else { + timeout = MAX_SCHEDULE_TIMEOUT; + } + + /* + * Set the task to idle while holding the lock. This makes sure + * that we don't sleep too long when the bus is reenabled before + * schedule_timeout is reached. + */ + if (timeout > 0) + set_current_state(TASK_IDLE); + + siox_master_unlock(smaster); + + if (timeout > 0) + schedule_timeout(timeout); + + /* + * I'm not clear if/why it is important to set the state to + * RUNNING again, but it fixes a "do not call blocking ops when + * !TASK_RUNNING;"-warning. + */ + set_current_state(TASK_RUNNING); + } +} + +static int __siox_start(struct siox_master *smaster) +{ + if (!(smaster->setbuf_len + smaster->getbuf_len)) + return -ENODEV; + + if (!smaster->buf) + return -ENOMEM; + + if (smaster->active) + return 0; + + smaster->active = 1; + wake_up_process(smaster->poll_thread); + + return 1; +} + +static int siox_start(struct siox_master *smaster) +{ + int ret; + + siox_master_lock(smaster); + ret = __siox_start(smaster); + siox_master_unlock(smaster); + + return ret; +} + +static int __siox_stop(struct siox_master *smaster) +{ + if (smaster->active) { + struct siox_device *sdevice; + + smaster->active = 0; + + list_for_each_entry(sdevice, &smaster->devices, node) { + if (sdevice->connected) + sysfs_notify_dirent(sdevice->connected_kn); + sdevice->connected = false; + } + + return 1; + } + return 0; +} + +static int siox_stop(struct siox_master *smaster) +{ + int ret; + + siox_master_lock(smaster); + ret = __siox_stop(smaster); + siox_master_unlock(smaster); + + return ret; +} + +static ssize_t type_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct siox_device *sdev = to_siox_device(dev); + + return sprintf(buf, "%s\n", sdev->type); +} + +static DEVICE_ATTR_RO(type); + +static ssize_t inbytes_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct siox_device *sdev = to_siox_device(dev); + + return sprintf(buf, "%zu\n", sdev->inbytes); +} + +static DEVICE_ATTR_RO(inbytes); + +static ssize_t outbytes_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct siox_device *sdev = to_siox_device(dev); + + return sprintf(buf, "%zu\n", sdev->outbytes); +} + +static DEVICE_ATTR_RO(outbytes); + +static ssize_t status_errors_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct siox_device *sdev = to_siox_device(dev); + unsigned int status_errors; + + siox_master_lock(sdev->smaster); + + status_errors = sdev->status_errors; + + siox_master_unlock(sdev->smaster); + + return sprintf(buf, "%u\n", status_errors); +} + +static DEVICE_ATTR_RO(status_errors); + +static ssize_t connected_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct siox_device *sdev = to_siox_device(dev); + bool connected; + + siox_master_lock(sdev->smaster); + + connected = sdev->connected; + + siox_master_unlock(sdev->smaster); + + return sprintf(buf, "%u\n", connected); +} + +static DEVICE_ATTR_RO(connected); + +static ssize_t watchdog_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct siox_device *sdev = to_siox_device(dev); + u8 status; + + siox_master_lock(sdev->smaster); + + status = sdev->status_read_clean; + + siox_master_unlock(sdev->smaster); + + return sprintf(buf, "%d\n", status & SIOX_STATUS_WDG); +} + +static DEVICE_ATTR_RO(watchdog); + +static ssize_t watchdog_errors_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct siox_device *sdev = to_siox_device(dev); + unsigned int watchdog_errors; + + siox_master_lock(sdev->smaster); + + watchdog_errors = sdev->watchdog_errors; + + siox_master_unlock(sdev->smaster); + + return sprintf(buf, "%u\n", watchdog_errors); +} + +static DEVICE_ATTR_RO(watchdog_errors); + +static struct attribute *siox_device_attrs[] = { + &dev_attr_type.attr, + &dev_attr_inbytes.attr, + &dev_attr_outbytes.attr, + &dev_attr_status_errors.attr, + &dev_attr_connected.attr, + &dev_attr_watchdog.attr, + &dev_attr_watchdog_errors.attr, + NULL +}; +ATTRIBUTE_GROUPS(siox_device); + +static void siox_device_release(struct device *dev) +{ + struct siox_device *sdevice = to_siox_device(dev); + + kfree(sdevice); +} + +static struct device_type siox_device_type = { + .groups = siox_device_groups, + .release = siox_device_release, +}; + +static int siox_match(struct device *dev, struct device_driver *drv) +{ + if (dev->type != &siox_device_type) + return 0; + + /* up to now there is only a single driver so keeping this simple */ + return 1; +} + +static struct bus_type siox_bus_type = { + .name = "siox", + .match = siox_match, +}; + +static int siox_driver_probe(struct device *dev) +{ + struct siox_driver *sdriver = to_siox_driver(dev->driver); + struct siox_device *sdevice = to_siox_device(dev); + int ret; + + ret = sdriver->probe(sdevice); + return ret; +} + +static int siox_driver_remove(struct device *dev) +{ + struct siox_driver *sdriver = + container_of(dev->driver, struct siox_driver, driver); + struct siox_device *sdevice = to_siox_device(dev); + int ret; + + ret = sdriver->remove(sdevice); + return ret; +} + +static void siox_driver_shutdown(struct device *dev) +{ + struct siox_driver *sdriver = + container_of(dev->driver, struct siox_driver, driver); + struct siox_device *sdevice = to_siox_device(dev); + + sdriver->shutdown(sdevice); +} + +static ssize_t active_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct siox_master *smaster = to_siox_master(dev); + + return sprintf(buf, "%d\n", smaster->active); +} + +static ssize_t active_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct siox_master *smaster = to_siox_master(dev); + int ret; + int active; + + ret = kstrtoint(buf, 0, &active); + if (ret < 0) + return ret; + + if (active) + ret = siox_start(smaster); + else + ret = siox_stop(smaster); + + if (ret < 0) + return ret; + + return count; +} + +static DEVICE_ATTR_RW(active); + +static struct siox_device *siox_device_add(struct siox_master *smaster, + const char *type, size_t inbytes, + size_t outbytes, u8 statustype); + +static ssize_t device_add_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct siox_master *smaster = to_siox_master(dev); + int ret; + char type[20] = ""; + size_t inbytes = 0, outbytes = 0; + u8 statustype = 0; + + ret = sscanf(buf, "%20s %zu %zu %hhu", type, &inbytes, + &outbytes, &statustype); + if (ret != 3 && ret != 4) + return -EINVAL; + + if (strcmp(type, "siox-12x8") || inbytes != 2 || outbytes != 4) + return -EINVAL; + + siox_device_add(smaster, "siox-12x8", inbytes, outbytes, statustype); + + return count; +} + +static DEVICE_ATTR_WO(device_add); + +static void siox_device_remove(struct siox_master *smaster); + +static ssize_t device_remove_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct siox_master *smaster = to_siox_master(dev); + + /* XXX? require to write */ + siox_device_remove(smaster); + + return count; +} + +static DEVICE_ATTR_WO(device_remove); + +static ssize_t poll_interval_ns_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct siox_master *smaster = to_siox_master(dev); + + return sprintf(buf, "%lld\n", jiffies_to_nsecs(smaster->poll_interval)); +} + +static ssize_t poll_interval_ns_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct siox_master *smaster = to_siox_master(dev); + int ret; + u64 val; + + ret = kstrtou64(buf, 0, &val); + if (ret < 0) + return ret; + + siox_master_lock(smaster); + + smaster->poll_interval = nsecs_to_jiffies(val); + + siox_master_unlock(smaster); + + return count; +} + +static DEVICE_ATTR_RW(poll_interval_ns); + +static struct attribute *siox_master_attrs[] = { + &dev_attr_active.attr, + &dev_attr_device_add.attr, + &dev_attr_device_remove.attr, + &dev_attr_poll_interval_ns.attr, + NULL +}; +ATTRIBUTE_GROUPS(siox_master); + +static void siox_master_release(struct device *dev) +{ + struct siox_master *smaster = to_siox_master(dev); + + kfree(smaster); +} + +static struct device_type siox_master_type = { + .groups = siox_master_groups, + .release = siox_master_release, +}; + +struct siox_master *siox_master_alloc(struct device *dev, + size_t size) +{ + struct siox_master *smaster; + + if (!dev) + return NULL; + + smaster = kzalloc(sizeof(*smaster) + size, GFP_KERNEL); + if (!smaster) + return NULL; + + device_initialize(&smaster->dev); + + smaster->busno = -1; + smaster->dev.bus = &siox_bus_type; + smaster->dev.type = &siox_master_type; + smaster->dev.parent = dev; + smaster->poll_interval = DIV_ROUND_UP(HZ, 40); + + dev_set_drvdata(&smaster->dev, &smaster[1]); + + return smaster; +} +EXPORT_SYMBOL_GPL(siox_master_alloc); + +int siox_master_register(struct siox_master *smaster) +{ + int ret; + + if (!siox_is_registered) + return -EPROBE_DEFER; + + if (!smaster->pushpull) + return -EINVAL; + + dev_set_name(&smaster->dev, "siox-%d", smaster->busno); + + smaster->last_poll = jiffies; + smaster->poll_thread = kthread_create(siox_poll_thread, smaster, + "siox-%d", smaster->busno); + if (IS_ERR(smaster->poll_thread)) { + smaster->active = 0; + return PTR_ERR(smaster->poll_thread); + } + + mutex_init(&smaster->lock); + INIT_LIST_HEAD(&smaster->devices); + + ret = device_add(&smaster->dev); + if (ret) + kthread_stop(smaster->poll_thread); + + return ret; +} +EXPORT_SYMBOL_GPL(siox_master_register); + +void siox_master_unregister(struct siox_master *smaster) +{ + /* remove device */ + device_del(&smaster->dev); + + siox_master_lock(smaster); + + __siox_stop(smaster); + + while (smaster->num_devices) { + struct siox_device *sdevice; + + sdevice = container_of(smaster->devices.prev, + struct siox_device, node); + list_del(&sdevice->node); + smaster->num_devices--; + + siox_master_unlock(smaster); + + device_unregister(&sdevice->dev); + + siox_master_lock(smaster); + } + + siox_master_unlock(smaster); + + put_device(&smaster->dev); +} +EXPORT_SYMBOL_GPL(siox_master_unregister); + +static struct siox_device *siox_device_add(struct siox_master *smaster, + const char *type, size_t inbytes, + size_t outbytes, u8 statustype) +{ + struct siox_device *sdevice; + int ret; + size_t buf_len; + + sdevice = kzalloc(sizeof(*sdevice), GFP_KERNEL); + if (!sdevice) + return ERR_PTR(-ENOMEM); + + sdevice->type = type; + sdevice->inbytes = inbytes; + sdevice->outbytes = outbytes; + sdevice->statustype = statustype; + + sdevice->smaster = smaster; + sdevice->dev.parent = &smaster->dev; + sdevice->dev.bus = &siox_bus_type; + sdevice->dev.type = &siox_device_type; + + siox_master_lock(smaster); + + dev_set_name(&sdevice->dev, "siox-%d-%d", + smaster->busno, smaster->num_devices); + + buf_len = smaster->setbuf_len + inbytes + + smaster->getbuf_len + outbytes; + if (smaster->buf_len < buf_len) { + u8 *buf = krealloc(smaster->buf, buf_len, GFP_KERNEL); + + if (!buf) { + dev_err(&smaster->dev, + "failed to realloc buffer to %zu\n", buf_len); + ret = -ENOMEM; + goto err_buf_alloc; + } + + smaster->buf_len = buf_len; + smaster->buf = buf; + } + + ret = device_register(&sdevice->dev); + if (ret) { + dev_err(&smaster->dev, "failed to register device: %d\n", ret); + + goto err_device_register; + } + + smaster->num_devices++; + list_add_tail(&sdevice->node, &smaster->devices); + + smaster->setbuf_len += sdevice->inbytes; + smaster->getbuf_len += sdevice->outbytes; + + sdevice->status_errors_kn = sysfs_get_dirent(sdevice->dev.kobj.sd, + "status_errors"); + sdevice->watchdog_kn = sysfs_get_dirent(sdevice->dev.kobj.sd, + "watchdog"); + sdevice->watchdog_errors_kn = sysfs_get_dirent(sdevice->dev.kobj.sd, + "watchdog_errors"); + sdevice->connected_kn = sysfs_get_dirent(sdevice->dev.kobj.sd, + "connected"); + + siox_master_unlock(smaster); + + return sdevice; + +err_device_register: + /* don't care to make the buffer smaller again */ + +err_buf_alloc: + siox_master_unlock(smaster); + + kfree(sdevice); + + return ERR_PTR(ret); +} + +static void siox_device_remove(struct siox_master *smaster) +{ + struct siox_device *sdevice; + + siox_master_lock(smaster); + + if (!smaster->num_devices) { + siox_master_unlock(smaster); + return; + } + + sdevice = container_of(smaster->devices.prev, struct siox_device, node); + list_del(&sdevice->node); + smaster->num_devices--; + + smaster->setbuf_len -= sdevice->inbytes; + smaster->getbuf_len -= sdevice->outbytes; + + if (!smaster->num_devices) + __siox_stop(smaster); + + siox_master_unlock(smaster); + + /* + * This must be done without holding the master lock because we're + * called from device_remove_store which also holds a sysfs mutex. + * device_unregister tries to aquire the same lock. + */ + device_unregister(&sdevice->dev); +} + +int __siox_driver_register(struct siox_driver *sdriver, struct module *owner) +{ + int ret; + + if (unlikely(!siox_is_registered)) + return -EPROBE_DEFER; + + if (!sdriver->set_data && !sdriver->get_data) { + pr_err("Driver %s doesn't provide needed callbacks\n", + sdriver->driver.name); + return -EINVAL; + } + + sdriver->driver.owner = owner; + sdriver->driver.bus = &siox_bus_type; + + if (sdriver->probe) + sdriver->driver.probe = siox_driver_probe; + if (sdriver->remove) + sdriver->driver.remove = siox_driver_remove; + if (sdriver->shutdown) + sdriver->driver.shutdown = siox_driver_shutdown; + + ret = driver_register(&sdriver->driver); + if (ret) + pr_err("Failed to register siox driver %s (%d)\n", + sdriver->driver.name, ret); + + return ret; +} +EXPORT_SYMBOL_GPL(__siox_driver_register); + +static int __init siox_init(void) +{ + int ret; + + ret = bus_register(&siox_bus_type); + if (ret) { + pr_err("Registration of SIOX bus type failed: %d\n", ret); + return ret; + } + + siox_is_registered = true; + + return 0; +} +subsys_initcall(siox_init); + +static void __exit siox_exit(void) +{ + bus_unregister(&siox_bus_type); +} +module_exit(siox_exit); + +MODULE_AUTHOR("Uwe Kleine-Koenig "); +MODULE_DESCRIPTION("Eckelmann SIOX driver core"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/siox/siox.h b/drivers/siox/siox.h new file mode 100644 index 000000000000..c674bf6fb119 --- /dev/null +++ b/drivers/siox/siox.h @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2015-2017 Pengutronix, Uwe Kleine-König + */ +#include +#include +#include + +#define to_siox_master(_dev) container_of((_dev), struct siox_master, dev) +struct siox_master { + /* these fields should be initialized by the driver */ + int busno; + int (*pushpull)(struct siox_master *smaster, + size_t setbuf_len, const u8 setbuf[], + size_t getbuf_len, u8 getbuf[]); + + /* might be initialized by the driver, if 0 it is set to HZ / 40 */ + unsigned long poll_interval; /* in jiffies */ + + /* framework private stuff */ + struct mutex lock; + bool active; + struct module *owner; + struct device dev; + unsigned int num_devices; + struct list_head devices; + + size_t setbuf_len, getbuf_len; + size_t buf_len; + u8 *buf; + u8 status; + + unsigned long last_poll; + struct task_struct *poll_thread; +}; + +static inline void *siox_master_get_devdata(struct siox_master *smaster) +{ + return dev_get_drvdata(&smaster->dev); +} + +struct siox_master *siox_master_alloc(struct device *dev, size_t size); +static inline void siox_master_put(struct siox_master *smaster) +{ + put_device(&smaster->dev); +} + +int siox_master_register(struct siox_master *smaster); +void siox_master_unregister(struct siox_master *smaster); diff --git a/include/linux/siox.h b/include/linux/siox.h new file mode 100644 index 000000000000..d79624e83134 --- /dev/null +++ b/include/linux/siox.h @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2015 Pengutronix, Uwe Kleine-König + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License version 2 as published by the + * Free Software Foundation. + */ + +#include + +#define to_siox_device(_dev) container_of((_dev), struct siox_device, dev) +struct siox_device { + struct list_head node; /* node in smaster->devices */ + struct siox_master *smaster; + struct device dev; + + const char *type; + size_t inbytes; + size_t outbytes; + u8 statustype; + + u8 status_read_clean; + u8 status_written; + u8 status_written_lastcycle; + bool connected; + + /* statistics */ + unsigned int watchdog_errors; + unsigned int status_errors; + + struct kernfs_node *status_errors_kn; + struct kernfs_node *watchdog_kn; + struct kernfs_node *watchdog_errors_kn; + struct kernfs_node *connected_kn; +}; + +bool siox_device_synced(struct siox_device *sdevice); +bool siox_device_connected(struct siox_device *sdevice); + +struct siox_driver { + int (*probe)(struct siox_device *sdevice); + int (*remove)(struct siox_device *sdevice); + void (*shutdown)(struct siox_device *sdevice); + + /* + * buf is big enough to hold sdev->inbytes - 1 bytes, the status byte + * is in the scope of the framework. + */ + int (*set_data)(struct siox_device *sdevice, u8 status, u8 buf[]); + /* + * buf is big enough to hold sdev->outbytes - 1 bytes, the status byte + * is in the scope of the framework + */ + int (*get_data)(struct siox_device *sdevice, const u8 buf[]); + + struct device_driver driver; +}; + +static inline struct siox_driver *to_siox_driver(struct device_driver *driver) +{ + if (driver) + return container_of(driver, struct siox_driver, driver); + else + return NULL; +} + +int __siox_driver_register(struct siox_driver *sdriver, struct module *owner); + +static inline int siox_driver_register(struct siox_driver *sdriver) +{ + return __siox_driver_register(sdriver, THIS_MODULE); +} + +static inline void siox_driver_unregister(struct siox_driver *sdriver) +{ + return driver_unregister(&sdriver->driver); +} -- cgit v1.2.3-59-g8ed1b From 064cbc4f6a1e910771e6c29cb819b6ee9eb881fc Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Thu, 30 Nov 2017 14:32:39 +0100 Subject: platform/x86: samsung-laptop: Grammar s/are can/can/ Signed-off-by: Geert Uytterhoeven Signed-off-by: Andy Shevchenko --- Documentation/ABI/testing/sysfs-driver-samsung-laptop | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Documentation/ABI/testing') diff --git a/Documentation/ABI/testing/sysfs-driver-samsung-laptop b/Documentation/ABI/testing/sysfs-driver-samsung-laptop index 63c1ad0212fc..34d3a3359cf4 100644 --- a/Documentation/ABI/testing/sysfs-driver-samsung-laptop +++ b/Documentation/ABI/testing/sysfs-driver-samsung-laptop @@ -3,7 +3,7 @@ Date: January 1, 2010 KernelVersion: 2.6.33 Contact: Greg Kroah-Hartman Description: Some Samsung laptops have different "performance levels" - that are can be modified by a function key, and by this + that can be modified by a function key, and by this sysfs file. These values don't always make a whole lot of sense, but some users like to modify them to keep their fans quiet at all costs. Reading from this file -- cgit v1.2.3-59-g8ed1b From f6df8f234e2502b7d8c6de42e066e01f908318cc Mon Sep 17 00:00:00 2001 From: Sheng Yong Date: Wed, 22 Nov 2017 18:23:38 +0800 Subject: f2fs: introduce sysfs readdir_ra to readahead inode block in readdir This patch introduces a sysfs interface readdir_ra to enable/disable readaheading inode block in f2fs_readdir. When readdir_ra is enabled, it improves the performance of "readdir + stat". For 300,000 files: time find /data/test > /dev/null disable readdir_ra: 1m25.69s real 0m01.94s user 0m50.80s system enable readdir_ra: 0m18.55s real 0m00.44s user 0m15.39s system Signed-off-by: Sheng Yong Reviewed-by: Chao Yu Signed-off-by: Jaegeuk Kim --- Documentation/ABI/testing/sysfs-fs-f2fs | 6 ++++++ fs/f2fs/dir.c | 4 ++++ fs/f2fs/f2fs.h | 1 + fs/f2fs/sysfs.c | 2 ++ 4 files changed, 13 insertions(+) (limited to 'Documentation/ABI/testing') diff --git a/Documentation/ABI/testing/sysfs-fs-f2fs b/Documentation/ABI/testing/sysfs-fs-f2fs index a7799c2fca28..d870b5514d15 100644 --- a/Documentation/ABI/testing/sysfs-fs-f2fs +++ b/Documentation/ABI/testing/sysfs-fs-f2fs @@ -186,3 +186,9 @@ Date: August 2017 Contact: "Jaegeuk Kim" Description: Controls sleep time of GC urgent mode + +What: /sys/fs/f2fs//readdir_ra +Date: November 2017 +Contact: "Sheng Yong" +Description: + Controls readahead inode block in readdir. diff --git a/fs/f2fs/dir.c b/fs/f2fs/dir.c index 2d98d877c09d..724304dc6143 100644 --- a/fs/f2fs/dir.c +++ b/fs/f2fs/dir.c @@ -798,6 +798,7 @@ int f2fs_fill_dentries(struct dir_context *ctx, struct f2fs_dentry_ptr *d, unsigned int bit_pos; struct f2fs_dir_entry *de = NULL; struct fscrypt_str de_name = FSTR_INIT(NULL, 0); + struct f2fs_sb_info *sbi = F2FS_I_SB(d->inode); bit_pos = ((unsigned long)ctx->pos % d->max); @@ -836,6 +837,9 @@ int f2fs_fill_dentries(struct dir_context *ctx, struct f2fs_dentry_ptr *d, le32_to_cpu(de->ino), d_type)) return 1; + if (sbi->readdir_ra == 1) + ra_node_page(sbi, le32_to_cpu(de->ino)); + bit_pos += GET_DENTRY_SLOTS(le16_to_cpu(de->name_len)); ctx->pos = start_pos + bit_pos; } diff --git a/fs/f2fs/f2fs.h b/fs/f2fs/f2fs.h index ef68433a2363..7cb79fbebb63 100644 --- a/fs/f2fs/f2fs.h +++ b/fs/f2fs/f2fs.h @@ -1093,6 +1093,7 @@ struct f2fs_sb_info { int dir_level; /* directory level */ int inline_xattr_size; /* inline xattr size */ unsigned int trigger_ssr_threshold; /* threshold to trigger ssr */ + int readdir_ra; /* readahead inode in readdir */ block_t user_block_count; /* # of user blocks */ block_t total_valid_block_count; /* # of valid blocks */ diff --git a/fs/f2fs/sysfs.c b/fs/f2fs/sysfs.c index 9835348b6e5d..93c3364250dd 100644 --- a/fs/f2fs/sysfs.c +++ b/fs/f2fs/sysfs.c @@ -299,6 +299,7 @@ F2FS_RW_ATTR(F2FS_SBI, f2fs_sb_info, dir_level, dir_level); F2FS_RW_ATTR(F2FS_SBI, f2fs_sb_info, cp_interval, interval_time[CP_TIME]); F2FS_RW_ATTR(F2FS_SBI, f2fs_sb_info, idle_interval, interval_time[REQ_TIME]); F2FS_RW_ATTR(F2FS_SBI, f2fs_sb_info, iostat_enable, iostat_enable); +F2FS_RW_ATTR(F2FS_SBI, f2fs_sb_info, readdir_ra, readdir_ra); #ifdef CONFIG_F2FS_FAULT_INJECTION F2FS_RW_ATTR(FAULT_INFO_RATE, f2fs_fault_info, inject_rate, inject_rate); F2FS_RW_ATTR(FAULT_INFO_TYPE, f2fs_fault_info, inject_type, inject_type); @@ -346,6 +347,7 @@ static struct attribute *f2fs_attrs[] = { ATTR_LIST(cp_interval), ATTR_LIST(idle_interval), ATTR_LIST(iostat_enable), + ATTR_LIST(readdir_ra), #ifdef CONFIG_F2FS_FAULT_INJECTION ATTR_LIST(inject_rate), ATTR_LIST(inject_type), -- cgit v1.2.3-59-g8ed1b From 87590ce6e373d1a5401f6539f0c59ef92dd924a9 Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Sun, 7 Jan 2018 22:48:00 +0100 Subject: sysfs/cpu: Add vulnerability folder As the meltdown/spectre problem affects several CPU architectures, it makes sense to have common way to express whether a system is affected by a particular vulnerability or not. If affected the way to express the mitigation should be common as well. Create /sys/devices/system/cpu/vulnerabilities folder and files for meltdown, spectre_v1 and spectre_v2. Allow architectures to override the show function. Signed-off-by: Thomas Gleixner Reviewed-by: Greg Kroah-Hartman Reviewed-by: Konrad Rzeszutek Wilk Cc: Peter Zijlstra Cc: Will Deacon Cc: Dave Hansen Cc: Linus Torvalds Cc: Borislav Petkov Cc: David Woodhouse Link: https://lkml.kernel.org/r/20180107214913.096657732@linutronix.de --- Documentation/ABI/testing/sysfs-devices-system-cpu | 16 ++++++++ drivers/base/Kconfig | 3 ++ drivers/base/cpu.c | 48 ++++++++++++++++++++++ include/linux/cpu.h | 7 ++++ 4 files changed, 74 insertions(+) (limited to 'Documentation/ABI/testing') diff --git a/Documentation/ABI/testing/sysfs-devices-system-cpu b/Documentation/ABI/testing/sysfs-devices-system-cpu index f3d5817c4ef0..bd3a88e16d8b 100644 --- a/Documentation/ABI/testing/sysfs-devices-system-cpu +++ b/Documentation/ABI/testing/sysfs-devices-system-cpu @@ -373,3 +373,19 @@ Contact: Linux kernel mailing list Description: information about CPUs heterogeneity. cpu_capacity: capacity of cpu#. + +What: /sys/devices/system/cpu/vulnerabilities + /sys/devices/system/cpu/vulnerabilities/meltdown + /sys/devices/system/cpu/vulnerabilities/spectre_v1 + /sys/devices/system/cpu/vulnerabilities/spectre_v2 +Date: Januar 2018 +Contact: Linux kernel mailing list +Description: Information about CPU vulnerabilities + + The files are named after the code names of CPU + vulnerabilities. The output of those files reflects the + state of the CPUs in the system. Possible output values: + + "Not affected" CPU is not affected by the vulnerability + "Vulnerable" CPU is affected and no mitigation in effect + "Mitigation: $M" CPU is affetcted and mitigation $M is in effect diff --git a/drivers/base/Kconfig b/drivers/base/Kconfig index 2f6614c9a229..37a71fd9043f 100644 --- a/drivers/base/Kconfig +++ b/drivers/base/Kconfig @@ -235,6 +235,9 @@ config GENERIC_CPU_DEVICES config GENERIC_CPU_AUTOPROBE bool +config GENERIC_CPU_VULNERABILITIES + bool + config SOC_BUS bool select GLOB diff --git a/drivers/base/cpu.c b/drivers/base/cpu.c index 321cd7b4d817..825964efda1d 100644 --- a/drivers/base/cpu.c +++ b/drivers/base/cpu.c @@ -501,10 +501,58 @@ static void __init cpu_dev_register_generic(void) #endif } +#ifdef CONFIG_GENERIC_CPU_VULNERABILITIES + +ssize_t __weak cpu_show_meltdown(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "Not affected\n"); +} + +ssize_t __weak cpu_show_spectre_v1(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "Not affected\n"); +} + +ssize_t __weak cpu_show_spectre_v2(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "Not affected\n"); +} + +static DEVICE_ATTR(meltdown, 0444, cpu_show_meltdown, NULL); +static DEVICE_ATTR(spectre_v1, 0444, cpu_show_spectre_v1, NULL); +static DEVICE_ATTR(spectre_v2, 0444, cpu_show_spectre_v2, NULL); + +static struct attribute *cpu_root_vulnerabilities_attrs[] = { + &dev_attr_meltdown.attr, + &dev_attr_spectre_v1.attr, + &dev_attr_spectre_v2.attr, + NULL +}; + +static const struct attribute_group cpu_root_vulnerabilities_group = { + .name = "vulnerabilities", + .attrs = cpu_root_vulnerabilities_attrs, +}; + +static void __init cpu_register_vulnerabilities(void) +{ + if (sysfs_create_group(&cpu_subsys.dev_root->kobj, + &cpu_root_vulnerabilities_group)) + pr_err("Unable to register CPU vulnerabilities\n"); +} + +#else +static inline void cpu_register_vulnerabilities(void) { } +#endif + void __init cpu_dev_init(void) { if (subsys_system_register(&cpu_subsys, cpu_root_attr_groups)) panic("Failed to register CPU subsystem"); cpu_dev_register_generic(); + cpu_register_vulnerabilities(); } diff --git a/include/linux/cpu.h b/include/linux/cpu.h index 938ea8ae0ba4..c816e6f2730c 100644 --- a/include/linux/cpu.h +++ b/include/linux/cpu.h @@ -47,6 +47,13 @@ extern void cpu_remove_dev_attr(struct device_attribute *attr); extern int cpu_add_dev_attr_group(struct attribute_group *attrs); extern void cpu_remove_dev_attr_group(struct attribute_group *attrs); +extern ssize_t cpu_show_meltdown(struct device *dev, + struct device_attribute *attr, char *buf); +extern ssize_t cpu_show_spectre_v1(struct device *dev, + struct device_attribute *attr, char *buf); +extern ssize_t cpu_show_spectre_v2(struct device *dev, + struct device_attribute *attr, char *buf); + extern __printf(4, 5) struct device *cpu_device_create(struct device *parent, void *drvdata, const struct attribute_group **groups, -- cgit v1.2.3-59-g8ed1b From 350f6c75f6933d5d58548d68f9336543c56b4a02 Mon Sep 17 00:00:00 2001 From: Matt Fornero Date: Wed, 6 Dec 2017 14:43:30 -0500 Subject: iio: buffer: Expose data available Add a sysfs attribute that exposes buffer data available to userspace. This attribute can be checked at runtime to determine the overall buffer fill level (across all allocated buffers). Signed-off-by: Matt Fornero Signed-off-by: Jonathan Cameron Signed-off-by: Greg Kroah-Hartman --- Documentation/ABI/testing/sysfs-bus-iio | 10 ++++++++++ drivers/iio/industrialio-buffer.c | 15 +++++++++++++++ 2 files changed, 25 insertions(+) (limited to 'Documentation/ABI/testing') diff --git a/Documentation/ABI/testing/sysfs-bus-iio b/Documentation/ABI/testing/sysfs-bus-iio index a478740e2783..9cc0ea1c0273 100644 --- a/Documentation/ABI/testing/sysfs-bus-iio +++ b/Documentation/ABI/testing/sysfs-bus-iio @@ -1413,6 +1413,16 @@ Description: the available samples after the timeout expires and thus have a maximum delay guarantee. +What: /sys/bus/iio/devices/iio:deviceX/buffer/data_available +KernelVersion: 4.16 +Contact: linux-iio@vger.kernel.org +Description: + A read-only value indicating the bytes of data available in the + buffer. In the case of an output buffer, this indicates the + amount of empty space available to write data to. In the case of + an input buffer, this indicates the amount of data available for + reading. + What: /sys/bus/iio/devices/iio:deviceX/buffer/hwfifo_enabled KernelVersion: 4.2 Contact: linux-iio@vger.kernel.org diff --git a/drivers/iio/industrialio-buffer.c b/drivers/iio/industrialio-buffer.c index d2b465140a6b..eda2a0f1658f 100644 --- a/drivers/iio/industrialio-buffer.c +++ b/drivers/iio/industrialio-buffer.c @@ -1198,6 +1198,18 @@ out: return ret ? ret : len; } +static ssize_t iio_dma_show_data_available(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + size_t bytes; + + bytes = iio_buffer_data_available(indio_dev->buffer); + + return sprintf(buf, "%zu\n", bytes); +} + static DEVICE_ATTR(length, S_IRUGO | S_IWUSR, iio_buffer_read_length, iio_buffer_write_length); static struct device_attribute dev_attr_length_ro = __ATTR(length, @@ -1208,11 +1220,14 @@ static DEVICE_ATTR(watermark, S_IRUGO | S_IWUSR, iio_buffer_show_watermark, iio_buffer_store_watermark); static struct device_attribute dev_attr_watermark_ro = __ATTR(watermark, S_IRUGO, iio_buffer_show_watermark, NULL); +static DEVICE_ATTR(data_available, S_IRUGO, + iio_dma_show_data_available, NULL); static struct attribute *iio_buffer_attrs[] = { &dev_attr_length.attr, &dev_attr_enable.attr, &dev_attr_watermark.attr, + &dev_attr_data_available.attr, }; int iio_buffer_alloc_sysfs_and_mask(struct iio_dev *indio_dev) -- cgit v1.2.3-59-g8ed1b From b9a3589332c2a25fb7edad25a26fcaada3209126 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Tue, 5 Dec 2017 11:57:27 +0100 Subject: iio: ABI: Fix name of timestamp sysfs file The name of the file is "current_timetamp_clock" not "timestamp_clock". Fixes: bc2b7dab629a ("iio:core: timestamping clock selection support") Cc: Gregor Boirie Signed-off-by: Linus Walleij Signed-off-by: Jonathan Cameron Signed-off-by: Greg Kroah-Hartman --- Documentation/ABI/testing/sysfs-bus-iio | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Documentation/ABI/testing') diff --git a/Documentation/ABI/testing/sysfs-bus-iio b/Documentation/ABI/testing/sysfs-bus-iio index 9cc0ea1c0273..6a5f34b4d5b9 100644 --- a/Documentation/ABI/testing/sysfs-bus-iio +++ b/Documentation/ABI/testing/sysfs-bus-iio @@ -32,7 +32,7 @@ Description: Description of the physical chip / device for device X. Typically a part number. -What: /sys/bus/iio/devices/iio:deviceX/timestamp_clock +What: /sys/bus/iio/devices/iio:deviceX/current_timestamp_clock KernelVersion: 4.5 Contact: linux-iio@vger.kernel.org Description: -- cgit v1.2.3-59-g8ed1b From 06f502f57d0d7728f9fa0f157ec5e4111ddb98f6 Mon Sep 17 00:00:00 2001 From: Ben Whitten Date: Sun, 10 Dec 2017 21:17:55 +0000 Subject: leds: trigger: Introduce a NETDEV trigger This commit introduces a NETDEV trigger for named device activity. Available triggers are link, rx, and tx. Signed-off-by: Ben Whitten Acked-by: Pavel Machek Signed-off-by: Jacek Anaszewski --- .../ABI/testing/sysfs-class-led-trigger-netdev | 45 ++ drivers/leds/trigger/Kconfig | 7 + drivers/leds/trigger/Makefile | 1 + drivers/leds/trigger/ledtrig-netdev.c | 496 +++++++++++++++++++++ 4 files changed, 549 insertions(+) create mode 100644 Documentation/ABI/testing/sysfs-class-led-trigger-netdev create mode 100644 drivers/leds/trigger/ledtrig-netdev.c (limited to 'Documentation/ABI/testing') diff --git a/Documentation/ABI/testing/sysfs-class-led-trigger-netdev b/Documentation/ABI/testing/sysfs-class-led-trigger-netdev new file mode 100644 index 000000000000..451af6d6768c --- /dev/null +++ b/Documentation/ABI/testing/sysfs-class-led-trigger-netdev @@ -0,0 +1,45 @@ +What: /sys/class/leds//device_name +Date: Dec 2017 +KernelVersion: 4.16 +Contact: linux-leds@vger.kernel.org +Description: + Specifies the network device name to monitor. + +What: /sys/class/leds//interval +Date: Dec 2017 +KernelVersion: 4.16 +Contact: linux-leds@vger.kernel.org +Description: + Specifies the duration of the LED blink in milliseconds. + Defaults to 50 ms. + +What: /sys/class/leds//link +Date: Dec 2017 +KernelVersion: 4.16 +Contact: linux-leds@vger.kernel.org +Description: + Signal the link state of the named network device. + If set to 0 (default), the LED's normal state is off. + If set to 1, the LED's normal state reflects the link state + of the named network device. + Setting this value also immediately changes the LED state. + +What: /sys/class/leds//tx +Date: Dec 2017 +KernelVersion: 4.16 +Contact: linux-leds@vger.kernel.org +Description: + Signal transmission of data on the named network device. + If set to 0 (default), the LED will not blink on transmission. + If set to 1, the LED will blink for the milliseconds specified + in interval to signal transmission. + +What: /sys/class/leds//rx +Date: Dec 2017 +KernelVersion: 4.16 +Contact: linux-leds@vger.kernel.org +Description: + Signal reception of data on the named network device. + If set to 0 (default), the LED will not blink on reception. + If set to 1, the LED will blink for the milliseconds specified + in interval to signal reception. diff --git a/drivers/leds/trigger/Kconfig b/drivers/leds/trigger/Kconfig index 6dac48c457d1..a2559b4fdfff 100644 --- a/drivers/leds/trigger/Kconfig +++ b/drivers/leds/trigger/Kconfig @@ -135,4 +135,11 @@ config LEDS_TRIGGER_PANIC a different trigger. If unsure, say Y. +config LEDS_TRIGGER_NETDEV + tristate "LED Netdev Trigger" + depends on NET && LEDS_TRIGGERS + help + This allows LEDs to be controlled by network device activity. + If unsure, say Y. + endif # LEDS_TRIGGERS diff --git a/drivers/leds/trigger/Makefile b/drivers/leds/trigger/Makefile index 4a8b6cff7761..f3cfe1950538 100644 --- a/drivers/leds/trigger/Makefile +++ b/drivers/leds/trigger/Makefile @@ -12,3 +12,4 @@ obj-$(CONFIG_LEDS_TRIGGER_DEFAULT_ON) += ledtrig-default-on.o obj-$(CONFIG_LEDS_TRIGGER_TRANSIENT) += ledtrig-transient.o obj-$(CONFIG_LEDS_TRIGGER_CAMERA) += ledtrig-camera.o obj-$(CONFIG_LEDS_TRIGGER_PANIC) += ledtrig-panic.o +obj-$(CONFIG_LEDS_TRIGGER_NETDEV) += ledtrig-netdev.o diff --git a/drivers/leds/trigger/ledtrig-netdev.c b/drivers/leds/trigger/ledtrig-netdev.c new file mode 100644 index 000000000000..6df4781a6308 --- /dev/null +++ b/drivers/leds/trigger/ledtrig-netdev.c @@ -0,0 +1,496 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright 2017 Ben Whitten +// Copyright 2007 Oliver Jowett +// +// LED Kernel Netdev Trigger +// +// Toggles the LED to reflect the link and traffic state of a named net device +// +// Derived from ledtrig-timer.c which is: +// Copyright 2005-2006 Openedhand Ltd. +// Author: Richard Purdie + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../leds.h" + +/* + * Configurable sysfs attributes: + * + * device_name - network device name to monitor + * interval - duration of LED blink, in milliseconds + * link - LED's normal state reflects whether the link is up + * (has carrier) or not + * tx - LED blinks on transmitted data + * rx - LED blinks on receive data + * + */ + +struct led_netdev_data { + spinlock_t lock; + + struct delayed_work work; + struct notifier_block notifier; + + struct led_classdev *led_cdev; + struct net_device *net_dev; + + char device_name[IFNAMSIZ]; + atomic_t interval; + unsigned int last_activity; + + unsigned long mode; +#define NETDEV_LED_LINK 0 +#define NETDEV_LED_TX 1 +#define NETDEV_LED_RX 2 +#define NETDEV_LED_MODE_LINKUP 3 +}; + +enum netdev_led_attr { + NETDEV_ATTR_LINK, + NETDEV_ATTR_TX, + NETDEV_ATTR_RX +}; + +static void set_baseline_state(struct led_netdev_data *trigger_data) +{ + int current_brightness; + struct led_classdev *led_cdev = trigger_data->led_cdev; + + current_brightness = led_cdev->brightness; + if (current_brightness) + led_cdev->blink_brightness = current_brightness; + if (!led_cdev->blink_brightness) + led_cdev->blink_brightness = led_cdev->max_brightness; + + if (!test_bit(NETDEV_LED_MODE_LINKUP, &trigger_data->mode)) + led_set_brightness(led_cdev, LED_OFF); + else { + if (test_bit(NETDEV_LED_LINK, &trigger_data->mode)) + led_set_brightness(led_cdev, + led_cdev->blink_brightness); + else + led_set_brightness(led_cdev, LED_OFF); + + /* If we are looking for RX/TX start periodically + * checking stats + */ + if (test_bit(NETDEV_LED_TX, &trigger_data->mode) || + test_bit(NETDEV_LED_RX, &trigger_data->mode)) + schedule_delayed_work(&trigger_data->work, 0); + } +} + +static ssize_t device_name_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct led_netdev_data *trigger_data = led_cdev->trigger_data; + ssize_t len; + + spin_lock_bh(&trigger_data->lock); + len = sprintf(buf, "%s\n", trigger_data->device_name); + spin_unlock_bh(&trigger_data->lock); + + return len; +} + +static ssize_t device_name_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t size) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct led_netdev_data *trigger_data = led_cdev->trigger_data; + + if (size >= IFNAMSIZ) + return -EINVAL; + + cancel_delayed_work_sync(&trigger_data->work); + + spin_lock_bh(&trigger_data->lock); + + if (trigger_data->net_dev) { + dev_put(trigger_data->net_dev); + trigger_data->net_dev = NULL; + } + + strncpy(trigger_data->device_name, buf, size); + if (size > 0 && trigger_data->device_name[size - 1] == '\n') + trigger_data->device_name[size - 1] = 0; + + if (trigger_data->device_name[0] != 0) + trigger_data->net_dev = + dev_get_by_name(&init_net, trigger_data->device_name); + + clear_bit(NETDEV_LED_MODE_LINKUP, &trigger_data->mode); + if (trigger_data->net_dev != NULL) + if (netif_carrier_ok(trigger_data->net_dev)) + set_bit(NETDEV_LED_MODE_LINKUP, &trigger_data->mode); + + trigger_data->last_activity = 0; + + set_baseline_state(trigger_data); + spin_unlock_bh(&trigger_data->lock); + + return size; +} + +static DEVICE_ATTR_RW(device_name); + +static ssize_t netdev_led_attr_show(struct device *dev, char *buf, + enum netdev_led_attr attr) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct led_netdev_data *trigger_data = led_cdev->trigger_data; + int bit; + + switch (attr) { + case NETDEV_ATTR_LINK: + bit = NETDEV_LED_LINK; + break; + case NETDEV_ATTR_TX: + bit = NETDEV_LED_TX; + break; + case NETDEV_ATTR_RX: + bit = NETDEV_LED_RX; + break; + default: + return -EINVAL; + } + + return sprintf(buf, "%u\n", test_bit(bit, &trigger_data->mode)); +} + +static ssize_t netdev_led_attr_store(struct device *dev, const char *buf, + size_t size, enum netdev_led_attr attr) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct led_netdev_data *trigger_data = led_cdev->trigger_data; + unsigned long state; + int ret; + int bit; + + ret = kstrtoul(buf, 0, &state); + if (ret) + return ret; + + switch (attr) { + case NETDEV_ATTR_LINK: + bit = NETDEV_LED_LINK; + break; + case NETDEV_ATTR_TX: + bit = NETDEV_LED_TX; + break; + case NETDEV_ATTR_RX: + bit = NETDEV_LED_RX; + break; + default: + return -EINVAL; + } + + cancel_delayed_work_sync(&trigger_data->work); + + if (state) + set_bit(bit, &trigger_data->mode); + else + clear_bit(bit, &trigger_data->mode); + + set_baseline_state(trigger_data); + + return size; +} + +static ssize_t link_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return netdev_led_attr_show(dev, buf, NETDEV_ATTR_LINK); +} + +static ssize_t link_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + return netdev_led_attr_store(dev, buf, size, NETDEV_ATTR_LINK); +} + +static DEVICE_ATTR_RW(link); + +static ssize_t tx_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return netdev_led_attr_show(dev, buf, NETDEV_ATTR_TX); +} + +static ssize_t tx_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + return netdev_led_attr_store(dev, buf, size, NETDEV_ATTR_TX); +} + +static DEVICE_ATTR_RW(tx); + +static ssize_t rx_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return netdev_led_attr_show(dev, buf, NETDEV_ATTR_RX); +} + +static ssize_t rx_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + return netdev_led_attr_store(dev, buf, size, NETDEV_ATTR_RX); +} + +static DEVICE_ATTR_RW(rx); + +static ssize_t interval_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct led_netdev_data *trigger_data = led_cdev->trigger_data; + + return sprintf(buf, "%u\n", + jiffies_to_msecs(atomic_read(&trigger_data->interval))); +} + +static ssize_t interval_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t size) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct led_netdev_data *trigger_data = led_cdev->trigger_data; + unsigned long value; + int ret; + + ret = kstrtoul(buf, 0, &value); + if (ret) + return ret; + + /* impose some basic bounds on the timer interval */ + if (value >= 5 && value <= 10000) { + cancel_delayed_work_sync(&trigger_data->work); + + atomic_set(&trigger_data->interval, msecs_to_jiffies(value)); + set_baseline_state(trigger_data); /* resets timer */ + } + + return size; +} + +static DEVICE_ATTR_RW(interval); + +static int netdev_trig_notify(struct notifier_block *nb, + unsigned long evt, void *dv) +{ + struct net_device *dev = + netdev_notifier_info_to_dev((struct netdev_notifier_info *)dv); + struct led_netdev_data *trigger_data = container_of(nb, + struct + led_netdev_data, + notifier); + + if (evt != NETDEV_UP && evt != NETDEV_DOWN && evt != NETDEV_CHANGE + && evt != NETDEV_REGISTER && evt != NETDEV_UNREGISTER + && evt != NETDEV_CHANGENAME) + return NOTIFY_DONE; + + if (strcmp(dev->name, trigger_data->device_name)) + return NOTIFY_DONE; + + cancel_delayed_work_sync(&trigger_data->work); + + spin_lock_bh(&trigger_data->lock); + + clear_bit(NETDEV_LED_MODE_LINKUP, &trigger_data->mode); + switch (evt) { + case NETDEV_REGISTER: + if (trigger_data->net_dev) + dev_put(trigger_data->net_dev); + dev_hold(dev); + trigger_data->net_dev = dev; + break; + case NETDEV_CHANGENAME: + case NETDEV_UNREGISTER: + if (trigger_data->net_dev) { + dev_put(trigger_data->net_dev); + trigger_data->net_dev = NULL; + } + break; + case NETDEV_UP: + case NETDEV_CHANGE: + if (netif_carrier_ok(dev)) + set_bit(NETDEV_LED_MODE_LINKUP, &trigger_data->mode); + break; + } + + set_baseline_state(trigger_data); + + spin_unlock_bh(&trigger_data->lock); + + return NOTIFY_DONE; +} + +/* here's the real work! */ +static void netdev_trig_work(struct work_struct *work) +{ + struct led_netdev_data *trigger_data = container_of(work, + struct + led_netdev_data, + work.work); + struct rtnl_link_stats64 *dev_stats; + unsigned int new_activity; + struct rtnl_link_stats64 temp; + unsigned long interval; + int invert; + + /* If we dont have a device, insure we are off */ + if (!trigger_data->net_dev) { + led_set_brightness(trigger_data->led_cdev, LED_OFF); + return; + } + + /* If we are not looking for RX/TX then return */ + if (!test_bit(NETDEV_LED_TX, &trigger_data->mode) && + !test_bit(NETDEV_LED_RX, &trigger_data->mode)) + return; + + dev_stats = dev_get_stats(trigger_data->net_dev, &temp); + new_activity = + (test_bit(NETDEV_LED_TX, &trigger_data->mode) ? + dev_stats->tx_packets : 0) + + (test_bit(NETDEV_LED_RX, &trigger_data->mode) ? + dev_stats->rx_packets : 0); + + if (trigger_data->last_activity != new_activity) { + led_stop_software_blink(trigger_data->led_cdev); + + invert = test_bit(NETDEV_LED_LINK, &trigger_data->mode); + interval = jiffies_to_msecs( + atomic_read(&trigger_data->interval)); + /* base state is ON (link present) */ + led_blink_set_oneshot(trigger_data->led_cdev, + &interval, + &interval, + invert); + trigger_data->last_activity = new_activity; + } + + schedule_delayed_work(&trigger_data->work, + (atomic_read(&trigger_data->interval)*2)); +} + +static void netdev_trig_activate(struct led_classdev *led_cdev) +{ + struct led_netdev_data *trigger_data; + int rc; + + trigger_data = kzalloc(sizeof(struct led_netdev_data), GFP_KERNEL); + if (!trigger_data) + return; + + spin_lock_init(&trigger_data->lock); + + trigger_data->notifier.notifier_call = netdev_trig_notify; + trigger_data->notifier.priority = 10; + + INIT_DELAYED_WORK(&trigger_data->work, netdev_trig_work); + + trigger_data->led_cdev = led_cdev; + trigger_data->net_dev = NULL; + trigger_data->device_name[0] = 0; + + trigger_data->mode = 0; + atomic_set(&trigger_data->interval, msecs_to_jiffies(50)); + trigger_data->last_activity = 0; + + led_cdev->trigger_data = trigger_data; + + rc = device_create_file(led_cdev->dev, &dev_attr_device_name); + if (rc) + goto err_out; + rc = device_create_file(led_cdev->dev, &dev_attr_link); + if (rc) + goto err_out_device_name; + rc = device_create_file(led_cdev->dev, &dev_attr_rx); + if (rc) + goto err_out_link; + rc = device_create_file(led_cdev->dev, &dev_attr_tx); + if (rc) + goto err_out_rx; + rc = device_create_file(led_cdev->dev, &dev_attr_interval); + if (rc) + goto err_out_tx; + rc = register_netdevice_notifier(&trigger_data->notifier); + if (rc) + goto err_out_interval; + return; + +err_out_interval: + device_remove_file(led_cdev->dev, &dev_attr_interval); +err_out_tx: + device_remove_file(led_cdev->dev, &dev_attr_tx); +err_out_rx: + device_remove_file(led_cdev->dev, &dev_attr_rx); +err_out_link: + device_remove_file(led_cdev->dev, &dev_attr_link); +err_out_device_name: + device_remove_file(led_cdev->dev, &dev_attr_device_name); +err_out: + led_cdev->trigger_data = NULL; + kfree(trigger_data); +} + +static void netdev_trig_deactivate(struct led_classdev *led_cdev) +{ + struct led_netdev_data *trigger_data = led_cdev->trigger_data; + + if (trigger_data) { + unregister_netdevice_notifier(&trigger_data->notifier); + + device_remove_file(led_cdev->dev, &dev_attr_device_name); + device_remove_file(led_cdev->dev, &dev_attr_link); + device_remove_file(led_cdev->dev, &dev_attr_rx); + device_remove_file(led_cdev->dev, &dev_attr_tx); + device_remove_file(led_cdev->dev, &dev_attr_interval); + + cancel_delayed_work_sync(&trigger_data->work); + + if (trigger_data->net_dev) + dev_put(trigger_data->net_dev); + + kfree(trigger_data); + } +} + +static struct led_trigger netdev_led_trigger = { + .name = "netdev", + .activate = netdev_trig_activate, + .deactivate = netdev_trig_deactivate, +}; + +static int __init netdev_trig_init(void) +{ + return led_trigger_register(&netdev_led_trigger); +} + +static void __exit netdev_trig_exit(void) +{ + led_trigger_unregister(&netdev_led_trigger); +} + +module_init(netdev_trig_init); +module_exit(netdev_trig_exit); + +MODULE_AUTHOR("Ben Whitten "); +MODULE_AUTHOR("Oliver Jowett "); +MODULE_DESCRIPTION("Netdev LED trigger"); +MODULE_LICENSE("GPL v2"); -- cgit v1.2.3-59-g8ed1b From 9ecccfaa7cb5249bd31bdceb93fcf5bedb8a24d8 Mon Sep 17 00:00:00 2001 From: David Woodhouse Date: Tue, 9 Jan 2018 15:02:51 +0000 Subject: sysfs/cpu: Fix typos in vulnerability documentation Fixes: 87590ce6e ("sysfs/cpu: Add vulnerability folder") Signed-off-by: David Woodhouse Signed-off-by: Thomas Gleixner --- Documentation/ABI/testing/sysfs-devices-system-cpu | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'Documentation/ABI/testing') diff --git a/Documentation/ABI/testing/sysfs-devices-system-cpu b/Documentation/ABI/testing/sysfs-devices-system-cpu index bd3a88e16d8b..258902db14bf 100644 --- a/Documentation/ABI/testing/sysfs-devices-system-cpu +++ b/Documentation/ABI/testing/sysfs-devices-system-cpu @@ -378,7 +378,7 @@ What: /sys/devices/system/cpu/vulnerabilities /sys/devices/system/cpu/vulnerabilities/meltdown /sys/devices/system/cpu/vulnerabilities/spectre_v1 /sys/devices/system/cpu/vulnerabilities/spectre_v2 -Date: Januar 2018 +Date: January 2018 Contact: Linux kernel mailing list Description: Information about CPU vulnerabilities @@ -388,4 +388,4 @@ Description: Information about CPU vulnerabilities "Not affected" CPU is not affected by the vulnerability "Vulnerable" CPU is affected and no mitigation in effect - "Mitigation: $M" CPU is affetcted and mitigation $M is in effect + "Mitigation: $M" CPU is affected and mitigation $M is in effect -- cgit v1.2.3-59-g8ed1b From eca949800d2dd761bc0c73b793e4e9ad4a997469 Mon Sep 17 00:00:00 2001 From: Arnaud Pouliquen Date: Wed, 10 Jan 2018 11:13:12 +0100 Subject: IIO: ADC: add stm32 DFSDM support for PDM microphone This code offers a way to handle PDM audio microphones in ASOC framework. Audio driver should use consumer API. A specific management is implemented for DMA, with a callback, to allows to handle audio buffers efficiently. Signed-off-by: Arnaud Pouliquen Reviewed-by: Jonathan Cameron Signed-off-by: Mark Brown --- .../ABI/testing/sysfs-bus-iio-dfsdm-adc-stm32 | 16 + drivers/iio/adc/stm32-dfsdm-adc.c | 502 ++++++++++++++++++++- include/linux/iio/adc/stm32-dfsdm-adc.h | 18 + 3 files changed, 529 insertions(+), 7 deletions(-) create mode 100644 Documentation/ABI/testing/sysfs-bus-iio-dfsdm-adc-stm32 create mode 100644 include/linux/iio/adc/stm32-dfsdm-adc.h (limited to 'Documentation/ABI/testing') diff --git a/Documentation/ABI/testing/sysfs-bus-iio-dfsdm-adc-stm32 b/Documentation/ABI/testing/sysfs-bus-iio-dfsdm-adc-stm32 new file mode 100644 index 000000000000..da9822309f07 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-bus-iio-dfsdm-adc-stm32 @@ -0,0 +1,16 @@ +What: /sys/bus/iio/devices/iio:deviceX/in_voltage_spi_clk_freq +KernelVersion: 4.14 +Contact: arnaud.pouliquen@st.com +Description: + For audio purpose only. + Used by audio driver to set/get the spi input frequency. + This is mandatory if DFSDM is slave on SPI bus, to + provide information on the SPI clock frequency during runtime + Notice that the SPI frequency should be a multiple of sample + frequency to ensure the precision. + if DFSDM input is SPI master + Reading SPI clkout frequency, + error on writing + If DFSDM input is SPI Slave: + Reading returns value previously set. + Writing value before starting conversions. \ No newline at end of file diff --git a/drivers/iio/adc/stm32-dfsdm-adc.c b/drivers/iio/adc/stm32-dfsdm-adc.c index 68b5920e92cb..b03ca3f94331 100644 --- a/drivers/iio/adc/stm32-dfsdm-adc.c +++ b/drivers/iio/adc/stm32-dfsdm-adc.c @@ -6,19 +6,23 @@ * Author: Arnaud Pouliquen . */ +#include +#include #include #include #include #include #include #include -#include +#include #include #include #include #include "stm32-dfsdm.h" +#define DFSDM_DMA_BUFFER_SIZE (4 * PAGE_SIZE) + /* Conversion timeout */ #define DFSDM_TIMEOUT_US 100000 #define DFSDM_TIMEOUT (msecs_to_jiffies(DFSDM_TIMEOUT_US / 1000)) @@ -58,6 +62,18 @@ struct stm32_dfsdm_adc { struct completion completion; u32 *buffer; + /* Audio specific */ + unsigned int spi_freq; /* SPI bus clock frequency */ + unsigned int sample_freq; /* Sample frequency after filter decimation */ + int (*cb)(const void *data, size_t size, void *cb_priv); + void *cb_priv; + + /* DMA */ + u8 *rx_buf; + unsigned int bufi; /* Buffer current position */ + unsigned int buf_sz; /* Buffer size */ + struct dma_chan *dma_chan; + dma_addr_t dma_buf; }; struct stm32_dfsdm_str2field { @@ -351,10 +367,63 @@ int stm32_dfsdm_channel_parse_of(struct stm32_dfsdm *dfsdm, return 0; } +static ssize_t dfsdm_adc_audio_get_spiclk(struct iio_dev *indio_dev, + uintptr_t priv, + const struct iio_chan_spec *chan, + char *buf) +{ + struct stm32_dfsdm_adc *adc = iio_priv(indio_dev); + + return snprintf(buf, PAGE_SIZE, "%d\n", adc->spi_freq); +} + +static ssize_t dfsdm_adc_audio_set_spiclk(struct iio_dev *indio_dev, + uintptr_t priv, + const struct iio_chan_spec *chan, + const char *buf, size_t len) +{ + struct stm32_dfsdm_adc *adc = iio_priv(indio_dev); + struct stm32_dfsdm_filter *fl = &adc->dfsdm->fl_list[adc->fl_id]; + struct stm32_dfsdm_channel *ch = &adc->dfsdm->ch_list[adc->ch_id]; + unsigned int sample_freq = adc->sample_freq; + unsigned int spi_freq; + int ret; + + dev_err(&indio_dev->dev, "enter %s\n", __func__); + /* If DFSDM is master on SPI, SPI freq can not be updated */ + if (ch->src != DFSDM_CHANNEL_SPI_CLOCK_EXTERNAL) + return -EPERM; + + ret = kstrtoint(buf, 0, &spi_freq); + if (ret) + return ret; + + if (!spi_freq) + return -EINVAL; + + if (sample_freq) { + if (spi_freq % sample_freq) + dev_warn(&indio_dev->dev, + "Sampling rate not accurate (%d)\n", + spi_freq / (spi_freq / sample_freq)); + + ret = stm32_dfsdm_set_osrs(fl, 0, (spi_freq / sample_freq)); + if (ret < 0) { + dev_err(&indio_dev->dev, + "No filter parameters that match!\n"); + return ret; + } + } + adc->spi_freq = spi_freq; + + return len; +} + static int stm32_dfsdm_start_conv(struct stm32_dfsdm_adc *adc, bool dma) { struct regmap *regmap = adc->dfsdm->regmap; int ret; + unsigned int dma_en = 0, cont_en = 0; ret = stm32_dfsdm_start_channel(adc->dfsdm, adc->ch_id); if (ret < 0) @@ -365,6 +434,24 @@ static int stm32_dfsdm_start_conv(struct stm32_dfsdm_adc *adc, bool dma) if (ret < 0) goto stop_channels; + if (dma) { + /* Enable DMA transfer*/ + dma_en = DFSDM_CR1_RDMAEN(1); + /* Enable conversion triggered by SPI clock*/ + cont_en = DFSDM_CR1_RCONT(1); + } + /* Enable DMA transfer*/ + ret = regmap_update_bits(regmap, DFSDM_CR1(adc->fl_id), + DFSDM_CR1_RDMAEN_MASK, dma_en); + if (ret < 0) + goto stop_channels; + + /* Enable conversion triggered by SPI clock*/ + ret = regmap_update_bits(regmap, DFSDM_CR1(adc->fl_id), + DFSDM_CR1_RCONT_MASK, cont_en); + if (ret < 0) + goto stop_channels; + ret = stm32_dfsdm_start_filter(adc->dfsdm, adc->fl_id); if (ret < 0) goto stop_channels; @@ -398,6 +485,231 @@ static void stm32_dfsdm_stop_conv(struct stm32_dfsdm_adc *adc) stm32_dfsdm_stop_channel(adc->dfsdm, adc->ch_id); } +static int stm32_dfsdm_set_watermark(struct iio_dev *indio_dev, + unsigned int val) +{ + struct stm32_dfsdm_adc *adc = iio_priv(indio_dev); + unsigned int watermark = DFSDM_DMA_BUFFER_SIZE / 2; + + /* + * DMA cyclic transfers are used, buffer is split into two periods. + * There should be : + * - always one buffer (period) DMA is working on + * - one buffer (period) driver pushed to ASoC side. + */ + watermark = min(watermark, val * (unsigned int)(sizeof(u32))); + adc->buf_sz = watermark * 2; + + return 0; +} + +static unsigned int stm32_dfsdm_adc_dma_residue(struct stm32_dfsdm_adc *adc) +{ + struct dma_tx_state state; + enum dma_status status; + + status = dmaengine_tx_status(adc->dma_chan, + adc->dma_chan->cookie, + &state); + if (status == DMA_IN_PROGRESS) { + /* Residue is size in bytes from end of buffer */ + unsigned int i = adc->buf_sz - state.residue; + unsigned int size; + + /* Return available bytes */ + if (i >= adc->bufi) + size = i - adc->bufi; + else + size = adc->buf_sz + i - adc->bufi; + + return size; + } + + return 0; +} + +static void stm32_dfsdm_audio_dma_buffer_done(void *data) +{ + struct iio_dev *indio_dev = data; + struct stm32_dfsdm_adc *adc = iio_priv(indio_dev); + int available = stm32_dfsdm_adc_dma_residue(adc); + size_t old_pos; + + /* + * FIXME: In Kernel interface does not support cyclic DMA buffer,and + * offers only an interface to push data samples per samples. + * For this reason IIO buffer interface is not used and interface is + * bypassed using a private callback registered by ASoC. + * This should be a temporary solution waiting a cyclic DMA engine + * support in IIO. + */ + + dev_dbg(&indio_dev->dev, "%s: pos = %d, available = %d\n", __func__, + adc->bufi, available); + old_pos = adc->bufi; + + while (available >= indio_dev->scan_bytes) { + u32 *buffer = (u32 *)&adc->rx_buf[adc->bufi]; + + /* Mask 8 LSB that contains the channel ID */ + *buffer = (*buffer & 0xFFFFFF00) << 8; + available -= indio_dev->scan_bytes; + adc->bufi += indio_dev->scan_bytes; + if (adc->bufi >= adc->buf_sz) { + if (adc->cb) + adc->cb(&adc->rx_buf[old_pos], + adc->buf_sz - old_pos, adc->cb_priv); + adc->bufi = 0; + old_pos = 0; + } + } + if (adc->cb) + adc->cb(&adc->rx_buf[old_pos], adc->bufi - old_pos, + adc->cb_priv); +} + +static int stm32_dfsdm_adc_dma_start(struct iio_dev *indio_dev) +{ + struct stm32_dfsdm_adc *adc = iio_priv(indio_dev); + struct dma_async_tx_descriptor *desc; + dma_cookie_t cookie; + int ret; + + if (!adc->dma_chan) + return -EINVAL; + + dev_dbg(&indio_dev->dev, "%s size=%d watermark=%d\n", __func__, + adc->buf_sz, adc->buf_sz / 2); + + /* Prepare a DMA cyclic transaction */ + desc = dmaengine_prep_dma_cyclic(adc->dma_chan, + adc->dma_buf, + adc->buf_sz, adc->buf_sz / 2, + DMA_DEV_TO_MEM, + DMA_PREP_INTERRUPT); + if (!desc) + return -EBUSY; + + desc->callback = stm32_dfsdm_audio_dma_buffer_done; + desc->callback_param = indio_dev; + + cookie = dmaengine_submit(desc); + ret = dma_submit_error(cookie); + if (ret) { + dmaengine_terminate_all(adc->dma_chan); + return ret; + } + + /* Issue pending DMA requests */ + dma_async_issue_pending(adc->dma_chan); + + return 0; +} + +static int stm32_dfsdm_postenable(struct iio_dev *indio_dev) +{ + struct stm32_dfsdm_adc *adc = iio_priv(indio_dev); + int ret; + + /* Reset adc buffer index */ + adc->bufi = 0; + + ret = stm32_dfsdm_start_dfsdm(adc->dfsdm); + if (ret < 0) + return ret; + + ret = stm32_dfsdm_start_conv(adc, true); + if (ret) { + dev_err(&indio_dev->dev, "Can't start conversion\n"); + goto stop_dfsdm; + } + + if (adc->dma_chan) { + ret = stm32_dfsdm_adc_dma_start(indio_dev); + if (ret) { + dev_err(&indio_dev->dev, "Can't start DMA\n"); + goto err_stop_conv; + } + } + + return 0; + +err_stop_conv: + stm32_dfsdm_stop_conv(adc); +stop_dfsdm: + stm32_dfsdm_stop_dfsdm(adc->dfsdm); + + return ret; +} + +static int stm32_dfsdm_predisable(struct iio_dev *indio_dev) +{ + struct stm32_dfsdm_adc *adc = iio_priv(indio_dev); + + if (adc->dma_chan) + dmaengine_terminate_all(adc->dma_chan); + + stm32_dfsdm_stop_conv(adc); + + stm32_dfsdm_stop_dfsdm(adc->dfsdm); + + return 0; +} + +static const struct iio_buffer_setup_ops stm32_dfsdm_buffer_setup_ops = { + .postenable = &stm32_dfsdm_postenable, + .predisable = &stm32_dfsdm_predisable, +}; + +/** + * stm32_dfsdm_get_buff_cb() - register a callback that will be called when + * DMA transfer period is achieved. + * + * @iio_dev: Handle to IIO device. + * @cb: Pointer to callback function: + * - data: pointer to data buffer + * - size: size in byte of the data buffer + * - private: pointer to consumer private structure. + * @private: Pointer to consumer private structure. + */ +int stm32_dfsdm_get_buff_cb(struct iio_dev *iio_dev, + int (*cb)(const void *data, size_t size, + void *private), + void *private) +{ + struct stm32_dfsdm_adc *adc; + + if (!iio_dev) + return -EINVAL; + adc = iio_priv(iio_dev); + + adc->cb = cb; + adc->cb_priv = private; + + return 0; +} +EXPORT_SYMBOL_GPL(stm32_dfsdm_get_buff_cb); + +/** + * stm32_dfsdm_release_buff_cb - unregister buffer callback + * + * @iio_dev: Handle to IIO device. + */ +int stm32_dfsdm_release_buff_cb(struct iio_dev *iio_dev) +{ + struct stm32_dfsdm_adc *adc; + + if (!iio_dev) + return -EINVAL; + adc = iio_priv(iio_dev); + + adc->cb = NULL; + adc->cb_priv = NULL; + + return 0; +} +EXPORT_SYMBOL_GPL(stm32_dfsdm_release_buff_cb); + static int stm32_dfsdm_single_conv(struct iio_dev *indio_dev, const struct iio_chan_spec *chan, int *res) { @@ -453,15 +765,41 @@ static int stm32_dfsdm_write_raw(struct iio_dev *indio_dev, { struct stm32_dfsdm_adc *adc = iio_priv(indio_dev); struct stm32_dfsdm_filter *fl = &adc->dfsdm->fl_list[adc->fl_id]; + struct stm32_dfsdm_channel *ch = &adc->dfsdm->ch_list[adc->ch_id]; + unsigned int spi_freq = adc->spi_freq; int ret = -EINVAL; - if (mask == IIO_CHAN_INFO_OVERSAMPLING_RATIO) { + switch (mask) { + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: ret = stm32_dfsdm_set_osrs(fl, 0, val); if (!ret) adc->oversamp = val; + + return ret; + + case IIO_CHAN_INFO_SAMP_FREQ: + if (!val) + return -EINVAL; + if (ch->src != DFSDM_CHANNEL_SPI_CLOCK_EXTERNAL) + spi_freq = adc->dfsdm->spi_master_freq; + + if (spi_freq % val) + dev_warn(&indio_dev->dev, + "Sampling rate not accurate (%d)\n", + spi_freq / (spi_freq / val)); + + ret = stm32_dfsdm_set_osrs(fl, 0, (spi_freq / val)); + if (ret < 0) { + dev_err(&indio_dev->dev, + "Not able to find parameter that match!\n"); + return ret; + } + adc->sample_freq = val; + + return 0; } - return ret; + return -EINVAL; } static int stm32_dfsdm_read_raw(struct iio_dev *indio_dev, @@ -493,12 +831,23 @@ static int stm32_dfsdm_read_raw(struct iio_dev *indio_dev, case IIO_CHAN_INFO_OVERSAMPLING_RATIO: *val = adc->oversamp; + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SAMP_FREQ: + *val = adc->sample_freq; + return IIO_VAL_INT; } return -EINVAL; } +static const struct iio_info stm32_dfsdm_info_audio = { + .hwfifo_set_watermark = stm32_dfsdm_set_watermark, + .read_raw = stm32_dfsdm_read_raw, + .write_raw = stm32_dfsdm_write_raw, +}; + static const struct iio_info stm32_dfsdm_info_adc = { .read_raw = stm32_dfsdm_read_raw, .write_raw = stm32_dfsdm_write_raw, @@ -531,6 +880,70 @@ static irqreturn_t stm32_dfsdm_irq(int irq, void *arg) return IRQ_HANDLED; } +/* + * Define external info for SPI Frequency and audio sampling rate that can be + * configured by ASoC driver through consumer.h API + */ +static const struct iio_chan_spec_ext_info dfsdm_adc_audio_ext_info[] = { + /* spi_clk_freq : clock freq on SPI/manchester bus used by channel */ + { + .name = "spi_clk_freq", + .shared = IIO_SHARED_BY_TYPE, + .read = dfsdm_adc_audio_get_spiclk, + .write = dfsdm_adc_audio_set_spiclk, + }, + {}, +}; + +static void stm32_dfsdm_dma_release(struct iio_dev *indio_dev) +{ + struct stm32_dfsdm_adc *adc = iio_priv(indio_dev); + + if (adc->dma_chan) { + dma_free_coherent(adc->dma_chan->device->dev, + DFSDM_DMA_BUFFER_SIZE, + adc->rx_buf, adc->dma_buf); + dma_release_channel(adc->dma_chan); + } +} + +static int stm32_dfsdm_dma_request(struct iio_dev *indio_dev) +{ + struct stm32_dfsdm_adc *adc = iio_priv(indio_dev); + struct dma_slave_config config = { + .src_addr = (dma_addr_t)adc->dfsdm->phys_base + + DFSDM_RDATAR(adc->fl_id), + .src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES, + }; + int ret; + + adc->dma_chan = dma_request_slave_channel(&indio_dev->dev, "rx"); + if (!adc->dma_chan) + return -EINVAL; + + adc->rx_buf = dma_alloc_coherent(adc->dma_chan->device->dev, + DFSDM_DMA_BUFFER_SIZE, + &adc->dma_buf, GFP_KERNEL); + if (!adc->rx_buf) { + ret = -ENOMEM; + goto err_release; + } + + ret = dmaengine_slave_config(adc->dma_chan, &config); + if (ret) + goto err_free; + + return 0; + +err_free: + dma_free_coherent(adc->dma_chan->device->dev, DFSDM_DMA_BUFFER_SIZE, + adc->rx_buf, adc->dma_buf); +err_release: + dma_release_channel(adc->dma_chan); + + return ret; +} + static int stm32_dfsdm_adc_chan_init_one(struct iio_dev *indio_dev, struct iio_chan_spec *ch) { @@ -551,7 +964,12 @@ static int stm32_dfsdm_adc_chan_init_one(struct iio_dev *indio_dev, ch->info_mask_separate = BIT(IIO_CHAN_INFO_RAW); ch->info_mask_shared_by_all = BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO); - ch->scan_type.sign = 'u'; + if (adc->dev_data->type == DFSDM_AUDIO) { + ch->scan_type.sign = 's'; + ch->ext_info = dfsdm_adc_audio_ext_info; + } else { + ch->scan_type.sign = 'u'; + } ch->scan_type.realbits = 24; ch->scan_type.storagebits = 32; adc->ch_id = ch->channel; @@ -560,6 +978,39 @@ static int stm32_dfsdm_adc_chan_init_one(struct iio_dev *indio_dev, &adc->dfsdm->ch_list[ch->channel]); } +static int stm32_dfsdm_audio_init(struct iio_dev *indio_dev) +{ + struct iio_chan_spec *ch; + struct stm32_dfsdm_adc *adc = iio_priv(indio_dev); + struct stm32_dfsdm_channel *d_ch; + int ret; + + indio_dev->modes |= INDIO_BUFFER_SOFTWARE; + indio_dev->setup_ops = &stm32_dfsdm_buffer_setup_ops; + + ch = devm_kzalloc(&indio_dev->dev, sizeof(*ch), GFP_KERNEL); + if (!ch) + return -ENOMEM; + + ch->scan_index = 0; + + ret = stm32_dfsdm_adc_chan_init_one(indio_dev, ch); + if (ret < 0) { + dev_err(&indio_dev->dev, "Channels init failed\n"); + return ret; + } + ch->info_mask_separate = BIT(IIO_CHAN_INFO_SAMP_FREQ); + + d_ch = &adc->dfsdm->ch_list[adc->ch_id]; + if (d_ch->src != DFSDM_CHANNEL_SPI_CLOCK_EXTERNAL) + adc->spi_freq = adc->dfsdm->spi_master_freq; + + indio_dev->num_channels = 1; + indio_dev->channels = ch; + + return stm32_dfsdm_dma_request(indio_dev); +} + static int stm32_dfsdm_adc_init(struct iio_dev *indio_dev) { struct iio_chan_spec *ch; @@ -612,11 +1063,20 @@ static const struct stm32_dfsdm_dev_data stm32h7_dfsdm_adc_data = { .init = stm32_dfsdm_adc_init, }; +static const struct stm32_dfsdm_dev_data stm32h7_dfsdm_audio_data = { + .type = DFSDM_AUDIO, + .init = stm32_dfsdm_audio_init, +}; + static const struct of_device_id stm32_dfsdm_adc_match[] = { { .compatible = "st,stm32-dfsdm-adc", .data = &stm32h7_dfsdm_adc_data, }, + { + .compatible = "st,stm32-dfsdm-dmic", + .data = &stm32h7_dfsdm_audio_data, + }, {} }; @@ -667,8 +1127,13 @@ static int stm32_dfsdm_adc_probe(struct platform_device *pdev) name = devm_kzalloc(dev, sizeof("dfsdm-adc0"), GFP_KERNEL); if (!name) return -ENOMEM; - iio->info = &stm32_dfsdm_info_adc; - snprintf(name, sizeof("dfsdm-adc0"), "dfsdm-adc%d", adc->fl_id); + if (dev_data->type == DFSDM_AUDIO) { + iio->info = &stm32_dfsdm_info_audio; + snprintf(name, sizeof("dfsdm-pdm0"), "dfsdm-pdm%d", adc->fl_id); + } else { + iio->info = &stm32_dfsdm_info_adc; + snprintf(name, sizeof("dfsdm-adc0"), "dfsdm-adc%d", adc->fl_id); + } iio->name = name; /* @@ -700,7 +1165,27 @@ static int stm32_dfsdm_adc_probe(struct platform_device *pdev) if (ret < 0) return ret; - return iio_device_register(iio); + ret = iio_device_register(iio); + if (ret < 0) + goto err_cleanup; + + dev_err(dev, "of_platform_populate\n"); + if (dev_data->type == DFSDM_AUDIO) { + ret = of_platform_populate(np, NULL, NULL, dev); + if (ret < 0) { + dev_err(dev, "Failed to find an audio DAI\n"); + goto err_unregister; + } + } + + return 0; + +err_unregister: + iio_device_unregister(iio); +err_cleanup: + stm32_dfsdm_dma_release(iio); + + return ret; } static int stm32_dfsdm_adc_remove(struct platform_device *pdev) @@ -708,7 +1193,10 @@ static int stm32_dfsdm_adc_remove(struct platform_device *pdev) struct stm32_dfsdm_adc *adc = platform_get_drvdata(pdev); struct iio_dev *indio_dev = iio_priv_to_dev(adc); + if (adc->dev_data->type == DFSDM_AUDIO) + of_platform_depopulate(&pdev->dev); iio_device_unregister(indio_dev); + stm32_dfsdm_dma_release(indio_dev); return 0; } diff --git a/include/linux/iio/adc/stm32-dfsdm-adc.h b/include/linux/iio/adc/stm32-dfsdm-adc.h new file mode 100644 index 000000000000..e7dc7a542a4e --- /dev/null +++ b/include/linux/iio/adc/stm32-dfsdm-adc.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * This file discribe the STM32 DFSDM IIO driver API for audio part + * + * Copyright (C) 2017, STMicroelectronics - All Rights Reserved + * Author(s): Arnaud Pouliquen . + */ + +#ifndef STM32_DFSDM_ADC_H +#define STM32_DFSDM_ADC_H + +int stm32_dfsdm_get_buff_cb(struct iio_dev *iio_dev, + int (*cb)(const void *data, size_t size, + void *private), + void *private); +int stm32_dfsdm_release_buff_cb(struct iio_dev *iio_dev); + +#endif -- cgit v1.2.3-59-g8ed1b From 81fdc7afdd7dbf2fdd5139049b887121472015ef Mon Sep 17 00:00:00 2001 From: Alexandre Belloni Date: Fri, 12 Jan 2018 00:00:21 +0100 Subject: Documentation: rtc: move sysfs documentation to ABI The sysfs documentation should be located under Documentation/ABI. Signed-off-by: Alexandre Belloni --- Documentation/ABI/testing/sysfs-class-rtc | 89 +++++++++++++++++++++++++++++++ Documentation/rtc.txt | 32 ----------- 2 files changed, 89 insertions(+), 32 deletions(-) create mode 100644 Documentation/ABI/testing/sysfs-class-rtc (limited to 'Documentation/ABI/testing') diff --git a/Documentation/ABI/testing/sysfs-class-rtc b/Documentation/ABI/testing/sysfs-class-rtc new file mode 100644 index 000000000000..a19f5e2def3a --- /dev/null +++ b/Documentation/ABI/testing/sysfs-class-rtc @@ -0,0 +1,89 @@ +What: /sys/class/rtc/ +Date: March 2006 +KernelVersion: 2.6.17 +Contact: linux-rtc@vger.kernel.org +Description: + The rtc/ class subdirectory belongs to the RTC subsystem. + +What: /sys/class/rtc/rtcX/ +Date: March 2006 +KernelVersion: 2.6.17 +Contact: linux-rtc@vger.kernel.org +Description: + The /sys/class/rtc/rtc{0,1,2,3,...} directories correspond + to each RTC device. + +What: /sys/class/rtc/rtcX/date +Date: March 2006 +KernelVersion: 2.6.17 +Contact: linux-rtc@vger.kernel.org +Description: + RTC-provided date in YYYY-MM-DD format + +What: /sys/class/rtc/rtcX/hctosys +Date: September 2009 +KernelVersion: 2.6.32 +Contact: linux-rtc@vger.kernel.org +Description: + 1 if the RTC provided the system time at boot via the + CONFIG_RTC_HCTOSYS kernel option, 0 otherwise + +What: /sys/class/rtc/rtcX/max_user_freq +Date: October 2007 +KernelVersion: 2.6.24 +Contact: linux-rtc@vger.kernel.org +Description: + The maximum interrupt rate an unprivileged user may request + from this RTC. + +What: /sys/class/rtc/rtcX/name +Date: March 2006 +KernelVersion: 2.6.17 +Contact: linux-rtc@vger.kernel.org +Description: + The name of the RTC corresponding to this sysfs directory + +What: /sys/class/rtc/rtcX/since_epoch +Date: March 2006 +KernelVersion: 2.6.17 +Contact: linux-rtc@vger.kernel.org +Description: + RTC-provided time as the number of seconds since the epoch + +What: /sys/class/rtc/rtcX/time +Date: March 2006 +KernelVersion: 2.6.17 +Contact: linux-rtc@vger.kernel.org +Description: + RTC-provided time in 24-hour notation (hh:mm:ss) + +What: /sys/class/rtc/rtcX/*/nvmem +Date: February 2016 +KernelVersion: 4.6 +Contact: linux-rtc@vger.kernel.org +Description: + The non volatile storage exported as a raw file, as described in + Documentation/nvmem/nvmem.txt + +What: /sys/class/rtc/rtcX/offset +Date: February 2016 +KernelVersion: 4.6 +Contact: linux-rtc@vger.kernel.org +Description: + The amount which the rtc clock has been adjusted in firmware. + Visible only if the driver supports clock offset adjustment. + The unit is parts per billion, i.e. The number of clock ticks + which are added to or removed from the rtc's base clock per + billion ticks. A positive value makes a day pass more slowly, + longer, and a negative value makes a day pass more quickly. + +What: /sys/class/rtc/rtcX/wakealarm +Date: February 2007 +KernelVersion: 2.6.20 +Contact: linux-rtc@vger.kernel.org +Description: + The time at which the clock will generate a system wakeup event. + This is a one shot wakeup event, so must be reset after wake if + a daily wakeup is required. Format is seconds since the epoch by + default, or if there's a leading +, seconds in the future, or if + there is a leading +=, seconds ahead of the current alarm. diff --git a/Documentation/rtc.txt b/Documentation/rtc.txt index c0c977445fb9..f521262cdc85 100644 --- a/Documentation/rtc.txt +++ b/Documentation/rtc.txt @@ -136,38 +136,6 @@ a high functionality RTC is integrated into the SOC. That system might read the system clock from the discrete RTC, but use the integrated one for all other tasks, because of its greater functionality. -SYSFS interface ---------------- - -The sysfs interface under /sys/class/rtc/rtcN provides access to various -rtc attributes without requiring the use of ioctls. All dates and times -are in the RTC's timezone, rather than in system time. - -================ ============================================================== -date RTC-provided date -hctosys 1 if the RTC provided the system time at boot via the - CONFIG_RTC_HCTOSYS kernel option, 0 otherwise -max_user_freq The maximum interrupt rate an unprivileged user may request - from this RTC. -name The name of the RTC corresponding to this sysfs directory -since_epoch The number of seconds since the epoch according to the RTC -time RTC-provided time -wakealarm The time at which the clock will generate a system wakeup - event. This is a one shot wakeup event, so must be reset - after wake if a daily wakeup is required. Format is seconds - since the epoch by default, or if there's a leading +, seconds - in the future, or if there is a leading +=, seconds ahead of - the current alarm. -offset The amount which the rtc clock has been adjusted in firmware. - Visible only if the driver supports clock offset adjustment. - The unit is parts per billion, i.e. The number of clock ticks - which are added to or removed from the rtc's base clock per - billion ticks. A positive value makes a day pass more slowly, - longer, and a negative value makes a day pass more quickly. -*/nvmem The non volatile storage exported as a raw file, as described - in Documentation/nvmem/nvmem.txt -================ ============================================================== - IOCTL interface --------------- -- cgit v1.2.3-59-g8ed1b From d5bc5cde844ebecbc6fac2d65d23610cfd3affc3 Mon Sep 17 00:00:00 2001 From: Aishwarya Pant Date: Mon, 1 Jan 2018 23:00:42 +0530 Subject: Documentation: rtc: add sysfs file permissions Annotate the sysfs interface of rtc with file specific permissions (RO/RW). Signed-off-by: Aishwarya Pant Signed-off-by: Alexandre Belloni --- Documentation/ABI/testing/sysfs-class-rtc | 40 ++++++++++++++++--------------- 1 file changed, 21 insertions(+), 19 deletions(-) (limited to 'Documentation/ABI/testing') diff --git a/Documentation/ABI/testing/sysfs-class-rtc b/Documentation/ABI/testing/sysfs-class-rtc index a19f5e2def3a..cf60412882f0 100644 --- a/Documentation/ABI/testing/sysfs-class-rtc +++ b/Documentation/ABI/testing/sysfs-class-rtc @@ -18,14 +18,14 @@ Date: March 2006 KernelVersion: 2.6.17 Contact: linux-rtc@vger.kernel.org Description: - RTC-provided date in YYYY-MM-DD format + (RO) RTC-provided date in YYYY-MM-DD format What: /sys/class/rtc/rtcX/hctosys Date: September 2009 KernelVersion: 2.6.32 Contact: linux-rtc@vger.kernel.org Description: - 1 if the RTC provided the system time at boot via the + (RO) 1 if the RTC provided the system time at boot via the CONFIG_RTC_HCTOSYS kernel option, 0 otherwise What: /sys/class/rtc/rtcX/max_user_freq @@ -33,7 +33,7 @@ Date: October 2007 KernelVersion: 2.6.24 Contact: linux-rtc@vger.kernel.org Description: - The maximum interrupt rate an unprivileged user may request + (RW) The maximum interrupt rate an unprivileged user may request from this RTC. What: /sys/class/rtc/rtcX/name @@ -41,49 +41,51 @@ Date: March 2006 KernelVersion: 2.6.17 Contact: linux-rtc@vger.kernel.org Description: - The name of the RTC corresponding to this sysfs directory + (RO) The name of the RTC corresponding to this sysfs directory What: /sys/class/rtc/rtcX/since_epoch Date: March 2006 KernelVersion: 2.6.17 Contact: linux-rtc@vger.kernel.org Description: - RTC-provided time as the number of seconds since the epoch + (RO) RTC-provided time as the number of seconds since the epoch What: /sys/class/rtc/rtcX/time Date: March 2006 KernelVersion: 2.6.17 Contact: linux-rtc@vger.kernel.org Description: - RTC-provided time in 24-hour notation (hh:mm:ss) + (RO) RTC-provided time in 24-hour notation (hh:mm:ss) What: /sys/class/rtc/rtcX/*/nvmem Date: February 2016 KernelVersion: 4.6 Contact: linux-rtc@vger.kernel.org Description: - The non volatile storage exported as a raw file, as described in - Documentation/nvmem/nvmem.txt + (RW) The non volatile storage exported as a raw file, as + described in Documentation/nvmem/nvmem.txt What: /sys/class/rtc/rtcX/offset Date: February 2016 KernelVersion: 4.6 Contact: linux-rtc@vger.kernel.org Description: - The amount which the rtc clock has been adjusted in firmware. - Visible only if the driver supports clock offset adjustment. - The unit is parts per billion, i.e. The number of clock ticks - which are added to or removed from the rtc's base clock per - billion ticks. A positive value makes a day pass more slowly, - longer, and a negative value makes a day pass more quickly. + (RW) The amount which the rtc clock has been adjusted in + firmware. Visible only if the driver supports clock offset + adjustment. The unit is parts per billion, i.e. The number of + clock ticks which are added to or removed from the rtc's base + clock per billion ticks. A positive value makes a day pass more + slowly, longer, and a negative value makes a day pass more + quickly. What: /sys/class/rtc/rtcX/wakealarm Date: February 2007 KernelVersion: 2.6.20 Contact: linux-rtc@vger.kernel.org Description: - The time at which the clock will generate a system wakeup event. - This is a one shot wakeup event, so must be reset after wake if - a daily wakeup is required. Format is seconds since the epoch by - default, or if there's a leading +, seconds in the future, or if - there is a leading +=, seconds ahead of the current alarm. + (RW) The time at which the clock will generate a system wakeup + event. This is a one shot wakeup event, so must be reset after + wake if a daily wakeup is required. Format is seconds since the + epoch by default, or if there's a leading +, seconds in the + future, or if there is a leading +=, seconds ahead of the + current alarm. -- cgit v1.2.3-59-g8ed1b From 796c0ad79ee65abb9c891e44ccd6e347a6be486f Mon Sep 17 00:00:00 2001 From: Aishwarya Pant Date: Mon, 1 Jan 2018 23:01:24 +0530 Subject: Documentation: rtc: move iotcl interface documentation to ABI The ioctl interface should be in Documentation/ABI along with the rest of the interfaces. Signed-off-by: Aishwarya Pant Signed-off-by: Alexandre Belloni --- Documentation/ABI/testing/rtc-cdev | 42 ++++++++++++++++++++++++++++++++ Documentation/rtc.txt | 49 ++------------------------------------ 2 files changed, 44 insertions(+), 47 deletions(-) create mode 100644 Documentation/ABI/testing/rtc-cdev (limited to 'Documentation/ABI/testing') diff --git a/Documentation/ABI/testing/rtc-cdev b/Documentation/ABI/testing/rtc-cdev new file mode 100644 index 000000000000..97447283f13b --- /dev/null +++ b/Documentation/ABI/testing/rtc-cdev @@ -0,0 +1,42 @@ +What: /dev/rtcX +Date: April 2005 +KernelVersion: 2.6.12 +Contact: linux-rtc@vger.kernel.org +Description: + The ioctl interface to drivers for real-time clocks (RTCs). + Following actions are supported: + + * RTC_RD_TIME, RTC_SET_TIME: Read or set the RTC time. Time + format is a Gregorian calendar date and 24 hour wall clock + time. + + * RTC_AIE_ON, RTC_AIE_OFF: Enable or disable the alarm interrupt + for RTCs that support alarms + + * RTC_ALM_READ, RTC_ALM_SET: Read or set the alarm time for + RTCs that support alarms. Can be set upto 24 hours in the + future. Requires a separate RTC_AIE_ON call to enable the + alarm interrupt. (Prefer to use RTC_WKALM_*) + + * RTC_WKALM_RD, RTC_WKALM_SET: For RTCs that support a more + powerful interface, which can issue alarms beyond 24 hours and + enable IRQs in the same request. + + * RTC_PIE_ON, RTC_PIE_OFF: Enable or disable the periodic + interrupt for RTCs that support periodic interrupts. + + * RTC_UIE_ON, RTC_UIE_OFF: Enable or disable the update + interrupt for RTCs that support it. + + * RTC_IRQP_READ, RTC_IRQP_SET: Read or set the frequency for + periodic interrupts for RTCs that support periodic interrupts. + Requires a separate RTC_PIE_ON call to enable the periodic + interrupts. + + The ioctl() calls supported by the older /dev/rtc interface are + also supported by the newer RTC class framework. However, + because the chips and systems are not standardized, some PC/AT + functionality might not be provided. And in the same way, some + newer features -- including those enabled by ACPI -- are exposed + by the RTC class framework, but can't be supported by the older + driver. diff --git a/Documentation/rtc.txt b/Documentation/rtc.txt index f521262cdc85..a129acf38537 100644 --- a/Documentation/rtc.txt +++ b/Documentation/rtc.txt @@ -136,50 +136,5 @@ a high functionality RTC is integrated into the SOC. That system might read the system clock from the discrete RTC, but use the integrated one for all other tasks, because of its greater functionality. -IOCTL interface ---------------- - -The ioctl() calls supported by /dev/rtc are also supported by the RTC class -framework. However, because the chips and systems are not standardized, -some PC/AT functionality might not be provided. And in the same way, some -newer features -- including those enabled by ACPI -- are exposed by the -RTC class framework, but can't be supported by the older driver. - - * RTC_RD_TIME, RTC_SET_TIME ... every RTC supports at least reading - time, returning the result as a Gregorian calendar date and 24 hour - wall clock time. To be most useful, this time may also be updated. - - * RTC_AIE_ON, RTC_AIE_OFF, RTC_ALM_SET, RTC_ALM_READ ... when the RTC - is connected to an IRQ line, it can often issue an alarm IRQ up to - 24 hours in the future. (Use RTC_WKALM_* by preference.) - - * RTC_WKALM_SET, RTC_WKALM_RD ... RTCs that can issue alarms beyond - the next 24 hours use a slightly more powerful API, which supports - setting the longer alarm time and enabling its IRQ using a single - request (using the same model as EFI firmware). - - * RTC_UIE_ON, RTC_UIE_OFF ... if the RTC offers IRQs, the RTC framework - will emulate this mechanism. - - * RTC_PIE_ON, RTC_PIE_OFF, RTC_IRQP_SET, RTC_IRQP_READ ... these icotls - are emulated via a kernel hrtimer. - -In many cases, the RTC alarm can be a system wake event, used to force -Linux out of a low power sleep state (or hibernation) back to a fully -operational state. For example, a system could enter a deep power saving -state until it's time to execute some scheduled tasks. - -Note that many of these ioctls are handled by the common rtc-dev interface. -Some common examples: - - * RTC_RD_TIME, RTC_SET_TIME: the read_time/set_time functions will be - called with appropriate values. - - * RTC_ALM_SET, RTC_ALM_READ, RTC_WKALM_SET, RTC_WKALM_RD: gets or sets - the alarm rtc_timer. May call the set_alarm driver function. - - * RTC_IRQP_SET, RTC_IRQP_READ: These are emulated by the generic code. - - * RTC_PIE_ON, RTC_PIE_OFF: These are also emulated by the generic code. - -If all else fails, check out the tools/testing/selftests/timers/rtctest.c test! +Check out tools/testing/selftests/timers/rtctest.c for an example usage of the +ioctl interface. -- cgit v1.2.3-59-g8ed1b From f261708b92fbc51200fee782845a2a891f67234d Mon Sep 17 00:00:00 2001 From: Arkadi Sharshevsky Date: Mon, 15 Jan 2018 08:59:12 +0100 Subject: mlxsw: documentation: Add resources ABI documentation Add resources ABI documentation. Signed-off-by: Arkadi Sharhsevsky Signed-off-by: Jiri Pirko Signed-off-by: David S. Miller --- Documentation/ABI/testing/devlink-resource-mlxsw | 33 ++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 Documentation/ABI/testing/devlink-resource-mlxsw (limited to 'Documentation/ABI/testing') diff --git a/Documentation/ABI/testing/devlink-resource-mlxsw b/Documentation/ABI/testing/devlink-resource-mlxsw new file mode 100644 index 000000000000..259ed2948ec0 --- /dev/null +++ b/Documentation/ABI/testing/devlink-resource-mlxsw @@ -0,0 +1,33 @@ +What: /kvd/ +Date: 08-Jan-2018 +KernelVersion: v4.16 +Contact: mlxsw@mellanox.com +Description: The main database in the Spectrum device is a centralized + KVD database used for many of the tables used to configure + the chip including L2 FDB, L3 LPM, ECMP and more. The KVD + is divided into two sections, the first is hash-based table + and the second is a linear access table. The division + between the linear and hash-based sections is static and + require reload before the changes take effect. + +What: /kvd/linear +Date: 08-Jan-2018 +KernelVersion: v4.16 +Contact: mlxsw@mellanox.com +Description: The linear section of the KVD is managed by software as a + flat memory accessed using an index. + +What: /kvd/hash_single +Date: 08-Jan-2018 +KernelVersion: v4.16 +Contact: mlxsw@mellanox.com +Description: The hash based section of the KVD is managed by the switch + device. Used in case the key size is smaller or equal to + 64bit. + +What: /kvd/hash_double +Date: 08-Jan-2018 +KernelVersion: v4.16 +Contact: mlxsw@mellanox.com +Description: The hash based section of the KVD is managed by the switch + device. Used in case the key is larger than 64 bit. -- cgit v1.2.3-59-g8ed1b From b2d3bcfa26a7a8de41f358a6cae8b848673b3c6e Mon Sep 17 00:00:00 2001 From: David Decotigny Date: Thu, 18 Jan 2018 09:59:13 -0800 Subject: net: core: Expose number of link up/down transitions Expose the number of times the link has been going UP or DOWN, and update the "carrier_changes" counter to be the sum of these two events. While at it, also update the sysfs-class-net documentation to cover: carrier_changes (3.15), carrier_up_count (4.16) and carrier_down_count (4.16) Signed-off-by: David Decotigny [Florian: * rebase * add documentation * merge carrier_changes with up/down counters] Signed-off-by: Florian Fainelli Signed-off-by: David S. Miller --- Documentation/ABI/testing/sysfs-class-net | 24 ++++++++++++++++++++++++ include/linux/netdevice.h | 6 ++++-- include/uapi/linux/if_link.h | 2 ++ net/core/net-sysfs.c | 25 ++++++++++++++++++++++++- net/core/rtnetlink.c | 13 +++++++++++-- net/sched/sch_generic.c | 4 ++-- 6 files changed, 67 insertions(+), 7 deletions(-) (limited to 'Documentation/ABI/testing') diff --git a/Documentation/ABI/testing/sysfs-class-net b/Documentation/ABI/testing/sysfs-class-net index 6856da99b6f7..2f1788111cd9 100644 --- a/Documentation/ABI/testing/sysfs-class-net +++ b/Documentation/ABI/testing/sysfs-class-net @@ -259,3 +259,27 @@ Contact: netdev@vger.kernel.org Description: Symbolic link to the PHY device this network device is attached to. + +What: /sys/class/net//carrier_changes +Date: Mar 2014 +KernelVersion: 3.15 +Contact: netdev@vger.kernel.org +Description: + 32-bit unsigned integer counting the number of times the link has + seen a change from UP to DOWN and vice versa + +What: /sys/class/net//carrier_up_count +Date: Jan 2018 +KernelVersion: 4.16 +Contact: netdev@vger.kernel.org +Description: + 32-bit unsigned integer counting the number of times the link has + been up + +What: /sys/class/net//carrier_down_count +Date: Jan 2018 +KernelVersion: 4.16 +Contact: netdev@vger.kernel.org +Description: + 32-bit unsigned integer counting the number of times the link has + been down diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h index ed0799a12bf2..837e9cb7e358 100644 --- a/include/linux/netdevice.h +++ b/include/linux/netdevice.h @@ -1680,8 +1680,6 @@ struct net_device { unsigned long base_addr; int irq; - atomic_t carrier_changes; - /* * Some hardware also needs these fields (state,dev_list, * napi_list,unreg_list,close_list) but they are not @@ -1719,6 +1717,10 @@ struct net_device { atomic_long_t tx_dropped; atomic_long_t rx_nohandler; + /* Stats to monitor link on/off, flapping */ + atomic_t carrier_up_count; + atomic_t carrier_down_count; + #ifdef CONFIG_WIRELESS_EXT const struct iw_handler_def *wireless_handlers; struct iw_public_data *wireless_data; diff --git a/include/uapi/linux/if_link.h b/include/uapi/linux/if_link.h index f8f04fed6186..8616131e2c61 100644 --- a/include/uapi/linux/if_link.h +++ b/include/uapi/linux/if_link.h @@ -161,6 +161,8 @@ enum { IFLA_EVENT, IFLA_NEW_NETNSID, IFLA_IF_NETNSID, + IFLA_CARRIER_UP_COUNT, + IFLA_CARRIER_DOWN_COUNT, __IFLA_MAX }; diff --git a/net/core/net-sysfs.c b/net/core/net-sysfs.c index 7bf8b85ade16..c4a28f4667b6 100644 --- a/net/core/net-sysfs.c +++ b/net/core/net-sysfs.c @@ -295,10 +295,31 @@ static ssize_t carrier_changes_show(struct device *dev, struct net_device *netdev = to_net_dev(dev); return sprintf(buf, fmt_dec, - atomic_read(&netdev->carrier_changes)); + atomic_read(&netdev->carrier_up_count) + + atomic_read(&netdev->carrier_down_count)); } static DEVICE_ATTR_RO(carrier_changes); +static ssize_t carrier_up_count_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct net_device *netdev = to_net_dev(dev); + + return sprintf(buf, fmt_dec, atomic_read(&netdev->carrier_up_count)); +} +static DEVICE_ATTR_RO(carrier_up_count); + +static ssize_t carrier_down_count_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct net_device *netdev = to_net_dev(dev); + + return sprintf(buf, fmt_dec, atomic_read(&netdev->carrier_down_count)); +} +static DEVICE_ATTR_RO(carrier_down_count); + /* read-write attributes */ static int change_mtu(struct net_device *dev, unsigned long new_mtu) @@ -547,6 +568,8 @@ static struct attribute *net_class_attrs[] __ro_after_init = { &dev_attr_phys_port_name.attr, &dev_attr_phys_switch_id.attr, &dev_attr_proto_down.attr, + &dev_attr_carrier_up_count.attr, + &dev_attr_carrier_down_count.attr, NULL, }; ATTRIBUTE_GROUPS(net_class); diff --git a/net/core/rtnetlink.c b/net/core/rtnetlink.c index 16d644a4f974..97874daa1336 100644 --- a/net/core/rtnetlink.c +++ b/net/core/rtnetlink.c @@ -990,6 +990,8 @@ static noinline size_t if_nlmsg_size(const struct net_device *dev, + nla_total_size(4) /* IFLA_NEW_NETNSID */ + nla_total_size(1) /* IFLA_PROTO_DOWN */ + nla_total_size(4) /* IFLA_IF_NETNSID */ + + nla_total_size(4) /* IFLA_CARRIER_UP_COUNT */ + + nla_total_size(4) /* IFLA_CARRIER_DOWN_COUNT */ + 0; } @@ -1551,8 +1553,13 @@ static int rtnl_fill_ifinfo(struct sk_buff *skb, nla_put_string(skb, IFLA_QDISC, dev->qdisc->ops->id)) || nla_put_ifalias(skb, dev) || nla_put_u32(skb, IFLA_CARRIER_CHANGES, - atomic_read(&dev->carrier_changes)) || - nla_put_u8(skb, IFLA_PROTO_DOWN, dev->proto_down)) + atomic_read(&dev->carrier_up_count) + + atomic_read(&dev->carrier_down_count)) || + nla_put_u8(skb, IFLA_PROTO_DOWN, dev->proto_down) || + nla_put_u32(skb, IFLA_CARRIER_UP_COUNT, + atomic_read(&dev->carrier_up_count)) || + nla_put_u32(skb, IFLA_CARRIER_DOWN_COUNT, + atomic_read(&dev->carrier_down_count))) goto nla_put_failure; if (event != IFLA_EVENT_NONE) { @@ -1656,6 +1663,8 @@ static const struct nla_policy ifla_policy[IFLA_MAX+1] = { [IFLA_EVENT] = { .type = NLA_U32 }, [IFLA_GROUP] = { .type = NLA_U32 }, [IFLA_IF_NETNSID] = { .type = NLA_S32 }, + [IFLA_CARRIER_UP_COUNT] = { .type = NLA_U32 }, + [IFLA_CARRIER_DOWN_COUNT] = { .type = NLA_U32 }, }; static const struct nla_policy ifla_info_policy[IFLA_INFO_MAX+1] = { diff --git a/net/sched/sch_generic.c b/net/sched/sch_generic.c index ef8b4ecde2ac..1816bde47256 100644 --- a/net/sched/sch_generic.c +++ b/net/sched/sch_generic.c @@ -510,7 +510,7 @@ void netif_carrier_on(struct net_device *dev) if (test_and_clear_bit(__LINK_STATE_NOCARRIER, &dev->state)) { if (dev->reg_state == NETREG_UNINITIALIZED) return; - atomic_inc(&dev->carrier_changes); + atomic_inc(&dev->carrier_up_count); linkwatch_fire_event(dev); if (netif_running(dev)) __netdev_watchdog_up(dev); @@ -529,7 +529,7 @@ void netif_carrier_off(struct net_device *dev) if (!test_and_set_bit(__LINK_STATE_NOCARRIER, &dev->state)) { if (dev->reg_state == NETREG_UNINITIALIZED) return; - atomic_inc(&dev->carrier_changes); + atomic_inc(&dev->carrier_down_count); linkwatch_fire_event(dev); } } -- cgit v1.2.3-59-g8ed1b From 36d1d09af1b97113cd014fd50694000ac3ca2824 Mon Sep 17 00:00:00 2001 From: Arend van Spriel Date: Thu, 11 Jan 2018 09:36:37 +0100 Subject: sysfs: add attribute specification for /sysfs/devices/.../coredump This patch adds the specification for /sysfs/devices/.../coredump which allows user-space to trigger a device coredump obtaining binary data from the device for (fault) analysis. It relies on CONFIG_DEV_COREDUMP being enabled. Signed-off-by: Arend van Spriel Signed-off-by: Greg Kroah-Hartman --- Documentation/ABI/testing/sysfs-devices-coredump | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 Documentation/ABI/testing/sysfs-devices-coredump (limited to 'Documentation/ABI/testing') diff --git a/Documentation/ABI/testing/sysfs-devices-coredump b/Documentation/ABI/testing/sysfs-devices-coredump new file mode 100644 index 000000000000..e459368533a4 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-devices-coredump @@ -0,0 +1,10 @@ +What: /sys/devices/.../coredump +Date: December 2017 +Contact: Arend van Spriel +Description: + The /sys/devices/.../coredump attribute is only present when the + device is bound to a driver, which provides the .coredump() + callback. The attribute is write only. Anything written to this + file will trigger the .coredump() callback. + + Available when CONFIG_DEV_COREDUMP is enabled. -- cgit v1.2.3-59-g8ed1b From 00b96c0e3c54a1fff9e8b909f46a672e43ef9598 Mon Sep 17 00:00:00 2001 From: Frederic Barrat Date: Tue, 23 Jan 2018 12:31:47 +0100 Subject: ocxl: Documentation ocxl.rst gives a quick, high-level view of opencapi. Update ioctl-number.txt to reflect ioctl numbers being used by the ocxl driver Signed-off-by: Frederic Barrat [mpe: Fix up mixed whitespace as spotted by gregkh] Signed-off-by: Michael Ellerman --- Documentation/ABI/testing/sysfs-class-ocxl | 35 +++++++ Documentation/accelerators/ocxl.rst | 160 +++++++++++++++++++++++++++++ Documentation/ioctl/ioctl-number.txt | 1 + 3 files changed, 196 insertions(+) create mode 100644 Documentation/ABI/testing/sysfs-class-ocxl create mode 100644 Documentation/accelerators/ocxl.rst (limited to 'Documentation/ABI/testing') diff --git a/Documentation/ABI/testing/sysfs-class-ocxl b/Documentation/ABI/testing/sysfs-class-ocxl new file mode 100644 index 000000000000..b5b1fa197592 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-class-ocxl @@ -0,0 +1,35 @@ +What: /sys/class/ocxl//afu_version +Date: January 2018 +Contact: linuxppc-dev@lists.ozlabs.org +Description: read only + Version of the AFU, in the format : + Reflects what is read in the configuration space of the AFU + +What: /sys/class/ocxl//contexts +Date: January 2018 +Contact: linuxppc-dev@lists.ozlabs.org +Description: read only + Number of contexts for the AFU, in the format / + where: + n: number of currently active contexts, for debug + max: maximum number of contexts supported by the AFU + +What: /sys/class/ocxl//pp_mmio_size +Date: January 2018 +Contact: linuxppc-dev@lists.ozlabs.org +Description: read only + Size of the per-process mmio area, as defined in the + configuration space of the AFU + +What: /sys/class/ocxl//global_mmio_size +Date: January 2018 +Contact: linuxppc-dev@lists.ozlabs.org +Description: read only + Size of the global mmio area, as defined in the + configuration space of the AFU + +What: /sys/class/ocxl//global_mmio_area +Date: January 2018 +Contact: linuxppc-dev@lists.ozlabs.org +Description: read/write + Give access the global mmio area for the AFU diff --git a/Documentation/accelerators/ocxl.rst b/Documentation/accelerators/ocxl.rst new file mode 100644 index 000000000000..4f7af841d935 --- /dev/null +++ b/Documentation/accelerators/ocxl.rst @@ -0,0 +1,160 @@ +======================================================== +OpenCAPI (Open Coherent Accelerator Processor Interface) +======================================================== + +OpenCAPI is an interface between processors and accelerators. It aims +at being low-latency and high-bandwidth. The specification is +developed by the `OpenCAPI Consortium `_. + +It allows an accelerator (which could be a FPGA, ASICs, ...) to access +the host memory coherently, using virtual addresses. An OpenCAPI +device can also host its own memory, that can be accessed from the +host. + +OpenCAPI is known in linux as 'ocxl', as the open, processor-agnostic +evolution of 'cxl' (the driver for the IBM CAPI interface for +powerpc), which was named that way to avoid confusion with the ISDN +CAPI subsystem. + + +High-level view +=============== + +OpenCAPI defines a Data Link Layer (DL) and Transaction Layer (TL), to +be implemented on top of a physical link. Any processor or device +implementing the DL and TL can start sharing memory. + +:: + + +-----------+ +-------------+ + | | | | + | | | Accelerated | + | Processor | | Function | + | | +--------+ | Unit | +--------+ + | |--| Memory | | (AFU) |--| Memory | + | | +--------+ | | +--------+ + +-----------+ +-------------+ + | | + +-----------+ +-------------+ + | TL | | TLX | + +-----------+ +-------------+ + | | + +-----------+ +-------------+ + | DL | | DLX | + +-----------+ +-------------+ + | | + | PHY | + +---------------------------------------+ + + + +Device discovery +================ + +OpenCAPI relies on a PCI-like configuration space, implemented on the +device. So the host can discover AFUs by querying the config space. + +OpenCAPI devices in Linux are treated like PCI devices (with a few +caveats). The firmware is expected to abstract the hardware as if it +was a PCI link. A lot of the existing PCI infrastructure is reused: +devices are scanned and BARs are assigned during the standard PCI +enumeration. Commands like 'lspci' can therefore be used to see what +devices are available. + +The configuration space defines the AFU(s) that can be found on the +physical adapter, such as its name, how many memory contexts it can +work with, the size of its MMIO areas, ... + + + +MMIO +==== + +OpenCAPI defines two MMIO areas for each AFU: + +* the global MMIO area, with registers pertinent to the whole AFU. +* a per-process MMIO area, which has a fixed size for each context. + + + +AFU interrupts +============== + +OpenCAPI includes the possibility for an AFU to send an interrupt to a +host process. It is done through a 'intrp_req' defined in the +Transaction Layer, specifying a 64-bit object handle which defines the +interrupt. + +The driver allows a process to allocate an interrupt and obtain its +64-bit object handle, that can be passed to the AFU. + + + +char devices +============ + +The driver creates one char device per AFU found on the physical +device. A physical device may have multiple functions and each +function can have multiple AFUs. At the time of this writing though, +it has only been tested with devices exporting only one AFU. + +Char devices can be found in /dev/ocxl/ and are named as: +/dev/ocxl/.. + +where is a max 20-character long name, as found in the +config space of the AFU. + is added by the driver and can help distinguish devices +when a system has more than one instance of the same OpenCAPI device. + is also to help distinguish AFUs in the unlikely case where a +device carries multiple copies of the same AFU. + + + +Sysfs class +=========== + +An ocxl class is added for the devices representing the AFUs. See +/sys/class/ocxl. The layout is described in +Documentation/ABI/testing/sysfs-class-ocxl + + + +User API +======== + +open +---- + +Based on the AFU definition found in the config space, an AFU may +support working with more than one memory context, in which case the +associated char device may be opened multiple times by different +processes. + + +ioctl +----- + +OCXL_IOCTL_ATTACH: + + Attach the memory context of the calling process to the AFU so that + the AFU can access its memory. + +OCXL_IOCTL_IRQ_ALLOC: + + Allocate an AFU interrupt and return an identifier. + +OCXL_IOCTL_IRQ_FREE: + + Free a previously allocated AFU interrupt. + +OCXL_IOCTL_IRQ_SET_FD: + + Associate an event fd to an AFU interrupt so that the user process + can be notified when the AFU sends an interrupt. + + +mmap +---- + +A process can mmap the per-process MMIO area for interactions with the +AFU. diff --git a/Documentation/ioctl/ioctl-number.txt b/Documentation/ioctl/ioctl-number.txt index 3e3fdae5f3ed..6501389d55b9 100644 --- a/Documentation/ioctl/ioctl-number.txt +++ b/Documentation/ioctl/ioctl-number.txt @@ -326,6 +326,7 @@ Code Seq#(hex) Include File Comments 0xB5 00-0F uapi/linux/rpmsg.h 0xC0 00-0F linux/usb/iowarrior.h 0xCA 00-0F uapi/misc/cxl.h +0xCA 10-2F uapi/misc/ocxl.h 0xCA 80-BF uapi/scsi/cxlflash_ioctl.h 0xCB 00-1F CBM serial IEC bus in development: -- cgit v1.2.3-59-g8ed1b From b6d8ef86cb7b8b6920b6815ebf1352757d3adb87 Mon Sep 17 00:00:00 2001 From: Aishwarya Pant Date: Wed, 7 Feb 2018 19:04:36 +0530 Subject: Documentation/ABI: update cpuidle sysfs documentation Update cpuidle documentation using git logs and existing documentation in Documentation/cpuidle/sysfs.txt. This might be useful for scripting and tracking changes in the ABI. Signed-off-by: Aishwarya Pant Signed-off-by: Rafael J. Wysocki --- Documentation/ABI/testing/sysfs-devices-system-cpu | 77 +++++++++++++++++++++- 1 file changed, 75 insertions(+), 2 deletions(-) (limited to 'Documentation/ABI/testing') diff --git a/Documentation/ABI/testing/sysfs-devices-system-cpu b/Documentation/ABI/testing/sysfs-devices-system-cpu index bfd29bc8d37a..4ed63b6cfb15 100644 --- a/Documentation/ABI/testing/sysfs-devices-system-cpu +++ b/Documentation/ABI/testing/sysfs-devices-system-cpu @@ -108,6 +108,8 @@ Description: CPU topology files that describe a logical CPU's relationship What: /sys/devices/system/cpu/cpuidle/current_driver /sys/devices/system/cpu/cpuidle/current_governer_ro + /sys/devices/system/cpu/cpuidle/available_governors + /sys/devices/system/cpu/cpuidle/current_governor Date: September 2007 Contact: Linux kernel mailing list Description: Discover cpuidle policy and mechanism @@ -119,13 +121,84 @@ Description: Discover cpuidle policy and mechanism Idle policy (governor) is differentiated from idle mechanism (driver) - current_driver: displays current idle mechanism + current_driver: (RO) displays current idle mechanism - current_governor_ro: displays current idle policy + current_governor_ro: (RO) displays current idle policy + + With the cpuidle_sysfs_switch boot option enabled (meant for + developer testing), the following three attributes are visible + instead: + + current_driver: same as described above + + available_governors: (RO) displays a space separated list of + available governors + + current_governor: (RW) displays current idle policy. Users can + switch the governor at runtime by writing to this file. See files in Documentation/cpuidle/ for more information. +What: /sys/devices/system/cpu/cpuX/cpuidle/stateN/name + /sys/devices/system/cpu/cpuX/cpuidle/stateN/latency + /sys/devices/system/cpu/cpuX/cpuidle/stateN/power + /sys/devices/system/cpu/cpuX/cpuidle/stateN/time + /sys/devices/system/cpu/cpuX/cpuidle/stateN/usage +Date: September 2007 +KernelVersion: v2.6.24 +Contact: Linux power management list +Description: + The directory /sys/devices/system/cpu/cpuX/cpuidle contains per + logical CPU specific cpuidle information for each online cpu X. + The processor idle states which are available for use have the + following attributes: + + name: (RO) Name of the idle state (string). + + latency: (RO) The latency to exit out of this idle state (in + microseconds). + + power: (RO) The power consumed while in this idle state (in + milliwatts). + + time: (RO) The total time spent in this idle state (in microseconds). + + usage: (RO) Number of times this state was entered (a count). + + +What: /sys/devices/system/cpu/cpuX/cpuidle/stateN/desc +Date: February 2008 +KernelVersion: v2.6.25 +Contact: Linux power management list +Description: + (RO) A small description about the idle state (string). + + +What: /sys/devices/system/cpu/cpuX/cpuidle/stateN/disable +Date: March 2012 +KernelVersion: v3.10 +Contact: Linux power management list +Description: + (RW) Option to disable this idle state (bool). The behavior and + the effect of the disable variable depends on the implementation + of a particular governor. In the ladder governor, for example, + it is not coherent, i.e. if one is disabling a light state, then + all deeper states are disabled as well, but the disable variable + does not reflect it. Likewise, if one enables a deep state but a + lighter state still is disabled, then this has no effect. + + +What: /sys/devices/system/cpu/cpuX/cpuidle/stateN/residency +Date: March 2014 +KernelVersion: v3.15 +Contact: Linux power management list +Description: + (RO) Display the target residency i.e. the minimum amount of + time (in microseconds) this cpu should spend in this idle state + to make the transition worth the effort. + + What: /sys/devices/system/cpu/cpu#/cpufreq/* Date: pre-git history Contact: linux-pm@vger.kernel.org -- cgit v1.2.3-59-g8ed1b From 6b4af818c7d7a35a861c94596e05e43596e5fd28 Mon Sep 17 00:00:00 2001 From: Aishwarya Pant Date: Sat, 10 Feb 2018 14:27:19 +0530 Subject: ACPI / DPTF: Document dptf_power sysfs atttributes The descriptions have been collected from git commit logs and reading through code. Signed-off-by: Aishwarya Pant Signed-off-by: Rafael J. Wysocki --- Documentation/ABI/testing/sysfs-platform-dptf | 40 +++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 Documentation/ABI/testing/sysfs-platform-dptf (limited to 'Documentation/ABI/testing') diff --git a/Documentation/ABI/testing/sysfs-platform-dptf b/Documentation/ABI/testing/sysfs-platform-dptf new file mode 100644 index 000000000000..325dc0667dbb --- /dev/null +++ b/Documentation/ABI/testing/sysfs-platform-dptf @@ -0,0 +1,40 @@ +What: /sys/bus/platform/devices/INT3407:00/dptf_power/charger_type +Date: Jul, 2016 +KernelVersion: v4.10 +Contact: linux-acpi@vger.kernel.org +Description: + (RO) The charger type - Traditional, Hybrid or NVDC. + +What: /sys/bus/platform/devices/INT3407:00/dptf_power/adapter_rating_mw +Date: Jul, 2016 +KernelVersion: v4.10 +Contact: linux-acpi@vger.kernel.org +Description: + (RO) Adapter rating in milliwatts (the maximum Adapter power). + Must be 0 if no AC Adaptor is plugged in. + +What: /sys/bus/platform/devices/INT3407:00/dptf_power/max_platform_power_mw +Date: Jul, 2016 +KernelVersion: v4.10 +Contact: linux-acpi@vger.kernel.org +Description: + (RO) Maximum platform power that can be supported by the battery + in milliwatts. + +What: /sys/bus/platform/devices/INT3407:00/dptf_power/platform_power_source +Date: Jul, 2016 +KernelVersion: v4.10 +Contact: linux-acpi@vger.kernel.org +Description: + (RO) Display the platform power source + 0x00 = DC + 0x01 = AC + 0x02 = USB + 0x03 = Wireless Charger + +What: /sys/bus/platform/devices/INT3407:00/dptf_power/battery_steady_power +Date: Jul, 2016 +KernelVersion: v4.10 +Contact: linux-acpi@vger.kernel.org +Description: + (RO) The maximum sustained power for battery in milliwatts. -- cgit v1.2.3-59-g8ed1b From 22029845ad81033115910cdef35170de6a10a1eb Mon Sep 17 00:00:00 2001 From: Aishwarya Pant Date: Sat, 10 Feb 2018 14:27:38 +0530 Subject: ACPI: dock: document sysfs interface Description has been collected from git commit history and reading through code. Signed-off-by: Aishwarya Pant Signed-off-by: Rafael J. Wysocki --- .../ABI/testing/sysfs-devices-platform-dock | 39 ++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 Documentation/ABI/testing/sysfs-devices-platform-dock (limited to 'Documentation/ABI/testing') diff --git a/Documentation/ABI/testing/sysfs-devices-platform-dock b/Documentation/ABI/testing/sysfs-devices-platform-dock new file mode 100644 index 000000000000..1d8c18f905c7 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-devices-platform-dock @@ -0,0 +1,39 @@ +What: /sys/devices/platform/dock.N/docked +Date: Dec, 2006 +KernelVersion: 2.6.19 +Contact: linux-acpi@vger.kernel.org +Description: + (RO) Value 1 or 0 indicates whether the software believes the + laptop is docked in a docking station. + +What: /sys/devices/platform/dock.N/undock +Date: Dec, 2006 +KernelVersion: 2.6.19 +Contact: linux-acpi@vger.kernel.org +Description: + (WO) Writing to this file causes the software to initiate an + undock request to the firmware. + +What: /sys/devices/platform/dock.N/uid +Date: Feb, 2007 +KernelVersion: v2.6.21 +Contact: linux-acpi@vger.kernel.org +Description: + (RO) Displays the docking station the laptop is docked to. + +What: /sys/devices/platform/dock.N/flags +Date: May, 2007 +KernelVersion: v2.6.21 +Contact: linux-acpi@vger.kernel.org +Description: + (RO) Show dock station flags, useful for checking if undock + request has been made by the user (from the immediate_undock + option). + +What: /sys/devices/platform/dock.N/type +Date: Aug, 2008 +KernelVersion: v2.6.27 +Contact: linux-acpi@vger.kernel.org +Description: + (RO) Display the dock station type- dock_station, ata_bay or + battery_bay. -- cgit v1.2.3-59-g8ed1b