From 9c1c4b2753fea36a072e78a5efc82fca0d13b455 Mon Sep 17 00:00:00 2001 From: Alan Tull Date: Wed, 15 Nov 2017 14:20:11 -0600 Subject: fpga: bridge: support getting bridge from device Add two functions for getting the FPGA bridge from the device rather than device tree node. This is to enable writing code that will support using FPGA bridges without device tree. Rename one old function to make it clear that it is device tree-ish. This leaves us with 3 functions for getting a bridge: * fpga_bridge_get Get the bridge given the device. * fpga_bridges_get_to_list Given the device, get the bridge and add it to a list. * of_fpga_bridges_get_to_list Renamed from priviously existing fpga_bridges_get_to_list. Given the device node, get the bridge and add it to a list. Signed-off-by: Alan Tull Acked-by: Moritz Fischer Signed-off-by: Greg Kroah-Hartman --- include/linux/fpga/fpga-bridge.h | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) (limited to 'include') diff --git a/include/linux/fpga/fpga-bridge.h b/include/linux/fpga/fpga-bridge.h index aa66c87c120b..6ca41f8f949f 100644 --- a/include/linux/fpga/fpga-bridge.h +++ b/include/linux/fpga/fpga-bridge.h @@ -1,10 +1,11 @@ /* SPDX-License-Identifier: GPL-2.0 */ -#include -#include #ifndef _LINUX_FPGA_BRIDGE_H #define _LINUX_FPGA_BRIDGE_H +#include +#include + struct fpga_bridge; /** @@ -43,6 +44,8 @@ struct fpga_bridge { struct fpga_bridge *of_fpga_bridge_get(struct device_node *node, struct fpga_image_info *info); +struct fpga_bridge *fpga_bridge_get(struct device *dev, + struct fpga_image_info *info); void fpga_bridge_put(struct fpga_bridge *bridge); int fpga_bridge_enable(struct fpga_bridge *bridge); int fpga_bridge_disable(struct fpga_bridge *bridge); @@ -50,9 +53,12 @@ int fpga_bridge_disable(struct fpga_bridge *bridge); int fpga_bridges_enable(struct list_head *bridge_list); int fpga_bridges_disable(struct list_head *bridge_list); void fpga_bridges_put(struct list_head *bridge_list); -int fpga_bridge_get_to_list(struct device_node *np, +int fpga_bridge_get_to_list(struct device *dev, struct fpga_image_info *info, struct list_head *bridge_list); +int of_fpga_bridge_get_to_list(struct device_node *np, + struct fpga_image_info *info, + struct list_head *bridge_list); int fpga_bridge_register(struct device *dev, const char *name, const struct fpga_bridge_ops *br_ops, void *priv); -- cgit v1.2.3-58-ga151 From 5cf0c7f6502f26332b46fa87914553a4d6ae75ac Mon Sep 17 00:00:00 2001 From: Alan Tull Date: Wed, 15 Nov 2017 14:20:12 -0600 Subject: fpga: mgr: API change to replace fpga load functions with single function fpga-mgr has three methods for programming FPGAs, depending on whether the image is in a scatter gather list, a contiguous buffer, or a firmware file. This makes it difficult to write upper layers as the caller has to assume whether the FPGA image is in a sg table, as a single buffer, or a firmware file. This commit moves these parameters to struct fpga_image_info and adds a single function for programming fpgas. New functions: * fpga_mgr_load - given fpga manager and struct fpga_image_info, program the fpga. * fpga_image_info_alloc - alloc a struct fpga_image_info. * fpga_image_info_free - free a struct fpga_image_info. These three functions are unexported: * fpga_mgr_buf_load_sg * fpga_mgr_buf_load * fpga_mgr_firmware_load Also use devm_kstrdup to copy firmware_name so we aren't making assumptions about where it comes from when allocing/freeing the struct fpga_image_info. API documentation has been updated and a new document for FPGA region has been added. Signed-off-by: Alan Tull Acked-by: Moritz Fischer Signed-off-by: Greg Kroah-Hartman --- Documentation/fpga/fpga-mgr.txt | 119 ++++++++++++++++--------------------- Documentation/fpga/fpga-region.txt | 95 +++++++++++++++++++++++++++++ Documentation/fpga/overview.txt | 23 +++++++ drivers/fpga/fpga-mgr.c | 68 +++++++++++++++++---- drivers/fpga/fpga-region.c | 43 +++++++++----- include/linux/fpga/fpga-mgr.h | 30 ++++++---- 6 files changed, 273 insertions(+), 105 deletions(-) create mode 100644 Documentation/fpga/fpga-region.txt create mode 100644 Documentation/fpga/overview.txt (limited to 'include') diff --git a/Documentation/fpga/fpga-mgr.txt b/Documentation/fpga/fpga-mgr.txt index 78f197fadfd1..6ebc714f4b03 100644 --- a/Documentation/fpga/fpga-mgr.txt +++ b/Documentation/fpga/fpga-mgr.txt @@ -11,61 +11,53 @@ hidden away in a low level driver which registers a set of ops with the core. The FPGA image data itself is very manufacturer specific, but for our purposes it's just binary data. The FPGA manager core won't parse it. +The FPGA image to be programmed can be in a scatter gather list, a single +contiguous buffer, or a firmware file. Because allocating contiguous kernel +memory for the buffer should be avoided, users are encouraged to use a scatter +gather list instead if possible. + +The particulars for programming the image are presented in a structure (struct +fpga_image_info). This struct contains parameters such as pointers to the +FPGA image as well as image-specific particulars such as whether the image was +built for full or partial reconfiguration. API Functions: ============== -To program the FPGA from a file or from a buffer: -------------------------------------------------- - - int fpga_mgr_buf_load(struct fpga_manager *mgr, - struct fpga_image_info *info, - const char *buf, size_t count); - -Load the FPGA from an image which exists as a contiguous buffer in -memory. Allocating contiguous kernel memory for the buffer should be avoided, -users are encouraged to use the _sg interface instead of this. - - int fpga_mgr_buf_load_sg(struct fpga_manager *mgr, - struct fpga_image_info *info, - struct sg_table *sgt); +To program the FPGA: +-------------------- -Load the FPGA from an image from non-contiguous in memory. Callers can -construct a sg_table using alloc_page backed memory. + int fpga_mgr_load(struct fpga_manager *mgr, + struct fpga_image_info *info); - int fpga_mgr_firmware_load(struct fpga_manager *mgr, - struct fpga_image_info *info, - const char *image_name); - -Load the FPGA from an image which exists as a file. The image file must be on -the firmware search path (see the firmware class documentation). If successful, +Load the FPGA from an image which is indicated in the info. If successful, the FPGA ends up in operating mode. Return 0 on success or a negative error code. -A FPGA design contained in a FPGA image file will likely have particulars that -affect how the image is programmed to the FPGA. These are contained in struct -fpga_image_info. Currently the only such particular is a single flag bit -indicating whether the image is for full or partial reconfiguration. +To allocate or free a struct fpga_image_info: +--------------------------------------------- + + struct fpga_image_info *fpga_image_info_alloc(struct device *dev); + + void fpga_image_info_free(struct fpga_image_info *info); To get/put a reference to a FPGA manager: ----------------------------------------- struct fpga_manager *of_fpga_mgr_get(struct device_node *node); struct fpga_manager *fpga_mgr_get(struct device *dev); - -Given a DT node or device, get an exclusive reference to a FPGA manager. - void fpga_mgr_put(struct fpga_manager *mgr); -Release the reference. +Given a DT node or device, get an exclusive reference to a FPGA manager. +fpga_mgr_put releases the reference. To register or unregister the low level FPGA-specific driver: ------------------------------------------------------------- int fpga_mgr_register(struct device *dev, const char *name, - const struct fpga_manager_ops *mops, - void *priv); + const struct fpga_manager_ops *mops, + void *priv); void fpga_mgr_unregister(struct device *dev); @@ -78,59 +70,50 @@ How to write an image buffer to a supported FPGA /* Include to get the API */ #include -/* device node that specifies the FPGA manager to use */ -struct device_node *mgr_node = ... - -/* FPGA image is in this buffer. count is size of the buffer. */ -char *buf = ... -int count = ... +struct fpga_manager *mgr; +struct fpga_image_info *info; +int ret; /* struct with information about the FPGA image to program. */ -struct fpga_image_info info; +info = fpga_image_info_alloc(dev); /* flags indicates whether to do full or partial reconfiguration */ -info.flags = 0; - -int ret; +info->flags = FPGA_MGR_PARTIAL_RECONFIG; -/* Get exclusive control of FPGA manager */ -struct fpga_manager *mgr = of_fpga_mgr_get(mgr_node); +/* + * At this point, indicate where the image is. This is pseudo-code; you're + * going to use one of these three. + */ +if (image is in a scatter gather table) { -/* Load the buffer to the FPGA */ -ret = fpga_mgr_buf_load(mgr, &info, buf, count); - -/* Release the FPGA manager */ -fpga_mgr_put(mgr); + info->sgt = [your scatter gather table] +} else if (image is in a buffer) { -How to write an image file to a supported FPGA -============================================== -/* Include to get the API */ -#include + info->buf = [your image buffer] + info->count = [image buffer size] -/* device node that specifies the FPGA manager to use */ -struct device_node *mgr_node = ... +} else if (image is in a firmware file) { -/* FPGA image is in this file which is in the firmware search path */ -const char *path = "fpga-image-9.rbf" + info->firmware_name = devm_kstrdup(dev, firmware_name, GFP_KERNEL); -/* struct with information about the FPGA image to program. */ -struct fpga_image_info info; - -/* flags indicates whether to do full or partial reconfiguration */ -info.flags = 0; - -int ret; +} -/* Get exclusive control of FPGA manager */ -struct fpga_manager *mgr = of_fpga_mgr_get(mgr_node); +/* + * Get a reference to FPGA manager. This example uses the device node of the + * manager. You could use fpga_mgr_get() instead if you have the device instead + * of the device node. + */ +mgr = of_fpga_mgr_get(mgr_node); -/* Get the firmware image (path) and load it to the FPGA */ -ret = fpga_mgr_firmware_load(mgr, &info, path); +/* Load the buffer to the FPGA */ +ret = fpga_mgr_buf_load(mgr, &info, buf, count); /* Release the FPGA manager */ fpga_mgr_put(mgr); +/* Deallocate the image info if you're done with it */ +fpga_image_info_free(info); How to support a new FPGA device ================================ diff --git a/Documentation/fpga/fpga-region.txt b/Documentation/fpga/fpga-region.txt new file mode 100644 index 000000000000..139a02ba1ff6 --- /dev/null +++ b/Documentation/fpga/fpga-region.txt @@ -0,0 +1,95 @@ +FPGA Regions + +Alan Tull 2017 + +CONTENTS + - Introduction + - The FPGA region API + - Usage example + +Introduction +============ + +This document is meant to be an brief overview of the FPGA region API usage. A +more conceptual look at regions can be found in [1]. + +For the purposes of this API document, let's just say that a region associates +an FPGA Manager and a bridge (or bridges) with a reprogrammable region of an +FPGA or the whole FPGA. The API provides a way to register a region and to +program a region. + +Currently the only layer above fpga-region.c in the kernel is the Device Tree +support (of-fpga-region.c) described in [1]. The DT support layer uses regions +to program the FPGA and then DT to handle enumeration. The common region code +is intended to be used by other schemes that have other ways of accomplishing +enumeration after programming. + +An fpga-region can be set up to know the following things: +* which FPGA manager to use to do the programming +* which bridges to disable before programming and enable afterwards. + +Additional info needed to program the FPGA image is passed in the struct +fpga_image_info [2] including: +* pointers to the image as either a scatter-gather buffer, a contiguous + buffer, or the name of firmware file +* flags indicating specifics such as whether the image if for partial + reconfiguration. + +=================== +The FPGA region API +=================== + +To register or unregister a region: +----------------------------------- + + int fpga_region_register(struct device *dev, + struct fpga_region *region); + int fpga_region_unregister(struct fpga_region *region); + +An example of usage can be seen in the probe function of [3] + +To program an FPGA: +------------------- + int fpga_region_program_fpga(struct fpga_region *region); + +This function operates on info passed in the fpga_image_info +(region->info). + +This function will attempt to: + * lock the region's mutex + * lock the region's FPGA manager + * build a list of FPGA bridges if a method has been specified to do so + * disable the bridges + * program the FPGA + * re-enable the bridges + * release the locks + +============= +Usage example +============= + +First, allocate the info struct: + + info = fpga_image_info_alloc(dev); + if (!info) + return -ENOMEM; + +Set flags as needed, i.e. + + info->flags |= FPGA_MGR_PARTIAL_RECONFIG; + +Point to your FPGA image, such as: + + info->sgt = &sgt; + +Add info to region and do the programming: + + region->info = info; + ret = fpga_region_program_fpga(region); + +Then enumerate whatever hardware has appeared in the FPGA. + +-- +[1] ../devicetree/bindings/fpga/fpga-region.txt +[2] ./fpga-mgr.txt +[3] ../../drivers/fpga/of-fpga-region.c diff --git a/Documentation/fpga/overview.txt b/Documentation/fpga/overview.txt new file mode 100644 index 000000000000..0f1236e7e675 --- /dev/null +++ b/Documentation/fpga/overview.txt @@ -0,0 +1,23 @@ +Linux kernel FPGA support + +Alan Tull 2017 + +The main point of this project has been to separate the out the upper layers +that know when to reprogram a FPGA from the lower layers that know how to +reprogram a specific FPGA device. The intention is to make this manufacturer +agnostic, understanding that of course the FPGA images are very device specific +themselves. + +The framework in the kernel includes: +* low level FPGA manager drivers that know how to program a specific device +* the fpga-mgr framework they are registered with +* low level FPGA bridge drivers for hard/soft bridges which are intended to + be disable during FPGA programming +* the fpga-bridge framework they are registered with +* the fpga-region framework which associates and controls managers and bridges + as reconfigurable regions +* the of-fpga-region support for reprogramming FPGAs when device tree overlays + are applied. + +I would encourage you the user to add code that creates FPGA regions rather +that trying to control managers and bridges separately. diff --git a/drivers/fpga/fpga-mgr.c b/drivers/fpga/fpga-mgr.c index 188ffefa3cc3..a8dd54945470 100644 --- a/drivers/fpga/fpga-mgr.c +++ b/drivers/fpga/fpga-mgr.c @@ -2,6 +2,7 @@ * FPGA Manager Core * * Copyright (C) 2013-2015 Altera Corporation + * Copyright (C) 2017 Intel Corporation * * With code from the mailing list: * Copyright (C) 2013 Xilinx, Inc. @@ -31,6 +32,40 @@ static DEFINE_IDA(fpga_mgr_ida); static struct class *fpga_mgr_class; +struct fpga_image_info *fpga_image_info_alloc(struct device *dev) +{ + struct fpga_image_info *info; + + get_device(dev); + + info = devm_kzalloc(dev, sizeof(*info), GFP_KERNEL); + if (!info) { + put_device(dev); + return NULL; + } + + info->dev = dev; + + return info; +} +EXPORT_SYMBOL_GPL(fpga_image_info_alloc); + +void fpga_image_info_free(struct fpga_image_info *info) +{ + struct device *dev; + + if (!info) + return; + + dev = info->dev; + if (info->firmware_name) + devm_kfree(dev, info->firmware_name); + + devm_kfree(dev, info); + put_device(dev); +} +EXPORT_SYMBOL_GPL(fpga_image_info_free); + /* * Call the low level driver's write_init function. This will do the * device-specific things to get the FPGA into the state where it is ready to @@ -137,8 +172,9 @@ static int fpga_mgr_write_complete(struct fpga_manager *mgr, * * Return: 0 on success, negative error code otherwise. */ -int fpga_mgr_buf_load_sg(struct fpga_manager *mgr, struct fpga_image_info *info, - struct sg_table *sgt) +static int fpga_mgr_buf_load_sg(struct fpga_manager *mgr, + struct fpga_image_info *info, + struct sg_table *sgt) { int ret; @@ -170,7 +206,6 @@ int fpga_mgr_buf_load_sg(struct fpga_manager *mgr, struct fpga_image_info *info, return fpga_mgr_write_complete(mgr, info); } -EXPORT_SYMBOL_GPL(fpga_mgr_buf_load_sg); static int fpga_mgr_buf_load_mapped(struct fpga_manager *mgr, struct fpga_image_info *info, @@ -210,8 +245,9 @@ static int fpga_mgr_buf_load_mapped(struct fpga_manager *mgr, * * Return: 0 on success, negative error code otherwise. */ -int fpga_mgr_buf_load(struct fpga_manager *mgr, struct fpga_image_info *info, - const char *buf, size_t count) +static int fpga_mgr_buf_load(struct fpga_manager *mgr, + struct fpga_image_info *info, + const char *buf, size_t count) { struct page **pages; struct sg_table sgt; @@ -266,7 +302,6 @@ int fpga_mgr_buf_load(struct fpga_manager *mgr, struct fpga_image_info *info, return rc; } -EXPORT_SYMBOL_GPL(fpga_mgr_buf_load); /** * fpga_mgr_firmware_load - request firmware and load to fpga @@ -282,9 +317,9 @@ EXPORT_SYMBOL_GPL(fpga_mgr_buf_load); * * Return: 0 on success, negative error code otherwise. */ -int fpga_mgr_firmware_load(struct fpga_manager *mgr, - struct fpga_image_info *info, - const char *image_name) +static int fpga_mgr_firmware_load(struct fpga_manager *mgr, + struct fpga_image_info *info, + const char *image_name) { struct device *dev = &mgr->dev; const struct firmware *fw; @@ -307,7 +342,18 @@ int fpga_mgr_firmware_load(struct fpga_manager *mgr, return ret; } -EXPORT_SYMBOL_GPL(fpga_mgr_firmware_load); + +int fpga_mgr_load(struct fpga_manager *mgr, struct fpga_image_info *info) +{ + if (info->sgt) + return fpga_mgr_buf_load_sg(mgr, info, info->sgt); + if (info->buf && info->count) + return fpga_mgr_buf_load(mgr, info, info->buf, info->count); + if (info->firmware_name) + return fpga_mgr_firmware_load(mgr, info, info->firmware_name); + return -EINVAL; +} +EXPORT_SYMBOL_GPL(fpga_mgr_load); static const char * const state_str[] = { [FPGA_MGR_STATE_UNKNOWN] = "unknown", @@ -578,7 +624,7 @@ static void __exit fpga_mgr_class_exit(void) ida_destroy(&fpga_mgr_ida); } -MODULE_AUTHOR("Alan Tull "); +MODULE_AUTHOR("Alan Tull "); MODULE_DESCRIPTION("FPGA manager framework"); MODULE_LICENSE("GPL v2"); diff --git a/drivers/fpga/fpga-region.c b/drivers/fpga/fpga-region.c index 9175556215b1..120c496eb7bd 100644 --- a/drivers/fpga/fpga-region.c +++ b/drivers/fpga/fpga-region.c @@ -226,14 +226,11 @@ static int fpga_region_get_bridges(struct fpga_region *region, /** * fpga_region_program_fpga - program FPGA * @region: FPGA region - * @firmware_name: name of FPGA image firmware file * @overlay: device node of the overlay - * Program an FPGA using information in the device tree. - * Function assumes that there is a firmware-name property. + * Program an FPGA using information in the region's fpga image info. * Return 0 for success or negative error code. */ static int fpga_region_program_fpga(struct fpga_region *region, - const char *firmware_name, struct device_node *overlay) { struct fpga_manager *mgr; @@ -264,7 +261,7 @@ static int fpga_region_program_fpga(struct fpga_region *region, goto err_put_br; } - ret = fpga_mgr_firmware_load(mgr, region->info, firmware_name); + ret = fpga_mgr_load(mgr, region->info); if (ret) { pr_err("failed to load fpga image\n"); goto err_put_br; @@ -357,16 +354,15 @@ static int child_regions_with_firmware(struct device_node *overlay) static int fpga_region_notify_pre_apply(struct fpga_region *region, struct of_overlay_notify_data *nd) { - const char *firmware_name = NULL; + struct device *dev = ®ion->dev; struct fpga_image_info *info; + const char *firmware_name; int ret; - info = devm_kzalloc(®ion->dev, sizeof(*info), GFP_KERNEL); + info = fpga_image_info_alloc(dev); if (!info) return -ENOMEM; - region->info = info; - /* Reject overlay if child FPGA Regions have firmware-name property */ ret = child_regions_with_firmware(nd->overlay); if (ret) @@ -382,7 +378,13 @@ static int fpga_region_notify_pre_apply(struct fpga_region *region, if (of_property_read_bool(nd->overlay, "encrypted-fpga-config")) info->flags |= FPGA_MGR_ENCRYPTED_BITSTREAM; - of_property_read_string(nd->overlay, "firmware-name", &firmware_name); + if (!of_property_read_string(nd->overlay, "firmware-name", + &firmware_name)) { + info->firmware_name = devm_kstrdup(dev, firmware_name, + GFP_KERNEL); + if (!info->firmware_name) + return -ENOMEM; + } of_property_read_u32(nd->overlay, "region-unfreeze-timeout-us", &info->enable_timeout_us); @@ -394,22 +396,33 @@ static int fpga_region_notify_pre_apply(struct fpga_region *region, &info->config_complete_timeout_us); /* If FPGA was externally programmed, don't specify firmware */ - if ((info->flags & FPGA_MGR_EXTERNAL_CONFIG) && firmware_name) { + if ((info->flags & FPGA_MGR_EXTERNAL_CONFIG) && info->firmware_name) { pr_err("error: specified firmware and external-fpga-config"); + fpga_image_info_free(info); return -EINVAL; } /* FPGA is already configured externally. We're done. */ - if (info->flags & FPGA_MGR_EXTERNAL_CONFIG) + if (info->flags & FPGA_MGR_EXTERNAL_CONFIG) { + fpga_image_info_free(info); return 0; + } /* If we got this far, we should be programming the FPGA */ - if (!firmware_name) { + if (!info->firmware_name) { pr_err("should specify firmware-name or external-fpga-config\n"); + fpga_image_info_free(info); return -EINVAL; } - return fpga_region_program_fpga(region, firmware_name, nd->overlay); + region->info = info; + ret = fpga_region_program_fpga(region, nd->overlay); + if (ret) { + fpga_image_info_free(info); + region->info = NULL; + } + + return ret; } /** @@ -426,7 +439,7 @@ static void fpga_region_notify_post_remove(struct fpga_region *region, { fpga_bridges_disable(®ion->bridge_list); fpga_bridges_put(®ion->bridge_list); - devm_kfree(®ion->dev, region->info); + fpga_image_info_free(region->info); region->info = NULL; } diff --git a/include/linux/fpga/fpga-mgr.h b/include/linux/fpga/fpga-mgr.h index bfa14bc023fb..6b791348023b 100644 --- a/include/linux/fpga/fpga-mgr.h +++ b/include/linux/fpga/fpga-mgr.h @@ -1,7 +1,8 @@ /* * FPGA Framework * - * Copyright (C) 2013-2015 Altera Corporation + * Copyright (C) 2013-2016 Altera Corporation + * Copyright (C) 2017 Intel Corporation * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU General Public License, @@ -15,12 +16,12 @@ * You should have received a copy of the GNU General Public License along with * this program. If not, see . */ -#include -#include - #ifndef _LINUX_FPGA_MGR_H #define _LINUX_FPGA_MGR_H +#include +#include + struct fpga_manager; struct sg_table; @@ -83,12 +84,22 @@ enum fpga_mgr_states { * @disable_timeout_us: maximum time to disable traffic through bridge (uSec) * @config_complete_timeout_us: maximum time for FPGA to switch to operating * status in the write_complete op. + * @firmware_name: name of FPGA image firmware file + * @sgt: scatter/gather table containing FPGA image + * @buf: contiguous buffer containing FPGA image + * @count: size of buf + * @dev: device that owns this */ struct fpga_image_info { u32 flags; u32 enable_timeout_us; u32 disable_timeout_us; u32 config_complete_timeout_us; + char *firmware_name; + struct sg_table *sgt; + const char *buf; + size_t count; + struct device *dev; }; /** @@ -138,14 +149,11 @@ struct fpga_manager { #define to_fpga_manager(d) container_of(d, struct fpga_manager, dev) -int fpga_mgr_buf_load(struct fpga_manager *mgr, struct fpga_image_info *info, - const char *buf, size_t count); -int fpga_mgr_buf_load_sg(struct fpga_manager *mgr, struct fpga_image_info *info, - struct sg_table *sgt); +struct fpga_image_info *fpga_image_info_alloc(struct device *dev); + +void fpga_image_info_free(struct fpga_image_info *info); -int fpga_mgr_firmware_load(struct fpga_manager *mgr, - struct fpga_image_info *info, - const char *image_name); +int fpga_mgr_load(struct fpga_manager *mgr, struct fpga_image_info *info); struct fpga_manager *of_fpga_mgr_get(struct device_node *node); -- cgit v1.2.3-58-ga151 From ebf877a51ad7b65e4ab024f021b60a4f7928864a Mon Sep 17 00:00:00 2001 From: Alan Tull Date: Wed, 15 Nov 2017 14:20:13 -0600 Subject: fpga: mgr: separate getting/locking FPGA manager Previously when the user gets a FPGA manager, it was locked and nobody else could use it for programming. This commit makes it straightforward to save a reference to an FPGA manager and only lock it when programming the FPGA. Add functions that get an FPGA manager's mutex for exclusive use: * fpga_mgr_lock * fpga_mgr_unlock The following functions no longer lock an FPGA manager's mutex: * of_fpga_mgr_get * fpga_mgr_get * fpga_mgr_put Signed-off-by: Alan Tull Acked-by: Moritz Fischer Signed-off-by: Greg Kroah-Hartman --- Documentation/fpga/fpga-mgr.txt | 35 ++++++++++++++++++++------- drivers/fpga/fpga-mgr.c | 52 ++++++++++++++++++++++++++++------------- drivers/fpga/fpga-region.c | 14 +++++++++-- include/linux/fpga/fpga-mgr.h | 3 +++ 4 files changed, 77 insertions(+), 27 deletions(-) (limited to 'include') diff --git a/Documentation/fpga/fpga-mgr.txt b/Documentation/fpga/fpga-mgr.txt index 6ebc714f4b03..cc6413ed6fc9 100644 --- a/Documentation/fpga/fpga-mgr.txt +++ b/Documentation/fpga/fpga-mgr.txt @@ -48,8 +48,20 @@ To get/put a reference to a FPGA manager: struct fpga_manager *fpga_mgr_get(struct device *dev); void fpga_mgr_put(struct fpga_manager *mgr); -Given a DT node or device, get an exclusive reference to a FPGA manager. -fpga_mgr_put releases the reference. +Given a DT node or device, get a reference to a FPGA manager. This pointer +can be saved until you are ready to program the FPGA. fpga_mgr_put releases +the reference. + + +To get exclusive control of a FPGA manager: +------------------------------------------- + + int fpga_mgr_lock(struct fpga_manager *mgr); + void fpga_mgr_unlock(struct fpga_manager *mgr); + +The user should call fpga_mgr_lock and verify that it returns 0 before +attempting to program the FPGA. Likewise, the user should call +fpga_mgr_unlock when done programming the FPGA. To register or unregister the low level FPGA-specific driver: @@ -67,13 +79,21 @@ device." How to write an image buffer to a supported FPGA ================================================ -/* Include to get the API */ #include struct fpga_manager *mgr; struct fpga_image_info *info; int ret; +/* + * Get a reference to FPGA manager. The manager is not locked, so you can + * hold onto this reference without it preventing programming. + * + * This example uses the device node of the manager. Alternatively, use + * fpga_mgr_get(dev) instead if you have the device. + */ +mgr = of_fpga_mgr_get(mgr_node); + /* struct with information about the FPGA image to program. */ info = fpga_image_info_alloc(dev); @@ -99,17 +119,14 @@ if (image is in a scatter gather table) { } -/* - * Get a reference to FPGA manager. This example uses the device node of the - * manager. You could use fpga_mgr_get() instead if you have the device instead - * of the device node. - */ -mgr = of_fpga_mgr_get(mgr_node); +/* Get exclusive control of FPGA manager */ +ret = fpga_mgr_lock(mgr); /* Load the buffer to the FPGA */ ret = fpga_mgr_buf_load(mgr, &info, buf, count); /* Release the FPGA manager */ +fpga_mgr_unlock(mgr); fpga_mgr_put(mgr); /* Deallocate the image info if you're done with it */ diff --git a/drivers/fpga/fpga-mgr.c b/drivers/fpga/fpga-mgr.c index a8dd54945470..d27e8d2a149c 100644 --- a/drivers/fpga/fpga-mgr.c +++ b/drivers/fpga/fpga-mgr.c @@ -410,28 +410,19 @@ ATTRIBUTE_GROUPS(fpga_mgr); static struct fpga_manager *__fpga_mgr_get(struct device *dev) { struct fpga_manager *mgr; - int ret = -ENODEV; mgr = to_fpga_manager(dev); if (!mgr) goto err_dev; - /* Get exclusive use of fpga manager */ - if (!mutex_trylock(&mgr->ref_mutex)) { - ret = -EBUSY; - goto err_dev; - } - if (!try_module_get(dev->parent->driver->owner)) - goto err_ll_mod; + goto err_dev; return mgr; -err_ll_mod: - mutex_unlock(&mgr->ref_mutex); err_dev: put_device(dev); - return ERR_PTR(ret); + return ERR_PTR(-ENODEV); } static int fpga_mgr_dev_match(struct device *dev, const void *data) @@ -440,10 +431,10 @@ static int fpga_mgr_dev_match(struct device *dev, const void *data) } /** - * fpga_mgr_get - get an exclusive reference to a fpga mgr + * fpga_mgr_get - get a reference to a fpga mgr * @dev: parent device that fpga mgr was registered with * - * Given a device, get an exclusive reference to a fpga mgr. + * Given a device, get a reference to a fpga mgr. * * Return: fpga manager struct or IS_ERR() condition containing error code. */ @@ -464,10 +455,10 @@ static int fpga_mgr_of_node_match(struct device *dev, const void *data) } /** - * of_fpga_mgr_get - get an exclusive reference to a fpga mgr + * of_fpga_mgr_get - get a reference to a fpga mgr * @node: device node * - * Given a device node, get an exclusive reference to a fpga mgr. + * Given a device node, get a reference to a fpga mgr. * * Return: fpga manager struct or IS_ERR() condition containing error code. */ @@ -491,11 +482,40 @@ EXPORT_SYMBOL_GPL(of_fpga_mgr_get); void fpga_mgr_put(struct fpga_manager *mgr) { module_put(mgr->dev.parent->driver->owner); - mutex_unlock(&mgr->ref_mutex); put_device(&mgr->dev); } EXPORT_SYMBOL_GPL(fpga_mgr_put); +/** + * fpga_mgr_lock - Lock FPGA manager for exclusive use + * @mgr: fpga manager + * + * Given a pointer to FPGA Manager (from fpga_mgr_get() or + * of_fpga_mgr_put()) attempt to get the mutex. + * + * Return: 0 for success or -EBUSY + */ +int fpga_mgr_lock(struct fpga_manager *mgr) +{ + if (!mutex_trylock(&mgr->ref_mutex)) { + dev_err(&mgr->dev, "FPGA manager is in use.\n"); + return -EBUSY; + } + + return 0; +} +EXPORT_SYMBOL_GPL(fpga_mgr_lock); + +/** + * fpga_mgr_unlock - Unlock FPGA manager + * @mgr: fpga manager + */ +void fpga_mgr_unlock(struct fpga_manager *mgr) +{ + mutex_unlock(&mgr->ref_mutex); +} +EXPORT_SYMBOL_GPL(fpga_mgr_unlock); + /** * fpga_mgr_register - register a low level fpga manager driver * @dev: fpga manager device from pdev diff --git a/drivers/fpga/fpga-region.c b/drivers/fpga/fpga-region.c index 120c496eb7bd..1e1640a29306 100644 --- a/drivers/fpga/fpga-region.c +++ b/drivers/fpga/fpga-region.c @@ -125,7 +125,7 @@ static void fpga_region_put(struct fpga_region *region) } /** - * fpga_region_get_manager - get exclusive reference for FPGA manager + * fpga_region_get_manager - get reference for FPGA manager * @region: FPGA region * * Get FPGA Manager from "fpga-mgr" property or from ancestor region. @@ -233,6 +233,7 @@ static int fpga_region_get_bridges(struct fpga_region *region, static int fpga_region_program_fpga(struct fpga_region *region, struct device_node *overlay) { + struct device *dev = ®ion->dev; struct fpga_manager *mgr; int ret; @@ -249,10 +250,16 @@ static int fpga_region_program_fpga(struct fpga_region *region, goto err_put_region; } + ret = fpga_mgr_lock(mgr); + if (ret) { + dev_err(dev, "FPGA manager is busy\n"); + goto err_put_mgr; + } + ret = fpga_region_get_bridges(region, overlay); if (ret) { pr_err("failed to get fpga region bridges\n"); - goto err_put_mgr; + goto err_unlock_mgr; } ret = fpga_bridges_disable(®ion->bridge_list); @@ -273,6 +280,7 @@ static int fpga_region_program_fpga(struct fpga_region *region, goto err_put_br; } + fpga_mgr_unlock(mgr); fpga_mgr_put(mgr); fpga_region_put(region); @@ -280,6 +288,8 @@ static int fpga_region_program_fpga(struct fpga_region *region, err_put_br: fpga_bridges_put(®ion->bridge_list); +err_unlock_mgr: + fpga_mgr_unlock(mgr); err_put_mgr: fpga_mgr_put(mgr); err_put_region: diff --git a/include/linux/fpga/fpga-mgr.h b/include/linux/fpga/fpga-mgr.h index 6b791348023b..cb5615c87504 100644 --- a/include/linux/fpga/fpga-mgr.h +++ b/include/linux/fpga/fpga-mgr.h @@ -155,6 +155,9 @@ void fpga_image_info_free(struct fpga_image_info *info); int fpga_mgr_load(struct fpga_manager *mgr, struct fpga_image_info *info); +int fpga_mgr_lock(struct fpga_manager *mgr); +void fpga_mgr_unlock(struct fpga_manager *mgr); + struct fpga_manager *of_fpga_mgr_get(struct device_node *node); struct fpga_manager *fpga_mgr_get(struct device *dev); -- cgit v1.2.3-58-ga151 From 61c32102391ff38dfd4aba835dd0f99db6b46908 Mon Sep 17 00:00:00 2001 From: Alan Tull Date: Wed, 15 Nov 2017 14:20:19 -0600 Subject: fpga: region: use image info as parameter for programming region Use FPGA image info (region->info) when region code is programming the FPGA to pass in multiple parameters. This is a baby step in refactoring the FPGA region code to separate out common FPGA region code from FPGA region Device Tree overlay support. Signed-off-by: Alan Tull Acked-by: Moritz Fischer Signed-off-by: Greg Kroah-Hartman --- drivers/fpga/fpga-region.c | 16 +++++++++------- include/linux/fpga/fpga-mgr.h | 4 ++++ 2 files changed, 13 insertions(+), 7 deletions(-) (limited to 'include') diff --git a/drivers/fpga/fpga-region.c b/drivers/fpga/fpga-region.c index 35af952a889a..eaacf5049381 100644 --- a/drivers/fpga/fpga-region.c +++ b/drivers/fpga/fpga-region.c @@ -223,14 +223,13 @@ static int fpga_region_get_bridges(struct fpga_region *region, /** * fpga_region_program_fpga - program FPGA * @region: FPGA region - * @overlay: device node of the overlay - * Program an FPGA using information in the region's fpga image info. + * Program an FPGA using fpga image info (region->info). * Return 0 for success or negative error code. */ -static int fpga_region_program_fpga(struct fpga_region *region, - struct device_node *overlay) +static int fpga_region_program_fpga(struct fpga_region *region) { struct device *dev = ®ion->dev; + struct fpga_image_info *info = region->info; int ret; region = fpga_region_get(region); @@ -245,7 +244,7 @@ static int fpga_region_program_fpga(struct fpga_region *region, goto err_put_region; } - ret = fpga_region_get_bridges(region, overlay); + ret = fpga_region_get_bridges(region, info->overlay); if (ret) { dev_err(dev, "failed to get FPGA bridges\n"); goto err_unlock_mgr; @@ -257,7 +256,7 @@ static int fpga_region_program_fpga(struct fpga_region *region, goto err_put_br; } - ret = fpga_mgr_load(region->mgr, region->info); + ret = fpga_mgr_load(region->mgr, info); if (ret) { dev_err(dev, "failed to load FPGA image\n"); goto err_put_br; @@ -373,6 +372,8 @@ static int fpga_region_notify_pre_apply(struct fpga_region *region, if (!info) return -ENOMEM; + info->overlay = nd->overlay; + /* Read FPGA region properties from the overlay */ if (of_property_read_bool(nd->overlay, "partial-fpga-config")) info->flags |= FPGA_MGR_PARTIAL_RECONFIG; @@ -421,7 +422,8 @@ static int fpga_region_notify_pre_apply(struct fpga_region *region, } region->info = info; - ret = fpga_region_program_fpga(region, nd->overlay); + + ret = fpga_region_program_fpga(region); if (ret) { fpga_image_info_free(info); region->info = NULL; diff --git a/include/linux/fpga/fpga-mgr.h b/include/linux/fpga/fpga-mgr.h index cb5615c87504..4fb706bd9aba 100644 --- a/include/linux/fpga/fpga-mgr.h +++ b/include/linux/fpga/fpga-mgr.h @@ -89,6 +89,7 @@ enum fpga_mgr_states { * @buf: contiguous buffer containing FPGA image * @count: size of buf * @dev: device that owns this + * @overlay: Device Tree overlay */ struct fpga_image_info { u32 flags; @@ -100,6 +101,9 @@ struct fpga_image_info { const char *buf; size_t count; struct device *dev; +#ifdef CONFIG_OF + struct device_node *overlay; +#endif }; /** -- cgit v1.2.3-58-ga151 From 59460a9305458ac3e7f2415b602dbaa6cfcb8a3b Mon Sep 17 00:00:00 2001 From: Alan Tull Date: Wed, 15 Nov 2017 14:20:21 -0600 Subject: fpga: region: add fpga-region.h header * Create fpga-region.h. * Export fpga_region_program_fpga. * Move struct fpga_region and other things to the header. This is a step in separating FPGA region common code from Device Tree support. Signed-off-by: Alan Tull Acked-by: Moritz Fischer Signed-off-by: Greg Kroah-Hartman --- drivers/fpga/fpga-region.c | 24 ++++-------------------- include/linux/fpga/fpga-region.h | 28 ++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 20 deletions(-) create mode 100644 include/linux/fpga/fpga-region.h (limited to 'include') diff --git a/drivers/fpga/fpga-region.c b/drivers/fpga/fpga-region.c index 2a8621db5f5b..402d0b68b97a 100644 --- a/drivers/fpga/fpga-region.c +++ b/drivers/fpga/fpga-region.c @@ -18,6 +18,7 @@ #include #include +#include #include #include #include @@ -26,24 +27,6 @@ #include #include -/** - * struct fpga_region - FPGA Region structure - * @dev: FPGA Region device - * @mutex: enforces exclusive reference to region - * @bridge_list: list of FPGA bridges specified in region - * @mgr: FPGA manager - * @info: fpga image specific information - */ -struct fpga_region { - struct device dev; - struct mutex mutex; /* for exclusive reference to region */ - struct list_head bridge_list; - struct fpga_manager *mgr; - struct fpga_image_info *info; -}; - -#define to_fpga_region(d) container_of(d, struct fpga_region, dev) - static DEFINE_IDA(fpga_region_ida); static struct class *fpga_region_class; @@ -226,7 +209,7 @@ static int fpga_region_get_bridges(struct fpga_region *region, * Program an FPGA using fpga image info (region->info). * Return 0 for success or negative error code. */ -static int fpga_region_program_fpga(struct fpga_region *region) +int fpga_region_program_fpga(struct fpga_region *region) { struct device *dev = ®ion->dev; struct fpga_image_info *info = region->info; @@ -282,6 +265,7 @@ err_put_region: return ret; } +EXPORT_SYMBOL_GPL(fpga_region_program_fpga); /** * child_regions_with_firmware @@ -667,5 +651,5 @@ subsys_initcall(fpga_region_init); module_exit(fpga_region_exit); MODULE_DESCRIPTION("FPGA Region"); -MODULE_AUTHOR("Alan Tull "); +MODULE_AUTHOR("Alan Tull "); MODULE_LICENSE("GPL v2"); diff --git a/include/linux/fpga/fpga-region.h b/include/linux/fpga/fpga-region.h new file mode 100644 index 000000000000..8a355171406b --- /dev/null +++ b/include/linux/fpga/fpga-region.h @@ -0,0 +1,28 @@ +#ifndef _FPGA_REGION_H +#define _FPGA_REGION_H + +#include +#include +#include + +/** + * struct fpga_region - FPGA Region structure + * @dev: FPGA Region device + * @mutex: enforces exclusive reference to region + * @bridge_list: list of FPGA bridges specified in region + * @mgr: FPGA manager + * @info: FPGA image info + */ +struct fpga_region { + struct device dev; + struct mutex mutex; /* for exclusive reference to region */ + struct list_head bridge_list; + struct fpga_manager *mgr; + struct fpga_image_info *info; +}; + +#define to_fpga_region(d) container_of(d, struct fpga_region, dev) + +int fpga_region_program_fpga(struct fpga_region *region); + +#endif /* _FPGA_REGION_H */ -- cgit v1.2.3-58-ga151 From 52a3a7ccce07e73323fc1bae9eb0b0b63375391c Mon Sep 17 00:00:00 2001 From: Alan Tull Date: Wed, 15 Nov 2017 14:20:23 -0600 Subject: fpga: region: add register/unregister functions Another step in separating common code from device tree specific code for FPGA regions. * add FPGA region register/unregister functions. * add the register/unregister functions to the header * use devm_kzalloc to alloc the region. * add a method for getting bridges to the region struct * add priv to the region struct * use region->info in of_fpga_region_get_bridges Signed-off-by: Alan Tull Acked-by: Moritz Fischer Signed-off-by: Greg Kroah-Hartman --- drivers/fpga/fpga-region.c | 105 ++++++++++++++++++++++++--------------- include/linux/fpga/fpga-region.h | 7 +++ 2 files changed, 72 insertions(+), 40 deletions(-) (limited to 'include') diff --git a/drivers/fpga/fpga-region.c b/drivers/fpga/fpga-region.c index 92ab21651aeb..76db81de2cc0 100644 --- a/drivers/fpga/fpga-region.c +++ b/drivers/fpga/fpga-region.c @@ -143,7 +143,6 @@ static struct fpga_manager *of_fpga_region_get_mgr(struct device_node *np) /** * of_fpga_region_get_bridges - create a list of bridges * @region: FPGA region - * @info: FPGA image info * * Create a list of bridges including the parent bridge and the bridges * specified by "fpga-bridges" property. Note that the @@ -156,11 +155,11 @@ static struct fpga_manager *of_fpga_region_get_mgr(struct device_node *np) * Return 0 for success (even if there are no bridges specified) * or -EBUSY if any of the bridges are in use. */ -static int of_fpga_region_get_bridges(struct fpga_region *region, - struct fpga_image_info *info) +static int of_fpga_region_get_bridges(struct fpga_region *region) { struct device *dev = ®ion->dev; struct device_node *region_np = dev->of_node; + struct fpga_image_info *info = region->info; struct device_node *br, *np, *parent_br = NULL; int i, ret; @@ -192,7 +191,7 @@ static int of_fpga_region_get_bridges(struct fpga_region *region, continue; /* If node is a bridge, get it and add to list */ - ret = of_fpga_bridge_get_to_list(br, region->info, + ret = of_fpga_bridge_get_to_list(br, info, ®ion->bridge_list); /* If any of the bridges are in use, give up */ @@ -229,10 +228,16 @@ int fpga_region_program_fpga(struct fpga_region *region) goto err_put_region; } - ret = of_fpga_region_get_bridges(region, info); - if (ret) { - dev_err(dev, "failed to get FPGA bridges\n"); - goto err_unlock_mgr; + /* + * In some cases, we already have a list of bridges in the + * fpga region struct. Or we don't have any bridges. + */ + if (region->get_bridges) { + ret = region->get_bridges(region); + if (ret) { + dev_err(dev, "failed to get fpga region bridges\n"); + goto err_unlock_mgr; + } } ret = fpga_bridges_disable(®ion->bridge_list); @@ -259,7 +264,8 @@ int fpga_region_program_fpga(struct fpga_region *region) return 0; err_put_br: - fpga_bridges_put(®ion->bridge_list); + if (region->get_bridges) + fpga_bridges_put(®ion->bridge_list); err_unlock_mgr: fpga_mgr_unlock(region->mgr); err_put_region: @@ -522,39 +528,20 @@ static struct notifier_block fpga_region_of_nb = { .notifier_call = of_fpga_region_notify, }; -static int of_fpga_region_probe(struct platform_device *pdev) +int fpga_region_register(struct device *dev, struct fpga_region *region) { - struct device *dev = &pdev->dev; - struct device_node *np = dev->of_node; - struct fpga_region *region; - struct fpga_manager *mgr; int id, ret = 0; - mgr = of_fpga_region_get_mgr(np); - if (IS_ERR(mgr)) - return -EPROBE_DEFER; - - region = kzalloc(sizeof(*region), GFP_KERNEL); - if (!region) { - ret = -ENOMEM; - goto err_put_mgr; - } - - region->mgr = mgr; - id = ida_simple_get(&fpga_region_ida, 0, 0, GFP_KERNEL); - if (id < 0) { - ret = id; - goto err_kfree; - } + if (id < 0) + return id; mutex_init(®ion->mutex); INIT_LIST_HEAD(®ion->bridge_list); - device_initialize(®ion->dev); region->dev.class = fpga_region_class; region->dev.parent = dev; - region->dev.of_node = np; + region->dev.of_node = dev->of_node; region->dev.id = id; dev_set_drvdata(dev, region); @@ -566,19 +553,58 @@ static int of_fpga_region_probe(struct platform_device *pdev) if (ret) goto err_remove; + return 0; + +err_remove: + ida_simple_remove(&fpga_region_ida, id); + return ret; +} +EXPORT_SYMBOL_GPL(fpga_region_register); + +int fpga_region_unregister(struct fpga_region *region) +{ + device_unregister(®ion->dev); + + return 0; +} +EXPORT_SYMBOL_GPL(fpga_region_unregister); + +static int of_fpga_region_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct fpga_region *region; + struct fpga_manager *mgr; + int ret; + + /* Find the FPGA mgr specified by region or parent region. */ + mgr = of_fpga_region_get_mgr(np); + if (IS_ERR(mgr)) + return -EPROBE_DEFER; + + region = devm_kzalloc(dev, sizeof(*region), GFP_KERNEL); + if (!region) { + ret = -ENOMEM; + goto eprobe_mgr_put; + } + + region->mgr = mgr; + + /* Specify how to get bridges for this type of region. */ + region->get_bridges = of_fpga_region_get_bridges; + + ret = fpga_region_register(dev, region); + if (ret) + goto eprobe_mgr_put; + of_platform_populate(np, fpga_region_of_match, NULL, ®ion->dev); dev_info(dev, "FPGA Region probed\n"); return 0; -err_remove: - ida_simple_remove(&fpga_region_ida, id); -err_kfree: - kfree(region); -err_put_mgr: +eprobe_mgr_put: fpga_mgr_put(mgr); - return ret; } @@ -586,7 +612,7 @@ static int of_fpga_region_remove(struct platform_device *pdev) { struct fpga_region *region = platform_get_drvdata(pdev); - device_unregister(®ion->dev); + fpga_region_unregister(region); fpga_mgr_put(region->mgr); return 0; @@ -606,7 +632,6 @@ static void fpga_region_dev_release(struct device *dev) struct fpga_region *region = to_fpga_region(dev); ida_simple_remove(&fpga_region_ida, region->dev.id); - kfree(region); } /** diff --git a/include/linux/fpga/fpga-region.h b/include/linux/fpga/fpga-region.h index 8a355171406b..8c8a3249f96c 100644 --- a/include/linux/fpga/fpga-region.h +++ b/include/linux/fpga/fpga-region.h @@ -12,6 +12,8 @@ * @bridge_list: list of FPGA bridges specified in region * @mgr: FPGA manager * @info: FPGA image info + * @priv: private data + * @get_bridges: optional function to get bridges to a list */ struct fpga_region { struct device dev; @@ -19,10 +21,15 @@ struct fpga_region { struct list_head bridge_list; struct fpga_manager *mgr; struct fpga_image_info *info; + void *priv; + int (*get_bridges)(struct fpga_region *region); }; #define to_fpga_region(d) container_of(d, struct fpga_region, dev) int fpga_region_program_fpga(struct fpga_region *region); +int fpga_region_register(struct device *dev, struct fpga_region *region); +int fpga_region_unregister(struct fpga_region *region); + #endif /* _FPGA_REGION_H */ -- cgit v1.2.3-58-ga151 From 503d4b7a446b3838785fa7f21e339941a5d1c2d5 Mon Sep 17 00:00:00 2001 From: Alan Tull Date: Wed, 15 Nov 2017 14:20:24 -0600 Subject: fpga: region: add fpga_region_class_find Add a function for searching the fpga-region class. This will be useful when device tree code is no longer in the same file that declares the fpga-region class. Another step in separating common FPGA region code from device tree support. Signed-off-by: Alan Tull Acked-by: Moritz Fischer Signed-off-by: Greg Kroah-Hartman --- drivers/fpga/fpga-region.c | 23 +++++++++++++++-------- include/linux/fpga/fpga-region.h | 5 ++++- 2 files changed, 19 insertions(+), 9 deletions(-) (limited to 'include') diff --git a/drivers/fpga/fpga-region.c b/drivers/fpga/fpga-region.c index 76db81de2cc0..5c0869576cd1 100644 --- a/drivers/fpga/fpga-region.c +++ b/drivers/fpga/fpga-region.c @@ -30,6 +30,20 @@ static DEFINE_IDA(fpga_region_ida); static struct class *fpga_region_class; +struct fpga_region *fpga_region_class_find( + struct device *start, const void *data, + int (*match)(struct device *, const void *)) +{ + struct device *dev; + + dev = class_find_device(fpga_region_class, start, data, match); + if (!dev) + return NULL; + + return to_fpga_region(dev); +} +EXPORT_SYMBOL_GPL(fpga_region_class_find); + static const struct of_device_id fpga_region_of_match[] = { { .compatible = "fpga-region", }, {}, @@ -51,14 +65,7 @@ static int fpga_region_of_node_match(struct device *dev, const void *data) */ static struct fpga_region *of_fpga_region_find(struct device_node *np) { - struct device *dev; - - dev = class_find_device(fpga_region_class, NULL, np, - fpga_region_of_node_match); - if (!dev) - return NULL; - - return to_fpga_region(dev); + return fpga_region_class_find(NULL, np, fpga_region_of_node_match); } /** diff --git a/include/linux/fpga/fpga-region.h b/include/linux/fpga/fpga-region.h index 8c8a3249f96c..704844944631 100644 --- a/include/linux/fpga/fpga-region.h +++ b/include/linux/fpga/fpga-region.h @@ -27,8 +27,11 @@ struct fpga_region { #define to_fpga_region(d) container_of(d, struct fpga_region, dev) -int fpga_region_program_fpga(struct fpga_region *region); +struct fpga_region *fpga_region_class_find( + struct device *start, const void *data, + int (*match)(struct device *, const void *)); +int fpga_region_program_fpga(struct fpga_region *region); int fpga_region_register(struct device *dev, struct fpga_region *region); int fpga_region_unregister(struct fpga_region *region); -- cgit v1.2.3-58-ga151 From 845089bbf589be75143d0c9fb326d5595c1b5787 Mon Sep 17 00:00:00 2001 From: Alan Tull Date: Wed, 15 Nov 2017 14:20:28 -0600 Subject: fpga: add attribute groups Make it easy to add attributes to low level FPGA drivers the right way. Add attribute groups pointers to structures that are used when registering a manager, bridge, or group. When the low level driver registers, set the device attribute group. The attributes are created in device_add. Signed-off-by: Alan Tull Acked-by: Moritz Fischer Signed-off-by: Greg Kroah-Hartman --- drivers/fpga/fpga-bridge.c | 1 + drivers/fpga/fpga-mgr.c | 1 + drivers/fpga/fpga-region.c | 1 + include/linux/fpga/fpga-bridge.h | 2 ++ include/linux/fpga/fpga-mgr.h | 2 ++ include/linux/fpga/fpga-region.h | 2 ++ 6 files changed, 9 insertions(+) (limited to 'include') diff --git a/drivers/fpga/fpga-bridge.c b/drivers/fpga/fpga-bridge.c index 0dfe9d78cee2..e693c3607a14 100644 --- a/drivers/fpga/fpga-bridge.c +++ b/drivers/fpga/fpga-bridge.c @@ -367,6 +367,7 @@ int fpga_bridge_register(struct device *dev, const char *name, bridge->priv = priv; device_initialize(&bridge->dev); + bridge->dev.groups = br_ops->groups; bridge->dev.class = fpga_bridge_class; bridge->dev.parent = dev; bridge->dev.of_node = dev->of_node; diff --git a/drivers/fpga/fpga-mgr.c b/drivers/fpga/fpga-mgr.c index d27e8d2a149c..223f2401939b 100644 --- a/drivers/fpga/fpga-mgr.c +++ b/drivers/fpga/fpga-mgr.c @@ -569,6 +569,7 @@ int fpga_mgr_register(struct device *dev, const char *name, device_initialize(&mgr->dev); mgr->dev.class = fpga_mgr_class; + mgr->dev.groups = mops->groups; mgr->dev.parent = dev; mgr->dev.of_node = dev->of_node; mgr->dev.id = id; diff --git a/drivers/fpga/fpga-region.c b/drivers/fpga/fpga-region.c index afc61885a601..edab2a2e03ef 100644 --- a/drivers/fpga/fpga-region.c +++ b/drivers/fpga/fpga-region.c @@ -173,6 +173,7 @@ int fpga_region_register(struct device *dev, struct fpga_region *region) mutex_init(®ion->mutex); INIT_LIST_HEAD(®ion->bridge_list); device_initialize(®ion->dev); + region->dev.groups = region->groups; region->dev.class = fpga_region_class; region->dev.parent = dev; region->dev.of_node = dev->of_node; diff --git a/include/linux/fpga/fpga-bridge.h b/include/linux/fpga/fpga-bridge.h index 6ca41f8f949f..3694821a6d2d 100644 --- a/include/linux/fpga/fpga-bridge.h +++ b/include/linux/fpga/fpga-bridge.h @@ -13,11 +13,13 @@ struct fpga_bridge; * @enable_show: returns the FPGA bridge's status * @enable_set: set a FPGA bridge as enabled or disabled * @fpga_bridge_remove: set FPGA into a specific state during driver remove + * @groups: optional attribute groups. */ struct fpga_bridge_ops { int (*enable_show)(struct fpga_bridge *bridge); int (*enable_set)(struct fpga_bridge *bridge, bool enable); void (*fpga_bridge_remove)(struct fpga_bridge *bridge); + const struct attribute_group **groups; }; /** diff --git a/include/linux/fpga/fpga-mgr.h b/include/linux/fpga/fpga-mgr.h index 4fb706bd9aba..3c6de23aabdf 100644 --- a/include/linux/fpga/fpga-mgr.h +++ b/include/linux/fpga/fpga-mgr.h @@ -115,6 +115,7 @@ struct fpga_image_info { * @write_sg: write the scatter list of configuration data to the FPGA * @write_complete: set FPGA to operating state after writing is done * @fpga_remove: optional: Set FPGA into a specific state during driver remove + * @groups: optional attribute groups. * * fpga_manager_ops are the low level functions implemented by a specific * fpga manager driver. The optional ones are tested for NULL before being @@ -131,6 +132,7 @@ struct fpga_manager_ops { int (*write_complete)(struct fpga_manager *mgr, struct fpga_image_info *info); void (*fpga_remove)(struct fpga_manager *mgr); + const struct attribute_group **groups; }; /** diff --git a/include/linux/fpga/fpga-region.h b/include/linux/fpga/fpga-region.h index 704844944631..b6520318ab9c 100644 --- a/include/linux/fpga/fpga-region.h +++ b/include/linux/fpga/fpga-region.h @@ -14,6 +14,7 @@ * @info: FPGA image info * @priv: private data * @get_bridges: optional function to get bridges to a list + * @groups: optional attribute groups. */ struct fpga_region { struct device dev; @@ -23,6 +24,7 @@ struct fpga_region { struct fpga_image_info *info; void *priv; int (*get_bridges)(struct fpga_region *region); + const struct attribute_group **groups; }; #define to_fpga_region(d) container_of(d, struct fpga_region, dev) -- cgit v1.2.3-58-ga151 From 9a450484089dfa8b6348eff2a918f3c8f38585b9 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Mon, 27 Nov 2017 12:29:50 +0100 Subject: lp: support 64-bit time_t user space Once we get a glibc with 64-bit time_t, the LPSETTIMEOUT ioctl stops working, since the command number and data structure no longer match. To work around that, this introduces a new command number LPSETTIMEOUT_NEW that is used whenever the modified user space evaluates the LPSETTIMEOUT macro. The trick we use is a bit convoluted but necessary: we cannot check for any macros set by the C library in linux/lp.h, because this particular header can be included before including sys/time.h. However, we can assume that by the time that LPSETTIMEOUT is seen in the code, the definition for 'timeval' and 'time_t' has been seen as well, so we can use the sizeof() operator to determine whether we should use the old or the new definition. We use the old one not only for traditional 32-bit user space with 32-bit time_t, but also for all 64-bit architectures and x32, which always use a 64-bit time_t, the new definition will be used only for 32-bit user space with 64-bit time_t, which also requires a newer kernel. The compat_ioctl() handler now implements both commands, but has to use a special case for existing x32 binaries. The native ioctl handler now implements both command numbers on both 32-bit and 64-bit, though the latter version use the same interpretation for both. This is based on an earlier patch from Bamvor. Cc: Bamvor Jian Zhang Link: http://www.spinics.net/lists/y2038/msg01162.html Signed-off-by: Arnd Bergmann Signed-off-by: Greg Kroah-Hartman --- drivers/char/lp.c | 67 +++++++++++++++++++++++++++++++++++++------------ include/uapi/linux/lp.h | 12 ++++++++- 2 files changed, 62 insertions(+), 17 deletions(-) (limited to 'include') diff --git a/drivers/char/lp.c b/drivers/char/lp.c index 8249762192d5..be14abf70da1 100644 --- a/drivers/char/lp.c +++ b/drivers/char/lp.c @@ -659,17 +659,31 @@ static int lp_do_ioctl(unsigned int minor, unsigned int cmd, return retval; } -static int lp_set_timeout(unsigned int minor, struct timeval *par_timeout) +static int lp_set_timeout(unsigned int minor, s64 tv_sec, long tv_usec) { long to_jiffies; /* Convert to jiffies, place in lp_table */ - if ((par_timeout->tv_sec < 0) || - (par_timeout->tv_usec < 0)) { + if (tv_sec < 0 || tv_usec < 0) return -EINVAL; + + /* + * we used to not check, so let's not make this fatal, + * but deal with user space passing a 32-bit tv_nsec in + * a 64-bit field, capping the timeout to 1 second + * worth of microseconds, and capping the total at + * MAX_JIFFY_OFFSET. + */ + if (tv_usec > 999999) + tv_usec = 999999; + + if (tv_sec >= MAX_SEC_IN_JIFFIES - 1) { + to_jiffies = MAX_JIFFY_OFFSET; + } else { + to_jiffies = DIV_ROUND_UP(tv_usec, 1000000/HZ); + to_jiffies += tv_sec * (long) HZ; } - to_jiffies = DIV_ROUND_UP(par_timeout->tv_usec, 1000000/HZ); - to_jiffies += par_timeout->tv_sec * (long) HZ; + if (to_jiffies <= 0) { return -EINVAL; } @@ -677,23 +691,43 @@ static int lp_set_timeout(unsigned int minor, struct timeval *par_timeout) return 0; } +static int lp_set_timeout32(unsigned int minor, void __user *arg) +{ + s32 karg[2]; + + if (copy_from_user(karg, arg, sizeof(karg))) + return -EFAULT; + + return lp_set_timeout(minor, karg[0], karg[1]); +} + +static int lp_set_timeout64(unsigned int minor, void __user *arg) +{ + s64 karg[2]; + + if (copy_from_user(karg, arg, sizeof(karg))) + return -EFAULT; + + return lp_set_timeout(minor, karg[0], karg[1]); +} + static long lp_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { unsigned int minor; - struct timeval par_timeout; int ret; minor = iminor(file_inode(file)); mutex_lock(&lp_mutex); switch (cmd) { - case LPSETTIMEOUT: - if (copy_from_user(&par_timeout, (void __user *)arg, - sizeof (struct timeval))) { - ret = -EFAULT; + case LPSETTIMEOUT_OLD: + if (BITS_PER_LONG == 32) { + ret = lp_set_timeout32(minor, (void __user *)arg); break; } - ret = lp_set_timeout(minor, &par_timeout); + /* fallthrough for 64-bit */ + case LPSETTIMEOUT_NEW: + ret = lp_set_timeout64(minor, (void __user *)arg); break; default: ret = lp_do_ioctl(minor, cmd, arg, (void __user *)arg); @@ -709,18 +743,19 @@ static long lp_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { unsigned int minor; - struct timeval par_timeout; int ret; minor = iminor(file_inode(file)); mutex_lock(&lp_mutex); switch (cmd) { - case LPSETTIMEOUT: - if (compat_get_timeval(&par_timeout, compat_ptr(arg))) { - ret = -EFAULT; + case LPSETTIMEOUT_OLD: + if (!COMPAT_USE_64BIT_TIME) { + ret = lp_set_timeout32(minor, (void __user *)arg); break; } - ret = lp_set_timeout(minor, &par_timeout); + /* fallthrough for x32 mode */ + case LPSETTIMEOUT_NEW: + ret = lp_set_timeout64(minor, (void __user *)arg); break; #ifdef LP_STATS case LPGETSTATS: diff --git a/include/uapi/linux/lp.h b/include/uapi/linux/lp.h index dafcfe4e4834..8589a27037d7 100644 --- a/include/uapi/linux/lp.h +++ b/include/uapi/linux/lp.h @@ -8,6 +8,8 @@ #ifndef _UAPI_LINUX_LP_H #define _UAPI_LINUX_LP_H +#include +#include /* * Per POSIX guidelines, this module reserves the LP and lp prefixes @@ -88,7 +90,15 @@ #define LPGETSTATS 0x060d /* get statistics (struct lp_stats) */ #endif #define LPGETFLAGS 0x060e /* get status flags */ -#define LPSETTIMEOUT 0x060f /* set parport timeout */ +#define LPSETTIMEOUT_OLD 0x060f /* set parport timeout */ +#define LPSETTIMEOUT_NEW \ + _IOW(0x6, 0xf, __s64[2]) /* set parport timeout */ +#if __BITS_PER_LONG == 64 +#define LPSETTIMEOUT LPSETTIMEOUT_OLD +#else +#define LPSETTIMEOUT (sizeof(time_t) > sizeof(__kernel_long_t) ? \ + LPSETTIMEOUT_NEW : LPSETTIMEOUT_OLD) +#endif /* timeout for printk'ing a timeout, in jiffies (100ths of a second). This is also used for re-checking error conditions if LP_ABORT is -- cgit v1.2.3-58-ga151 From f6ddd094f5793447d594aa9f42032a7aba12b4d2 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Thu, 30 Nov 2017 17:01:25 +0100 Subject: virt: Add vboxguest driver for Virtual Box Guest integration UAPI This commit adds the headers describing the ioctl API for the /dev/vboxguest device used by the Virtual Box Guest Additions in Virtual Box virtual machines. The driver providing the /dev/vboxguest device will allow Virtual Box Guest Additions features such as copy-and-paste, seamless mode and OpenGL pass-through. Signed-off-by: Hans de Goede Reviewed-by: Larry Finger Signed-off-by: Greg Kroah-Hartman --- MAINTAINERS | 7 + include/uapi/linux/vbox_err.h | 151 +++++++++++++++ include/uapi/linux/vbox_vmmdev_types.h | 226 ++++++++++++++++++++++ include/uapi/linux/vboxguest.h | 330 +++++++++++++++++++++++++++++++++ 4 files changed, 714 insertions(+) create mode 100644 include/uapi/linux/vbox_err.h create mode 100644 include/uapi/linux/vbox_vmmdev_types.h create mode 100644 include/uapi/linux/vboxguest.h (limited to 'include') diff --git a/MAINTAINERS b/MAINTAINERS index 8d83ed21a62b..405d4d28b612 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -14557,6 +14557,13 @@ S: Maintained F: drivers/virtio/virtio_input.c F: include/uapi/linux/virtio_input.h +VIRTUAL BOX GUEST DEVICE DRIVER +M: Hans de Goede +M: Arnd Bergmann +M: Greg Kroah-Hartman +S: Maintained +F: include/uapi/linux/vbox*.h + VIRTUAL SERIO DEVICE DRIVER M: Stephen Chandler Paul S: Maintained diff --git a/include/uapi/linux/vbox_err.h b/include/uapi/linux/vbox_err.h new file mode 100644 index 000000000000..7eae536ff1e6 --- /dev/null +++ b/include/uapi/linux/vbox_err.h @@ -0,0 +1,151 @@ +/* SPDX-License-Identifier: MIT */ +/* Copyright (C) 2017 Oracle Corporation */ + +#ifndef __UAPI_VBOX_ERR_H__ +#define __UAPI_VBOX_ERR_H__ + +#define VINF_SUCCESS 0 +#define VERR_GENERAL_FAILURE (-1) +#define VERR_INVALID_PARAMETER (-2) +#define VERR_INVALID_MAGIC (-3) +#define VERR_INVALID_HANDLE (-4) +#define VERR_LOCK_FAILED (-5) +#define VERR_INVALID_POINTER (-6) +#define VERR_IDT_FAILED (-7) +#define VERR_NO_MEMORY (-8) +#define VERR_ALREADY_LOADED (-9) +#define VERR_PERMISSION_DENIED (-10) +#define VERR_VERSION_MISMATCH (-11) +#define VERR_NOT_IMPLEMENTED (-12) +#define VERR_INVALID_FLAGS (-13) + +#define VERR_NOT_EQUAL (-18) +#define VERR_NOT_SYMLINK (-19) +#define VERR_NO_TMP_MEMORY (-20) +#define VERR_INVALID_FMODE (-21) +#define VERR_WRONG_ORDER (-22) +#define VERR_NO_TLS_FOR_SELF (-23) +#define VERR_FAILED_TO_SET_SELF_TLS (-24) +#define VERR_NO_CONT_MEMORY (-26) +#define VERR_NO_PAGE_MEMORY (-27) +#define VERR_THREAD_IS_DEAD (-29) +#define VERR_THREAD_NOT_WAITABLE (-30) +#define VERR_PAGE_TABLE_NOT_PRESENT (-31) +#define VERR_INVALID_CONTEXT (-32) +#define VERR_TIMER_BUSY (-33) +#define VERR_ADDRESS_CONFLICT (-34) +#define VERR_UNRESOLVED_ERROR (-35) +#define VERR_INVALID_FUNCTION (-36) +#define VERR_NOT_SUPPORTED (-37) +#define VERR_ACCESS_DENIED (-38) +#define VERR_INTERRUPTED (-39) +#define VERR_TIMEOUT (-40) +#define VERR_BUFFER_OVERFLOW (-41) +#define VERR_TOO_MUCH_DATA (-42) +#define VERR_MAX_THRDS_REACHED (-43) +#define VERR_MAX_PROCS_REACHED (-44) +#define VERR_SIGNAL_REFUSED (-45) +#define VERR_SIGNAL_PENDING (-46) +#define VERR_SIGNAL_INVALID (-47) +#define VERR_STATE_CHANGED (-48) +#define VERR_INVALID_UUID_FORMAT (-49) +#define VERR_PROCESS_NOT_FOUND (-50) +#define VERR_PROCESS_RUNNING (-51) +#define VERR_TRY_AGAIN (-52) +#define VERR_PARSE_ERROR (-53) +#define VERR_OUT_OF_RANGE (-54) +#define VERR_NUMBER_TOO_BIG (-55) +#define VERR_NO_DIGITS (-56) +#define VERR_NEGATIVE_UNSIGNED (-57) +#define VERR_NO_TRANSLATION (-58) + +#define VERR_NOT_FOUND (-78) +#define VERR_INVALID_STATE (-79) +#define VERR_OUT_OF_RESOURCES (-80) + +#define VERR_FILE_NOT_FOUND (-102) +#define VERR_PATH_NOT_FOUND (-103) +#define VERR_INVALID_NAME (-104) +#define VERR_ALREADY_EXISTS (-105) +#define VERR_TOO_MANY_OPEN_FILES (-106) +#define VERR_SEEK (-107) +#define VERR_NEGATIVE_SEEK (-108) +#define VERR_SEEK_ON_DEVICE (-109) +#define VERR_EOF (-110) +#define VERR_READ_ERROR (-111) +#define VERR_WRITE_ERROR (-112) +#define VERR_WRITE_PROTECT (-113) +#define VERR_SHARING_VIOLATION (-114) +#define VERR_FILE_LOCK_FAILED (-115) +#define VERR_FILE_LOCK_VIOLATION (-116) +#define VERR_CANT_CREATE (-117) +#define VERR_CANT_DELETE_DIRECTORY (-118) +#define VERR_NOT_SAME_DEVICE (-119) +#define VERR_FILENAME_TOO_LONG (-120) +#define VERR_MEDIA_NOT_PRESENT (-121) +#define VERR_MEDIA_NOT_RECOGNIZED (-122) +#define VERR_FILE_NOT_LOCKED (-123) +#define VERR_FILE_LOCK_LOST (-124) +#define VERR_DIR_NOT_EMPTY (-125) +#define VERR_NOT_A_DIRECTORY (-126) +#define VERR_IS_A_DIRECTORY (-127) +#define VERR_FILE_TOO_BIG (-128) + +#define VERR_NET_IO_ERROR (-400) +#define VERR_NET_OUT_OF_RESOURCES (-401) +#define VERR_NET_HOST_NOT_FOUND (-402) +#define VERR_NET_PATH_NOT_FOUND (-403) +#define VERR_NET_PRINT_ERROR (-404) +#define VERR_NET_NO_NETWORK (-405) +#define VERR_NET_NOT_UNIQUE_NAME (-406) + +#define VERR_NET_IN_PROGRESS (-436) +#define VERR_NET_ALREADY_IN_PROGRESS (-437) +#define VERR_NET_NOT_SOCKET (-438) +#define VERR_NET_DEST_ADDRESS_REQUIRED (-439) +#define VERR_NET_MSG_SIZE (-440) +#define VERR_NET_PROTOCOL_TYPE (-441) +#define VERR_NET_PROTOCOL_NOT_AVAILABLE (-442) +#define VERR_NET_PROTOCOL_NOT_SUPPORTED (-443) +#define VERR_NET_SOCKET_TYPE_NOT_SUPPORTED (-444) +#define VERR_NET_OPERATION_NOT_SUPPORTED (-445) +#define VERR_NET_PROTOCOL_FAMILY_NOT_SUPPORTED (-446) +#define VERR_NET_ADDRESS_FAMILY_NOT_SUPPORTED (-447) +#define VERR_NET_ADDRESS_IN_USE (-448) +#define VERR_NET_ADDRESS_NOT_AVAILABLE (-449) +#define VERR_NET_DOWN (-450) +#define VERR_NET_UNREACHABLE (-451) +#define VERR_NET_CONNECTION_RESET (-452) +#define VERR_NET_CONNECTION_ABORTED (-453) +#define VERR_NET_CONNECTION_RESET_BY_PEER (-454) +#define VERR_NET_NO_BUFFER_SPACE (-455) +#define VERR_NET_ALREADY_CONNECTED (-456) +#define VERR_NET_NOT_CONNECTED (-457) +#define VERR_NET_SHUTDOWN (-458) +#define VERR_NET_TOO_MANY_REFERENCES (-459) +#define VERR_NET_CONNECTION_TIMED_OUT (-460) +#define VERR_NET_CONNECTION_REFUSED (-461) +#define VERR_NET_HOST_DOWN (-464) +#define VERR_NET_HOST_UNREACHABLE (-465) +#define VERR_NET_PROTOCOL_ERROR (-466) +#define VERR_NET_INCOMPLETE_TX_PACKET (-467) + +/* misc. unsorted codes */ +#define VERR_RESOURCE_BUSY (-138) +#define VERR_DISK_FULL (-152) +#define VERR_TOO_MANY_SYMLINKS (-156) +#define VERR_NO_MORE_FILES (-201) +#define VERR_INTERNAL_ERROR (-225) +#define VERR_INTERNAL_ERROR_2 (-226) +#define VERR_INTERNAL_ERROR_3 (-227) +#define VERR_INTERNAL_ERROR_4 (-228) +#define VERR_DEV_IO_ERROR (-250) +#define VERR_IO_BAD_LENGTH (-255) +#define VERR_BROKEN_PIPE (-301) +#define VERR_NO_DATA (-304) +#define VERR_SEM_DESTROYED (-363) +#define VERR_DEADLOCK (-365) +#define VERR_BAD_EXE_FORMAT (-608) +#define VINF_HGCM_ASYNC_EXECUTE (2903) + +#endif diff --git a/include/uapi/linux/vbox_vmmdev_types.h b/include/uapi/linux/vbox_vmmdev_types.h new file mode 100644 index 000000000000..0e68024f36c7 --- /dev/null +++ b/include/uapi/linux/vbox_vmmdev_types.h @@ -0,0 +1,226 @@ +/* SPDX-License-Identifier: (GPL-2.0 OR CDDL-1.0) */ +/* + * Virtual Device for Guest <-> VMM/Host communication, type definitions + * which are also used for the vboxguest ioctl interface / by vboxsf + * + * Copyright (C) 2006-2016 Oracle Corporation + */ + +#ifndef __UAPI_VBOX_VMMDEV_TYPES_H__ +#define __UAPI_VBOX_VMMDEV_TYPES_H__ + +#include +#include + +/* + * We cannot use linux' compiletime_assert here because it expects to be used + * inside a function only. Use a typedef to a char array with a negative size. + */ +#define VMMDEV_ASSERT_SIZE(type, size) \ + typedef char type ## _asrt_size[1 - 2*!!(sizeof(struct type) != (size))] + +/** enum vmmdev_request_type - VMMDev request types. */ +enum vmmdev_request_type { + VMMDEVREQ_INVALID_REQUEST = 0, + VMMDEVREQ_GET_MOUSE_STATUS = 1, + VMMDEVREQ_SET_MOUSE_STATUS = 2, + VMMDEVREQ_SET_POINTER_SHAPE = 3, + VMMDEVREQ_GET_HOST_VERSION = 4, + VMMDEVREQ_IDLE = 5, + VMMDEVREQ_GET_HOST_TIME = 10, + VMMDEVREQ_GET_HYPERVISOR_INFO = 20, + VMMDEVREQ_SET_HYPERVISOR_INFO = 21, + VMMDEVREQ_REGISTER_PATCH_MEMORY = 22, /* since version 3.0.6 */ + VMMDEVREQ_DEREGISTER_PATCH_MEMORY = 23, /* since version 3.0.6 */ + VMMDEVREQ_SET_POWER_STATUS = 30, + VMMDEVREQ_ACKNOWLEDGE_EVENTS = 41, + VMMDEVREQ_CTL_GUEST_FILTER_MASK = 42, + VMMDEVREQ_REPORT_GUEST_INFO = 50, + VMMDEVREQ_REPORT_GUEST_INFO2 = 58, /* since version 3.2.0 */ + VMMDEVREQ_REPORT_GUEST_STATUS = 59, /* since version 3.2.8 */ + VMMDEVREQ_REPORT_GUEST_USER_STATE = 74, /* since version 4.3 */ + /* Retrieve a display resize request sent by the host, deprecated. */ + VMMDEVREQ_GET_DISPLAY_CHANGE_REQ = 51, + VMMDEVREQ_VIDEMODE_SUPPORTED = 52, + VMMDEVREQ_GET_HEIGHT_REDUCTION = 53, + /** + * @VMMDEVREQ_GET_DISPLAY_CHANGE_REQ2: + * Retrieve a display resize request sent by the host. + * + * Queries a display resize request sent from the host. If the + * event_ack member is sent to true and there is an unqueried request + * available for one of the virtual display then that request will + * be returned. If several displays have unqueried requests the lowest + * numbered display will be chosen first. Only the most recent unseen + * request for each display is remembered. + * If event_ack is set to false, the last host request queried with + * event_ack set is resent, or failing that the most recent received + * from the host. If no host request was ever received then all zeros + * are returned. + */ + VMMDEVREQ_GET_DISPLAY_CHANGE_REQ2 = 54, + VMMDEVREQ_REPORT_GUEST_CAPABILITIES = 55, + VMMDEVREQ_SET_GUEST_CAPABILITIES = 56, + VMMDEVREQ_VIDEMODE_SUPPORTED2 = 57, /* since version 3.2.0 */ + VMMDEVREQ_GET_DISPLAY_CHANGE_REQEX = 80, /* since version 4.2.4 */ + VMMDEVREQ_HGCM_CONNECT = 60, + VMMDEVREQ_HGCM_DISCONNECT = 61, + VMMDEVREQ_HGCM_CALL32 = 62, + VMMDEVREQ_HGCM_CALL64 = 63, + VMMDEVREQ_HGCM_CANCEL = 64, + VMMDEVREQ_HGCM_CANCEL2 = 65, + VMMDEVREQ_VIDEO_ACCEL_ENABLE = 70, + VMMDEVREQ_VIDEO_ACCEL_FLUSH = 71, + VMMDEVREQ_VIDEO_SET_VISIBLE_REGION = 72, + VMMDEVREQ_GET_SEAMLESS_CHANGE_REQ = 73, + VMMDEVREQ_QUERY_CREDENTIALS = 100, + VMMDEVREQ_REPORT_CREDENTIALS_JUDGEMENT = 101, + VMMDEVREQ_REPORT_GUEST_STATS = 110, + VMMDEVREQ_GET_MEMBALLOON_CHANGE_REQ = 111, + VMMDEVREQ_GET_STATISTICS_CHANGE_REQ = 112, + VMMDEVREQ_CHANGE_MEMBALLOON = 113, + VMMDEVREQ_GET_VRDPCHANGE_REQ = 150, + VMMDEVREQ_LOG_STRING = 200, + VMMDEVREQ_GET_CPU_HOTPLUG_REQ = 210, + VMMDEVREQ_SET_CPU_HOTPLUG_STATUS = 211, + VMMDEVREQ_REGISTER_SHARED_MODULE = 212, + VMMDEVREQ_UNREGISTER_SHARED_MODULE = 213, + VMMDEVREQ_CHECK_SHARED_MODULES = 214, + VMMDEVREQ_GET_PAGE_SHARING_STATUS = 215, + VMMDEVREQ_DEBUG_IS_PAGE_SHARED = 216, + VMMDEVREQ_GET_SESSION_ID = 217, /* since version 3.2.8 */ + VMMDEVREQ_WRITE_COREDUMP = 218, + VMMDEVREQ_GUEST_HEARTBEAT = 219, + VMMDEVREQ_HEARTBEAT_CONFIGURE = 220, + /* Ensure the enum is a 32 bit data-type */ + VMMDEVREQ_SIZEHACK = 0x7fffffff +}; + +#if __BITS_PER_LONG == 64 +#define VMMDEVREQ_HGCM_CALL VMMDEVREQ_HGCM_CALL64 +#else +#define VMMDEVREQ_HGCM_CALL VMMDEVREQ_HGCM_CALL32 +#endif + +/** HGCM service location types. */ +enum vmmdev_hgcm_service_location_type { + VMMDEV_HGCM_LOC_INVALID = 0, + VMMDEV_HGCM_LOC_LOCALHOST = 1, + VMMDEV_HGCM_LOC_LOCALHOST_EXISTING = 2, + /* Ensure the enum is a 32 bit data-type */ + VMMDEV_HGCM_LOC_SIZEHACK = 0x7fffffff +}; + +/** HGCM host service location. */ +struct vmmdev_hgcm_service_location_localhost { + /** Service name */ + char service_name[128]; +}; +VMMDEV_ASSERT_SIZE(vmmdev_hgcm_service_location_localhost, 128); + +/** HGCM service location. */ +struct vmmdev_hgcm_service_location { + /** Type of the location. */ + enum vmmdev_hgcm_service_location_type type; + + union { + struct vmmdev_hgcm_service_location_localhost localhost; + } u; +}; +VMMDEV_ASSERT_SIZE(vmmdev_hgcm_service_location, 128 + 4); + +/** HGCM function parameter type. */ +enum vmmdev_hgcm_function_parameter_type { + VMMDEV_HGCM_PARM_TYPE_INVALID = 0, + VMMDEV_HGCM_PARM_TYPE_32BIT = 1, + VMMDEV_HGCM_PARM_TYPE_64BIT = 2, + /** Deprecated Doesn't work, use PAGELIST. */ + VMMDEV_HGCM_PARM_TYPE_PHYSADDR = 3, + /** In and Out, user-memory */ + VMMDEV_HGCM_PARM_TYPE_LINADDR = 4, + /** In, user-memory (read; host<-guest) */ + VMMDEV_HGCM_PARM_TYPE_LINADDR_IN = 5, + /** Out, user-memory (write; host->guest) */ + VMMDEV_HGCM_PARM_TYPE_LINADDR_OUT = 6, + /** In and Out, kernel-memory */ + VMMDEV_HGCM_PARM_TYPE_LINADDR_KERNEL = 7, + /** In, kernel-memory (read; host<-guest) */ + VMMDEV_HGCM_PARM_TYPE_LINADDR_KERNEL_IN = 8, + /** Out, kernel-memory (write; host->guest) */ + VMMDEV_HGCM_PARM_TYPE_LINADDR_KERNEL_OUT = 9, + /** Physical addresses of locked pages for a buffer. */ + VMMDEV_HGCM_PARM_TYPE_PAGELIST = 10, + /* Ensure the enum is a 32 bit data-type */ + VMMDEV_HGCM_PARM_TYPE_SIZEHACK = 0x7fffffff +}; + +/** HGCM function parameter, 32-bit client. */ +struct vmmdev_hgcm_function_parameter32 { + enum vmmdev_hgcm_function_parameter_type type; + union { + __u32 value32; + __u64 value64; + struct { + __u32 size; + union { + __u32 phys_addr; + __u32 linear_addr; + } u; + } pointer; + struct { + /** Size of the buffer described by the page list. */ + __u32 size; + /** Relative to the request header. */ + __u32 offset; + } page_list; + } u; +} __packed; +VMMDEV_ASSERT_SIZE(vmmdev_hgcm_function_parameter32, 4 + 8); + +/** HGCM function parameter, 64-bit client. */ +struct vmmdev_hgcm_function_parameter64 { + enum vmmdev_hgcm_function_parameter_type type; + union { + __u32 value32; + __u64 value64; + struct { + __u32 size; + union { + __u64 phys_addr; + __u64 linear_addr; + } u; + } __packed pointer; + struct { + /** Size of the buffer described by the page list. */ + __u32 size; + /** Relative to the request header. */ + __u32 offset; + } page_list; + } __packed u; +} __packed; +VMMDEV_ASSERT_SIZE(vmmdev_hgcm_function_parameter64, 4 + 12); + +#if __BITS_PER_LONG == 64 +#define vmmdev_hgcm_function_parameter vmmdev_hgcm_function_parameter64 +#else +#define vmmdev_hgcm_function_parameter vmmdev_hgcm_function_parameter32 +#endif + +#define VMMDEV_HGCM_F_PARM_DIRECTION_NONE 0x00000000U +#define VMMDEV_HGCM_F_PARM_DIRECTION_TO_HOST 0x00000001U +#define VMMDEV_HGCM_F_PARM_DIRECTION_FROM_HOST 0x00000002U +#define VMMDEV_HGCM_F_PARM_DIRECTION_BOTH 0x00000003U + +/** + * struct vmmdev_hgcm_pagelist - VMMDEV_HGCM_PARM_TYPE_PAGELIST parameters + * point to this structure to actually describe the buffer. + */ +struct vmmdev_hgcm_pagelist { + __u32 flags; /** VMMDEV_HGCM_F_PARM_*. */ + __u16 offset_first_page; /** Data offset in the first page. */ + __u16 page_count; /** Number of pages. */ + __u64 pages[1]; /** Page addresses. */ +}; +VMMDEV_ASSERT_SIZE(vmmdev_hgcm_pagelist, 4 + 2 + 2 + 8); + +#endif diff --git a/include/uapi/linux/vboxguest.h b/include/uapi/linux/vboxguest.h new file mode 100644 index 000000000000..612f0c7d3558 --- /dev/null +++ b/include/uapi/linux/vboxguest.h @@ -0,0 +1,330 @@ +/* SPDX-License-Identifier: (GPL-2.0 OR CDDL-1.0) */ +/* + * VBoxGuest - VirtualBox Guest Additions Driver Interface. + * + * Copyright (C) 2006-2016 Oracle Corporation + */ + +#ifndef __UAPI_VBOXGUEST_H__ +#define __UAPI_VBOXGUEST_H__ + +#include +#include +#include +#include + +/* Version of vbg_ioctl_hdr structure. */ +#define VBG_IOCTL_HDR_VERSION 0x10001 +/* Default request type. Use this for non-VMMDev requests. */ +#define VBG_IOCTL_HDR_TYPE_DEFAULT 0 + +/** + * Common ioctl header. + * + * This is a mirror of vmmdev_request_header to prevent duplicating data and + * needing to verify things multiple times. + */ +struct vbg_ioctl_hdr { + /** IN: The request input size, and output size if size_out is zero. */ + __u32 size_in; + /** IN: Structure version (VBG_IOCTL_HDR_VERSION) */ + __u32 version; + /** IN: The VMMDev request type or VBG_IOCTL_HDR_TYPE_DEFAULT. */ + __u32 type; + /** + * OUT: The VBox status code of the operation, out direction only. + * This is a VINF_ or VERR_ value as defined in vbox_err.h. + */ + __s32 rc; + /** IN: Output size. Set to zero to use size_in as output size. */ + __u32 size_out; + /** Reserved, MBZ. */ + __u32 reserved; +}; +VMMDEV_ASSERT_SIZE(vbg_ioctl_hdr, 24); + + +/* + * The VBoxGuest I/O control version. + * + * As usual, the high word contains the major version and changes to it + * signifies incompatible changes. + * + * The lower word is the minor version number, it is increased when new + * functions are added or existing changed in a backwards compatible manner. + */ +#define VBG_IOC_VERSION 0x00010000u + +/** + * VBG_IOCTL_DRIVER_VERSION_INFO data structure + * + * Note VBG_IOCTL_DRIVER_VERSION_INFO may switch the session to a backwards + * compatible interface version if uClientVersion indicates older client code. + */ +struct vbg_ioctl_driver_version_info { + /** The header. */ + struct vbg_ioctl_hdr hdr; + union { + struct { + /** Requested interface version (VBG_IOC_VERSION). */ + __u32 req_version; + /** + * Minimum interface version number (typically the + * major version part of VBG_IOC_VERSION). + */ + __u32 min_version; + /** Reserved, MBZ. */ + __u32 reserved1; + /** Reserved, MBZ. */ + __u32 reserved2; + } in; + struct { + /** Version for this session (typ. VBG_IOC_VERSION). */ + __u32 session_version; + /** Version of the IDC interface (VBG_IOC_VERSION). */ + __u32 driver_version; + /** The SVN revision of the driver, or 0. */ + __u32 driver_revision; + /** Reserved \#1 (zero until defined). */ + __u32 reserved1; + /** Reserved \#2 (zero until defined). */ + __u32 reserved2; + } out; + } u; +}; +VMMDEV_ASSERT_SIZE(vbg_ioctl_driver_version_info, 24 + 20); + +#define VBG_IOCTL_DRIVER_VERSION_INFO \ + _IOWR('V', 0, struct vbg_ioctl_driver_version_info) + + +/* IOCTL to perform a VMM Device request less than 1KB in size. */ +#define VBG_IOCTL_VMMDEV_REQUEST(s) _IOC(_IOC_READ | _IOC_WRITE, 'V', 2, s) + + +/* IOCTL to perform a VMM Device request larger then 1KB. */ +#define VBG_IOCTL_VMMDEV_REQUEST_BIG _IOC(_IOC_READ | _IOC_WRITE, 'V', 3, 0) + + +/** VBG_IOCTL_HGCM_CONNECT data structure. */ +struct vbg_ioctl_hgcm_connect { + struct vbg_ioctl_hdr hdr; + union { + struct { + struct vmmdev_hgcm_service_location loc; + } in; + struct { + __u32 client_id; + } out; + } u; +}; +VMMDEV_ASSERT_SIZE(vbg_ioctl_hgcm_connect, 24 + 132); + +#define VBG_IOCTL_HGCM_CONNECT \ + _IOWR('V', 4, struct vbg_ioctl_hgcm_connect) + + +/** VBG_IOCTL_HGCM_DISCONNECT data structure. */ +struct vbg_ioctl_hgcm_disconnect { + struct vbg_ioctl_hdr hdr; + union { + struct { + __u32 client_id; + } in; + } u; +}; +VMMDEV_ASSERT_SIZE(vbg_ioctl_hgcm_disconnect, 24 + 4); + +#define VBG_IOCTL_HGCM_DISCONNECT \ + _IOWR('V', 5, struct vbg_ioctl_hgcm_disconnect) + + +/** VBG_IOCTL_HGCM_CALL data structure. */ +struct vbg_ioctl_hgcm_call { + /** The header. */ + struct vbg_ioctl_hdr hdr; + /** Input: The id of the caller. */ + __u32 client_id; + /** Input: Function number. */ + __u32 function; + /** + * Input: How long to wait (milliseconds) for completion before + * cancelling the call. Set to -1 to wait indefinitely. + */ + __u32 timeout_ms; + /** Interruptable flag, ignored for userspace calls. */ + __u8 interruptible; + /** Explicit padding, MBZ. */ + __u8 reserved; + /** + * Input: How many parameters following this structure. + * + * The parameters are either HGCMFunctionParameter64 or 32, + * depending on whether we're receiving a 64-bit or 32-bit request. + * + * The current maximum is 61 parameters (given a 1KB max request size, + * and a 64-bit parameter size of 16 bytes). + */ + __u16 parm_count; + /* + * Parameters follow in form: + * struct hgcm_function_parameter<32|64> parms[parm_count] + */ +}; +VMMDEV_ASSERT_SIZE(vbg_ioctl_hgcm_call, 24 + 16); + +#define VBG_IOCTL_HGCM_CALL_32(s) _IOC(_IOC_READ | _IOC_WRITE, 'V', 6, s) +#define VBG_IOCTL_HGCM_CALL_64(s) _IOC(_IOC_READ | _IOC_WRITE, 'V', 7, s) +#if __BITS_PER_LONG == 64 +#define VBG_IOCTL_HGCM_CALL(s) VBG_IOCTL_HGCM_CALL_64(s) +#else +#define VBG_IOCTL_HGCM_CALL(s) VBG_IOCTL_HGCM_CALL_32(s) +#endif + + +/** VBG_IOCTL_LOG data structure. */ +struct vbg_ioctl_log { + /** The header. */ + struct vbg_ioctl_hdr hdr; + union { + struct { + /** + * The log message, this may be zero terminated. If it + * is not zero terminated then the length is determined + * from the input size. + */ + char msg[1]; + } in; + } u; +}; + +#define VBG_IOCTL_LOG(s) _IOC(_IOC_READ | _IOC_WRITE, 'V', 9, s) + + +/** VBG_IOCTL_WAIT_FOR_EVENTS data structure. */ +struct vbg_ioctl_wait_for_events { + /** The header. */ + struct vbg_ioctl_hdr hdr; + union { + struct { + /** Timeout in milliseconds. */ + __u32 timeout_ms; + /** Events to wait for. */ + __u32 events; + } in; + struct { + /** Events that occurred. */ + __u32 events; + } out; + } u; +}; +VMMDEV_ASSERT_SIZE(vbg_ioctl_wait_for_events, 24 + 8); + +#define VBG_IOCTL_WAIT_FOR_EVENTS \ + _IOWR('V', 10, struct vbg_ioctl_wait_for_events) + + +/* + * IOCTL to VBoxGuest to interrupt (cancel) any pending + * VBG_IOCTL_WAIT_FOR_EVENTS and return. + * + * Handled inside the vboxguest driver and not seen by the host at all. + * After calling this, VBG_IOCTL_WAIT_FOR_EVENTS should no longer be called in + * the same session. Any VBOXGUEST_IOCTL_WAITEVENT calls in the same session + * done after calling this will directly exit with -EINTR. + */ +#define VBG_IOCTL_INTERRUPT_ALL_WAIT_FOR_EVENTS \ + _IOWR('V', 11, struct vbg_ioctl_hdr) + + +/** VBG_IOCTL_CHANGE_FILTER_MASK data structure. */ +struct vbg_ioctl_change_filter { + /** The header. */ + struct vbg_ioctl_hdr hdr; + union { + struct { + /** Flags to set. */ + __u32 or_mask; + /** Flags to remove. */ + __u32 not_mask; + } in; + } u; +}; +VMMDEV_ASSERT_SIZE(vbg_ioctl_change_filter, 24 + 8); + +/* IOCTL to VBoxGuest to control the event filter mask. */ +#define VBG_IOCTL_CHANGE_FILTER_MASK \ + _IOWR('V', 12, struct vbg_ioctl_change_filter) + + +/** VBG_IOCTL_CHANGE_GUEST_CAPABILITIES data structure. */ +struct vbg_ioctl_set_guest_caps { + /** The header. */ + struct vbg_ioctl_hdr hdr; + union { + struct { + /** Capabilities to set (VMMDEV_GUEST_SUPPORTS_XXX). */ + __u32 or_mask; + /** Capabilities to drop (VMMDEV_GUEST_SUPPORTS_XXX). */ + __u32 not_mask; + } in; + struct { + /** Capabilities held by the session after the call. */ + __u32 session_caps; + /** Capabilities for all the sessions after the call. */ + __u32 global_caps; + } out; + } u; +}; +VMMDEV_ASSERT_SIZE(vbg_ioctl_set_guest_caps, 24 + 8); + +#define VBG_IOCTL_CHANGE_GUEST_CAPABILITIES \ + _IOWR('V', 14, struct vbg_ioctl_set_guest_caps) + + +/** VBG_IOCTL_CHECK_BALLOON data structure. */ +struct vbg_ioctl_check_balloon { + /** The header. */ + struct vbg_ioctl_hdr hdr; + union { + struct { + /** The size of the balloon in chunks of 1MB. */ + __u32 balloon_chunks; + /** + * false = handled in R0, no further action required. + * true = allocate balloon memory in R3. + */ + __u8 handle_in_r3; + /** Explicit padding, MBZ. */ + __u8 padding[3]; + } out; + } u; +}; +VMMDEV_ASSERT_SIZE(vbg_ioctl_check_balloon, 24 + 8); + +/* + * IOCTL to check memory ballooning. + * + * The guest kernel module will ask the host for the current size of the + * balloon and adjust the size. Or it will set handle_in_r3 = true and R3 is + * responsible for allocating memory and calling VBG_IOCTL_CHANGE_BALLOON. + */ +#define VBG_IOCTL_CHECK_BALLOON \ + _IOWR('V', 17, struct vbg_ioctl_check_balloon) + + +/** VBG_IOCTL_WRITE_CORE_DUMP data structure. */ +struct vbg_ioctl_write_coredump { + struct vbg_ioctl_hdr hdr; + union { + struct { + __u32 flags; /** Flags (reserved, MBZ). */ + } in; + } u; +}; +VMMDEV_ASSERT_SIZE(vbg_ioctl_write_coredump, 24 + 4); + +#define VBG_IOCTL_WRITE_CORE_DUMP \ + _IOWR('V', 19, struct vbg_ioctl_write_coredump) + +#endif -- cgit v1.2.3-58-ga151 From 579db9d45cb4e8e7cedff9e6079331a1e2ea9f5d Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Thu, 30 Nov 2017 17:01:26 +0100 Subject: virt: Add vboxguest VMMDEV communication code This commits adds a header describing the hardware interface for the Virtual Box Guest PCI device used in Virtual Box virtual machines and utility functions for talking to the Virtual Box hypervisor over this interface. These utility functions will used both by the vboxguest driver for the PCI device which offers the /dev/vboxguest ioctl API and by the vboxfs driver which offers folder sharing support. Signed-off-by: Hans de Goede Reviewed-by: Larry Finger Signed-off-by: Greg Kroah-Hartman --- MAINTAINERS | 2 + drivers/virt/vboxguest/vboxguest_utils.c | 801 +++++++++++++++++++++++++++++++ drivers/virt/vboxguest/vmmdev.h | 449 +++++++++++++++++ include/linux/vbox_utils.h | 79 +++ 4 files changed, 1331 insertions(+) create mode 100644 drivers/virt/vboxguest/vboxguest_utils.c create mode 100644 drivers/virt/vboxguest/vmmdev.h create mode 100644 include/linux/vbox_utils.h (limited to 'include') diff --git a/MAINTAINERS b/MAINTAINERS index 405d4d28b612..673250e0867e 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -14562,7 +14562,9 @@ M: Hans de Goede M: Arnd Bergmann M: Greg Kroah-Hartman S: Maintained +F: include/linux/vbox_utils.h F: include/uapi/linux/vbox*.h +F: drivers/virt/vboxguest/ VIRTUAL SERIO DEVICE DRIVER M: Stephen Chandler Paul diff --git a/drivers/virt/vboxguest/vboxguest_utils.c b/drivers/virt/vboxguest/vboxguest_utils.c new file mode 100644 index 000000000000..8daea691fbb5 --- /dev/null +++ b/drivers/virt/vboxguest/vboxguest_utils.c @@ -0,0 +1,801 @@ +/* SPDX-License-Identifier: (GPL-2.0 OR CDDL-1.0) */ +/* + * vboxguest vmm-req and hgcm-call code, VBoxGuestR0LibHGCMInternal.cpp, + * VBoxGuestR0LibGenericRequest.cpp and RTErrConvertToErrno.cpp in vbox svn. + * + * Copyright (C) 2006-2016 Oracle Corporation + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "vboxguest_core.h" + +/* Get the pointer to the first parameter of a HGCM call request. */ +#define VMMDEV_HGCM_CALL_PARMS(a) \ + ((struct vmmdev_hgcm_function_parameter *)( \ + (u8 *)(a) + sizeof(struct vmmdev_hgcm_call))) + +/* The max parameter buffer size for a user request. */ +#define VBG_MAX_HGCM_USER_PARM (24 * SZ_1M) +/* The max parameter buffer size for a kernel request. */ +#define VBG_MAX_HGCM_KERNEL_PARM (16 * SZ_1M) + +#define VBG_DEBUG_PORT 0x504 + +/* This protects vbg_log_buf and serializes VBG_DEBUG_PORT accesses */ +static DEFINE_SPINLOCK(vbg_log_lock); +static char vbg_log_buf[128]; + +#define VBG_LOG(name, pr_func) \ +void name(const char *fmt, ...) \ +{ \ + unsigned long flags; \ + va_list args; \ + int i, count; \ + \ + va_start(args, fmt); \ + spin_lock_irqsave(&vbg_log_lock, flags); \ + \ + count = vscnprintf(vbg_log_buf, sizeof(vbg_log_buf), fmt, args);\ + for (i = 0; i < count; i++) \ + outb(vbg_log_buf[i], VBG_DEBUG_PORT); \ + \ + pr_func("%s", vbg_log_buf); \ + \ + spin_unlock_irqrestore(&vbg_log_lock, flags); \ + va_end(args); \ +} \ +EXPORT_SYMBOL(name) + +VBG_LOG(vbg_info, pr_info); +VBG_LOG(vbg_warn, pr_warn); +VBG_LOG(vbg_err, pr_err); +#if defined(DEBUG) && !defined(CONFIG_DYNAMIC_DEBUG) +VBG_LOG(vbg_debug, pr_debug); +#endif + +void *vbg_req_alloc(size_t len, enum vmmdev_request_type req_type) +{ + struct vmmdev_request_header *req; + + req = kmalloc(len, GFP_KERNEL | __GFP_DMA32); + if (!req) + return NULL; + + memset(req, 0xaa, len); + + req->size = len; + req->version = VMMDEV_REQUEST_HEADER_VERSION; + req->request_type = req_type; + req->rc = VERR_GENERAL_FAILURE; + req->reserved1 = 0; + req->reserved2 = 0; + + return req; +} + +/* Note this function returns a VBox status code, not a negative errno!! */ +int vbg_req_perform(struct vbg_dev *gdev, void *req) +{ + unsigned long phys_req = virt_to_phys(req); + + outl(phys_req, gdev->io_port + VMMDEV_PORT_OFF_REQUEST); + /* + * The host changes the request as a result of the outl, make sure + * the outl and any reads of the req happen in the correct order. + */ + mb(); + + return ((struct vmmdev_request_header *)req)->rc; +} + +static bool hgcm_req_done(struct vbg_dev *gdev, + struct vmmdev_hgcmreq_header *header) +{ + unsigned long flags; + bool done; + + spin_lock_irqsave(&gdev->event_spinlock, flags); + done = header->flags & VMMDEV_HGCM_REQ_DONE; + spin_unlock_irqrestore(&gdev->event_spinlock, flags); + + return done; +} + +int vbg_hgcm_connect(struct vbg_dev *gdev, + struct vmmdev_hgcm_service_location *loc, + u32 *client_id, int *vbox_status) +{ + struct vmmdev_hgcm_connect *hgcm_connect = NULL; + int rc; + + hgcm_connect = vbg_req_alloc(sizeof(*hgcm_connect), + VMMDEVREQ_HGCM_CONNECT); + if (!hgcm_connect) + return -ENOMEM; + + hgcm_connect->header.flags = 0; + memcpy(&hgcm_connect->loc, loc, sizeof(*loc)); + hgcm_connect->client_id = 0; + + rc = vbg_req_perform(gdev, hgcm_connect); + + if (rc == VINF_HGCM_ASYNC_EXECUTE) + wait_event(gdev->hgcm_wq, + hgcm_req_done(gdev, &hgcm_connect->header)); + + if (rc >= 0) { + *client_id = hgcm_connect->client_id; + rc = hgcm_connect->header.result; + } + + kfree(hgcm_connect); + + *vbox_status = rc; + return 0; +} +EXPORT_SYMBOL(vbg_hgcm_connect); + +int vbg_hgcm_disconnect(struct vbg_dev *gdev, u32 client_id, int *vbox_status) +{ + struct vmmdev_hgcm_disconnect *hgcm_disconnect = NULL; + int rc; + + hgcm_disconnect = vbg_req_alloc(sizeof(*hgcm_disconnect), + VMMDEVREQ_HGCM_DISCONNECT); + if (!hgcm_disconnect) + return -ENOMEM; + + hgcm_disconnect->header.flags = 0; + hgcm_disconnect->client_id = client_id; + + rc = vbg_req_perform(gdev, hgcm_disconnect); + + if (rc == VINF_HGCM_ASYNC_EXECUTE) + wait_event(gdev->hgcm_wq, + hgcm_req_done(gdev, &hgcm_disconnect->header)); + + if (rc >= 0) + rc = hgcm_disconnect->header.result; + + kfree(hgcm_disconnect); + + *vbox_status = rc; + return 0; +} +EXPORT_SYMBOL(vbg_hgcm_disconnect); + +static u32 hgcm_call_buf_size_in_pages(void *buf, u32 len) +{ + u32 size = PAGE_ALIGN(len + ((unsigned long)buf & ~PAGE_MASK)); + + return size >> PAGE_SHIFT; +} + +static void hgcm_call_add_pagelist_size(void *buf, u32 len, size_t *extra) +{ + u32 page_count; + + page_count = hgcm_call_buf_size_in_pages(buf, len); + *extra += offsetof(struct vmmdev_hgcm_pagelist, pages[page_count]); +} + +static int hgcm_call_preprocess_linaddr( + const struct vmmdev_hgcm_function_parameter *src_parm, + void **bounce_buf_ret, size_t *extra) +{ + void *buf, *bounce_buf; + bool copy_in; + u32 len; + int ret; + + buf = (void *)src_parm->u.pointer.u.linear_addr; + len = src_parm->u.pointer.size; + copy_in = src_parm->type != VMMDEV_HGCM_PARM_TYPE_LINADDR_OUT; + + if (len > VBG_MAX_HGCM_USER_PARM) + return -E2BIG; + + bounce_buf = kvmalloc(len, GFP_KERNEL); + if (!bounce_buf) + return -ENOMEM; + + if (copy_in) { + ret = copy_from_user(bounce_buf, (void __user *)buf, len); + if (ret) + return -EFAULT; + } else { + memset(bounce_buf, 0, len); + } + + *bounce_buf_ret = bounce_buf; + hgcm_call_add_pagelist_size(bounce_buf, len, extra); + return 0; +} + +/** + * Preprocesses the HGCM call, validate parameters, alloc bounce buffers and + * figure out how much extra storage we need for page lists. + * Return: 0 or negative errno value. + * @src_parm: Pointer to source function call parameters + * @parm_count: Number of function call parameters. + * @bounce_bufs_ret: Where to return the allocated bouncebuffer array + * @extra: Where to return the extra request space needed for + * physical page lists. + */ +static int hgcm_call_preprocess( + const struct vmmdev_hgcm_function_parameter *src_parm, + u32 parm_count, void ***bounce_bufs_ret, size_t *extra) +{ + void *buf, **bounce_bufs = NULL; + u32 i, len; + int ret; + + for (i = 0; i < parm_count; i++, src_parm++) { + switch (src_parm->type) { + case VMMDEV_HGCM_PARM_TYPE_32BIT: + case VMMDEV_HGCM_PARM_TYPE_64BIT: + break; + + case VMMDEV_HGCM_PARM_TYPE_LINADDR: + case VMMDEV_HGCM_PARM_TYPE_LINADDR_IN: + case VMMDEV_HGCM_PARM_TYPE_LINADDR_OUT: + if (!bounce_bufs) { + bounce_bufs = kcalloc(parm_count, + sizeof(void *), + GFP_KERNEL); + if (!bounce_bufs) + return -ENOMEM; + + *bounce_bufs_ret = bounce_bufs; + } + + ret = hgcm_call_preprocess_linaddr(src_parm, + &bounce_bufs[i], + extra); + if (ret) + return ret; + + break; + + case VMMDEV_HGCM_PARM_TYPE_LINADDR_KERNEL: + case VMMDEV_HGCM_PARM_TYPE_LINADDR_KERNEL_IN: + case VMMDEV_HGCM_PARM_TYPE_LINADDR_KERNEL_OUT: + buf = (void *)src_parm->u.pointer.u.linear_addr; + len = src_parm->u.pointer.size; + if (WARN_ON(len > VBG_MAX_HGCM_KERNEL_PARM)) + return -E2BIG; + + hgcm_call_add_pagelist_size(buf, len, extra); + break; + + default: + return -EINVAL; + } + } + + return 0; +} + +/** + * Translates linear address types to page list direction flags. + * + * Return: page list flags. + * @type: The type. + */ +static u32 hgcm_call_linear_addr_type_to_pagelist_flags( + enum vmmdev_hgcm_function_parameter_type type) +{ + switch (type) { + default: + WARN_ON(1); + /* Fall through */ + case VMMDEV_HGCM_PARM_TYPE_LINADDR: + case VMMDEV_HGCM_PARM_TYPE_LINADDR_KERNEL: + return VMMDEV_HGCM_F_PARM_DIRECTION_BOTH; + + case VMMDEV_HGCM_PARM_TYPE_LINADDR_IN: + case VMMDEV_HGCM_PARM_TYPE_LINADDR_KERNEL_IN: + return VMMDEV_HGCM_F_PARM_DIRECTION_TO_HOST; + + case VMMDEV_HGCM_PARM_TYPE_LINADDR_OUT: + case VMMDEV_HGCM_PARM_TYPE_LINADDR_KERNEL_OUT: + return VMMDEV_HGCM_F_PARM_DIRECTION_FROM_HOST; + } +} + +static void hgcm_call_init_linaddr(struct vmmdev_hgcm_call *call, + struct vmmdev_hgcm_function_parameter *dst_parm, void *buf, u32 len, + enum vmmdev_hgcm_function_parameter_type type, u32 *off_extra) +{ + struct vmmdev_hgcm_pagelist *dst_pg_lst; + struct page *page; + bool is_vmalloc; + u32 i, page_count; + + dst_parm->type = type; + + if (len == 0) { + dst_parm->u.pointer.size = 0; + dst_parm->u.pointer.u.linear_addr = 0; + return; + } + + dst_pg_lst = (void *)call + *off_extra; + page_count = hgcm_call_buf_size_in_pages(buf, len); + is_vmalloc = is_vmalloc_addr(buf); + + dst_parm->type = VMMDEV_HGCM_PARM_TYPE_PAGELIST; + dst_parm->u.page_list.size = len; + dst_parm->u.page_list.offset = *off_extra; + dst_pg_lst->flags = hgcm_call_linear_addr_type_to_pagelist_flags(type); + dst_pg_lst->offset_first_page = (unsigned long)buf & ~PAGE_MASK; + dst_pg_lst->page_count = page_count; + + for (i = 0; i < page_count; i++) { + if (is_vmalloc) + page = vmalloc_to_page(buf); + else + page = virt_to_page(buf); + + dst_pg_lst->pages[i] = page_to_phys(page); + buf += PAGE_SIZE; + } + + *off_extra += offsetof(struct vmmdev_hgcm_pagelist, pages[page_count]); +} + +/** + * Initializes the call request that we're sending to the host. + * @call: The call to initialize. + * @client_id: The client ID of the caller. + * @function: The function number of the function to call. + * @src_parm: Pointer to source function call parameters. + * @parm_count: Number of function call parameters. + * @bounce_bufs: The bouncebuffer array. + */ +static void hgcm_call_init_call( + struct vmmdev_hgcm_call *call, u32 client_id, u32 function, + const struct vmmdev_hgcm_function_parameter *src_parm, + u32 parm_count, void **bounce_bufs) +{ + struct vmmdev_hgcm_function_parameter *dst_parm = + VMMDEV_HGCM_CALL_PARMS(call); + u32 i, off_extra = (uintptr_t)(dst_parm + parm_count) - (uintptr_t)call; + void *buf; + + call->header.flags = 0; + call->header.result = VINF_SUCCESS; + call->client_id = client_id; + call->function = function; + call->parm_count = parm_count; + + for (i = 0; i < parm_count; i++, src_parm++, dst_parm++) { + switch (src_parm->type) { + case VMMDEV_HGCM_PARM_TYPE_32BIT: + case VMMDEV_HGCM_PARM_TYPE_64BIT: + *dst_parm = *src_parm; + break; + + case VMMDEV_HGCM_PARM_TYPE_LINADDR: + case VMMDEV_HGCM_PARM_TYPE_LINADDR_IN: + case VMMDEV_HGCM_PARM_TYPE_LINADDR_OUT: + hgcm_call_init_linaddr(call, dst_parm, bounce_bufs[i], + src_parm->u.pointer.size, + src_parm->type, &off_extra); + break; + + case VMMDEV_HGCM_PARM_TYPE_LINADDR_KERNEL: + case VMMDEV_HGCM_PARM_TYPE_LINADDR_KERNEL_IN: + case VMMDEV_HGCM_PARM_TYPE_LINADDR_KERNEL_OUT: + buf = (void *)src_parm->u.pointer.u.linear_addr; + hgcm_call_init_linaddr(call, dst_parm, buf, + src_parm->u.pointer.size, + src_parm->type, &off_extra); + break; + + default: + WARN_ON(1); + dst_parm->type = VMMDEV_HGCM_PARM_TYPE_INVALID; + } + } +} + +/** + * Tries to cancel a pending HGCM call. + * + * Return: VBox status code + */ +static int hgcm_cancel_call(struct vbg_dev *gdev, struct vmmdev_hgcm_call *call) +{ + int rc; + + /* + * We use a pre-allocated request for cancellations, which is + * protected by cancel_req_mutex. This means that all cancellations + * get serialized, this should be fine since they should be rare. + */ + mutex_lock(&gdev->cancel_req_mutex); + gdev->cancel_req->phys_req_to_cancel = virt_to_phys(call); + rc = vbg_req_perform(gdev, gdev->cancel_req); + mutex_unlock(&gdev->cancel_req_mutex); + + if (rc == VERR_NOT_IMPLEMENTED) { + call->header.flags |= VMMDEV_HGCM_REQ_CANCELLED; + call->header.header.request_type = VMMDEVREQ_HGCM_CANCEL; + + rc = vbg_req_perform(gdev, call); + if (rc == VERR_INVALID_PARAMETER) + rc = VERR_NOT_FOUND; + } + + if (rc >= 0) + call->header.flags |= VMMDEV_HGCM_REQ_CANCELLED; + + return rc; +} + +/** + * Performs the call and completion wait. + * Return: 0 or negative errno value. + * @gdev: The VBoxGuest device extension. + * @call: The call to execute. + * @timeout_ms: Timeout in ms. + * @leak_it: Where to return the leak it / free it, indicator. + * Cancellation fun. + */ +static int vbg_hgcm_do_call(struct vbg_dev *gdev, struct vmmdev_hgcm_call *call, + u32 timeout_ms, bool *leak_it) +{ + int rc, cancel_rc, ret; + long timeout; + + *leak_it = false; + + rc = vbg_req_perform(gdev, call); + + /* + * If the call failed, then pretend success. Upper layers will + * interpret the result code in the packet. + */ + if (rc < 0) { + call->header.result = rc; + return 0; + } + + if (rc != VINF_HGCM_ASYNC_EXECUTE) + return 0; + + /* Host decided to process the request asynchronously, wait for it */ + if (timeout_ms == U32_MAX) + timeout = MAX_SCHEDULE_TIMEOUT; + else + timeout = msecs_to_jiffies(timeout_ms); + + timeout = wait_event_interruptible_timeout( + gdev->hgcm_wq, + hgcm_req_done(gdev, &call->header), + timeout); + + /* timeout > 0 means hgcm_req_done has returned true, so success */ + if (timeout > 0) + return 0; + + if (timeout == 0) + ret = -ETIMEDOUT; + else + ret = -EINTR; + + /* Cancel the request */ + cancel_rc = hgcm_cancel_call(gdev, call); + if (cancel_rc >= 0) + return ret; + + /* + * Failed to cancel, this should mean that the cancel has lost the + * race with normal completion, wait while the host completes it. + */ + if (cancel_rc == VERR_NOT_FOUND || cancel_rc == VERR_SEM_DESTROYED) + timeout = msecs_to_jiffies(500); + else + timeout = msecs_to_jiffies(2000); + + timeout = wait_event_timeout(gdev->hgcm_wq, + hgcm_req_done(gdev, &call->header), + timeout); + + if (WARN_ON(timeout == 0)) { + /* We really should never get here */ + vbg_err("%s: Call timedout and cancellation failed, leaking the request\n", + __func__); + *leak_it = true; + return ret; + } + + /* The call has completed normally after all */ + return 0; +} + +/** + * Copies the result of the call back to the caller info structure and user + * buffers. + * Return: 0 or negative errno value. + * @call: HGCM call request. + * @dst_parm: Pointer to function call parameters destination. + * @parm_count: Number of function call parameters. + * @bounce_bufs: The bouncebuffer array. + */ +static int hgcm_call_copy_back_result( + const struct vmmdev_hgcm_call *call, + struct vmmdev_hgcm_function_parameter *dst_parm, + u32 parm_count, void **bounce_bufs) +{ + const struct vmmdev_hgcm_function_parameter *src_parm = + VMMDEV_HGCM_CALL_PARMS(call); + void __user *p; + int ret; + u32 i; + + /* Copy back parameters. */ + for (i = 0; i < parm_count; i++, src_parm++, dst_parm++) { + switch (dst_parm->type) { + case VMMDEV_HGCM_PARM_TYPE_32BIT: + case VMMDEV_HGCM_PARM_TYPE_64BIT: + *dst_parm = *src_parm; + break; + + case VMMDEV_HGCM_PARM_TYPE_PAGELIST: + dst_parm->u.page_list.size = src_parm->u.page_list.size; + break; + + case VMMDEV_HGCM_PARM_TYPE_LINADDR_IN: + case VMMDEV_HGCM_PARM_TYPE_LINADDR_KERNEL: + case VMMDEV_HGCM_PARM_TYPE_LINADDR_KERNEL_IN: + case VMMDEV_HGCM_PARM_TYPE_LINADDR_KERNEL_OUT: + dst_parm->u.pointer.size = src_parm->u.pointer.size; + break; + + case VMMDEV_HGCM_PARM_TYPE_LINADDR: + case VMMDEV_HGCM_PARM_TYPE_LINADDR_OUT: + dst_parm->u.pointer.size = src_parm->u.pointer.size; + + p = (void __user *)dst_parm->u.pointer.u.linear_addr; + ret = copy_to_user(p, bounce_bufs[i], + min(src_parm->u.pointer.size, + dst_parm->u.pointer.size)); + if (ret) + return -EFAULT; + break; + + default: + WARN_ON(1); + return -EINVAL; + } + } + + return 0; +} + +int vbg_hgcm_call(struct vbg_dev *gdev, u32 client_id, u32 function, + u32 timeout_ms, struct vmmdev_hgcm_function_parameter *parms, + u32 parm_count, int *vbox_status) +{ + struct vmmdev_hgcm_call *call; + void **bounce_bufs = NULL; + bool leak_it; + size_t size; + int i, ret; + + size = sizeof(struct vmmdev_hgcm_call) + + parm_count * sizeof(struct vmmdev_hgcm_function_parameter); + /* + * Validate and buffer the parameters for the call. This also increases + * call_size with the amount of extra space needed for page lists. + */ + ret = hgcm_call_preprocess(parms, parm_count, &bounce_bufs, &size); + if (ret) { + /* Even on error bounce bufs may still have been allocated */ + goto free_bounce_bufs; + } + + call = vbg_req_alloc(size, VMMDEVREQ_HGCM_CALL); + if (!call) { + ret = -ENOMEM; + goto free_bounce_bufs; + } + + hgcm_call_init_call(call, client_id, function, parms, parm_count, + bounce_bufs); + + ret = vbg_hgcm_do_call(gdev, call, timeout_ms, &leak_it); + if (ret == 0) { + *vbox_status = call->header.result; + ret = hgcm_call_copy_back_result(call, parms, parm_count, + bounce_bufs); + } + + if (!leak_it) + kfree(call); + +free_bounce_bufs: + if (bounce_bufs) { + for (i = 0; i < parm_count; i++) + kvfree(bounce_bufs[i]); + kfree(bounce_bufs); + } + + return ret; +} +EXPORT_SYMBOL(vbg_hgcm_call); + +#ifdef CONFIG_COMPAT +int vbg_hgcm_call32( + struct vbg_dev *gdev, u32 client_id, u32 function, u32 timeout_ms, + struct vmmdev_hgcm_function_parameter32 *parm32, u32 parm_count, + int *vbox_status) +{ + struct vmmdev_hgcm_function_parameter *parm64 = NULL; + u32 i, size; + int ret = 0; + + /* KISS allocate a temporary request and convert the parameters. */ + size = parm_count * sizeof(struct vmmdev_hgcm_function_parameter); + parm64 = kzalloc(size, GFP_KERNEL); + if (!parm64) + return -ENOMEM; + + for (i = 0; i < parm_count; i++) { + switch (parm32[i].type) { + case VMMDEV_HGCM_PARM_TYPE_32BIT: + parm64[i].type = VMMDEV_HGCM_PARM_TYPE_32BIT; + parm64[i].u.value32 = parm32[i].u.value32; + break; + + case VMMDEV_HGCM_PARM_TYPE_64BIT: + parm64[i].type = VMMDEV_HGCM_PARM_TYPE_64BIT; + parm64[i].u.value64 = parm32[i].u.value64; + break; + + case VMMDEV_HGCM_PARM_TYPE_LINADDR_OUT: + case VMMDEV_HGCM_PARM_TYPE_LINADDR: + case VMMDEV_HGCM_PARM_TYPE_LINADDR_IN: + parm64[i].type = parm32[i].type; + parm64[i].u.pointer.size = parm32[i].u.pointer.size; + parm64[i].u.pointer.u.linear_addr = + parm32[i].u.pointer.u.linear_addr; + break; + + default: + ret = -EINVAL; + } + if (ret < 0) + goto out_free; + } + + ret = vbg_hgcm_call(gdev, client_id, function, timeout_ms, + parm64, parm_count, vbox_status); + if (ret < 0) + goto out_free; + + /* Copy back. */ + for (i = 0; i < parm_count; i++, parm32++, parm64++) { + switch (parm64[i].type) { + case VMMDEV_HGCM_PARM_TYPE_32BIT: + parm32[i].u.value32 = parm64[i].u.value32; + break; + + case VMMDEV_HGCM_PARM_TYPE_64BIT: + parm32[i].u.value64 = parm64[i].u.value64; + break; + + case VMMDEV_HGCM_PARM_TYPE_LINADDR_OUT: + case VMMDEV_HGCM_PARM_TYPE_LINADDR: + case VMMDEV_HGCM_PARM_TYPE_LINADDR_IN: + parm32[i].u.pointer.size = parm64[i].u.pointer.size; + break; + + default: + WARN_ON(1); + ret = -EINVAL; + } + } + +out_free: + kfree(parm64); + return ret; +} +#endif + +static const int vbg_status_code_to_errno_table[] = { + [-VERR_ACCESS_DENIED] = -EPERM, + [-VERR_FILE_NOT_FOUND] = -ENOENT, + [-VERR_PROCESS_NOT_FOUND] = -ESRCH, + [-VERR_INTERRUPTED] = -EINTR, + [-VERR_DEV_IO_ERROR] = -EIO, + [-VERR_TOO_MUCH_DATA] = -E2BIG, + [-VERR_BAD_EXE_FORMAT] = -ENOEXEC, + [-VERR_INVALID_HANDLE] = -EBADF, + [-VERR_TRY_AGAIN] = -EAGAIN, + [-VERR_NO_MEMORY] = -ENOMEM, + [-VERR_INVALID_POINTER] = -EFAULT, + [-VERR_RESOURCE_BUSY] = -EBUSY, + [-VERR_ALREADY_EXISTS] = -EEXIST, + [-VERR_NOT_SAME_DEVICE] = -EXDEV, + [-VERR_NOT_A_DIRECTORY] = -ENOTDIR, + [-VERR_PATH_NOT_FOUND] = -ENOTDIR, + [-VERR_IS_A_DIRECTORY] = -EISDIR, + [-VERR_INVALID_PARAMETER] = -EINVAL, + [-VERR_TOO_MANY_OPEN_FILES] = -ENFILE, + [-VERR_INVALID_FUNCTION] = -ENOTTY, + [-VERR_SHARING_VIOLATION] = -ETXTBSY, + [-VERR_FILE_TOO_BIG] = -EFBIG, + [-VERR_DISK_FULL] = -ENOSPC, + [-VERR_SEEK_ON_DEVICE] = -ESPIPE, + [-VERR_WRITE_PROTECT] = -EROFS, + [-VERR_BROKEN_PIPE] = -EPIPE, + [-VERR_DEADLOCK] = -EDEADLK, + [-VERR_FILENAME_TOO_LONG] = -ENAMETOOLONG, + [-VERR_FILE_LOCK_FAILED] = -ENOLCK, + [-VERR_NOT_IMPLEMENTED] = -ENOSYS, + [-VERR_NOT_SUPPORTED] = -ENOSYS, + [-VERR_DIR_NOT_EMPTY] = -ENOTEMPTY, + [-VERR_TOO_MANY_SYMLINKS] = -ELOOP, + [-VERR_NO_DATA] = -ENODATA, + [-VERR_NET_NO_NETWORK] = -ENONET, + [-VERR_NET_NOT_UNIQUE_NAME] = -ENOTUNIQ, + [-VERR_NO_TRANSLATION] = -EILSEQ, + [-VERR_NET_NOT_SOCKET] = -ENOTSOCK, + [-VERR_NET_DEST_ADDRESS_REQUIRED] = -EDESTADDRREQ, + [-VERR_NET_MSG_SIZE] = -EMSGSIZE, + [-VERR_NET_PROTOCOL_TYPE] = -EPROTOTYPE, + [-VERR_NET_PROTOCOL_NOT_AVAILABLE] = -ENOPROTOOPT, + [-VERR_NET_PROTOCOL_NOT_SUPPORTED] = -EPROTONOSUPPORT, + [-VERR_NET_SOCKET_TYPE_NOT_SUPPORTED] = -ESOCKTNOSUPPORT, + [-VERR_NET_OPERATION_NOT_SUPPORTED] = -EOPNOTSUPP, + [-VERR_NET_PROTOCOL_FAMILY_NOT_SUPPORTED] = -EPFNOSUPPORT, + [-VERR_NET_ADDRESS_FAMILY_NOT_SUPPORTED] = -EAFNOSUPPORT, + [-VERR_NET_ADDRESS_IN_USE] = -EADDRINUSE, + [-VERR_NET_ADDRESS_NOT_AVAILABLE] = -EADDRNOTAVAIL, + [-VERR_NET_DOWN] = -ENETDOWN, + [-VERR_NET_UNREACHABLE] = -ENETUNREACH, + [-VERR_NET_CONNECTION_RESET] = -ENETRESET, + [-VERR_NET_CONNECTION_ABORTED] = -ECONNABORTED, + [-VERR_NET_CONNECTION_RESET_BY_PEER] = -ECONNRESET, + [-VERR_NET_NO_BUFFER_SPACE] = -ENOBUFS, + [-VERR_NET_ALREADY_CONNECTED] = -EISCONN, + [-VERR_NET_NOT_CONNECTED] = -ENOTCONN, + [-VERR_NET_SHUTDOWN] = -ESHUTDOWN, + [-VERR_NET_TOO_MANY_REFERENCES] = -ETOOMANYREFS, + [-VERR_TIMEOUT] = -ETIMEDOUT, + [-VERR_NET_CONNECTION_REFUSED] = -ECONNREFUSED, + [-VERR_NET_HOST_DOWN] = -EHOSTDOWN, + [-VERR_NET_HOST_UNREACHABLE] = -EHOSTUNREACH, + [-VERR_NET_ALREADY_IN_PROGRESS] = -EALREADY, + [-VERR_NET_IN_PROGRESS] = -EINPROGRESS, + [-VERR_MEDIA_NOT_PRESENT] = -ENOMEDIUM, + [-VERR_MEDIA_NOT_RECOGNIZED] = -EMEDIUMTYPE, +}; + +int vbg_status_code_to_errno(int rc) +{ + if (rc >= 0) + return 0; + + rc = -rc; + if (rc >= ARRAY_SIZE(vbg_status_code_to_errno_table) || + vbg_status_code_to_errno_table[rc] == 0) { + vbg_warn("%s: Unhandled err %d\n", __func__, -rc); + return -EPROTO; + } + + return vbg_status_code_to_errno_table[rc]; +} +EXPORT_SYMBOL(vbg_status_code_to_errno); diff --git a/drivers/virt/vboxguest/vmmdev.h b/drivers/virt/vboxguest/vmmdev.h new file mode 100644 index 000000000000..5e2ae978935d --- /dev/null +++ b/drivers/virt/vboxguest/vmmdev.h @@ -0,0 +1,449 @@ +/* SPDX-License-Identifier: (GPL-2.0 OR CDDL-1.0) */ +/* + * Virtual Device for Guest <-> VMM/Host communication interface + * + * Copyright (C) 2006-2016 Oracle Corporation + */ + +#ifndef __VBOX_VMMDEV_H__ +#define __VBOX_VMMDEV_H__ + +#include +#include +#include +#include + +/* Port for generic request interface (relative offset). */ +#define VMMDEV_PORT_OFF_REQUEST 0 + +/** Layout of VMMDEV RAM region that contains information for guest. */ +struct vmmdev_memory { + /** The size of this structure. */ + u32 size; + /** The structure version. (VMMDEV_MEMORY_VERSION) */ + u32 version; + + union { + struct { + /** Flag telling that VMMDev has events pending. */ + u8 have_events; + /** Explicit padding, MBZ. */ + u8 padding[3]; + } V1_04; + + struct { + /** Pending events flags, set by host. */ + u32 host_events; + /** Mask of events the guest wants, set by guest. */ + u32 guest_event_mask; + } V1_03; + } V; + + /* struct vbva_memory, not used */ +}; +VMMDEV_ASSERT_SIZE(vmmdev_memory, 8 + 8); + +/** Version of vmmdev_memory structure (vmmdev_memory::version). */ +#define VMMDEV_MEMORY_VERSION (1) + +/* Host mouse capabilities has been changed. */ +#define VMMDEV_EVENT_MOUSE_CAPABILITIES_CHANGED BIT(0) +/* HGCM event. */ +#define VMMDEV_EVENT_HGCM BIT(1) +/* A display change request has been issued. */ +#define VMMDEV_EVENT_DISPLAY_CHANGE_REQUEST BIT(2) +/* Credentials are available for judgement. */ +#define VMMDEV_EVENT_JUDGE_CREDENTIALS BIT(3) +/* The guest has been restored. */ +#define VMMDEV_EVENT_RESTORED BIT(4) +/* Seamless mode state changed. */ +#define VMMDEV_EVENT_SEAMLESS_MODE_CHANGE_REQUEST BIT(5) +/* Memory balloon size changed. */ +#define VMMDEV_EVENT_BALLOON_CHANGE_REQUEST BIT(6) +/* Statistics interval changed. */ +#define VMMDEV_EVENT_STATISTICS_INTERVAL_CHANGE_REQUEST BIT(7) +/* VRDP status changed. */ +#define VMMDEV_EVENT_VRDP BIT(8) +/* New mouse position data available. */ +#define VMMDEV_EVENT_MOUSE_POSITION_CHANGED BIT(9) +/* CPU hotplug event occurred. */ +#define VMMDEV_EVENT_CPU_HOTPLUG BIT(10) +/* The mask of valid events, for sanity checking. */ +#define VMMDEV_EVENT_VALID_EVENT_MASK 0x000007ffU + +/* + * Additions are allowed to work only if additions_major == vmmdev_current && + * additions_minor <= vmmdev_current. Additions version is reported to host + * (VMMDev) by VMMDEVREQ_REPORT_GUEST_INFO. + */ +#define VMMDEV_VERSION 0x00010004 +#define VMMDEV_VERSION_MAJOR (VMMDEV_VERSION >> 16) +#define VMMDEV_VERSION_MINOR (VMMDEV_VERSION & 0xffff) + +/* Maximum request packet size. */ +#define VMMDEV_MAX_VMMDEVREQ_SIZE 1048576 + +/* Version of vmmdev_request_header structure. */ +#define VMMDEV_REQUEST_HEADER_VERSION 0x10001 + +/** struct vmmdev_request_header - Generic VMMDev request header. */ +struct vmmdev_request_header { + /** IN: Size of the structure in bytes (including body). */ + u32 size; + /** IN: Version of the structure. */ + u32 version; + /** IN: Type of the request. */ + enum vmmdev_request_type request_type; + /** OUT: Return code. */ + s32 rc; + /** Reserved field no.1. MBZ. */ + u32 reserved1; + /** Reserved field no.2. MBZ. */ + u32 reserved2; +}; +VMMDEV_ASSERT_SIZE(vmmdev_request_header, 24); + +/** + * struct vmmdev_mouse_status - Mouse status request structure. + * + * Used by VMMDEVREQ_GET_MOUSE_STATUS and VMMDEVREQ_SET_MOUSE_STATUS. + */ +struct vmmdev_mouse_status { + /** header */ + struct vmmdev_request_header header; + /** Mouse feature mask. See VMMDEV_MOUSE_*. */ + u32 mouse_features; + /** Mouse x position. */ + s32 pointer_pos_x; + /** Mouse y position. */ + s32 pointer_pos_y; +}; +VMMDEV_ASSERT_SIZE(vmmdev_mouse_status, 24 + 12); + +/* The guest can (== wants to) handle absolute coordinates. */ +#define VMMDEV_MOUSE_GUEST_CAN_ABSOLUTE BIT(0) +/* + * The host can (== wants to) send absolute coordinates. + * (Input not captured.) + */ +#define VMMDEV_MOUSE_HOST_WANTS_ABSOLUTE BIT(1) +/* + * The guest can *NOT* switch to software cursor and therefore depends on the + * host cursor. + * + * When guest additions are installed and the host has promised to display the + * cursor itself, the guest installs a hardware mouse driver. Don't ask the + * guest to switch to a software cursor then. + */ +#define VMMDEV_MOUSE_GUEST_NEEDS_HOST_CURSOR BIT(2) +/* The host does NOT provide support for drawing the cursor itself. */ +#define VMMDEV_MOUSE_HOST_CANNOT_HWPOINTER BIT(3) +/* The guest can read VMMDev events to find out about pointer movement */ +#define VMMDEV_MOUSE_NEW_PROTOCOL BIT(4) +/* + * If the guest changes the status of the VMMDEV_MOUSE_GUEST_NEEDS_HOST_CURSOR + * bit, the host will honour this. + */ +#define VMMDEV_MOUSE_HOST_RECHECKS_NEEDS_HOST_CURSOR BIT(5) +/* + * The host supplies an absolute pointing device. The Guest Additions may + * wish to use this to decide whether to install their own driver. + */ +#define VMMDEV_MOUSE_HOST_HAS_ABS_DEV BIT(6) + +/* The minimum value our pointing device can return. */ +#define VMMDEV_MOUSE_RANGE_MIN 0 +/* The maximum value our pointing device can return. */ +#define VMMDEV_MOUSE_RANGE_MAX 0xFFFF + +/** + * struct vmmdev_host_version - VirtualBox host version request structure. + * + * VBG uses this to detect the precense of new features in the interface. + */ +struct vmmdev_host_version { + /** Header. */ + struct vmmdev_request_header header; + /** Major version. */ + u16 major; + /** Minor version. */ + u16 minor; + /** Build number. */ + u32 build; + /** SVN revision. */ + u32 revision; + /** Feature mask. */ + u32 features; +}; +VMMDEV_ASSERT_SIZE(vmmdev_host_version, 24 + 16); + +/* Physical page lists are supported by HGCM. */ +#define VMMDEV_HVF_HGCM_PHYS_PAGE_LIST BIT(0) + +/** + * struct vmmdev_mask - Structure to set / clear bits in a mask used for + * VMMDEVREQ_SET_GUEST_CAPABILITIES and VMMDEVREQ_CTL_GUEST_FILTER_MASK. + */ +struct vmmdev_mask { + /** Header. */ + struct vmmdev_request_header header; + /** Mask of bits to be set. */ + u32 or_mask; + /** Mask of bits to be cleared. */ + u32 not_mask; +}; +VMMDEV_ASSERT_SIZE(vmmdev_mask, 24 + 8); + +/* The guest supports seamless display rendering. */ +#define VMMDEV_GUEST_SUPPORTS_SEAMLESS BIT(0) +/* The guest supports mapping guest to host windows. */ +#define VMMDEV_GUEST_SUPPORTS_GUEST_HOST_WINDOW_MAPPING BIT(1) +/* + * The guest graphical additions are active. + * Used for fast activation and deactivation of certain graphical operations + * (e.g. resizing & seamless). The legacy VMMDEVREQ_REPORT_GUEST_CAPABILITIES + * request sets this automatically, but VMMDEVREQ_SET_GUEST_CAPABILITIES does + * not. + */ +#define VMMDEV_GUEST_SUPPORTS_GRAPHICS BIT(2) + +/** struct vmmdev_hypervisorinfo - Hypervisor info structure. */ +struct vmmdev_hypervisorinfo { + /** Header. */ + struct vmmdev_request_header header; + /** + * Guest virtual address of proposed hypervisor start. + * Not used by VMMDEVREQ_GET_HYPERVISOR_INFO. + */ + u32 hypervisor_start; + /** Hypervisor size in bytes. */ + u32 hypervisor_size; +}; +VMMDEV_ASSERT_SIZE(vmmdev_hypervisorinfo, 24 + 8); + +/** struct vmmdev_events - Pending events structure. */ +struct vmmdev_events { + /** Header. */ + struct vmmdev_request_header header; + /** OUT: Pending event mask. */ + u32 events; +}; +VMMDEV_ASSERT_SIZE(vmmdev_events, 24 + 4); + +#define VMMDEV_OSTYPE_LINUX26 0x53000 +#define VMMDEV_OSTYPE_X64 BIT(8) + +/** struct vmmdev_guestinfo - Guest information report. */ +struct vmmdev_guest_info { + /** Header. */ + struct vmmdev_request_header header; + /** + * The VMMDev interface version expected by additions. + * *Deprecated*, do not use anymore! Will be removed. + */ + u32 interface_version; + /** Guest OS type. */ + u32 os_type; +}; +VMMDEV_ASSERT_SIZE(vmmdev_guest_info, 24 + 8); + +/** struct vmmdev_guestinfo2 - Guest information report, version 2. */ +struct vmmdev_guest_info2 { + /** Header. */ + struct vmmdev_request_header header; + /** Major version. */ + u16 additions_major; + /** Minor version. */ + u16 additions_minor; + /** Build number. */ + u32 additions_build; + /** SVN revision. */ + u32 additions_revision; + /** Feature mask, currently unused. */ + u32 additions_features; + /** + * The intentional meaning of this field was: + * Some additional information, for example 'Beta 1' or something like + * that. + * + * The way it was implemented was implemented: VBG_VERSION_STRING. + * + * This means the first three members are duplicated in this field (if + * the guest build config is sane). So, the user must check this and + * chop it off before usage. There is, because of the Main code's blind + * trust in the field's content, no way back. + */ + char name[128]; +}; +VMMDEV_ASSERT_SIZE(vmmdev_guest_info2, 24 + 144); + +enum vmmdev_guest_facility_type { + VBOXGUEST_FACILITY_TYPE_UNKNOWN = 0, + VBOXGUEST_FACILITY_TYPE_VBOXGUEST_DRIVER = 20, + /* VBoxGINA / VBoxCredProv / pam_vbox. */ + VBOXGUEST_FACILITY_TYPE_AUTO_LOGON = 90, + VBOXGUEST_FACILITY_TYPE_VBOX_SERVICE = 100, + /* VBoxTray (Windows), VBoxClient (Linux, Unix). */ + VBOXGUEST_FACILITY_TYPE_VBOX_TRAY_CLIENT = 101, + VBOXGUEST_FACILITY_TYPE_SEAMLESS = 1000, + VBOXGUEST_FACILITY_TYPE_GRAPHICS = 1100, + VBOXGUEST_FACILITY_TYPE_ALL = 0x7ffffffe, + /* Ensure the enum is a 32 bit data-type */ + VBOXGUEST_FACILITY_TYPE_SIZEHACK = 0x7fffffff +}; + +enum vmmdev_guest_facility_status { + VBOXGUEST_FACILITY_STATUS_INACTIVE = 0, + VBOXGUEST_FACILITY_STATUS_PAUSED = 1, + VBOXGUEST_FACILITY_STATUS_PRE_INIT = 20, + VBOXGUEST_FACILITY_STATUS_INIT = 30, + VBOXGUEST_FACILITY_STATUS_ACTIVE = 50, + VBOXGUEST_FACILITY_STATUS_TERMINATING = 100, + VBOXGUEST_FACILITY_STATUS_TERMINATED = 101, + VBOXGUEST_FACILITY_STATUS_FAILED = 800, + VBOXGUEST_FACILITY_STATUS_UNKNOWN = 999, + /* Ensure the enum is a 32 bit data-type */ + VBOXGUEST_FACILITY_STATUS_SIZEHACK = 0x7fffffff +}; + +/** struct vmmdev_guest_status - Guest Additions status structure. */ +struct vmmdev_guest_status { + /** Header. */ + struct vmmdev_request_header header; + /** Facility the status is indicated for. */ + enum vmmdev_guest_facility_type facility; + /** Current guest status. */ + enum vmmdev_guest_facility_status status; + /** Flags, not used at the moment. */ + u32 flags; +}; +VMMDEV_ASSERT_SIZE(vmmdev_guest_status, 24 + 12); + +#define VMMDEV_MEMORY_BALLOON_CHUNK_SIZE (1048576) +#define VMMDEV_MEMORY_BALLOON_CHUNK_PAGES (1048576 / 4096) + +/** struct vmmdev_memballoon_info - Memory-balloon info structure. */ +struct vmmdev_memballoon_info { + /** Header. */ + struct vmmdev_request_header header; + /** Balloon size in megabytes. */ + u32 balloon_chunks; + /** Guest ram size in megabytes. */ + u32 phys_mem_chunks; + /** + * Setting this to VMMDEV_EVENT_BALLOON_CHANGE_REQUEST indicates that + * the request is a response to that event. + * (Don't confuse this with VMMDEVREQ_ACKNOWLEDGE_EVENTS.) + */ + u32 event_ack; +}; +VMMDEV_ASSERT_SIZE(vmmdev_memballoon_info, 24 + 12); + +/** struct vmmdev_memballoon_change - Change the size of the balloon. */ +struct vmmdev_memballoon_change { + /** Header. */ + struct vmmdev_request_header header; + /** The number of pages in the array. */ + u32 pages; + /** true = inflate, false = deflate. */ + u32 inflate; + /** Physical address (u64) of each page. */ + u64 phys_page[VMMDEV_MEMORY_BALLOON_CHUNK_PAGES]; +}; + +/** struct vmmdev_write_core_dump - Write Core Dump request data. */ +struct vmmdev_write_core_dump { + /** Header. */ + struct vmmdev_request_header header; + /** Flags (reserved, MBZ). */ + u32 flags; +}; +VMMDEV_ASSERT_SIZE(vmmdev_write_core_dump, 24 + 4); + +/** struct vmmdev_heartbeat - Heart beat check state structure. */ +struct vmmdev_heartbeat { + /** Header. */ + struct vmmdev_request_header header; + /** OUT: Guest heartbeat interval in nanosec. */ + u64 interval_ns; + /** Heartbeat check flag. */ + u8 enabled; + /** Explicit padding, MBZ. */ + u8 padding[3]; +} __packed; +VMMDEV_ASSERT_SIZE(vmmdev_heartbeat, 24 + 12); + +#define VMMDEV_HGCM_REQ_DONE BIT(0) +#define VMMDEV_HGCM_REQ_CANCELLED BIT(1) + +/** struct vmmdev_hgcmreq_header - vmmdev HGCM requests header. */ +struct vmmdev_hgcmreq_header { + /** Request header. */ + struct vmmdev_request_header header; + + /** HGCM flags. */ + u32 flags; + + /** Result code. */ + s32 result; +}; +VMMDEV_ASSERT_SIZE(vmmdev_hgcmreq_header, 24 + 8); + +/** struct vmmdev_hgcm_connect - HGCM connect request structure. */ +struct vmmdev_hgcm_connect { + /** HGCM request header. */ + struct vmmdev_hgcmreq_header header; + + /** IN: Description of service to connect to. */ + struct vmmdev_hgcm_service_location loc; + + /** OUT: Client identifier assigned by local instance of HGCM. */ + u32 client_id; +}; +VMMDEV_ASSERT_SIZE(vmmdev_hgcm_connect, 32 + 132 + 4); + +/** struct vmmdev_hgcm_disconnect - HGCM disconnect request structure. */ +struct vmmdev_hgcm_disconnect { + /** HGCM request header. */ + struct vmmdev_hgcmreq_header header; + + /** IN: Client identifier. */ + u32 client_id; +}; +VMMDEV_ASSERT_SIZE(vmmdev_hgcm_disconnect, 32 + 4); + +#define VMMDEV_HGCM_MAX_PARMS 32 + +/** struct vmmdev_hgcm_call - HGCM call request structure. */ +struct vmmdev_hgcm_call { + /* request header */ + struct vmmdev_hgcmreq_header header; + + /** IN: Client identifier. */ + u32 client_id; + /** IN: Service function number. */ + u32 function; + /** IN: Number of parameters. */ + u32 parm_count; + /** Parameters follow in form: HGCMFunctionParameter32|64 parms[X]; */ +}; +VMMDEV_ASSERT_SIZE(vmmdev_hgcm_call, 32 + 12); + +/** + * struct vmmdev_hgcm_cancel2 - HGCM cancel request structure, version 2. + * + * After the request header.rc will be: + * + * VINF_SUCCESS when cancelled. + * VERR_NOT_FOUND if the specified request cannot be found. + * VERR_INVALID_PARAMETER if the address is invalid valid. + */ +struct vmmdev_hgcm_cancel2 { + /** Header. */ + struct vmmdev_request_header header; + /** The physical address of the request to cancel. */ + u32 phys_req_to_cancel; +}; +VMMDEV_ASSERT_SIZE(vmmdev_hgcm_cancel2, 24 + 4); + +#endif diff --git a/include/linux/vbox_utils.h b/include/linux/vbox_utils.h new file mode 100644 index 000000000000..c71def6b310f --- /dev/null +++ b/include/linux/vbox_utils.h @@ -0,0 +1,79 @@ +/* SPDX-License-Identifier: (GPL-2.0 OR CDDL-1.0) */ +/* Copyright (C) 2006-2016 Oracle Corporation */ + +#ifndef __VBOX_UTILS_H__ +#define __VBOX_UTILS_H__ + +#include +#include + +struct vbg_dev; + +/** + * vboxguest logging functions, these log both to the backdoor and call + * the equivalent kernel pr_foo function. + */ +__printf(1, 2) void vbg_info(const char *fmt, ...); +__printf(1, 2) void vbg_warn(const char *fmt, ...); +__printf(1, 2) void vbg_err(const char *fmt, ...); + +/* Only use backdoor logging for non-dynamic debug builds */ +#if defined(DEBUG) && !defined(CONFIG_DYNAMIC_DEBUG) +__printf(1, 2) void vbg_debug(const char *fmt, ...); +#else +#define vbg_debug pr_debug +#endif + +/** + * Allocate memory for generic request and initialize the request header. + * + * Return: the allocated memory + * @len: Size of memory block required for the request. + * @req_type: The generic request type. + */ +void *vbg_req_alloc(size_t len, enum vmmdev_request_type req_type); + +/** + * Perform a generic request. + * + * Return: VBox status code + * @gdev: The Guest extension device. + * @req: Pointer to the request structure. + */ +int vbg_req_perform(struct vbg_dev *gdev, void *req); + +int vbg_hgcm_connect(struct vbg_dev *gdev, + struct vmmdev_hgcm_service_location *loc, + u32 *client_id, int *vbox_status); + +int vbg_hgcm_disconnect(struct vbg_dev *gdev, u32 client_id, int *vbox_status); + +int vbg_hgcm_call(struct vbg_dev *gdev, u32 client_id, u32 function, + u32 timeout_ms, struct vmmdev_hgcm_function_parameter *parms, + u32 parm_count, int *vbox_status); + +int vbg_hgcm_call32( + struct vbg_dev *gdev, u32 client_id, u32 function, u32 timeout_ms, + struct vmmdev_hgcm_function_parameter32 *parm32, u32 parm_count, + int *vbox_status); + +/** + * Convert a VirtualBox status code to a standard Linux kernel return value. + * Return: 0 or negative errno value. + * @rc: VirtualBox status code to convert. + */ +int vbg_status_code_to_errno(int rc); + +/** + * Helper for the vboxsf driver to get a reference to the guest device. + * Return: a pointer to the gdev; or a ERR_PTR value on error. + */ +struct vbg_dev *vbg_get_gdev(void); + +/** + * Helper for the vboxsf driver to put a guest device reference. + * @gdev: Reference returned by vbg_get_gdev to put. + */ +void vbg_put_gdev(struct vbg_dev *gdev); + +#endif -- cgit v1.2.3-58-ga151 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 'include') 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-58-ga151 From 297a344d5238c049fdc234c34dd7bb0d83f01271 Mon Sep 17 00:00:00 2001 From: Uwe Kleine-König Date: Tue, 19 Dec 2017 10:00:12 +0100 Subject: siox: add support for tracing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implement tracing for SIOX. There are events for the data that is written to the bus and for data being read from it. Acked-by: Gavin Schenk Signed-off-by: Uwe Kleine-König Signed-off-by: Greg Kroah-Hartman --- drivers/siox/siox-core.c | 12 +++++++++ include/trace/events/siox.h | 66 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+) create mode 100644 include/trace/events/siox.h (limited to 'include') diff --git a/drivers/siox/siox-core.c b/drivers/siox/siox-core.c index 16585c1b2b9e..fdfcdea25867 100644 --- a/drivers/siox/siox-core.c +++ b/drivers/siox/siox-core.c @@ -33,6 +33,9 @@ */ #define SIOX_STATUS_TYPE 0xf0 +#define CREATE_TRACE_POINTS +#include + static bool siox_is_registered; static void siox_master_lock(struct siox_master *smaster) @@ -126,6 +129,7 @@ static void siox_poll(struct siox_master *smaster) { struct siox_device *sdevice; size_t i = smaster->setbuf_len; + unsigned int devno = 0; int unsync_error = 0; smaster->last_poll = jiffies; @@ -172,6 +176,10 @@ static void siox_poll(struct siox_master *smaster) sdevice->status_written &= ~SIOX_STATUS_WDG; smaster->buf[i] = sdevice->status_written; + + trace_siox_set_data(smaster, sdevice, devno, i); + + devno++; } smaster->pushpull(smaster, smaster->setbuf_len, smaster->buf, @@ -181,6 +189,7 @@ static void siox_poll(struct siox_master *smaster) unsync_error = 0; /* interpret data pulled in from devices in buf[setbuf_len..] */ + devno = 0; i = smaster->setbuf_len; list_for_each_entry(sdevice, &smaster->devices, node) { struct siox_driver *sdriver = @@ -255,10 +264,13 @@ static void siox_poll(struct siox_master *smaster) sdevice->status_written_lastcycle = sdevice->status_written; sdevice->connected = connected; + trace_siox_get_data(smaster, sdevice, devno, status_clean, i); + /* only give data read to driver if the device is connected */ if (sdriver && connected) sdriver->get_data(sdevice, &smaster->buf[i]); + devno++; i += sdevice->outbytes; } } diff --git a/include/trace/events/siox.h b/include/trace/events/siox.h new file mode 100644 index 000000000000..68a43fc2c3a5 --- /dev/null +++ b/include/trace/events/siox.h @@ -0,0 +1,66 @@ +#undef TRACE_SYSTEM +#define TRACE_SYSTEM siox + +#if !defined(_TRACE_SIOX_H) || defined(TRACE_HEADER_MULTI_READ) +#define _TRACE_SIOX_H + +#include + +TRACE_EVENT(siox_set_data, + TP_PROTO(const struct siox_master *smaster, + const struct siox_device *sdevice, + unsigned int devno, size_t bufoffset), + TP_ARGS(smaster, sdevice, devno, bufoffset), + TP_STRUCT__entry( + __field(int, busno) + __field(unsigned int, devno) + __field(size_t, inbytes) + __dynamic_array(u8, buf, sdevice->inbytes) + ), + TP_fast_assign( + __entry->busno = smaster->busno; + __entry->devno = devno; + __entry->inbytes = sdevice->inbytes; + memcpy(__get_dynamic_array(buf), + smaster->buf + bufoffset, sdevice->inbytes); + ), + TP_printk("siox-%d-%u [%*phD]", + __entry->busno, + __entry->devno, + (int)__entry->inbytes, __get_dynamic_array(buf) + ) +); + +TRACE_EVENT(siox_get_data, + TP_PROTO(const struct siox_master *smaster, + const struct siox_device *sdevice, + unsigned int devno, u8 status_clean, + size_t bufoffset), + TP_ARGS(smaster, sdevice, devno, status_clean, bufoffset), + TP_STRUCT__entry( + __field(int, busno) + __field(unsigned int, devno) + __field(u8, status_clean) + __field(size_t, outbytes) + __dynamic_array(u8, buf, sdevice->outbytes) + ), + TP_fast_assign( + __entry->busno = smaster->busno; + __entry->devno = devno; + __entry->status_clean = status_clean; + __entry->outbytes = sdevice->outbytes; + memcpy(__get_dynamic_array(buf), + smaster->buf + bufoffset, sdevice->outbytes); + ), + TP_printk("siox-%d-%u (%02hhx) [%*phD]", + __entry->busno, + __entry->devno, + __entry->status_clean, + (int)__entry->outbytes, __get_dynamic_array(buf) + ) +); + +#endif /* if !defined(_TRACE_SIOX_H) || defined(TRACE_HEADER_MULTI_READ) */ + +/* This part must be outside protection */ +#include -- cgit v1.2.3-58-ga151 From 3648e78ec701843ff8fab461071ba05067274f26 Mon Sep 17 00:00:00 2001 From: Sagar Dharia Date: Mon, 11 Dec 2017 23:42:57 +0000 Subject: slimbus: Add SLIMbus bus type SLIMbus (Serial Low Power Interchip Media Bus) is a specification developed by MIPI (Mobile Industry Processor Interface) alliance. SLIMbus is a 2-wire implementation, which is used to communicate with peripheral components like audio-codec. SLIMbus uses Time-Division-Multiplexing to accommodate multiple data channels, and control channel. Control channel has messages to do device-enumeration, messages to send/receive control-data to/from SLIMbus devices, messages for port/channel management, and messages to do bandwidth allocation. The framework supports multiple instances of the bus (1 controller per bus), and multiple slave devices per controller. This patch adds support to basic silmbus core which includes support to SLIMbus type, slimbus device registeration and some basic data structures. Signed-off-by: Sagar Dharia Signed-off-by: Srinivas Kandagatla Reviwed-by: Mark Brown Signed-off-by: Greg Kroah-Hartman --- drivers/Kconfig | 2 + drivers/Makefile | 1 + drivers/slimbus/Kconfig | 17 ++++++ drivers/slimbus/Makefile | 6 +++ drivers/slimbus/core.c | 108 +++++++++++++++++++++++++++++++++++++ include/linux/mod_devicetable.h | 13 +++++ include/linux/slimbus.h | 116 ++++++++++++++++++++++++++++++++++++++++ 7 files changed, 263 insertions(+) create mode 100644 drivers/slimbus/Kconfig create mode 100644 drivers/slimbus/Makefile create mode 100644 drivers/slimbus/core.c create mode 100644 include/linux/slimbus.h (limited to 'include') diff --git a/drivers/Kconfig b/drivers/Kconfig index 5458b623c00c..b57dcee2c938 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -213,4 +213,6 @@ source "drivers/opp/Kconfig" source "drivers/siox/Kconfig" +source "drivers/slimbus/Kconfig" + endmenu diff --git a/drivers/Makefile b/drivers/Makefile index 82b1e9a50989..3f862ef11797 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -87,6 +87,7 @@ obj-$(CONFIG_MTD) += mtd/ obj-$(CONFIG_SPI) += spi/ obj-$(CONFIG_SPMI) += spmi/ obj-$(CONFIG_HSI) += hsi/ +obj-$(CONFIG_SLIMBUS) += slimbus/ obj-y += net/ obj-$(CONFIG_ATM) += atm/ obj-$(CONFIG_FUSION) += message/ diff --git a/drivers/slimbus/Kconfig b/drivers/slimbus/Kconfig new file mode 100644 index 000000000000..9b6bb84d66ed --- /dev/null +++ b/drivers/slimbus/Kconfig @@ -0,0 +1,17 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# SLIMbus driver configuration +# +menuconfig SLIMBUS + tristate "SLIMbus support" + help + SLIMbus is standard interface between System-on-Chip and audio codec, + and other peripheral components in typical embedded systems. + + If unsure, choose N. + +if SLIMBUS + +# SLIMbus controllers + +endif diff --git a/drivers/slimbus/Makefile b/drivers/slimbus/Makefile new file mode 100644 index 000000000000..506ff17d6346 --- /dev/null +++ b/drivers/slimbus/Makefile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for kernel SLIMbus framework. +# +obj-$(CONFIG_SLIMBUS) += slimbus.o +slimbus-y := core.o diff --git a/drivers/slimbus/core.c b/drivers/slimbus/core.c new file mode 100644 index 000000000000..02f5075a9309 --- /dev/null +++ b/drivers/slimbus/core.c @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2011-2017, The Linux Foundation + */ + +#include +#include +#include +#include +#include + +static const struct slim_device_id *slim_match(const struct slim_device_id *id, + const struct slim_device *sbdev) +{ + while (id->manf_id != 0 || id->prod_code != 0) { + if (id->manf_id == sbdev->e_addr.manf_id && + id->prod_code == sbdev->e_addr.prod_code) + return id; + id++; + } + return NULL; +} + +static int slim_device_match(struct device *dev, struct device_driver *drv) +{ + struct slim_device *sbdev = to_slim_device(dev); + struct slim_driver *sbdrv = to_slim_driver(drv); + + return !!slim_match(sbdrv->id_table, sbdev); +} + +static int slim_device_probe(struct device *dev) +{ + struct slim_device *sbdev = to_slim_device(dev); + struct slim_driver *sbdrv = to_slim_driver(dev->driver); + + return sbdrv->probe(sbdev); +} + +static int slim_device_remove(struct device *dev) +{ + struct slim_device *sbdev = to_slim_device(dev); + struct slim_driver *sbdrv; + + if (dev->driver) { + sbdrv = to_slim_driver(dev->driver); + if (sbdrv->remove) + sbdrv->remove(sbdev); + } + + return 0; +} + +struct bus_type slimbus_bus = { + .name = "slimbus", + .match = slim_device_match, + .probe = slim_device_probe, + .remove = slim_device_remove, +}; +EXPORT_SYMBOL_GPL(slimbus_bus); + +/* + * __slim_driver_register() - Client driver registration with SLIMbus + * + * @drv:Client driver to be associated with client-device. + * @owner: owning module/driver + * + * This API will register the client driver with the SLIMbus + * It is called from the driver's module-init function. + */ +int __slim_driver_register(struct slim_driver *drv, struct module *owner) +{ + /* ID table and probe are mandatory */ + if (!drv->id_table || !drv->probe) + return -EINVAL; + + drv->driver.bus = &slimbus_bus; + drv->driver.owner = owner; + + return driver_register(&drv->driver); +} +EXPORT_SYMBOL_GPL(__slim_driver_register); + +/* + * slim_driver_unregister() - Undo effect of slim_driver_register + * + * @drv: Client driver to be unregistered + */ +void slim_driver_unregister(struct slim_driver *drv) +{ + driver_unregister(&drv->driver); +} +EXPORT_SYMBOL_GPL(slim_driver_unregister); + +static void __exit slimbus_exit(void) +{ + bus_unregister(&slimbus_bus); +} +module_exit(slimbus_exit); + +static int __init slimbus_init(void) +{ + return bus_register(&slimbus_bus); +} +postcore_initcall(slimbus_init); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("SLIMbus core"); diff --git a/include/linux/mod_devicetable.h b/include/linux/mod_devicetable.h index abb6dc2ebbf8..48e188327c02 100644 --- a/include/linux/mod_devicetable.h +++ b/include/linux/mod_devicetable.h @@ -452,6 +452,19 @@ struct spi_device_id { kernel_ulong_t driver_data; /* Data private to the driver */ }; +/* SLIMbus */ + +#define SLIMBUS_NAME_SIZE 32 +#define SLIMBUS_MODULE_PREFIX "slim:" + +struct slim_device_id { + __u16 manf_id, prod_code; + __u16 dev_index, instance; + + /* Data private to the driver */ + kernel_ulong_t driver_data; +}; + #define SPMI_NAME_SIZE 32 #define SPMI_MODULE_PREFIX "spmi:" diff --git a/include/linux/slimbus.h b/include/linux/slimbus.h new file mode 100644 index 000000000000..6b4ed290fbb0 --- /dev/null +++ b/include/linux/slimbus.h @@ -0,0 +1,116 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2011-2017, The Linux Foundation + */ + +#ifndef _LINUX_SLIMBUS_H +#define _LINUX_SLIMBUS_H +#include +#include +#include + +extern struct bus_type slimbus_bus; + +/** + * struct slim_eaddr - Enumeration address for a SLIMbus device + * @manf_id: Manufacturer Id for the device + * @prod_code: Product code + * @dev_index: Device index + * @instance: Instance value + */ +struct slim_eaddr { + u16 manf_id; + u16 prod_code; + u8 dev_index; + u8 instance; +} __packed; + +/** + * enum slim_device_status - slim device status + * @SLIM_DEVICE_STATUS_DOWN: Slim device is absent or not reported yet. + * @SLIM_DEVICE_STATUS_UP: Slim device is announced on the bus. + * @SLIM_DEVICE_STATUS_RESERVED: Reserved for future use. + */ +enum slim_device_status { + SLIM_DEVICE_STATUS_DOWN = 0, + SLIM_DEVICE_STATUS_UP, + SLIM_DEVICE_STATUS_RESERVED, +}; + +/** + * struct slim_device - Slim device handle. + * @dev: Driver model representation of the device. + * @e_addr: Enumeration address of this device. + * @status: slim device status + * @laddr: 1-byte Logical address of this device. + * @is_laddr_valid: indicates if the laddr is valid or not + * + * This is the client/device handle returned when a SLIMbus + * device is registered with a controller. + * Pointer to this structure is used by client-driver as a handle. + */ +struct slim_device { + struct device dev; + struct slim_eaddr e_addr; + enum slim_device_status status; + u8 laddr; + bool is_laddr_valid; +}; + +#define to_slim_device(d) container_of(d, struct slim_device, dev) + +/** + * struct slim_driver - SLIMbus 'generic device' (slave) device driver + * (similar to 'spi_device' on SPI) + * @probe: Binds this driver to a SLIMbus device. + * @remove: Unbinds this driver from the SLIMbus device. + * @shutdown: Standard shutdown callback used during powerdown/halt. + * @device_status: This callback is called when + * - The device reports present and gets a laddr assigned + * - The device reports absent, or the bus goes down. + * @driver: SLIMbus device drivers should initialize name and owner field of + * this structure + * @id_table: List of SLIMbus devices supported by this driver + */ + +struct slim_driver { + int (*probe)(struct slim_device *sl); + void (*remove)(struct slim_device *sl); + void (*shutdown)(struct slim_device *sl); + int (*device_status)(struct slim_device *sl, + enum slim_device_status s); + struct device_driver driver; + const struct slim_device_id *id_table; +}; +#define to_slim_driver(d) container_of(d, struct slim_driver, driver) + +/* + * use a macro to avoid include chaining to get THIS_MODULE + */ +#define slim_driver_register(drv) \ + __slim_driver_register(drv, THIS_MODULE) +int __slim_driver_register(struct slim_driver *drv, struct module *owner); +void slim_driver_unregister(struct slim_driver *drv); + +/** + * module_slim_driver() - Helper macro for registering a SLIMbus driver + * @__slim_driver: slimbus_driver struct + * + * Helper macro for SLIMbus drivers which do not do anything special in module + * init/exit. This eliminates a lot of boilerplate. Each module may only + * use this macro once, and calling it replaces module_init() and module_exit() + */ +#define module_slim_driver(__slim_driver) \ + module_driver(__slim_driver, slim_driver_register, \ + slim_driver_unregister) + +static inline void *slim_get_devicedata(const struct slim_device *dev) +{ + return dev_get_drvdata(&dev->dev); +} + +static inline void slim_set_devicedata(struct slim_device *dev, void *data) +{ + dev_set_drvdata(&dev->dev, data); +} +#endif /* _LINUX_SLIMBUS_H */ -- cgit v1.2.3-58-ga151 From 46a2bb5a7f7ea2728be50f8f5b29a20267f700fe Mon Sep 17 00:00:00 2001 From: Sagar Dharia Date: Mon, 11 Dec 2017 23:42:58 +0000 Subject: slimbus: core: Add slim controllers support This patch adds support to slim controllers in the slim core, including some utility functions invoked by the controller and slim device drivers. Signed-off-by: Srinivas Kandagatla Reviwed-by: Mark Brown Signed-off-by: Greg Kroah-Hartman --- drivers/slimbus/core.c | 306 ++++++++++++++++++++++++++++++++++++++++++++++ drivers/slimbus/slimbus.h | 108 ++++++++++++++++ include/linux/slimbus.h | 8 ++ 3 files changed, 422 insertions(+) create mode 100644 drivers/slimbus/slimbus.h (limited to 'include') diff --git a/drivers/slimbus/core.c b/drivers/slimbus/core.c index 02f5075a9309..ed53ae6bd1cc 100644 --- a/drivers/slimbus/core.c +++ b/drivers/slimbus/core.c @@ -7,7 +7,11 @@ #include #include #include +#include #include +#include "slimbus.h" + +static DEFINE_IDA(ctrl_ida); static const struct slim_device_id *slim_match(const struct slim_device_id *id, const struct slim_device *sbdev) @@ -92,6 +96,308 @@ void slim_driver_unregister(struct slim_driver *drv) } EXPORT_SYMBOL_GPL(slim_driver_unregister); +static void slim_dev_release(struct device *dev) +{ + struct slim_device *sbdev = to_slim_device(dev); + + kfree(sbdev); +} + +static int slim_add_device(struct slim_controller *ctrl, + struct slim_device *sbdev, + struct device_node *node) +{ + sbdev->dev.bus = &slimbus_bus; + sbdev->dev.parent = ctrl->dev; + sbdev->dev.release = slim_dev_release; + sbdev->dev.driver = NULL; + sbdev->ctrl = ctrl; + + dev_set_name(&sbdev->dev, "%x:%x:%x:%x", + sbdev->e_addr.manf_id, + sbdev->e_addr.prod_code, + sbdev->e_addr.dev_index, + sbdev->e_addr.instance); + + return device_register(&sbdev->dev); +} + +static struct slim_device *slim_alloc_device(struct slim_controller *ctrl, + struct slim_eaddr *eaddr, + struct device_node *node) +{ + struct slim_device *sbdev; + int ret; + + sbdev = kzalloc(sizeof(*sbdev), GFP_KERNEL); + if (!sbdev) + return NULL; + + sbdev->e_addr = *eaddr; + ret = slim_add_device(ctrl, sbdev, node); + if (ret) { + kfree(sbdev); + return NULL; + } + + return sbdev; +} + +/* + * slim_register_controller() - Controller bring-up and registration. + * + * @ctrl: Controller to be registered. + * + * A controller is registered with the framework using this API. + * If devices on a controller were registered before controller, + * this will make sure that they get probed when controller is up + */ +int slim_register_controller(struct slim_controller *ctrl) +{ + int id; + + id = ida_simple_get(&ctrl_ida, 0, 0, GFP_KERNEL); + if (id < 0) + return id; + + ctrl->id = id; + + if (!ctrl->min_cg) + ctrl->min_cg = SLIM_MIN_CLK_GEAR; + if (!ctrl->max_cg) + ctrl->max_cg = SLIM_MAX_CLK_GEAR; + + ida_init(&ctrl->laddr_ida); + idr_init(&ctrl->tid_idr); + mutex_init(&ctrl->lock); + + dev_dbg(ctrl->dev, "Bus [%s] registered:dev:%p\n", + ctrl->name, ctrl->dev); + + return 0; +} +EXPORT_SYMBOL_GPL(slim_register_controller); + +/* slim_remove_device: Remove the effect of slim_add_device() */ +static void slim_remove_device(struct slim_device *sbdev) +{ + device_unregister(&sbdev->dev); +} + +static int slim_ctrl_remove_device(struct device *dev, void *null) +{ + slim_remove_device(to_slim_device(dev)); + return 0; +} + +/** + * slim_unregister_controller() - Controller tear-down. + * + * @ctrl: Controller to tear-down. + */ +int slim_unregister_controller(struct slim_controller *ctrl) +{ + /* Remove all clients */ + device_for_each_child(ctrl->dev, NULL, slim_ctrl_remove_device); + ida_simple_remove(&ctrl_ida, ctrl->id); + + return 0; +} +EXPORT_SYMBOL_GPL(slim_unregister_controller); + +static void slim_device_update_status(struct slim_device *sbdev, + enum slim_device_status status) +{ + struct slim_driver *sbdrv; + + if (sbdev->status == status) + return; + + sbdev->status = status; + if (!sbdev->dev.driver) + return; + + sbdrv = to_slim_driver(sbdev->dev.driver); + if (sbdrv->device_status) + sbdrv->device_status(sbdev, sbdev->status); +} + +/** + * slim_report_absent() - Controller calls this function when a device + * reports absent, OR when the device cannot be communicated with + * + * @sbdev: Device that cannot be reached, or sent report absent + */ +void slim_report_absent(struct slim_device *sbdev) +{ + struct slim_controller *ctrl = sbdev->ctrl; + + if (!ctrl) + return; + + /* invalidate logical addresses */ + mutex_lock(&ctrl->lock); + sbdev->is_laddr_valid = false; + mutex_unlock(&ctrl->lock); + + ida_simple_remove(&ctrl->laddr_ida, sbdev->laddr); + slim_device_update_status(sbdev, SLIM_DEVICE_STATUS_DOWN); +} +EXPORT_SYMBOL_GPL(slim_report_absent); + +static bool slim_eaddr_equal(struct slim_eaddr *a, struct slim_eaddr *b) +{ + return (a->manf_id == b->manf_id && + a->prod_code == b->prod_code && + a->dev_index == b->dev_index && + a->instance == b->instance); +} + +static int slim_match_dev(struct device *dev, void *data) +{ + struct slim_eaddr *e_addr = data; + struct slim_device *sbdev = to_slim_device(dev); + + return slim_eaddr_equal(&sbdev->e_addr, e_addr); +} + +static struct slim_device *find_slim_device(struct slim_controller *ctrl, + struct slim_eaddr *eaddr) +{ + struct slim_device *sbdev; + struct device *dev; + + dev = device_find_child(ctrl->dev, eaddr, slim_match_dev); + if (dev) { + sbdev = to_slim_device(dev); + return sbdev; + } + + return NULL; +} + +/** + * slim_get_device() - get handle to a device. + * + * @ctrl: Controller on which this device will be added/queried + * @e_addr: Enumeration address of the device to be queried + * + * Return: pointer to a device if it has already reported. Creates a new + * device and returns pointer to it if the device has not yet enumerated. + */ +struct slim_device *slim_get_device(struct slim_controller *ctrl, + struct slim_eaddr *e_addr) +{ + struct slim_device *sbdev; + + sbdev = find_slim_device(ctrl, e_addr); + if (!sbdev) { + sbdev = slim_alloc_device(ctrl, e_addr, NULL); + if (!sbdev) + return ERR_PTR(-ENOMEM); + } + + return sbdev; +} +EXPORT_SYMBOL_GPL(slim_get_device); + +static int slim_device_alloc_laddr(struct slim_device *sbdev, + bool report_present) +{ + struct slim_controller *ctrl = sbdev->ctrl; + u8 laddr; + int ret; + + mutex_lock(&ctrl->lock); + if (ctrl->get_laddr) { + ret = ctrl->get_laddr(ctrl, &sbdev->e_addr, &laddr); + if (ret < 0) + goto err; + } else if (report_present) { + ret = ida_simple_get(&ctrl->laddr_ida, + 0, SLIM_LA_MANAGER - 1, GFP_KERNEL); + if (ret < 0) + goto err; + + laddr = ret; + } else { + ret = -EINVAL; + goto err; + } + + if (ctrl->set_laddr) { + ret = ctrl->set_laddr(ctrl, &sbdev->e_addr, laddr); + if (ret) { + ret = -EINVAL; + goto err; + } + } + + sbdev->laddr = laddr; + sbdev->is_laddr_valid = true; + + slim_device_update_status(sbdev, SLIM_DEVICE_STATUS_UP); + + dev_dbg(ctrl->dev, "setting slimbus l-addr:%x, ea:%x,%x,%x,%x\n", + laddr, sbdev->e_addr.manf_id, sbdev->e_addr.prod_code, + sbdev->e_addr.dev_index, sbdev->e_addr.instance); + +err: + mutex_unlock(&ctrl->lock); + return ret; + +} + +/** + * slim_device_report_present() - Report enumerated device. + * + * @ctrl: Controller with which device is enumerated. + * @e_addr: Enumeration address of the device. + * @laddr: Return logical address (if valid flag is false) + * + * Called by controller in response to REPORT_PRESENT. Framework will assign + * a logical address to this enumeration address. + * Function returns -EXFULL to indicate that all logical addresses are already + * taken. + */ +int slim_device_report_present(struct slim_controller *ctrl, + struct slim_eaddr *e_addr, u8 *laddr) +{ + struct slim_device *sbdev; + int ret; + + sbdev = slim_get_device(ctrl, e_addr); + if (IS_ERR(sbdev)) + return -ENODEV; + + if (sbdev->is_laddr_valid) { + *laddr = sbdev->laddr; + return 0; + } + + ret = slim_device_alloc_laddr(sbdev, true); + + return ret; +} +EXPORT_SYMBOL_GPL(slim_device_report_present); + +/** + * slim_get_logical_addr() - get/allocate logical address of a SLIMbus device. + * + * @sbdev: client handle requesting the address. + * + * Return: zero if a logical address is valid or a new logical address + * has been assigned. error code in case of error. + */ +int slim_get_logical_addr(struct slim_device *sbdev) +{ + if (!sbdev->is_laddr_valid) + return slim_device_alloc_laddr(sbdev, false); + + return 0; +} +EXPORT_SYMBOL_GPL(slim_get_logical_addr); + static void __exit slimbus_exit(void) { bus_unregister(&slimbus_bus); diff --git a/drivers/slimbus/slimbus.h b/drivers/slimbus/slimbus.h new file mode 100644 index 000000000000..66657722f50f --- /dev/null +++ b/drivers/slimbus/slimbus.h @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2011-2017, The Linux Foundation + */ + +#ifndef _DRIVERS_SLIMBUS_H +#define _DRIVERS_SLIMBUS_H +#include +#include +#include +#include + +/* Standard values per SLIMbus spec needed by controllers and devices */ +#define SLIM_MAX_CLK_GEAR 10 +#define SLIM_MIN_CLK_GEAR 1 + +/* Manager's logical address is set to 0xFF per spec */ +#define SLIM_LA_MANAGER 0xFF + +/** + * struct slim_framer - Represents SLIMbus framer. + * Every controller may have multiple framers. There is 1 active framer device + * responsible for clocking the bus. + * Manager is responsible for framer hand-over. + * @dev: Driver model representation of the device. + * @e_addr: Enumeration address of the framer. + * @rootfreq: Root Frequency at which the framer can run. This is maximum + * frequency ('clock gear 10') at which the bus can operate. + * @superfreq: Superframes per root frequency. Every frame is 6144 bits. + */ +struct slim_framer { + struct device dev; + struct slim_eaddr e_addr; + int rootfreq; + int superfreq; +}; + +#define to_slim_framer(d) container_of(d, struct slim_framer, dev) + +/** + * struct slim_controller - Controls every instance of SLIMbus + * (similar to 'master' on SPI) + * @dev: Device interface to this driver + * @id: Board-specific number identifier for this controller/bus + * @name: Name for this controller + * @min_cg: Minimum clock gear supported by this controller (default value: 1) + * @max_cg: Maximum clock gear supported by this controller (default value: 10) + * @clkgear: Current clock gear in which this bus is running + * @laddr_ida: logical address id allocator + * @a_framer: Active framer which is clocking the bus managed by this controller + * @lock: Mutex protecting controller data structures + * @devices: Slim device list + * @tid_idr: tid id allocator + * @txn_lock: Lock to protect table of transactions + * @set_laddr: Setup logical address at laddr for the slave with elemental + * address e_addr. Drivers implementing controller will be expected to + * send unicast message to this device with its logical address. + * @get_laddr: It is possible that controller needs to set fixed logical + * address table and get_laddr can be used in that case so that controller + * can do this assignment. Use case is when the master is on the remote + * processor side, who is resposible for allocating laddr. + * + * 'Manager device' is responsible for device management, bandwidth + * allocation, channel setup, and port associations per channel. + * Device management means Logical address assignment/removal based on + * enumeration (report-present, report-absent) of a device. + * Bandwidth allocation is done dynamically by the manager based on active + * channels on the bus, message-bandwidth requests made by SLIMbus devices. + * Based on current bandwidth usage, manager chooses a frequency to run + * the bus at (in steps of 'clock-gear', 1 through 10, each clock gear + * representing twice the frequency than the previous gear). + * Manager is also responsible for entering (and exiting) low-power-mode + * (known as 'clock pause'). + * Manager can do handover of framer if there are multiple framers on the + * bus and a certain usecase warrants using certain framer to avoid keeping + * previous framer being powered-on. + * + * Controller here performs duties of the manager device, and 'interface + * device'. Interface device is responsible for monitoring the bus and + * reporting information such as loss-of-synchronization, data + * slot-collision. + */ +struct slim_controller { + struct device *dev; + unsigned int id; + char name[SLIMBUS_NAME_SIZE]; + int min_cg; + int max_cg; + int clkgear; + struct ida laddr_ida; + struct slim_framer *a_framer; + struct mutex lock; + struct list_head devices; + struct idr tid_idr; + spinlock_t txn_lock; + int (*set_laddr)(struct slim_controller *ctrl, + struct slim_eaddr *ea, u8 laddr); + int (*get_laddr)(struct slim_controller *ctrl, + struct slim_eaddr *ea, u8 *laddr); +}; + +int slim_device_report_present(struct slim_controller *ctrl, + struct slim_eaddr *e_addr, u8 *laddr); +void slim_report_absent(struct slim_device *sbdev); +int slim_register_controller(struct slim_controller *ctrl); +int slim_unregister_controller(struct slim_controller *ctrl); + +#endif /* _LINUX_SLIMBUS_H */ diff --git a/include/linux/slimbus.h b/include/linux/slimbus.h index 6b4ed290fbb0..aeed98a683be 100644 --- a/include/linux/slimbus.h +++ b/include/linux/slimbus.h @@ -37,11 +37,14 @@ enum slim_device_status { SLIM_DEVICE_STATUS_RESERVED, }; +struct slim_controller; + /** * struct slim_device - Slim device handle. * @dev: Driver model representation of the device. * @e_addr: Enumeration address of this device. * @status: slim device status + * @ctrl: slim controller instance. * @laddr: 1-byte Logical address of this device. * @is_laddr_valid: indicates if the laddr is valid or not * @@ -52,6 +55,7 @@ enum slim_device_status { struct slim_device { struct device dev; struct slim_eaddr e_addr; + struct slim_controller *ctrl; enum slim_device_status status; u8 laddr; bool is_laddr_valid; @@ -113,4 +117,8 @@ static inline void slim_set_devicedata(struct slim_device *dev, void *data) { dev_set_drvdata(&dev->dev, data); } + +struct slim_device *slim_get_device(struct slim_controller *ctrl, + struct slim_eaddr *e_addr); +int slim_get_logical_addr(struct slim_device *sbdev); #endif /* _LINUX_SLIMBUS_H */ -- cgit v1.2.3-58-ga151 From afbdcc7c384b0d446da08b1e0901dc176b41b9e0 Mon Sep 17 00:00:00 2001 From: Sagar Dharia Date: Mon, 11 Dec 2017 23:43:00 +0000 Subject: slimbus: Add messaging APIs to slimbus framework SLIMbus devices use value-element, and information elements to control device parameters (e.g. value element is used to represent gain for codec, information element is used to represent interrupt status for codec when codec interrupt fires). Messaging APIs are used to set/get these value and information elements. SLIMbus specification uses 8-bit "transaction IDs" for messages where a read-value is anticipated. Framework uses a table of pointers to store those TIDs and responds back to the caller in O(1). Caller can do synchronous and asynchronous reads/writes. Signed-off-by: Srinivas Kandagatla Reviwed-by: Mark Brown Signed-off-by: Greg Kroah-Hartman --- drivers/slimbus/Makefile | 2 +- drivers/slimbus/messaging.c | 297 ++++++++++++++++++++++++++++++++++++++++++++ drivers/slimbus/slimbus.h | 67 ++++++++++ include/linux/slimbus.h | 40 ++++++ 4 files changed, 405 insertions(+), 1 deletion(-) create mode 100644 drivers/slimbus/messaging.c (limited to 'include') diff --git a/drivers/slimbus/Makefile b/drivers/slimbus/Makefile index 506ff17d6346..568a14c7be78 100644 --- a/drivers/slimbus/Makefile +++ b/drivers/slimbus/Makefile @@ -3,4 +3,4 @@ # Makefile for kernel SLIMbus framework. # obj-$(CONFIG_SLIMBUS) += slimbus.o -slimbus-y := core.o +slimbus-y := core.o messaging.o diff --git a/drivers/slimbus/messaging.c b/drivers/slimbus/messaging.c new file mode 100644 index 000000000000..031e67648d7c --- /dev/null +++ b/drivers/slimbus/messaging.c @@ -0,0 +1,297 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2011-2017, The Linux Foundation + */ + +#include +#include "slimbus.h" + +/** + * slim_msg_response() - Deliver Message response received from a device to the + * framework. + * + * @ctrl: Controller handle + * @reply: Reply received from the device + * @len: Length of the reply + * @tid: Transaction ID received with which framework can associate reply. + * + * Called by controller to inform framework about the response received. + * This helps in making the API asynchronous, and controller-driver doesn't need + * to manage 1 more table other than the one managed by framework mapping TID + * with buffers + */ +void slim_msg_response(struct slim_controller *ctrl, u8 *reply, u8 tid, u8 len) +{ + struct slim_msg_txn *txn; + struct slim_val_inf *msg; + unsigned long flags; + + spin_lock_irqsave(&ctrl->txn_lock, flags); + txn = idr_find(&ctrl->tid_idr, tid); + if (txn == NULL) { + spin_unlock_irqrestore(&ctrl->txn_lock, flags); + return; + } + + msg = txn->msg; + if (msg == NULL || msg->rbuf == NULL) { + dev_err(ctrl->dev, "Got response to invalid TID:%d, len:%d\n", + tid, len); + return; + } + + idr_remove(&ctrl->tid_idr, tid); + spin_unlock_irqrestore(&ctrl->txn_lock, flags); + + memcpy(msg->rbuf, reply, len); + if (txn->comp) + complete(txn->comp); +} +EXPORT_SYMBOL_GPL(slim_msg_response); + +/** + * slim_do_transfer() - Process a SLIMbus-messaging transaction + * + * @ctrl: Controller handle + * @txn: Transaction to be sent over SLIMbus + * + * Called by controller to transmit messaging transactions not dealing with + * Interface/Value elements. (e.g. transmittting a message to assign logical + * address to a slave device + * + * Return: -ETIMEDOUT: If transmission of this message timed out + * (e.g. due to bus lines not being clocked or driven by controller) + */ +int slim_do_transfer(struct slim_controller *ctrl, struct slim_msg_txn *txn) +{ + DECLARE_COMPLETION_ONSTACK(done); + bool need_tid; + unsigned long flags; + int ret, tid, timeout; + + need_tid = slim_tid_txn(txn->mt, txn->mc); + + if (need_tid) { + spin_lock_irqsave(&ctrl->txn_lock, flags); + tid = idr_alloc(&ctrl->tid_idr, txn, 0, + SLIM_MAX_TIDS, GFP_KERNEL); + txn->tid = tid; + + if (!txn->msg->comp) + txn->comp = &done; + else + txn->comp = txn->comp; + + spin_unlock_irqrestore(&ctrl->txn_lock, flags); + + if (tid < 0) + return tid; + } + + ret = ctrl->xfer_msg(ctrl, txn); + + if (ret && need_tid && !txn->msg->comp) { + unsigned long ms = txn->rl + HZ; + + timeout = wait_for_completion_timeout(txn->comp, + msecs_to_jiffies(ms)); + if (!timeout) { + ret = -ETIMEDOUT; + spin_lock_irqsave(&ctrl->txn_lock, flags); + idr_remove(&ctrl->tid_idr, tid); + spin_unlock_irqrestore(&ctrl->txn_lock, flags); + } + } + + if (ret) + dev_err(ctrl->dev, "Tx:MT:0x%x, MC:0x%x, LA:0x%x failed:%d\n", + txn->mt, txn->mc, txn->la, ret); + + return ret; +} +EXPORT_SYMBOL_GPL(slim_do_transfer); + +static int slim_val_inf_sanity(struct slim_controller *ctrl, + struct slim_val_inf *msg, u8 mc) +{ + if (!msg || msg->num_bytes > 16 || + (msg->start_offset + msg->num_bytes) > 0xC00) + goto reterr; + switch (mc) { + case SLIM_MSG_MC_REQUEST_VALUE: + case SLIM_MSG_MC_REQUEST_INFORMATION: + if (msg->rbuf != NULL) + return 0; + break; + + case SLIM_MSG_MC_CHANGE_VALUE: + case SLIM_MSG_MC_CLEAR_INFORMATION: + if (msg->wbuf != NULL) + return 0; + break; + + case SLIM_MSG_MC_REQUEST_CHANGE_VALUE: + case SLIM_MSG_MC_REQUEST_CLEAR_INFORMATION: + if (msg->rbuf != NULL && msg->wbuf != NULL) + return 0; + break; + } +reterr: + dev_err(ctrl->dev, "Sanity check failed:msg:offset:0x%x, mc:%d\n", + msg->start_offset, mc); + return -EINVAL; +} + +static u16 slim_slicesize(int code) +{ + static const u8 sizetocode[16] = { + 0, 1, 2, 3, 3, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6, 7 + }; + + clamp(code, 1, (int)ARRAY_SIZE(sizetocode)); + + return sizetocode[code - 1]; +} + +/** + * slim_xfer_msg() - Transfer a value info message on slim device + * + * @sbdev: slim device to which this msg has to be transfered + * @msg: value info message pointer + * @mc: message code of the message + * + * Called by drivers which want to transfer a vlaue or info elements. + * + * Return: -ETIMEDOUT: If transmission of this message timed out + */ +int slim_xfer_msg(struct slim_device *sbdev, struct slim_val_inf *msg, + u8 mc) +{ + DEFINE_SLIM_LDEST_TXN(txn_stack, mc, 6, sbdev->laddr, msg); + struct slim_msg_txn *txn = &txn_stack; + struct slim_controller *ctrl = sbdev->ctrl; + int ret; + u16 sl; + + if (!ctrl) + return -EINVAL; + + ret = slim_val_inf_sanity(ctrl, msg, mc); + if (ret) + return ret; + + sl = slim_slicesize(msg->num_bytes); + + dev_dbg(ctrl->dev, "SB xfer msg:os:%x, len:%d, MC:%x, sl:%x\n", + msg->start_offset, msg->num_bytes, mc, sl); + + txn->ec = ((sl | (1 << 3)) | ((msg->start_offset & 0xFFF) << 4)); + + switch (mc) { + case SLIM_MSG_MC_REQUEST_CHANGE_VALUE: + case SLIM_MSG_MC_CHANGE_VALUE: + case SLIM_MSG_MC_REQUEST_CLEAR_INFORMATION: + case SLIM_MSG_MC_CLEAR_INFORMATION: + txn->rl += msg->num_bytes; + default: + break; + } + + if (slim_tid_txn(txn->mt, txn->mc)) + txn->rl++; + + return slim_do_transfer(ctrl, txn); +} +EXPORT_SYMBOL_GPL(slim_xfer_msg); + +static void slim_fill_msg(struct slim_val_inf *msg, u32 addr, + size_t count, u8 *rbuf, u8 *wbuf) +{ + msg->start_offset = addr; + msg->num_bytes = count; + msg->rbuf = rbuf; + msg->wbuf = wbuf; +} + +/** + * slim_read() - Read SLIMbus value element + * + * @sdev: client handle. + * @addr: address of value element to read. + * @count: number of bytes to read. Maximum bytes allowed are 16. + * @val: will return what the value element value was + * + * Return: -EINVAL for Invalid parameters, -ETIMEDOUT If transmission of + * this message timed out (e.g. due to bus lines not being clocked + * or driven by controller) + */ +int slim_read(struct slim_device *sdev, u32 addr, size_t count, u8 *val) +{ + struct slim_val_inf msg; + + slim_fill_msg(&msg, addr, count, val, NULL); + + return slim_xfer_msg(sdev, &msg, SLIM_MSG_MC_REQUEST_VALUE); +} +EXPORT_SYMBOL_GPL(slim_read); + +/** + * slim_readb() - Read byte from SLIMbus value element + * + * @sdev: client handle. + * @addr: address in the value element to read. + * + * Return: byte value of value element. + */ +int slim_readb(struct slim_device *sdev, u32 addr) +{ + int ret; + u8 buf; + + ret = slim_read(sdev, addr, 1, &buf); + if (ret < 0) + return ret; + else + return buf; +} +EXPORT_SYMBOL_GPL(slim_readb); + +/** + * slim_write() - Write SLIMbus value element + * + * @sdev: client handle. + * @addr: address in the value element to write. + * @count: number of bytes to write. Maximum bytes allowed are 16. + * @val: value to write to value element + * + * Return: -EINVAL for Invalid parameters, -ETIMEDOUT If transmission of + * this message timed out (e.g. due to bus lines not being clocked + * or driven by controller) + */ +int slim_write(struct slim_device *sdev, u32 addr, size_t count, u8 *val) +{ + struct slim_val_inf msg; + + slim_fill_msg(&msg, addr, count, val, NULL); + + return slim_xfer_msg(sdev, &msg, SLIM_MSG_MC_CHANGE_VALUE); +} +EXPORT_SYMBOL_GPL(slim_write); + +/** + * slim_writeb() - Write byte to SLIMbus value element + * + * @sdev: client handle. + * @addr: address of value element to write. + * @value: value to write to value element + * + * Return: -EINVAL for Invalid parameters, -ETIMEDOUT If transmission of + * this message timed out (e.g. due to bus lines not being clocked + * or driven by controller) + * + */ +int slim_writeb(struct slim_device *sdev, u32 addr, u8 value) +{ + return slim_write(sdev, addr, 1, &value); +} +EXPORT_SYMBOL_GPL(slim_writeb); diff --git a/drivers/slimbus/slimbus.h b/drivers/slimbus/slimbus.h index 66657722f50f..0d40c2578c28 100644 --- a/drivers/slimbus/slimbus.h +++ b/drivers/slimbus/slimbus.h @@ -8,8 +8,17 @@ #include #include #include +#include #include +/* SLIMbus message types. Related to interpretation of message code. */ +#define SLIM_MSG_MT_CORE 0x0 + +/* Destination type Values */ +#define SLIM_MSG_DEST_LOGICALADDR 0 +#define SLIM_MSG_DEST_ENUMADDR 1 +#define SLIM_MSG_DEST_BROADCAST 3 + /* Standard values per SLIMbus spec needed by controllers and devices */ #define SLIM_MAX_CLK_GEAR 10 #define SLIM_MIN_CLK_GEAR 1 @@ -17,6 +26,7 @@ /* Manager's logical address is set to 0xFF per spec */ #define SLIM_LA_MANAGER 0xFF +#define SLIM_MAX_TIDS 256 /** * struct slim_framer - Represents SLIMbus framer. * Every controller may have multiple framers. There is 1 active framer device @@ -37,6 +47,39 @@ struct slim_framer { #define to_slim_framer(d) container_of(d, struct slim_framer, dev) +/** + * struct slim_msg_txn - Message to be sent by the controller. + * This structure has packet header, + * payload and buffer to be filled (if any) + * @rl: Header field. remaining length. + * @mt: Header field. Message type. + * @mc: Header field. LSB is message code for type mt. + * @dt: Header field. Destination type. + * @ec: Element code. Used for elemental access APIs. + * @tid: Transaction ID. Used for messages expecting response. + * (relevant for message-codes involving read operation) + * @la: Logical address of the device this message is going to. + * (Not used when destination type is broadcast.) + * @msg: Elemental access message to be read/written + * @comp: completion if read/write is synchronous, used internally + * for tid based transactions. + */ +struct slim_msg_txn { + u8 rl; + u8 mt; + u8 mc; + u8 dt; + u16 ec; + u8 tid; + u8 la; + struct slim_val_inf *msg; + struct completion *comp; +}; + +/* Frequently used message transaction structures */ +#define DEFINE_SLIM_LDEST_TXN(name, mc, rl, la, msg) \ + struct slim_msg_txn name = { rl, 0, mc, SLIM_MSG_DEST_LOGICALADDR, 0,\ + 0, la, msg, } /** * struct slim_controller - Controls every instance of SLIMbus * (similar to 'master' on SPI) @@ -52,6 +95,9 @@ struct slim_framer { * @devices: Slim device list * @tid_idr: tid id allocator * @txn_lock: Lock to protect table of transactions + * @xfer_msg: Transfer a message on this controller (this can be a broadcast + * control/status message like data channel setup, or a unicast message + * like value element read/write. * @set_laddr: Setup logical address at laddr for the slave with elemental * address e_addr. Drivers implementing controller will be expected to * send unicast message to this device with its logical address. @@ -93,6 +139,8 @@ struct slim_controller { struct list_head devices; struct idr tid_idr; spinlock_t txn_lock; + int (*xfer_msg)(struct slim_controller *ctrl, + struct slim_msg_txn *tx); int (*set_laddr)(struct slim_controller *ctrl, struct slim_eaddr *ea, u8 laddr); int (*get_laddr)(struct slim_controller *ctrl, @@ -104,5 +152,24 @@ int slim_device_report_present(struct slim_controller *ctrl, void slim_report_absent(struct slim_device *sbdev); int slim_register_controller(struct slim_controller *ctrl); int slim_unregister_controller(struct slim_controller *ctrl); +void slim_msg_response(struct slim_controller *ctrl, u8 *reply, u8 tid, u8 l); +int slim_do_transfer(struct slim_controller *ctrl, struct slim_msg_txn *txn); + +static inline bool slim_tid_txn(u8 mt, u8 mc) +{ + return (mt == SLIM_MSG_MT_CORE && + (mc == SLIM_MSG_MC_REQUEST_INFORMATION || + mc == SLIM_MSG_MC_REQUEST_CLEAR_INFORMATION || + mc == SLIM_MSG_MC_REQUEST_VALUE || + mc == SLIM_MSG_MC_REQUEST_CLEAR_INFORMATION)); +} +static inline bool slim_ec_txn(u8 mt, u8 mc) +{ + return (mt == SLIM_MSG_MT_CORE && + ((mc >= SLIM_MSG_MC_REQUEST_INFORMATION && + mc <= SLIM_MSG_MC_REPORT_INFORMATION) || + (mc >= SLIM_MSG_MC_REQUEST_VALUE && + mc <= SLIM_MSG_MC_CHANGE_VALUE))); +} #endif /* _LINUX_SLIMBUS_H */ diff --git a/include/linux/slimbus.h b/include/linux/slimbus.h index aeed98a683be..c36cf121d2cd 100644 --- a/include/linux/slimbus.h +++ b/include/linux/slimbus.h @@ -7,6 +7,7 @@ #define _LINUX_SLIMBUS_H #include #include +#include #include extern struct bus_type slimbus_bus; @@ -88,6 +89,25 @@ struct slim_driver { }; #define to_slim_driver(d) container_of(d, struct slim_driver, driver) +/** + * struct slim_val_inf - Slimbus value or information element + * @start_offset: Specifies starting offset in information/value element map + * @rbuf: buffer to read the values + * @wbuf: buffer to write + * @num_bytes: upto 16. This ensures that the message will fit the slicesize + * per SLIMbus spec + * @comp: completion for asynchronous operations, valid only if TID is + * required for transaction, like REQUEST operations. + * Rest of the transactions are synchronous anyway. + */ +struct slim_val_inf { + u16 start_offset; + u8 num_bytes; + u8 *rbuf; + const u8 *wbuf; + struct completion *comp; +}; + /* * use a macro to avoid include chaining to get THIS_MODULE */ @@ -121,4 +141,24 @@ static inline void slim_set_devicedata(struct slim_device *dev, void *data) struct slim_device *slim_get_device(struct slim_controller *ctrl, struct slim_eaddr *e_addr); int slim_get_logical_addr(struct slim_device *sbdev); + +/* Information Element management messages */ +#define SLIM_MSG_MC_REQUEST_INFORMATION 0x20 +#define SLIM_MSG_MC_REQUEST_CLEAR_INFORMATION 0x21 +#define SLIM_MSG_MC_REPLY_INFORMATION 0x24 +#define SLIM_MSG_MC_CLEAR_INFORMATION 0x28 +#define SLIM_MSG_MC_REPORT_INFORMATION 0x29 + +/* Value Element management messages */ +#define SLIM_MSG_MC_REQUEST_VALUE 0x60 +#define SLIM_MSG_MC_REQUEST_CHANGE_VALUE 0x61 +#define SLIM_MSG_MC_REPLY_VALUE 0x64 +#define SLIM_MSG_MC_CHANGE_VALUE 0x68 + +int slim_xfer_msg(struct slim_device *sbdev, struct slim_val_inf *msg, + u8 mc); +int slim_readb(struct slim_device *sdev, u32 addr); +int slim_writeb(struct slim_device *sdev, u32 addr, u8 value); +int slim_read(struct slim_device *sdev, u32 addr, size_t count, u8 *val); +int slim_write(struct slim_device *sdev, u32 addr, size_t count, u8 *val); #endif /* _LINUX_SLIMBUS_H */ -- cgit v1.2.3-58-ga151 From 7d6f7fb053ad543da74119df3c4cd7bb46220471 Mon Sep 17 00:00:00 2001 From: Srinivas Kandagatla Date: Mon, 11 Dec 2017 23:43:02 +0000 Subject: regmap: add SLIMbus support This patch adds support to read/write SLIMbus value elements. Currently it only supports byte read/write. Adding this support in regmap would give codec drivers more flexibility when there are more than 2 control interfaces like SLIMbus, i2c. Without this patch each codec driver has to directly call SLIMbus value element apis, and this could would get messy once we want to add i2c interface to it. Signed-off-by: Srinivas Kandagatla Reviwed-by: Mark Brown Signed-off-by: Greg Kroah-Hartman --- drivers/base/regmap/Kconfig | 4 ++ drivers/base/regmap/Makefile | 1 + drivers/base/regmap/regmap-slimbus.c | 80 ++++++++++++++++++++++++++++++++++++ include/linux/regmap.h | 18 ++++++++ 4 files changed, 103 insertions(+) create mode 100644 drivers/base/regmap/regmap-slimbus.c (limited to 'include') diff --git a/drivers/base/regmap/Kconfig b/drivers/base/regmap/Kconfig index 3a1535d812d8..cc162b48c6d7 100644 --- a/drivers/base/regmap/Kconfig +++ b/drivers/base/regmap/Kconfig @@ -21,6 +21,10 @@ config REGMAP_I2C tristate depends on I2C +config REGMAP_SLIMBUS + tristate + depends on SLIMBUS + config REGMAP_SPI tristate depends on SPI diff --git a/drivers/base/regmap/Makefile b/drivers/base/regmap/Makefile index 0d298c446108..63dec9222892 100644 --- a/drivers/base/regmap/Makefile +++ b/drivers/base/regmap/Makefile @@ -8,6 +8,7 @@ obj-$(CONFIG_REGCACHE_COMPRESSED) += regcache-lzo.o obj-$(CONFIG_DEBUG_FS) += regmap-debugfs.o obj-$(CONFIG_REGMAP_AC97) += regmap-ac97.o obj-$(CONFIG_REGMAP_I2C) += regmap-i2c.o +obj-$(CONFIG_REGMAP_SLIMBUS) += regmap-slimbus.o obj-$(CONFIG_REGMAP_SPI) += regmap-spi.o obj-$(CONFIG_REGMAP_SPMI) += regmap-spmi.o obj-$(CONFIG_REGMAP_MMIO) += regmap-mmio.o diff --git a/drivers/base/regmap/regmap-slimbus.c b/drivers/base/regmap/regmap-slimbus.c new file mode 100644 index 000000000000..c90bee81d954 --- /dev/null +++ b/drivers/base/regmap/regmap-slimbus.c @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2017, Linaro Ltd. + +#include +#include +#include + +#include "internal.h" + +static int regmap_slimbus_byte_reg_read(void *context, unsigned int reg, + unsigned int *val) +{ + struct slim_device *sdev = context; + int v; + + v = slim_readb(sdev, reg); + + if (v < 0) + return v; + + *val = v; + + return 0; +} + +static int regmap_slimbus_byte_reg_write(void *context, unsigned int reg, + unsigned int val) +{ + struct slim_device *sdev = context; + + return slim_writeb(sdev, reg, val); +} + +static struct regmap_bus regmap_slimbus_bus = { + .reg_write = regmap_slimbus_byte_reg_write, + .reg_read = regmap_slimbus_byte_reg_read, + .reg_format_endian_default = REGMAP_ENDIAN_LITTLE, + .val_format_endian_default = REGMAP_ENDIAN_LITTLE, +}; + +static const struct regmap_bus *regmap_get_slimbus(struct slim_device *slim, + const struct regmap_config *config) +{ + if (config->val_bits == 8 && config->reg_bits == 8) + return ®map_slimbus_bus; + + return ERR_PTR(-ENOTSUPP); +} + +struct regmap *__regmap_init_slimbus(struct slim_device *slimbus, + const struct regmap_config *config, + struct lock_class_key *lock_key, + const char *lock_name) +{ + const struct regmap_bus *bus = regmap_get_slimbus(slimbus, config); + + if (IS_ERR(bus)) + return ERR_CAST(bus); + + return __regmap_init(&slimbus->dev, bus, &slimbus->dev, config, + lock_key, lock_name); +} +EXPORT_SYMBOL_GPL(__regmap_init_slimbus); + +struct regmap *__devm_regmap_init_slimbus(struct slim_device *slimbus, + const struct regmap_config *config, + struct lock_class_key *lock_key, + const char *lock_name) +{ + const struct regmap_bus *bus = regmap_get_slimbus(slimbus, config); + + if (IS_ERR(bus)) + return ERR_CAST(bus); + + return __devm_regmap_init(&slimbus->dev, bus, &slimbus, config, + lock_key, lock_name); +} +EXPORT_SYMBOL_GPL(__devm_regmap_init_slimbus); + +MODULE_LICENSE("GPL v2"); diff --git a/include/linux/regmap.h b/include/linux/regmap.h index 15eddc1353ba..b2207737a159 100644 --- a/include/linux/regmap.h +++ b/include/linux/regmap.h @@ -24,6 +24,7 @@ struct module; struct device; struct i2c_client; struct irq_domain; +struct slim_device; struct spi_device; struct spmi_device; struct regmap; @@ -499,6 +500,10 @@ struct regmap *__regmap_init_i2c(struct i2c_client *i2c, const struct regmap_config *config, struct lock_class_key *lock_key, const char *lock_name); +struct regmap *__regmap_init_slimbus(struct slim_device *slimbus, + const struct regmap_config *config, + struct lock_class_key *lock_key, + const char *lock_name); struct regmap *__regmap_init_spi(struct spi_device *dev, const struct regmap_config *config, struct lock_class_key *lock_key, @@ -615,6 +620,19 @@ int regmap_attach_dev(struct device *dev, struct regmap *map, __regmap_lockdep_wrapper(__regmap_init_i2c, #config, \ i2c, config) +/** + * regmap_init_slimbus() - Initialise register map + * + * @slimbus: Device that will be interacted with + * @config: Configuration for register map + * + * The return value will be an ERR_PTR() on error or a valid pointer to + * a struct regmap. + */ +#define regmap_init_slimbus(slimbus, config) \ + __regmap_lockdep_wrapper(__regmap_init_slimbus, #config, \ + slimbus, config) + /** * regmap_init_spi() - Initialise register map * -- cgit v1.2.3-58-ga151 From 9251345dca24b62b14e4e53e6ee3387ae7d9c790 Mon Sep 17 00:00:00 2001 From: Vinod Koul Date: Thu, 14 Dec 2017 11:19:33 +0530 Subject: soundwire: Add SoundWire bus type This adds the base SoundWire bus type, bus and driver registration. along with changes to module device table for new SoundWire device type. Signed-off-by: Sanyog Kale Reviewed-by: Philippe Ombredanne Acked-By: Pierre-Louis Bossart Reviewed-by: Takashi Iwai Acked-by: Greg Kroah-Hartman Signed-off-by: Vinod Koul Signed-off-by: Greg Kroah-Hartman --- drivers/Kconfig | 2 + drivers/Makefile | 1 + drivers/soundwire/Kconfig | 22 +++++ drivers/soundwire/Makefile | 7 ++ drivers/soundwire/bus_type.c | 172 +++++++++++++++++++++++++++++++++++++ include/linux/mod_devicetable.h | 6 ++ include/linux/soundwire/sdw.h | 108 +++++++++++++++++++++++ include/linux/soundwire/sdw_type.h | 19 ++++ scripts/mod/devicetable-offsets.c | 4 + scripts/mod/file2alias.c | 15 ++++ 10 files changed, 356 insertions(+) create mode 100644 drivers/soundwire/Kconfig create mode 100644 drivers/soundwire/Makefile create mode 100644 drivers/soundwire/bus_type.c create mode 100644 include/linux/soundwire/sdw.h create mode 100644 include/linux/soundwire/sdw_type.h (limited to 'include') diff --git a/drivers/Kconfig b/drivers/Kconfig index b57dcee2c938..02b6fd0864c8 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -153,6 +153,8 @@ source "drivers/remoteproc/Kconfig" source "drivers/rpmsg/Kconfig" +source "drivers/soundwire/Kconfig" + source "drivers/soc/Kconfig" source "drivers/devfreq/Kconfig" diff --git a/drivers/Makefile b/drivers/Makefile index 3f862ef11797..e09663776d8b 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -158,6 +158,7 @@ obj-$(CONFIG_MAILBOX) += mailbox/ obj-$(CONFIG_HWSPINLOCK) += hwspinlock/ obj-$(CONFIG_REMOTEPROC) += remoteproc/ obj-$(CONFIG_RPMSG) += rpmsg/ +obj-$(CONFIG_SOUNDWIRE) += soundwire/ # Virtualization drivers obj-$(CONFIG_VIRT_DRIVERS) += virt/ diff --git a/drivers/soundwire/Kconfig b/drivers/soundwire/Kconfig new file mode 100644 index 000000000000..d7d3908f4913 --- /dev/null +++ b/drivers/soundwire/Kconfig @@ -0,0 +1,22 @@ +# +# SoundWire subsystem configuration +# + +menuconfig SOUNDWIRE + bool "SoundWire support" + ---help--- + SoundWire is a 2-Pin interface with data and clock line ratified + by the MIPI Alliance. SoundWire is used for transporting data + typically related to audio functions. SoundWire interface is + optimized to integrate audio devices in mobile or mobile inspired + systems. Say Y to enable this subsystem, N if you do not have such + a device + +if SOUNDWIRE + +comment "SoundWire Devices" + +config SOUNDWIRE_BUS + tristate + +endif diff --git a/drivers/soundwire/Makefile b/drivers/soundwire/Makefile new file mode 100644 index 000000000000..d1281def7662 --- /dev/null +++ b/drivers/soundwire/Makefile @@ -0,0 +1,7 @@ +# +# Makefile for soundwire core +# + +#Bus Objs +soundwire-bus-objs := bus_type.o +obj-$(CONFIG_SOUNDWIRE_BUS) += soundwire-bus.o diff --git a/drivers/soundwire/bus_type.c b/drivers/soundwire/bus_type.c new file mode 100644 index 000000000000..8d8dcc68e9a8 --- /dev/null +++ b/drivers/soundwire/bus_type.c @@ -0,0 +1,172 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright(c) 2015-17 Intel Corporation. + +#include +#include +#include +#include +#include + +/** + * sdw_get_device_id - find the matching SoundWire device id + * @slave: SoundWire Slave Device + * @drv: SoundWire Slave Driver + * + * The match is done by comparing the mfg_id and part_id from the + * struct sdw_device_id. + */ +static const struct sdw_device_id * +sdw_get_device_id(struct sdw_slave *slave, struct sdw_driver *drv) +{ + const struct sdw_device_id *id = drv->id_table; + + while (id && id->mfg_id) { + if (slave->id.mfg_id == id->mfg_id && + slave->id.part_id == id->part_id) + return id; + id++; + } + + return NULL; +} + +static int sdw_bus_match(struct device *dev, struct device_driver *ddrv) +{ + struct sdw_slave *slave = dev_to_sdw_dev(dev); + struct sdw_driver *drv = drv_to_sdw_driver(ddrv); + + return !!sdw_get_device_id(slave, drv); +} + +int sdw_slave_modalias(const struct sdw_slave *slave, char *buf, size_t size) +{ + /* modalias is sdw:mp */ + + return snprintf(buf, size, "sdw:m%04Xp%04X\n", + slave->id.mfg_id, slave->id.part_id); +} + +static int sdw_uevent(struct device *dev, struct kobj_uevent_env *env) +{ + struct sdw_slave *slave = dev_to_sdw_dev(dev); + char modalias[32]; + + sdw_slave_modalias(slave, modalias, sizeof(modalias)); + + if (add_uevent_var(env, "MODALIAS=%s", modalias)) + return -ENOMEM; + + return 0; +} + +struct bus_type sdw_bus_type = { + .name = "soundwire", + .match = sdw_bus_match, + .uevent = sdw_uevent, +}; +EXPORT_SYMBOL_GPL(sdw_bus_type); + +static int sdw_drv_probe(struct device *dev) +{ + struct sdw_slave *slave = dev_to_sdw_dev(dev); + struct sdw_driver *drv = drv_to_sdw_driver(dev->driver); + const struct sdw_device_id *id; + int ret; + + id = sdw_get_device_id(slave, drv); + if (!id) + return -ENODEV; + + /* + * attach to power domain but don't turn on (last arg) + */ + ret = dev_pm_domain_attach(dev, false); + if (ret != -EPROBE_DEFER) { + ret = drv->probe(slave, id); + if (ret) { + dev_err(dev, "Probe of %s failed: %d\n", drv->name, ret); + dev_pm_domain_detach(dev, false); + } + } + + return ret; +} + +static int sdw_drv_remove(struct device *dev) +{ + struct sdw_slave *slave = dev_to_sdw_dev(dev); + struct sdw_driver *drv = drv_to_sdw_driver(dev->driver); + int ret = 0; + + if (drv->remove) + ret = drv->remove(slave); + + dev_pm_domain_detach(dev, false); + + return ret; +} + +static void sdw_drv_shutdown(struct device *dev) +{ + struct sdw_slave *slave = dev_to_sdw_dev(dev); + struct sdw_driver *drv = drv_to_sdw_driver(dev->driver); + + if (drv->shutdown) + drv->shutdown(slave); +} + +/** + * __sdw_register_driver() - register a SoundWire Slave driver + * @drv: driver to register + * @owner: owning module/driver + * + * Return: zero on success, else a negative error code. + */ +int __sdw_register_driver(struct sdw_driver *drv, struct module *owner) +{ + drv->driver.bus = &sdw_bus_type; + + if (!drv->probe) { + pr_err("driver %s didn't provide SDW probe routine\n", + drv->name); + return -EINVAL; + } + + drv->driver.owner = owner; + drv->driver.probe = sdw_drv_probe; + + if (drv->remove) + drv->driver.remove = sdw_drv_remove; + + if (drv->shutdown) + drv->driver.shutdown = sdw_drv_shutdown; + + return driver_register(&drv->driver); +} +EXPORT_SYMBOL_GPL(__sdw_register_driver); + +/** + * sdw_unregister_driver() - unregisters the SoundWire Slave driver + * @drv: driver to unregister + */ +void sdw_unregister_driver(struct sdw_driver *drv) +{ + driver_unregister(&drv->driver); +} +EXPORT_SYMBOL_GPL(sdw_unregister_driver); + +static int __init sdw_bus_init(void) +{ + return bus_register(&sdw_bus_type); +} + +static void __exit sdw_bus_exit(void) +{ + bus_unregister(&sdw_bus_type); +} + +postcore_initcall(sdw_bus_init); +module_exit(sdw_bus_exit); + +MODULE_DESCRIPTION("SoundWire bus"); +MODULE_LICENSE("GPL v2"); diff --git a/include/linux/mod_devicetable.h b/include/linux/mod_devicetable.h index 48e188327c02..48fb2b43c35a 100644 --- a/include/linux/mod_devicetable.h +++ b/include/linux/mod_devicetable.h @@ -229,6 +229,12 @@ struct hda_device_id { unsigned long driver_data; }; +struct sdw_device_id { + __u16 mfg_id; + __u16 part_id; + kernel_ulong_t driver_data; +}; + /* * Struct used for matching a device */ diff --git a/include/linux/soundwire/sdw.h b/include/linux/soundwire/sdw.h new file mode 100644 index 000000000000..869d7041d9fc --- /dev/null +++ b/include/linux/soundwire/sdw.h @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) +// Copyright(c) 2015-17 Intel Corporation. + +#ifndef __SOUNDWIRE_H +#define __SOUNDWIRE_H + +struct sdw_bus; +struct sdw_slave; + +#define SDW_MAX_DEVICES 11 + +/** + * enum sdw_slave_status - Slave status + * @SDW_SLAVE_UNATTACHED: Slave is not attached with the bus. + * @SDW_SLAVE_ATTACHED: Slave is attached with bus. + * @SDW_SLAVE_ALERT: Some alert condition on the Slave + * @SDW_SLAVE_RESERVED: Reserved for future use + */ +enum sdw_slave_status { + SDW_SLAVE_UNATTACHED = 0, + SDW_SLAVE_ATTACHED = 1, + SDW_SLAVE_ALERT = 2, + SDW_SLAVE_RESERVED = 3, +}; + +/* + * SDW Slave Structures and APIs + */ + +/** + * struct sdw_slave_id - Slave ID + * @mfg_id: MIPI Manufacturer ID + * @part_id: Device Part ID + * @class_id: MIPI Class ID, unused now. + * Currently a placeholder in MIPI SoundWire Spec + * @unique_id: Device unique ID + * @sdw_version: SDW version implemented + * + * The order of the IDs here does not follow the DisCo spec definitions + */ +struct sdw_slave_id { + __u16 mfg_id; + __u16 part_id; + __u8 class_id; + __u8 unique_id:4; + __u8 sdw_version:4; +}; + +/** + * struct sdw_slave - SoundWire Slave + * @id: MIPI device ID + * @dev: Linux device + * @status: Status reported by the Slave + * @bus: Bus handle + * @node: node for bus list + * @dev_num: Device Number assigned by Bus + */ +struct sdw_slave { + struct sdw_slave_id id; + struct device dev; + enum sdw_slave_status status; + struct sdw_bus *bus; + struct list_head node; + u16 dev_num; +}; + +#define dev_to_sdw_dev(_dev) container_of(_dev, struct sdw_slave, dev) + +struct sdw_driver { + const char *name; + + int (*probe)(struct sdw_slave *sdw, + const struct sdw_device_id *id); + int (*remove)(struct sdw_slave *sdw); + void (*shutdown)(struct sdw_slave *sdw); + + const struct sdw_device_id *id_table; + const struct sdw_slave_ops *ops; + + struct device_driver driver; +}; + +#define SDW_SLAVE_ENTRY(_mfg_id, _part_id, _drv_data) \ + { .mfg_id = (_mfg_id), .part_id = (_part_id), \ + .driver_data = (unsigned long)(_drv_data) } + +/* + * SDW master structures and APIs + */ + +/** + * struct sdw_bus - SoundWire bus + * @dev: Master linux device + * @link_id: Link id number, can be 0 to N, unique for each Master + * @slaves: list of Slaves on this bus + * @assigned: Bitmap for Slave device numbers. + * Bit set implies used number, bit clear implies unused number. + * @bus_lock: bus lock + */ +struct sdw_bus { + struct device *dev; + unsigned int link_id; + struct list_head slaves; + DECLARE_BITMAP(assigned, SDW_MAX_DEVICES); + struct mutex bus_lock; +}; + +#endif /* __SOUNDWIRE_H */ diff --git a/include/linux/soundwire/sdw_type.h b/include/linux/soundwire/sdw_type.h new file mode 100644 index 000000000000..9fd553e553e9 --- /dev/null +++ b/include/linux/soundwire/sdw_type.h @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright(c) 2015-17 Intel Corporation. + +#ifndef __SOUNDWIRE_TYPES_H +#define __SOUNDWIRE_TYPES_H + +extern struct bus_type sdw_bus_type; + +#define drv_to_sdw_driver(_drv) container_of(_drv, struct sdw_driver, driver) + +#define sdw_register_driver(drv) \ + __sdw_register_driver(drv, THIS_MODULE) + +int __sdw_register_driver(struct sdw_driver *drv, struct module *); +void sdw_unregister_driver(struct sdw_driver *drv); + +int sdw_slave_modalias(const struct sdw_slave *slave, char *buf, size_t size); + +#endif /* __SOUNDWIRE_TYPES_H */ diff --git a/scripts/mod/devicetable-offsets.c b/scripts/mod/devicetable-offsets.c index 9826b9a6543c..9fad6afe4c41 100644 --- a/scripts/mod/devicetable-offsets.c +++ b/scripts/mod/devicetable-offsets.c @@ -203,6 +203,10 @@ int main(void) DEVID_FIELD(hda_device_id, rev_id); DEVID_FIELD(hda_device_id, api_version); + DEVID(sdw_device_id); + DEVID_FIELD(sdw_device_id, mfg_id); + DEVID_FIELD(sdw_device_id, part_id); + DEVID(fsl_mc_device_id); DEVID_FIELD(fsl_mc_device_id, vendor); DEVID_FIELD(fsl_mc_device_id, obj_type); diff --git a/scripts/mod/file2alias.c b/scripts/mod/file2alias.c index 6ef6e63f96fd..b9beeaa4695b 100644 --- a/scripts/mod/file2alias.c +++ b/scripts/mod/file2alias.c @@ -1289,6 +1289,21 @@ static int do_hda_entry(const char *filename, void *symval, char *alias) } ADD_TO_DEVTABLE("hdaudio", hda_device_id, do_hda_entry); +/* Looks like: sdw:mNpN */ +static int do_sdw_entry(const char *filename, void *symval, char *alias) +{ + DEF_FIELD(symval, sdw_device_id, mfg_id); + DEF_FIELD(symval, sdw_device_id, part_id); + + strcpy(alias, "sdw:"); + ADD(alias, "m", mfg_id != 0, mfg_id); + ADD(alias, "p", part_id != 0, part_id); + + add_wildcard(alias); + return 1; +} +ADD_TO_DEVTABLE("sdw", sdw_device_id, do_sdw_entry); + /* Looks like: fsl-mc:vNdN */ static int do_fsl_mc_entry(const char *filename, void *symval, char *alias) -- cgit v1.2.3-58-ga151 From 7c3cd189b86d2fcf0579a7b61e8d9c6d5717a72e Mon Sep 17 00:00:00 2001 From: Vinod Koul Date: Thu, 14 Dec 2017 11:19:34 +0530 Subject: soundwire: Add Master registration A Master adds a SoundWire bus instance which scans the firmware provided for device description. In this patch we scan ACPI namespaces and create SoundWire Slave devices based on ACPI description Signed-off-by: Sanyog Kale Reviewed-by: Philippe Ombredanne Acked-By: Pierre-Louis Bossart Reviewed-by: Takashi Iwai Acked-by: Greg Kroah-Hartman Signed-off-by: Vinod Koul Signed-off-by: Greg Kroah-Hartman --- drivers/soundwire/Makefile | 2 +- drivers/soundwire/bus.c | 124 ++++++++++++++++++++++++++++++++++++++++++ drivers/soundwire/bus.h | 19 +++++++ drivers/soundwire/slave.c | 114 ++++++++++++++++++++++++++++++++++++++ include/linux/soundwire/sdw.h | 18 ++++++ 5 files changed, 276 insertions(+), 1 deletion(-) create mode 100644 drivers/soundwire/bus.c create mode 100644 drivers/soundwire/bus.h create mode 100644 drivers/soundwire/slave.c (limited to 'include') diff --git a/drivers/soundwire/Makefile b/drivers/soundwire/Makefile index d1281def7662..c875e434f8b3 100644 --- a/drivers/soundwire/Makefile +++ b/drivers/soundwire/Makefile @@ -3,5 +3,5 @@ # #Bus Objs -soundwire-bus-objs := bus_type.o +soundwire-bus-objs := bus_type.o bus.o slave.o obj-$(CONFIG_SOUNDWIRE_BUS) += soundwire-bus.o diff --git a/drivers/soundwire/bus.c b/drivers/soundwire/bus.c new file mode 100644 index 000000000000..bc72c3d58bbf --- /dev/null +++ b/drivers/soundwire/bus.c @@ -0,0 +1,124 @@ +// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) +// Copyright(c) 2015-17 Intel Corporation. + +#include +#include +#include +#include "bus.h" + +/** + * sdw_add_bus_master() - add a bus Master instance + * @bus: bus instance + * + * Initializes the bus instance, read properties and create child + * devices. + */ +int sdw_add_bus_master(struct sdw_bus *bus) +{ + int ret; + + if (!bus->dev) { + pr_err("SoundWire bus has no device"); + return -ENODEV; + } + + mutex_init(&bus->bus_lock); + INIT_LIST_HEAD(&bus->slaves); + + /* + * Device numbers in SoundWire are 0 thru 15. Enumeration device + * number (0), Broadcast device number (15), Group numbers (12 and + * 13) and Master device number (14) are not used for assignment so + * mask these and other higher bits. + */ + + /* Set higher order bits */ + *bus->assigned = ~GENMASK(SDW_BROADCAST_DEV_NUM, SDW_ENUM_DEV_NUM); + + /* Set enumuration device number and broadcast device number */ + set_bit(SDW_ENUM_DEV_NUM, bus->assigned); + set_bit(SDW_BROADCAST_DEV_NUM, bus->assigned); + + /* Set group device numbers and master device number */ + set_bit(SDW_GROUP12_DEV_NUM, bus->assigned); + set_bit(SDW_GROUP13_DEV_NUM, bus->assigned); + set_bit(SDW_MASTER_DEV_NUM, bus->assigned); + + /* + * SDW is an enumerable bus, but devices can be powered off. So, + * they won't be able to report as present. + * + * Create Slave devices based on Slaves described in + * the respective firmware (ACPI/DT) + */ + if (IS_ENABLED(CONFIG_ACPI) && ACPI_HANDLE(bus->dev)) + ret = sdw_acpi_find_slaves(bus); + else + ret = -ENOTSUPP; /* No ACPI/DT so error out */ + + if (ret) { + dev_err(bus->dev, "Finding slaves failed:%d\n", ret); + return ret; + } + + return 0; +} +EXPORT_SYMBOL(sdw_add_bus_master); + +static int sdw_delete_slave(struct device *dev, void *data) +{ + struct sdw_slave *slave = dev_to_sdw_dev(dev); + struct sdw_bus *bus = slave->bus; + + mutex_lock(&bus->bus_lock); + + if (slave->dev_num) /* clear dev_num if assigned */ + clear_bit(slave->dev_num, bus->assigned); + + list_del_init(&slave->node); + mutex_unlock(&bus->bus_lock); + + device_unregister(dev); + return 0; +} + +/** + * sdw_delete_bus_master() - delete the bus master instance + * @bus: bus to be deleted + * + * Remove the instance, delete the child devices. + */ +void sdw_delete_bus_master(struct sdw_bus *bus) +{ + device_for_each_child(bus->dev, NULL, sdw_delete_slave); +} +EXPORT_SYMBOL(sdw_delete_bus_master); + +void sdw_extract_slave_id(struct sdw_bus *bus, + u64 addr, struct sdw_slave_id *id) +{ + dev_dbg(bus->dev, "SDW Slave Addr: %llx", addr); + + /* + * Spec definition + * Register Bit Contents + * DevId_0 [7:4] 47:44 sdw_version + * DevId_0 [3:0] 43:40 unique_id + * DevId_1 39:32 mfg_id [15:8] + * DevId_2 31:24 mfg_id [7:0] + * DevId_3 23:16 part_id [15:8] + * DevId_4 15:08 part_id [7:0] + * DevId_5 07:00 class_id + */ + id->sdw_version = (addr >> 44) & GENMASK(3, 0); + id->unique_id = (addr >> 40) & GENMASK(3, 0); + id->mfg_id = (addr >> 24) & GENMASK(15, 0); + id->part_id = (addr >> 8) & GENMASK(15, 0); + id->class_id = addr & GENMASK(7, 0); + + dev_dbg(bus->dev, + "SDW Slave class_id %x, part_id %x, mfg_id %x, unique_id %x, version %x", + id->class_id, id->part_id, id->mfg_id, + id->unique_id, id->sdw_version); + +} diff --git a/drivers/soundwire/bus.h b/drivers/soundwire/bus.h new file mode 100644 index 000000000000..a54921825ce0 --- /dev/null +++ b/drivers/soundwire/bus.h @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) +// Copyright(c) 2015-17 Intel Corporation. + +#ifndef __SDW_BUS_H +#define __SDW_BUS_H + +#if IS_ENABLED(CONFIG_ACPI) +int sdw_acpi_find_slaves(struct sdw_bus *bus); +#else +static inline int sdw_acpi_find_slaves(struct sdw_bus *bus) +{ + return -ENOTSUPP; +} +#endif + +void sdw_extract_slave_id(struct sdw_bus *bus, + u64 addr, struct sdw_slave_id *id); + +#endif /* __SDW_BUS_H */ diff --git a/drivers/soundwire/slave.c b/drivers/soundwire/slave.c new file mode 100644 index 000000000000..ac103bd0c176 --- /dev/null +++ b/drivers/soundwire/slave.c @@ -0,0 +1,114 @@ +// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) +// Copyright(c) 2015-17 Intel Corporation. + +#include +#include +#include +#include "bus.h" + +static void sdw_slave_release(struct device *dev) +{ + struct sdw_slave *slave = dev_to_sdw_dev(dev); + + kfree(slave); +} + +static int sdw_slave_add(struct sdw_bus *bus, + struct sdw_slave_id *id, struct fwnode_handle *fwnode) +{ + struct sdw_slave *slave; + int ret; + + slave = kzalloc(sizeof(*slave), GFP_KERNEL); + if (!slave) + return -ENOMEM; + + /* Initialize data structure */ + memcpy(&slave->id, id, sizeof(*id)); + slave->dev.parent = bus->dev; + slave->dev.fwnode = fwnode; + + /* name shall be sdw:link:mfg:part:class:unique */ + dev_set_name(&slave->dev, "sdw:%x:%x:%x:%x:%x", + bus->link_id, id->mfg_id, id->part_id, + id->class_id, id->unique_id); + + slave->dev.release = sdw_slave_release; + slave->dev.bus = &sdw_bus_type; + slave->bus = bus; + slave->status = SDW_SLAVE_UNATTACHED; + slave->dev_num = 0; + + mutex_lock(&bus->bus_lock); + list_add_tail(&slave->node, &bus->slaves); + mutex_unlock(&bus->bus_lock); + + ret = device_register(&slave->dev); + if (ret) { + dev_err(bus->dev, "Failed to add slave: ret %d\n", ret); + + /* + * On err, don't free but drop ref as this will be freed + * when release method is invoked. + */ + mutex_lock(&bus->bus_lock); + list_del(&slave->node); + mutex_unlock(&bus->bus_lock); + put_device(&slave->dev); + } + + return ret; +} + +#if IS_ENABLED(CONFIG_ACPI) +/* + * sdw_acpi_find_slaves() - Find Slave devices in Master ACPI node + * @bus: SDW bus instance + * + * Scans Master ACPI node for SDW child Slave devices and registers it. + */ +int sdw_acpi_find_slaves(struct sdw_bus *bus) +{ + struct acpi_device *adev, *parent; + + parent = ACPI_COMPANION(bus->dev); + if (!parent) { + dev_err(bus->dev, "Can't find parent for acpi bind\n"); + return -ENODEV; + } + + list_for_each_entry(adev, &parent->children, node) { + unsigned long long addr; + struct sdw_slave_id id; + unsigned int link_id; + acpi_status status; + + status = acpi_evaluate_integer(adev->handle, + METHOD_NAME__ADR, NULL, &addr); + + if (ACPI_FAILURE(status)) { + dev_err(bus->dev, "_ADR resolution failed: %x\n", + status); + return status; + } + + /* Extract link id from ADR, Bit 51 to 48 (included) */ + link_id = (addr >> 48) & GENMASK(3, 0); + + /* Check for link_id match */ + if (link_id != bus->link_id) + continue; + + sdw_extract_slave_id(bus, addr, &id); + + /* + * don't error check for sdw_slave_add as we want to continue + * adding Slaves + */ + sdw_slave_add(bus, &id, acpi_fwnode_handle(adev)); + } + + return 0; +} + +#endif diff --git a/include/linux/soundwire/sdw.h b/include/linux/soundwire/sdw.h index 869d7041d9fc..3bfa7f1e2b47 100644 --- a/include/linux/soundwire/sdw.h +++ b/include/linux/soundwire/sdw.h @@ -7,6 +7,21 @@ struct sdw_bus; struct sdw_slave; +/* SDW spec defines and enums, as defined by MIPI 1.1. Spec */ + +/* SDW Broadcast Device Number */ +#define SDW_BROADCAST_DEV_NUM 15 + +/* SDW Enumeration Device Number */ +#define SDW_ENUM_DEV_NUM 0 + +/* SDW Group Device Numbers */ +#define SDW_GROUP12_DEV_NUM 12 +#define SDW_GROUP13_DEV_NUM 13 + +/* SDW Master Device Number, not supported yet */ +#define SDW_MASTER_DEV_NUM 14 + #define SDW_MAX_DEVICES 11 /** @@ -105,4 +120,7 @@ struct sdw_bus { struct mutex bus_lock; }; +int sdw_add_bus_master(struct sdw_bus *bus); +void sdw_delete_bus_master(struct sdw_bus *bus); + #endif /* __SOUNDWIRE_H */ -- cgit v1.2.3-58-ga151 From 56d4fe31af77f684bed62fb8201e6327e6ddf4e6 Mon Sep 17 00:00:00 2001 From: Vinod Koul Date: Thu, 14 Dec 2017 11:19:35 +0530 Subject: soundwire: Add MIPI DisCo property helpers MIPI Discovery And Configuration (DisCo) Specification for SoundWire specifies properties to be implemented for SoundWire Masters and Slaves. The DisCo spec doesn't mandate these properties. However, SDW bus cannot work without knowing these values. The helper functions read the Master and Slave properties. Implementers of Master or Slave drivers can use any of the below three mechanisms: a) Use these APIs here as .read_prop() callback for Master and Slave b) Implement own methods and set those as .read_prop(), but invoke APIs in this file for generic read and override the values with platform specific data c) Implement ones own methods which do not use anything provided here Signed-off-by: Sanyog Kale Reviewed-by: Philippe Ombredanne Acked-By: Pierre-Louis Bossart Reviewed-by: Takashi Iwai Signed-off-by: Vinod Koul Signed-off-by: Greg Kroah-Hartman --- drivers/soundwire/Makefile | 2 +- drivers/soundwire/bus.c | 8 + drivers/soundwire/bus_type.c | 23 ++- drivers/soundwire/mipi_disco.c | 401 +++++++++++++++++++++++++++++++++++++++++ include/linux/soundwire/sdw.h | 274 ++++++++++++++++++++++++++++ 5 files changed, 706 insertions(+), 2 deletions(-) create mode 100644 drivers/soundwire/mipi_disco.c (limited to 'include') diff --git a/drivers/soundwire/Makefile b/drivers/soundwire/Makefile index c875e434f8b3..bcde0d26524c 100644 --- a/drivers/soundwire/Makefile +++ b/drivers/soundwire/Makefile @@ -3,5 +3,5 @@ # #Bus Objs -soundwire-bus-objs := bus_type.o bus.o slave.o +soundwire-bus-objs := bus_type.o bus.o slave.o mipi_disco.o obj-$(CONFIG_SOUNDWIRE_BUS) += soundwire-bus.o diff --git a/drivers/soundwire/bus.c b/drivers/soundwire/bus.c index bc72c3d58bbf..9626bd1ab271 100644 --- a/drivers/soundwire/bus.c +++ b/drivers/soundwire/bus.c @@ -25,6 +25,14 @@ int sdw_add_bus_master(struct sdw_bus *bus) mutex_init(&bus->bus_lock); INIT_LIST_HEAD(&bus->slaves); + if (bus->ops->read_prop) { + ret = bus->ops->read_prop(bus); + if (ret < 0) { + dev_err(bus->dev, "Bus read properties failed:%d", ret); + return ret; + } + } + /* * Device numbers in SoundWire are 0 thru 15. Enumeration device * number (0), Broadcast device number (15), Group numbers (12 and diff --git a/drivers/soundwire/bus_type.c b/drivers/soundwire/bus_type.c index 8d8dcc68e9a8..d5f3a70c06b0 100644 --- a/drivers/soundwire/bus_type.c +++ b/drivers/soundwire/bus_type.c @@ -77,6 +77,8 @@ static int sdw_drv_probe(struct device *dev) if (!id) return -ENODEV; + slave->ops = drv->ops; + /* * attach to power domain but don't turn on (last arg) */ @@ -89,7 +91,26 @@ static int sdw_drv_probe(struct device *dev) } } - return ret; + if (ret) + return ret; + + /* device is probed so let's read the properties now */ + if (slave->ops && slave->ops->read_prop) + slave->ops->read_prop(slave); + + /* + * Check for valid clk_stop_timeout, use DisCo worst case value of + * 300ms + * + * TODO: check the timeouts and driver removal case + */ + if (slave->prop.clk_stop_timeout == 0) + slave->prop.clk_stop_timeout = 300; + + slave->bus->clk_stop_timeout = max_t(u32, slave->bus->clk_stop_timeout, + slave->prop.clk_stop_timeout); + + return 0; } static int sdw_drv_remove(struct device *dev) diff --git a/drivers/soundwire/mipi_disco.c b/drivers/soundwire/mipi_disco.c new file mode 100644 index 000000000000..fdeba0c3b589 --- /dev/null +++ b/drivers/soundwire/mipi_disco.c @@ -0,0 +1,401 @@ +// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) +// Copyright(c) 2015-17 Intel Corporation. + +/* + * MIPI Discovery And Configuration (DisCo) Specification for SoundWire + * specifies properties to be implemented for SoundWire Masters and Slaves. + * The DisCo spec doesn't mandate these properties. However, SDW bus cannot + * work without knowing these values. + * + * The helper functions read the Master and Slave properties. Implementers + * of Master or Slave drivers can use any of the below three mechanisms: + * a) Use these APIs here as .read_prop() callback for Master and Slave + * b) Implement own methods and set those as .read_prop(), but invoke + * APIs in this file for generic read and override the values with + * platform specific data + * c) Implement ones own methods which do not use anything provided + * here + */ + +#include +#include +#include +#include +#include "bus.h" + +/** + * sdw_master_read_prop() - Read Master properties + * @bus: SDW bus instance + */ +int sdw_master_read_prop(struct sdw_bus *bus) +{ + struct sdw_master_prop *prop = &bus->prop; + struct fwnode_handle *link; + char name[32]; + int nval, i; + + device_property_read_u32(bus->dev, + "mipi-sdw-sw-interface-revision", &prop->revision); + + /* Find master handle */ + snprintf(name, sizeof(name), + "mipi-sdw-master-%d-subproperties", bus->link_id); + + link = device_get_named_child_node(bus->dev, name); + if (!link) { + dev_err(bus->dev, "Master node %s not found\n", name); + return -EIO; + } + + if (fwnode_property_read_bool(link, + "mipi-sdw-clock-stop-mode0-supported") == true) + prop->clk_stop_mode = SDW_CLK_STOP_MODE0; + + if (fwnode_property_read_bool(link, + "mipi-sdw-clock-stop-mode1-supported") == true) + prop->clk_stop_mode |= SDW_CLK_STOP_MODE1; + + fwnode_property_read_u32(link, + "mipi-sdw-max-clock-frequency", &prop->max_freq); + + nval = fwnode_property_read_u32_array(link, + "mipi-sdw-clock-frequencies-supported", NULL, 0); + if (nval > 0) { + + prop->num_freq = nval; + prop->freq = devm_kcalloc(bus->dev, prop->num_freq, + sizeof(*prop->freq), GFP_KERNEL); + if (!prop->freq) + return -ENOMEM; + + fwnode_property_read_u32_array(link, + "mipi-sdw-clock-frequencies-supported", + prop->freq, prop->num_freq); + } + + /* + * Check the frequencies supported. If FW doesn't provide max + * freq, then populate here by checking values. + */ + if (!prop->max_freq && prop->freq) { + prop->max_freq = prop->freq[0]; + for (i = 1; i < prop->num_freq; i++) { + if (prop->freq[i] > prop->max_freq) + prop->max_freq = prop->freq[i]; + } + } + + nval = fwnode_property_read_u32_array(link, + "mipi-sdw-supported-clock-gears", NULL, 0); + if (nval > 0) { + + prop->num_clk_gears = nval; + prop->clk_gears = devm_kcalloc(bus->dev, prop->num_clk_gears, + sizeof(*prop->clk_gears), GFP_KERNEL); + if (!prop->clk_gears) + return -ENOMEM; + + fwnode_property_read_u32_array(link, + "mipi-sdw-supported-clock-gears", + prop->clk_gears, prop->num_clk_gears); + } + + fwnode_property_read_u32(link, "mipi-sdw-default-frame-rate", + &prop->default_frame_rate); + + fwnode_property_read_u32(link, "mipi-sdw-default-frame-row-size", + &prop->default_row); + + fwnode_property_read_u32(link, "mipi-sdw-default-frame-col-size", + &prop->default_col); + + prop->dynamic_frame = fwnode_property_read_bool(link, + "mipi-sdw-dynamic-frame-shape"); + + fwnode_property_read_u32(link, "mipi-sdw-command-error-threshold", + &prop->err_threshold); + + return 0; +} +EXPORT_SYMBOL(sdw_master_read_prop); + +static int sdw_slave_read_dp0(struct sdw_slave *slave, + struct fwnode_handle *port, struct sdw_dp0_prop *dp0) +{ + int nval; + + fwnode_property_read_u32(port, "mipi-sdw-port-max-wordlength", + &dp0->max_word); + + fwnode_property_read_u32(port, "mipi-sdw-port-min-wordlength", + &dp0->min_word); + + nval = fwnode_property_read_u32_array(port, + "mipi-sdw-port-wordlength-configs", NULL, 0); + if (nval > 0) { + + dp0->num_words = nval; + dp0->words = devm_kcalloc(&slave->dev, + dp0->num_words, sizeof(*dp0->words), + GFP_KERNEL); + if (!dp0->words) + return -ENOMEM; + + fwnode_property_read_u32_array(port, + "mipi-sdw-port-wordlength-configs", + dp0->words, dp0->num_words); + } + + dp0->flow_controlled = fwnode_property_read_bool( + port, "mipi-sdw-bra-flow-controlled"); + + dp0->simple_ch_prep_sm = fwnode_property_read_bool( + port, "mipi-sdw-simplified-channel-prepare-sm"); + + dp0->device_interrupts = fwnode_property_read_bool( + port, "mipi-sdw-imp-def-dp0-interrupts-supported"); + + return 0; +} + +static int sdw_slave_read_dpn(struct sdw_slave *slave, + struct sdw_dpn_prop *dpn, int count, int ports, char *type) +{ + struct fwnode_handle *node; + u32 bit, i = 0; + int nval; + unsigned long addr; + char name[40]; + + addr = ports; + /* valid ports are 1 to 14 so apply mask */ + addr &= GENMASK(14, 1); + + for_each_set_bit(bit, &addr, 32) { + snprintf(name, sizeof(name), + "mipi-sdw-dp-%d-%s-subproperties", bit, type); + + dpn[i].num = bit; + + node = device_get_named_child_node(&slave->dev, name); + if (!node) { + dev_err(&slave->dev, "%s dpN not found\n", name); + return -EIO; + } + + fwnode_property_read_u32(node, "mipi-sdw-port-max-wordlength", + &dpn[i].max_word); + fwnode_property_read_u32(node, "mipi-sdw-port-min-wordlength", + &dpn[i].min_word); + + nval = fwnode_property_read_u32_array(node, + "mipi-sdw-port-wordlength-configs", NULL, 0); + if (nval > 0) { + + dpn[i].num_words = nval; + dpn[i].words = devm_kcalloc(&slave->dev, + dpn[i].num_words, + sizeof(*dpn[i].words), GFP_KERNEL); + if (!dpn[i].words) + return -ENOMEM; + + fwnode_property_read_u32_array(node, + "mipi-sdw-port-wordlength-configs", + dpn[i].words, dpn[i].num_words); + } + + fwnode_property_read_u32(node, "mipi-sdw-data-port-type", + &dpn[i].type); + + fwnode_property_read_u32(node, + "mipi-sdw-max-grouping-supported", + &dpn[i].max_grouping); + + dpn[i].simple_ch_prep_sm = fwnode_property_read_bool(node, + "mipi-sdw-simplified-channelprepare-sm"); + + fwnode_property_read_u32(node, + "mipi-sdw-port-channelprepare-timeout", + &dpn[i].ch_prep_timeout); + + fwnode_property_read_u32(node, + "mipi-sdw-imp-def-dpn-interrupts-supported", + &dpn[i].device_interrupts); + + fwnode_property_read_u32(node, "mipi-sdw-min-channel-number", + &dpn[i].min_ch); + + fwnode_property_read_u32(node, "mipi-sdw-max-channel-number", + &dpn[i].max_ch); + + nval = fwnode_property_read_u32_array(node, + "mipi-sdw-channel-number-list", NULL, 0); + if (nval > 0) { + + dpn[i].num_ch = nval; + dpn[i].ch = devm_kcalloc(&slave->dev, dpn[i].num_ch, + sizeof(*dpn[i].ch), GFP_KERNEL); + if (!dpn[i].ch) + return -ENOMEM; + + fwnode_property_read_u32_array(node, + "mipi-sdw-channel-number-list", + dpn[i].ch, dpn[i].num_ch); + } + + nval = fwnode_property_read_u32_array(node, + "mipi-sdw-channel-combination-list", NULL, 0); + if (nval > 0) { + + dpn[i].num_ch_combinations = nval; + dpn[i].ch_combinations = devm_kcalloc(&slave->dev, + dpn[i].num_ch_combinations, + sizeof(*dpn[i].ch_combinations), + GFP_KERNEL); + if (!dpn[i].ch_combinations) + return -ENOMEM; + + fwnode_property_read_u32_array(node, + "mipi-sdw-channel-combination-list", + dpn[i].ch_combinations, + dpn[i].num_ch_combinations); + } + + fwnode_property_read_u32(node, + "mipi-sdw-modes-supported", &dpn[i].modes); + + fwnode_property_read_u32(node, "mipi-sdw-max-async-buffer", + &dpn[i].max_async_buffer); + + dpn[i].block_pack_mode = fwnode_property_read_bool(node, + "mipi-sdw-block-packing-mode"); + + fwnode_property_read_u32(node, "mipi-sdw-port-encoding-type", + &dpn[i].port_encoding); + + /* TODO: Read audio mode */ + + i++; + } + + return 0; +} + +/** + * sdw_slave_read_prop() - Read Slave properties + * @slave: SDW Slave + */ +int sdw_slave_read_prop(struct sdw_slave *slave) +{ + struct sdw_slave_prop *prop = &slave->prop; + struct device *dev = &slave->dev; + struct fwnode_handle *port; + int num_of_ports, nval, i, dp0 = 0; + + device_property_read_u32(dev, "mipi-sdw-sw-interface-revision", + &prop->mipi_revision); + + prop->wake_capable = device_property_read_bool(dev, + "mipi-sdw-wake-up-unavailable"); + prop->wake_capable = !prop->wake_capable; + + prop->test_mode_capable = device_property_read_bool(dev, + "mipi-sdw-test-mode-supported"); + + prop->clk_stop_mode1 = false; + if (device_property_read_bool(dev, + "mipi-sdw-clock-stop-mode1-supported")) + prop->clk_stop_mode1 = true; + + prop->simple_clk_stop_capable = device_property_read_bool(dev, + "mipi-sdw-simplified-clockstopprepare-sm-supported"); + + device_property_read_u32(dev, "mipi-sdw-clockstopprepare-timeout", + &prop->clk_stop_timeout); + + device_property_read_u32(dev, "mipi-sdw-slave-channelprepare-timeout", + &prop->ch_prep_timeout); + + device_property_read_u32(dev, + "mipi-sdw-clockstopprepare-hard-reset-behavior", + &prop->reset_behave); + + prop->high_PHY_capable = device_property_read_bool(dev, + "mipi-sdw-highPHY-capable"); + + prop->paging_support = device_property_read_bool(dev, + "mipi-sdw-paging-support"); + + prop->bank_delay_support = device_property_read_bool(dev, + "mipi-sdw-bank-delay-support"); + + device_property_read_u32(dev, + "mipi-sdw-port15-read-behavior", &prop->p15_behave); + + device_property_read_u32(dev, "mipi-sdw-master-count", + &prop->master_count); + + device_property_read_u32(dev, "mipi-sdw-source-port-list", + &prop->source_ports); + + device_property_read_u32(dev, "mipi-sdw-sink-port-list", + &prop->sink_ports); + + /* Read dp0 properties */ + port = device_get_named_child_node(dev, "mipi-sdw-dp-0-subproperties"); + if (!port) { + dev_dbg(dev, "DP0 node not found!!\n"); + } else { + + prop->dp0_prop = devm_kzalloc(&slave->dev, + sizeof(*prop->dp0_prop), GFP_KERNEL); + if (!prop->dp0_prop) + return -ENOMEM; + + sdw_slave_read_dp0(slave, port, prop->dp0_prop); + dp0 = 1; + } + + /* + * Based on each DPn port, get source and sink dpn properties. + * Also, some ports can operate as both source or sink. + */ + + /* Allocate memory for set bits in port lists */ + nval = hweight32(prop->source_ports); + prop->src_dpn_prop = devm_kcalloc(&slave->dev, nval, + sizeof(*prop->src_dpn_prop), GFP_KERNEL); + if (!prop->src_dpn_prop) + return -ENOMEM; + + /* Read dpn properties for source port(s) */ + sdw_slave_read_dpn(slave, prop->src_dpn_prop, nval, + prop->source_ports, "source"); + + nval = hweight32(prop->sink_ports); + prop->sink_dpn_prop = devm_kcalloc(&slave->dev, nval, + sizeof(*prop->sink_dpn_prop), GFP_KERNEL); + if (!prop->sink_dpn_prop) + return -ENOMEM; + + /* Read dpn properties for sink port(s) */ + sdw_slave_read_dpn(slave, prop->sink_dpn_prop, nval, + prop->sink_ports, "sink"); + + /* some ports are bidirectional so check total ports by ORing */ + nval = prop->source_ports | prop->sink_ports; + num_of_ports = hweight32(nval) + dp0; /* add DP0 */ + + /* Allocate port_ready based on num_of_ports */ + slave->port_ready = devm_kcalloc(&slave->dev, num_of_ports, + sizeof(*slave->port_ready), GFP_KERNEL); + if (!slave->port_ready) + return -ENOMEM; + + /* Initialize completion */ + for (i = 0; i < num_of_ports; i++) + init_completion(&slave->port_ready[i]); + + return 0; +} +EXPORT_SYMBOL(sdw_slave_read_prop); diff --git a/include/linux/soundwire/sdw.h b/include/linux/soundwire/sdw.h index 3bfa7f1e2b47..3c13f580d42d 100644 --- a/include/linux/soundwire/sdw.h +++ b/include/linux/soundwire/sdw.h @@ -38,6 +38,252 @@ enum sdw_slave_status { SDW_SLAVE_RESERVED = 3, }; +/* + * SDW properties, defined in MIPI DisCo spec v1.0 + */ +enum sdw_clk_stop_reset_behave { + SDW_CLK_STOP_KEEP_STATUS = 1, +}; + +/** + * enum sdw_p15_behave - Slave Port 15 behaviour when the Master attempts a + * read + * @SDW_P15_READ_IGNORED: Read is ignored + * @SDW_P15_CMD_OK: Command is ok + */ +enum sdw_p15_behave { + SDW_P15_READ_IGNORED = 0, + SDW_P15_CMD_OK = 1, +}; + +/** + * enum sdw_dpn_type - Data port types + * @SDW_DPN_FULL: Full Data Port is supported + * @SDW_DPN_SIMPLE: Simplified Data Port as defined in spec. + * DPN_SampleCtrl2, DPN_OffsetCtrl2, DPN_HCtrl and DPN_BlockCtrl3 + * are not implemented. + * @SDW_DPN_REDUCED: Reduced Data Port as defined in spec. + * DPN_SampleCtrl2, DPN_HCtrl are not implemented. + */ +enum sdw_dpn_type { + SDW_DPN_FULL = 0, + SDW_DPN_SIMPLE = 1, + SDW_DPN_REDUCED = 2, +}; + +/** + * enum sdw_clk_stop_mode - Clock Stop modes + * @SDW_CLK_STOP_MODE0: Slave can continue operation seamlessly on clock + * restart + * @SDW_CLK_STOP_MODE1: Slave may have entered a deeper power-saving mode, + * not capable of continuing operation seamlessly when the clock restarts + */ +enum sdw_clk_stop_mode { + SDW_CLK_STOP_MODE0 = 0, + SDW_CLK_STOP_MODE1 = 1, +}; + +/** + * struct sdw_dp0_prop - DP0 properties + * @max_word: Maximum number of bits in a Payload Channel Sample, 1 to 64 + * (inclusive) + * @min_word: Minimum number of bits in a Payload Channel Sample, 1 to 64 + * (inclusive) + * @num_words: number of wordlengths supported + * @words: wordlengths supported + * @flow_controlled: Slave implementation results in an OK_NotReady + * response + * @simple_ch_prep_sm: If channel prepare sequence is required + * @device_interrupts: If implementation-defined interrupts are supported + * + * The wordlengths are specified by Spec as max, min AND number of + * discrete values, implementation can define based on the wordlengths they + * support + */ +struct sdw_dp0_prop { + u32 max_word; + u32 min_word; + u32 num_words; + u32 *words; + bool flow_controlled; + bool simple_ch_prep_sm; + bool device_interrupts; +}; + +/** + * struct sdw_dpn_audio_mode - Audio mode properties for DPn + * @bus_min_freq: Minimum bus frequency, in Hz + * @bus_max_freq: Maximum bus frequency, in Hz + * @bus_num_freq: Number of discrete frequencies supported + * @bus_freq: Discrete bus frequencies, in Hz + * @min_freq: Minimum sampling frequency, in Hz + * @max_freq: Maximum sampling bus frequency, in Hz + * @num_freq: Number of discrete sampling frequency supported + * @freq: Discrete sampling frequencies, in Hz + * @prep_ch_behave: Specifies the dependencies between Channel Prepare + * sequence and bus clock configuration + * If 0, Channel Prepare can happen at any Bus clock rate + * If 1, Channel Prepare sequence shall happen only after Bus clock is + * changed to a frequency supported by this mode or compatible modes + * described by the next field + * @glitchless: Bitmap describing possible glitchless transitions from this + * Audio Mode to other Audio Modes + */ +struct sdw_dpn_audio_mode { + u32 bus_min_freq; + u32 bus_max_freq; + u32 bus_num_freq; + u32 *bus_freq; + u32 max_freq; + u32 min_freq; + u32 num_freq; + u32 *freq; + u32 prep_ch_behave; + u32 glitchless; +}; + +/** + * struct sdw_dpn_prop - Data Port DPn properties + * @num: port number + * @max_word: Maximum number of bits in a Payload Channel Sample, 1 to 64 + * (inclusive) + * @min_word: Minimum number of bits in a Payload Channel Sample, 1 to 64 + * (inclusive) + * @num_words: Number of discrete supported wordlengths + * @words: Discrete supported wordlength + * @type: Data port type. Full, Simplified or Reduced + * @max_grouping: Maximum number of samples that can be grouped together for + * a full data port + * @simple_ch_prep_sm: If the port supports simplified channel prepare state + * machine + * @ch_prep_timeout: Port-specific timeout value, in milliseconds + * @device_interrupts: If set, each bit corresponds to support for + * implementation-defined interrupts + * @max_ch: Maximum channels supported + * @min_ch: Minimum channels supported + * @num_ch: Number of discrete channels supported + * @ch: Discrete channels supported + * @num_ch_combinations: Number of channel combinations supported + * @ch_combinations: Channel combinations supported + * @modes: SDW mode supported + * @max_async_buffer: Number of samples that this port can buffer in + * asynchronous modes + * @block_pack_mode: Type of block port mode supported + * @port_encoding: Payload Channel Sample encoding schemes supported + * @audio_modes: Audio modes supported + */ +struct sdw_dpn_prop { + u32 num; + u32 max_word; + u32 min_word; + u32 num_words; + u32 *words; + enum sdw_dpn_type type; + u32 max_grouping; + bool simple_ch_prep_sm; + u32 ch_prep_timeout; + u32 device_interrupts; + u32 max_ch; + u32 min_ch; + u32 num_ch; + u32 *ch; + u32 num_ch_combinations; + u32 *ch_combinations; + u32 modes; + u32 max_async_buffer; + bool block_pack_mode; + u32 port_encoding; + struct sdw_dpn_audio_mode *audio_modes; +}; + +/** + * struct sdw_slave_prop - SoundWire Slave properties + * @mipi_revision: Spec version of the implementation + * @wake_capable: Wake-up events are supported + * @test_mode_capable: If test mode is supported + * @clk_stop_mode1: Clock-Stop Mode 1 is supported + * @simple_clk_stop_capable: Simple clock mode is supported + * @clk_stop_timeout: Worst-case latency of the Clock Stop Prepare State + * Machine transitions, in milliseconds + * @ch_prep_timeout: Worst-case latency of the Channel Prepare State Machine + * transitions, in milliseconds + * @reset_behave: Slave keeps the status of the SlaveStopClockPrepare + * state machine (P=1 SCSP_SM) after exit from clock-stop mode1 + * @high_PHY_capable: Slave is HighPHY capable + * @paging_support: Slave implements paging registers SCP_AddrPage1 and + * SCP_AddrPage2 + * @bank_delay_support: Slave implements bank delay/bridge support registers + * SCP_BankDelay and SCP_NextFrame + * @p15_behave: Slave behavior when the Master attempts a read to the Port15 + * alias + * @lane_control_support: Slave supports lane control + * @master_count: Number of Masters present on this Slave + * @source_ports: Bitmap identifying source ports + * @sink_ports: Bitmap identifying sink ports + * @dp0_prop: Data Port 0 properties + * @src_dpn_prop: Source Data Port N properties + * @sink_dpn_prop: Sink Data Port N properties + */ +struct sdw_slave_prop { + u32 mipi_revision; + bool wake_capable; + bool test_mode_capable; + bool clk_stop_mode1; + bool simple_clk_stop_capable; + u32 clk_stop_timeout; + u32 ch_prep_timeout; + enum sdw_clk_stop_reset_behave reset_behave; + bool high_PHY_capable; + bool paging_support; + bool bank_delay_support; + enum sdw_p15_behave p15_behave; + bool lane_control_support; + u32 master_count; + u32 source_ports; + u32 sink_ports; + struct sdw_dp0_prop *dp0_prop; + struct sdw_dpn_prop *src_dpn_prop; + struct sdw_dpn_prop *sink_dpn_prop; +}; + +/** + * struct sdw_master_prop - Master properties + * @revision: MIPI spec version of the implementation + * @master_count: Number of masters + * @clk_stop_mode: Bitmap for Clock Stop modes supported + * @max_freq: Maximum Bus clock frequency, in Hz + * @num_clk_gears: Number of clock gears supported + * @clk_gears: Clock gears supported + * @num_freq: Number of clock frequencies supported, in Hz + * @freq: Clock frequencies supported, in Hz + * @default_frame_rate: Controller default Frame rate, in Hz + * @default_row: Number of rows + * @default_col: Number of columns + * @dynamic_frame: Dynamic frame supported + * @err_threshold: Number of times that software may retry sending a single + * command + * @dpn_prop: Data Port N properties + */ +struct sdw_master_prop { + u32 revision; + u32 master_count; + enum sdw_clk_stop_mode clk_stop_mode; + u32 max_freq; + u32 num_clk_gears; + u32 *clk_gears; + u32 num_freq; + u32 *freq; + u32 default_frame_rate; + u32 default_row; + u32 default_col; + bool dynamic_frame; + u32 err_threshold; + struct sdw_dpn_prop *dpn_prop; +}; + +int sdw_master_read_prop(struct sdw_bus *bus); +int sdw_slave_read_prop(struct sdw_slave *slave); + /* * SDW Slave Structures and APIs */ @@ -61,13 +307,24 @@ struct sdw_slave_id { __u8 sdw_version:4; }; +/** + * struct sdw_slave_ops - Slave driver callback ops + * @read_prop: Read Slave properties + */ +struct sdw_slave_ops { + int (*read_prop)(struct sdw_slave *sdw); +}; + /** * struct sdw_slave - SoundWire Slave * @id: MIPI device ID * @dev: Linux device * @status: Status reported by the Slave * @bus: Bus handle + * @ops: Slave callback ops + * @prop: Slave properties * @node: node for bus list + * @port_ready: Port ready completion flag for each Slave port * @dev_num: Device Number assigned by Bus */ struct sdw_slave { @@ -75,7 +332,10 @@ struct sdw_slave { struct device dev; enum sdw_slave_status status; struct sdw_bus *bus; + const struct sdw_slave_ops *ops; + struct sdw_slave_prop prop; struct list_head node; + struct completion *port_ready; u16 dev_num; }; @@ -103,6 +363,14 @@ struct sdw_driver { * SDW master structures and APIs */ +/** + * struct sdw_master_ops - Master driver ops + * @read_prop: Read Master properties + */ +struct sdw_master_ops { + int (*read_prop)(struct sdw_bus *bus); +}; + /** * struct sdw_bus - SoundWire bus * @dev: Master linux device @@ -111,6 +379,9 @@ struct sdw_driver { * @assigned: Bitmap for Slave device numbers. * Bit set implies used number, bit clear implies unused number. * @bus_lock: bus lock + * @ops: Master callback ops + * @prop: Master properties + * @clk_stop_timeout: Clock stop timeout computed */ struct sdw_bus { struct device *dev; @@ -118,6 +389,9 @@ struct sdw_bus { struct list_head slaves; DECLARE_BITMAP(assigned, SDW_MAX_DEVICES); struct mutex bus_lock; + const struct sdw_master_ops *ops; + struct sdw_master_prop prop; + unsigned int clk_stop_timeout; }; int sdw_add_bus_master(struct sdw_bus *bus); -- cgit v1.2.3-58-ga151 From 6f3da1f3881c8f2fd7e06cff54a2437399528b4a Mon Sep 17 00:00:00 2001 From: Sanyog Kale Date: Thu, 14 Dec 2017 11:19:36 +0530 Subject: soundwire: Add SoundWire MIPI defined registers MIPI SoundWire spec defines standard SoundWire registers mandatory for SoundWire Slave devices, so add them. Signed-off-by: Sanyog Kale Reviewed-by: Philippe Ombredanne Acked-By: Pierre-Louis Bossart Reviewed-by: Takashi Iwai Signed-off-by: Vinod Koul Signed-off-by: Greg Kroah-Hartman --- include/linux/soundwire/sdw_registers.h | 194 ++++++++++++++++++++++++++++++++ 1 file changed, 194 insertions(+) create mode 100644 include/linux/soundwire/sdw_registers.h (limited to 'include') diff --git a/include/linux/soundwire/sdw_registers.h b/include/linux/soundwire/sdw_registers.h new file mode 100644 index 000000000000..df472b1ab410 --- /dev/null +++ b/include/linux/soundwire/sdw_registers.h @@ -0,0 +1,194 @@ +// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) +// Copyright(c) 2015-17 Intel Corporation. + +#ifndef __SDW_REGISTERS_H +#define __SDW_REGISTERS_H + +/* + * typically we define register and shifts but if one observes carefully, + * the shift can be generated from MASKS using few bit primitaives like ffs + * etc, so we use that and avoid defining shifts + */ +#define SDW_REG_SHIFT(n) (ffs(n) - 1) + +/* + * SDW registers as defined by MIPI 1.1 Spec + */ +#define SDW_REGADDR GENMASK(14, 0) +#define SDW_SCP_ADDRPAGE2_MASK GENMASK(22, 15) +#define SDW_SCP_ADDRPAGE1_MASK GENMASK(30, 23) + +#define SDW_REG_NO_PAGE 0x00008000 +#define SDW_REG_OPTIONAL_PAGE 0x00010000 +#define SDW_REG_MAX 0x80000000 + +#define SDW_DPN_SIZE 0x100 +#define SDW_BANK1_OFFSET 0x10 + +/* + * DP0 Interrupt register & bits + * + * Spec treats Status (RO) and Clear (WC) as separate but they are same + * address, so treat as same register with WC. + */ + +/* both INT and STATUS register are same */ +#define SDW_DP0_INT 0x0 +#define SDW_DP0_INTMASK 0x1 +#define SDW_DP0_PORTCTRL 0x2 +#define SDW_DP0_BLOCKCTRL1 0x3 +#define SDW_DP0_PREPARESTATUS 0x4 +#define SDW_DP0_PREPARECTRL 0x5 + +#define SDW_DP0_INT_TEST_FAIL BIT(0) +#define SDW_DP0_INT_PORT_READY BIT(1) +#define SDW_DP0_INT_BRA_FAILURE BIT(2) +#define SDW_DP0_INT_IMPDEF1 BIT(5) +#define SDW_DP0_INT_IMPDEF2 BIT(6) +#define SDW_DP0_INT_IMPDEF3 BIT(7) + +#define SDW_DP0_PORTCTRL_DATAMODE GENMASK(3, 2) +#define SDW_DP0_PORTCTRL_NXTINVBANK BIT(4) +#define SDW_DP0_PORTCTRL_BPT_PAYLD GENMASK(7, 6) + +#define SDW_DP0_CHANNELEN 0x20 +#define SDW_DP0_SAMPLECTRL1 0x22 +#define SDW_DP0_SAMPLECTRL2 0x23 +#define SDW_DP0_OFFSETCTRL1 0x24 +#define SDW_DP0_OFFSETCTRL2 0x25 +#define SDW_DP0_HCTRL 0x26 +#define SDW_DP0_LANECTRL 0x28 + +/* Both INT and STATUS register are same */ +#define SDW_SCP_INT1 0x40 +#define SDW_SCP_INTMASK1 0x41 + +#define SDW_SCP_INT1_PARITY BIT(0) +#define SDW_SCP_INT1_BUS_CLASH BIT(1) +#define SDW_SCP_INT1_IMPL_DEF BIT(2) +#define SDW_SCP_INT1_SCP2_CASCADE BIT(7) +#define SDW_SCP_INT1_PORT0_3 GENMASK(6, 3) + +#define SDW_SCP_INTSTAT2 0x42 +#define SDW_SCP_INTSTAT2_SCP3_CASCADE BIT(7) +#define SDW_SCP_INTSTAT2_PORT4_10 GENMASK(6, 0) + + +#define SDW_SCP_INTSTAT3 0x43 +#define SDW_SCP_INTSTAT3_PORT11_14 GENMASK(3, 0) + +/* Number of interrupt status registers */ +#define SDW_NUM_INT_STAT_REGISTERS 3 + +/* Number of interrupt clear registers */ +#define SDW_NUM_INT_CLEAR_REGISTERS 1 + +#define SDW_SCP_CTRL 0x44 +#define SDW_SCP_CTRL_CLK_STP_NOW BIT(1) +#define SDW_SCP_CTRL_FORCE_RESET BIT(7) + +#define SDW_SCP_STAT 0x44 +#define SDW_SCP_STAT_CLK_STP_NF BIT(0) +#define SDW_SCP_STAT_HPHY_NOK BIT(5) +#define SDW_SCP_STAT_CURR_BANK BIT(6) + +#define SDW_SCP_SYSTEMCTRL 0x45 +#define SDW_SCP_SYSTEMCTRL_CLK_STP_PREP BIT(0) +#define SDW_SCP_SYSTEMCTRL_CLK_STP_MODE BIT(2) +#define SDW_SCP_SYSTEMCTRL_WAKE_UP_EN BIT(3) +#define SDW_SCP_SYSTEMCTRL_HIGH_PHY BIT(4) + +#define SDW_SCP_SYSTEMCTRL_CLK_STP_MODE0 0 +#define SDW_SCP_SYSTEMCTRL_CLK_STP_MODE1 BIT(2) + +#define SDW_SCP_DEVNUMBER 0x46 +#define SDW_SCP_HIGH_PHY_CHECK 0x47 +#define SDW_SCP_ADDRPAGE1 0x48 +#define SDW_SCP_ADDRPAGE2 0x49 +#define SDW_SCP_KEEPEREN 0x4A +#define SDW_SCP_BANKDELAY 0x4B +#define SDW_SCP_TESTMODE 0x4F +#define SDW_SCP_DEVID_0 0x50 +#define SDW_SCP_DEVID_1 0x51 +#define SDW_SCP_DEVID_2 0x52 +#define SDW_SCP_DEVID_3 0x53 +#define SDW_SCP_DEVID_4 0x54 +#define SDW_SCP_DEVID_5 0x55 + +/* Banked Registers */ +#define SDW_SCP_FRAMECTRL_B0 0x60 +#define SDW_SCP_FRAMECTRL_B1 (0x60 + SDW_BANK1_OFFSET) +#define SDW_SCP_NEXTFRAME_B0 0x61 +#define SDW_SCP_NEXTFRAME_B1 (0x61 + SDW_BANK1_OFFSET) + +/* Both INT and STATUS register is same */ +#define SDW_DPN_INT(n) (0x0 + SDW_DPN_SIZE * (n)) +#define SDW_DPN_INTMASK(n) (0x1 + SDW_DPN_SIZE * (n)) +#define SDW_DPN_PORTCTRL(n) (0x2 + SDW_DPN_SIZE * (n)) +#define SDW_DPN_BLOCKCTRL1(n) (0x3 + SDW_DPN_SIZE * (n)) +#define SDW_DPN_PREPARESTATUS(n) (0x4 + SDW_DPN_SIZE * (n)) +#define SDW_DPN_PREPARECTRL(n) (0x5 + SDW_DPN_SIZE * (n)) + +#define SDW_DPN_INT_TEST_FAIL BIT(0) +#define SDW_DPN_INT_PORT_READY BIT(1) +#define SDW_DPN_INT_IMPDEF1 BIT(5) +#define SDW_DPN_INT_IMPDEF2 BIT(6) +#define SDW_DPN_INT_IMPDEF3 BIT(7) + +#define SDW_DPN_PORTCTRL_FLOWMODE GENMASK(1, 0) +#define SDW_DPN_PORTCTRL_DATAMODE GENMASK(3, 2) +#define SDW_DPN_PORTCTRL_NXTINVBANK BIT(4) + +#define SDW_DPN_BLOCKCTRL1_WDLEN GENMASK(5, 0) + +#define SDW_DPN_PREPARECTRL_CH_PREP GENMASK(7, 0) + +#define SDW_DPN_CHANNELEN_B0(n) (0x20 + SDW_DPN_SIZE * (n)) +#define SDW_DPN_CHANNELEN_B1(n) (0x30 + SDW_DPN_SIZE * (n)) + +#define SDW_DPN_BLOCKCTRL2_B0(n) (0x21 + SDW_DPN_SIZE * (n)) +#define SDW_DPN_BLOCKCTRL2_B1(n) (0x31 + SDW_DPN_SIZE * (n)) + +#define SDW_DPN_SAMPLECTRL1_B0(n) (0x22 + SDW_DPN_SIZE * (n)) +#define SDW_DPN_SAMPLECTRL1_B1(n) (0x32 + SDW_DPN_SIZE * (n)) + +#define SDW_DPN_SAMPLECTRL2_B0(n) (0x23 + SDW_DPN_SIZE * (n)) +#define SDW_DPN_SAMPLECTRL2_B1(n) (0x33 + SDW_DPN_SIZE * (n)) + +#define SDW_DPN_OFFSETCTRL1_B0(n) (0x24 + SDW_DPN_SIZE * (n)) +#define SDW_DPN_OFFSETCTRL1_B1(n) (0x34 + SDW_DPN_SIZE * (n)) + +#define SDW_DPN_OFFSETCTRL2_B0(n) (0x25 + SDW_DPN_SIZE * (n)) +#define SDW_DPN_OFFSETCTRL2_B1(n) (0x35 + SDW_DPN_SIZE * (n)) + +#define SDW_DPN_HCTRL_B0(n) (0x26 + SDW_DPN_SIZE * (n)) +#define SDW_DPN_HCTRL_B1(n) (0x36 + SDW_DPN_SIZE * (n)) + +#define SDW_DPN_BLOCKCTRL3_B0(n) (0x27 + SDW_DPN_SIZE * (n)) +#define SDW_DPN_BLOCKCTRL3_B1(n) (0x37 + SDW_DPN_SIZE * (n)) + +#define SDW_DPN_LANECTRL_B0(n) (0x28 + SDW_DPN_SIZE * (n)) +#define SDW_DPN_LANECTRL_B1(n) (0x38 + SDW_DPN_SIZE * (n)) + +#define SDW_DPN_SAMPLECTRL_LOW GENMASK(7, 0) +#define SDW_DPN_SAMPLECTRL_HIGH GENMASK(15, 8) + +#define SDW_DPN_HCTRL_HSTART GENMASK(7, 4) +#define SDW_DPN_HCTRL_HSTOP GENMASK(3, 0) + +#define SDW_NUM_CASC_PORT_INTSTAT1 4 +#define SDW_CASC_PORT_START_INTSTAT1 0 +#define SDW_CASC_PORT_MASK_INTSTAT1 0x8 +#define SDW_CASC_PORT_REG_OFFSET_INTSTAT1 0x0 + +#define SDW_NUM_CASC_PORT_INTSTAT2 7 +#define SDW_CASC_PORT_START_INTSTAT2 4 +#define SDW_CASC_PORT_MASK_INTSTAT2 1 +#define SDW_CASC_PORT_REG_OFFSET_INTSTAT2 1 + +#define SDW_NUM_CASC_PORT_INTSTAT3 4 +#define SDW_CASC_PORT_START_INTSTAT3 11 +#define SDW_CASC_PORT_MASK_INTSTAT3 1 +#define SDW_CASC_PORT_REG_OFFSET_INTSTAT3 2 + +#endif /* __SDW_REGISTERS_H */ -- cgit v1.2.3-58-ga151 From 9d715fa005ebccc510a430df278665793528e31f Mon Sep 17 00:00:00 2001 From: Vinod Koul Date: Thu, 14 Dec 2017 11:19:37 +0530 Subject: soundwire: Add IO transfer SoundWire bus supports read or write register(s) for SoundWire Slave device. sdw_read() and sdw_write() APIs are provided for single register read/write. sdw_nread() and sdw_nwrite() for operations on contiguous registers. Signed-off-by: Sanyog Kale Reviewed-by: Philippe Ombredanne Acked-By: Pierre-Louis Bossart Reviewed-by: Takashi Iwai Signed-off-by: Vinod Koul Signed-off-by: Greg Kroah-Hartman --- drivers/soundwire/bus.c | 272 ++++++++++++++++++++++++++++++++++++++++++ drivers/soundwire/bus.h | 36 ++++++ include/linux/soundwire/sdw.h | 57 +++++++++ 3 files changed, 365 insertions(+) (limited to 'include') diff --git a/drivers/soundwire/bus.c b/drivers/soundwire/bus.c index 9626bd1ab271..fb9008d6c40e 100644 --- a/drivers/soundwire/bus.c +++ b/drivers/soundwire/bus.c @@ -3,6 +3,8 @@ #include #include +#include +#include #include #include "bus.h" @@ -22,6 +24,12 @@ int sdw_add_bus_master(struct sdw_bus *bus) return -ENODEV; } + if (!bus->ops) { + dev_err(bus->dev, "SoundWire Bus ops are not set"); + return -EINVAL; + } + + mutex_init(&bus->msg_lock); mutex_init(&bus->bus_lock); INIT_LIST_HEAD(&bus->slaves); @@ -102,6 +110,270 @@ void sdw_delete_bus_master(struct sdw_bus *bus) } EXPORT_SYMBOL(sdw_delete_bus_master); +/* + * SDW IO Calls + */ + +static inline int find_response_code(enum sdw_command_response resp) +{ + switch (resp) { + case SDW_CMD_OK: + return 0; + + case SDW_CMD_IGNORED: + return -ENODATA; + + case SDW_CMD_TIMEOUT: + return -ETIMEDOUT; + + default: + return -EIO; + } +} + +static inline int do_transfer(struct sdw_bus *bus, struct sdw_msg *msg) +{ + int retry = bus->prop.err_threshold; + enum sdw_command_response resp; + int ret = 0, i; + + for (i = 0; i <= retry; i++) { + resp = bus->ops->xfer_msg(bus, msg); + ret = find_response_code(resp); + + /* if cmd is ok or ignored return */ + if (ret == 0 || ret == -ENODATA) + return ret; + } + + return ret; +} + +static inline int do_transfer_defer(struct sdw_bus *bus, + struct sdw_msg *msg, struct sdw_defer *defer) +{ + int retry = bus->prop.err_threshold; + enum sdw_command_response resp; + int ret = 0, i; + + defer->msg = msg; + defer->length = msg->len; + + for (i = 0; i <= retry; i++) { + resp = bus->ops->xfer_msg_defer(bus, msg, defer); + ret = find_response_code(resp); + /* if cmd is ok or ignored return */ + if (ret == 0 || ret == -ENODATA) + return ret; + } + + return ret; +} + +static int sdw_reset_page(struct sdw_bus *bus, u16 dev_num) +{ + int retry = bus->prop.err_threshold; + enum sdw_command_response resp; + int ret = 0, i; + + for (i = 0; i <= retry; i++) { + resp = bus->ops->reset_page_addr(bus, dev_num); + ret = find_response_code(resp); + /* if cmd is ok or ignored return */ + if (ret == 0 || ret == -ENODATA) + return ret; + } + + return ret; +} + +/** + * sdw_transfer() - Synchronous transfer message to a SDW Slave device + * @bus: SDW bus + * @msg: SDW message to be xfered + */ +int sdw_transfer(struct sdw_bus *bus, struct sdw_msg *msg) +{ + int ret; + + mutex_lock(&bus->msg_lock); + + ret = do_transfer(bus, msg); + if (ret != 0 && ret != -ENODATA) + dev_err(bus->dev, "trf on Slave %d failed:%d\n", + msg->dev_num, ret); + + if (msg->page) + sdw_reset_page(bus, msg->dev_num); + + mutex_unlock(&bus->msg_lock); + + return ret; +} + +/** + * sdw_transfer_defer() - Asynchronously transfer message to a SDW Slave device + * @bus: SDW bus + * @msg: SDW message to be xfered + * @defer: Defer block for signal completion + * + * Caller needs to hold the msg_lock lock while calling this + */ +int sdw_transfer_defer(struct sdw_bus *bus, struct sdw_msg *msg, + struct sdw_defer *defer) +{ + int ret; + + if (!bus->ops->xfer_msg_defer) + return -ENOTSUPP; + + ret = do_transfer_defer(bus, msg, defer); + if (ret != 0 && ret != -ENODATA) + dev_err(bus->dev, "Defer trf on Slave %d failed:%d\n", + msg->dev_num, ret); + + if (msg->page) + sdw_reset_page(bus, msg->dev_num); + + return ret; +} + + +int sdw_fill_msg(struct sdw_msg *msg, struct sdw_slave *slave, + u32 addr, size_t count, u16 dev_num, u8 flags, u8 *buf) +{ + memset(msg, 0, sizeof(*msg)); + msg->addr = addr; /* addr is 16 bit and truncated here */ + msg->len = count; + msg->dev_num = dev_num; + msg->flags = flags; + msg->buf = buf; + msg->ssp_sync = false; + msg->page = false; + + if (addr < SDW_REG_NO_PAGE) { /* no paging area */ + return 0; + } else if (addr >= SDW_REG_MAX) { /* illegal addr */ + pr_err("SDW: Invalid address %x passed\n", addr); + return -EINVAL; + } + + if (addr < SDW_REG_OPTIONAL_PAGE) { /* 32k but no page */ + if (slave && !slave->prop.paging_support) + return 0; + /* no need for else as that will fall thru to paging */ + } + + /* paging mandatory */ + if (dev_num == SDW_ENUM_DEV_NUM || dev_num == SDW_BROADCAST_DEV_NUM) { + pr_err("SDW: Invalid device for paging :%d\n", dev_num); + return -EINVAL; + } + + if (!slave) { + pr_err("SDW: No slave for paging addr\n"); + return -EINVAL; + } else if (!slave->prop.paging_support) { + dev_err(&slave->dev, + "address %x needs paging but no support", addr); + return -EINVAL; + } + + msg->addr_page1 = (addr >> SDW_REG_SHIFT(SDW_SCP_ADDRPAGE1_MASK)); + msg->addr_page2 = (addr >> SDW_REG_SHIFT(SDW_SCP_ADDRPAGE2_MASK)); + msg->addr |= BIT(15); + msg->page = true; + + return 0; +} + +/** + * sdw_nread() - Read "n" contiguous SDW Slave registers + * @slave: SDW Slave + * @addr: Register address + * @count: length + * @val: Buffer for values to be read + */ +int sdw_nread(struct sdw_slave *slave, u32 addr, size_t count, u8 *val) +{ + struct sdw_msg msg; + int ret; + + ret = sdw_fill_msg(&msg, slave, addr, count, + slave->dev_num, SDW_MSG_FLAG_READ, val); + if (ret < 0) + return ret; + + ret = pm_runtime_get_sync(slave->bus->dev); + if (!ret) + return ret; + + ret = sdw_transfer(slave->bus, &msg); + pm_runtime_put(slave->bus->dev); + + return ret; +} +EXPORT_SYMBOL(sdw_nread); + +/** + * sdw_nwrite() - Write "n" contiguous SDW Slave registers + * @slave: SDW Slave + * @addr: Register address + * @count: length + * @val: Buffer for values to be read + */ +int sdw_nwrite(struct sdw_slave *slave, u32 addr, size_t count, u8 *val) +{ + struct sdw_msg msg; + int ret; + + ret = sdw_fill_msg(&msg, slave, addr, count, + slave->dev_num, SDW_MSG_FLAG_WRITE, val); + if (ret < 0) + return ret; + + ret = pm_runtime_get_sync(slave->bus->dev); + if (!ret) + return ret; + + ret = sdw_transfer(slave->bus, &msg); + pm_runtime_put(slave->bus->dev); + + return ret; +} +EXPORT_SYMBOL(sdw_nwrite); + +/** + * sdw_read() - Read a SDW Slave register + * @slave: SDW Slave + * @addr: Register address + */ +int sdw_read(struct sdw_slave *slave, u32 addr) +{ + u8 buf; + int ret; + + ret = sdw_nread(slave, addr, 1, &buf); + if (ret < 0) + return ret; + else + return buf; +} +EXPORT_SYMBOL(sdw_read); + +/** + * sdw_write() - Write a SDW Slave register + * @slave: SDW Slave + * @addr: Register address + * @value: Register value + */ +int sdw_write(struct sdw_slave *slave, u32 addr, u8 value) +{ + return sdw_nwrite(slave, addr, 1, &value); + +} +EXPORT_SYMBOL(sdw_write); + void sdw_extract_slave_id(struct sdw_bus *bus, u64 addr, struct sdw_slave_id *id) { diff --git a/drivers/soundwire/bus.h b/drivers/soundwire/bus.h index a54921825ce0..48eb7de8db7b 100644 --- a/drivers/soundwire/bus.h +++ b/drivers/soundwire/bus.h @@ -16,4 +16,40 @@ static inline int sdw_acpi_find_slaves(struct sdw_bus *bus) void sdw_extract_slave_id(struct sdw_bus *bus, u64 addr, struct sdw_slave_id *id); +enum { + SDW_MSG_FLAG_READ = 0, + SDW_MSG_FLAG_WRITE, +}; + +/** + * struct sdw_msg - Message structure + * @addr: Register address accessed in the Slave + * @len: number of messages + * @dev_num: Slave device number + * @addr_page1: SCP address page 1 Slave register + * @addr_page2: SCP address page 2 Slave register + * @flags: transfer flags, indicate if xfer is read or write + * @buf: message data buffer + * @ssp_sync: Send message at SSP (Stream Synchronization Point) + * @page: address requires paging + */ +struct sdw_msg { + u16 addr; + u16 len; + u8 dev_num; + u8 addr_page1; + u8 addr_page2; + u8 flags; + u8 *buf; + bool ssp_sync; + bool page; +}; + +int sdw_transfer(struct sdw_bus *bus, struct sdw_msg *msg); +int sdw_transfer_defer(struct sdw_bus *bus, struct sdw_msg *msg, + struct sdw_defer *defer); + +int sdw_fill_msg(struct sdw_msg *msg, struct sdw_slave *slave, + u32 addr, size_t count, u16 dev_num, u8 flags, u8 *buf); + #endif /* __SDW_BUS_H */ diff --git a/include/linux/soundwire/sdw.h b/include/linux/soundwire/sdw.h index 3c13f580d42d..d4fa95efe8cd 100644 --- a/include/linux/soundwire/sdw.h +++ b/include/linux/soundwire/sdw.h @@ -38,6 +38,27 @@ enum sdw_slave_status { SDW_SLAVE_RESERVED = 3, }; +/** + * enum sdw_command_response - Command response as defined by SDW spec + * @SDW_CMD_OK: cmd was successful + * @SDW_CMD_IGNORED: cmd was ignored + * @SDW_CMD_FAIL: cmd was NACKed + * @SDW_CMD_TIMEOUT: cmd timedout + * @SDW_CMD_FAIL_OTHER: cmd failed due to other reason than above + * + * NOTE: The enum is different than actual Spec as response in the Spec is + * combination of ACK/NAK bits + * + * SDW_CMD_TIMEOUT/FAIL_OTHER is defined for SW use, not in spec + */ +enum sdw_command_response { + SDW_CMD_OK = 0, + SDW_CMD_IGNORED = 1, + SDW_CMD_FAIL = 2, + SDW_CMD_TIMEOUT = 3, + SDW_CMD_FAIL_OTHER = 4, +}; + /* * SDW properties, defined in MIPI DisCo spec v1.0 */ @@ -363,12 +384,37 @@ struct sdw_driver { * SDW master structures and APIs */ +struct sdw_msg; + +/** + * struct sdw_defer - SDW deffered message + * @length: message length + * @complete: message completion + * @msg: SDW message + */ +struct sdw_defer { + int length; + struct completion complete; + struct sdw_msg *msg; +}; + /** * struct sdw_master_ops - Master driver ops * @read_prop: Read Master properties + * @xfer_msg: Transfer message callback + * @xfer_msg_defer: Defer version of transfer message callback + * @reset_page_addr: Reset the SCP page address registers */ struct sdw_master_ops { int (*read_prop)(struct sdw_bus *bus); + + enum sdw_command_response (*xfer_msg) + (struct sdw_bus *bus, struct sdw_msg *msg); + enum sdw_command_response (*xfer_msg_defer) + (struct sdw_bus *bus, struct sdw_msg *msg, + struct sdw_defer *defer); + enum sdw_command_response (*reset_page_addr) + (struct sdw_bus *bus, unsigned int dev_num); }; /** @@ -379,8 +425,10 @@ struct sdw_master_ops { * @assigned: Bitmap for Slave device numbers. * Bit set implies used number, bit clear implies unused number. * @bus_lock: bus lock + * @msg_lock: message lock * @ops: Master callback ops * @prop: Master properties + * @defer_msg: Defer message * @clk_stop_timeout: Clock stop timeout computed */ struct sdw_bus { @@ -389,12 +437,21 @@ struct sdw_bus { struct list_head slaves; DECLARE_BITMAP(assigned, SDW_MAX_DEVICES); struct mutex bus_lock; + struct mutex msg_lock; const struct sdw_master_ops *ops; struct sdw_master_prop prop; + struct sdw_defer defer_msg; unsigned int clk_stop_timeout; }; int sdw_add_bus_master(struct sdw_bus *bus); void sdw_delete_bus_master(struct sdw_bus *bus); +/* messaging and data APIs */ + +int sdw_read(struct sdw_slave *slave, u32 addr); +int sdw_write(struct sdw_slave *slave, u32 addr, u8 value); +int sdw_nread(struct sdw_slave *slave, u32 addr, size_t count, u8 *val); +int sdw_nwrite(struct sdw_slave *slave, u32 addr, size_t count, u8 *val); + #endif /* __SOUNDWIRE_H */ -- cgit v1.2.3-58-ga151 From d52d7a1be02cc6da287bfaea7eebb0de1a77fbe2 Mon Sep 17 00:00:00 2001 From: Sanyog Kale Date: Thu, 14 Dec 2017 11:19:39 +0530 Subject: soundwire: Add Slave status handling helpers SoundWire Slaves report status to bus. Add helpers to handle the status changes. Signed-off-by: Hardik T Shah Signed-off-by: Sanyog Kale Reviewed-by: Philippe Ombredanne Acked-By: Pierre-Louis Bossart Reviewed-by: Takashi Iwai Signed-off-by: Vinod Koul Signed-off-by: Greg Kroah-Hartman --- drivers/soundwire/bus.c | 214 ++++++++++++++++++++++++++++++++++++++++++ drivers/soundwire/bus.h | 14 +++ include/linux/soundwire/sdw.h | 2 + 3 files changed, 230 insertions(+) (limited to 'include') diff --git a/drivers/soundwire/bus.c b/drivers/soundwire/bus.c index fb9008d6c40e..e306bd4438bd 100644 --- a/drivers/soundwire/bus.c +++ b/drivers/soundwire/bus.c @@ -374,6 +374,93 @@ int sdw_write(struct sdw_slave *slave, u32 addr, u8 value) } EXPORT_SYMBOL(sdw_write); +/* + * SDW alert handling + */ + +/* called with bus_lock held */ +static struct sdw_slave *sdw_get_slave(struct sdw_bus *bus, int i) +{ + struct sdw_slave *slave = NULL; + + list_for_each_entry(slave, &bus->slaves, node) { + if (slave->dev_num == i) + return slave; + } + + return NULL; +} + +static int sdw_compare_devid(struct sdw_slave *slave, struct sdw_slave_id id) +{ + + if ((slave->id.unique_id != id.unique_id) || + (slave->id.mfg_id != id.mfg_id) || + (slave->id.part_id != id.part_id) || + (slave->id.class_id != id.class_id)) + return -ENODEV; + + return 0; +} + +/* called with bus_lock held */ +static int sdw_get_device_num(struct sdw_slave *slave) +{ + int bit; + + bit = find_first_zero_bit(slave->bus->assigned, SDW_MAX_DEVICES); + if (bit == SDW_MAX_DEVICES) { + bit = -ENODEV; + goto err; + } + + /* + * Do not update dev_num in Slave data structure here, + * Update once program dev_num is successful + */ + set_bit(bit, slave->bus->assigned); + +err: + return bit; +} + +static int sdw_assign_device_num(struct sdw_slave *slave) +{ + int ret, dev_num; + + /* check first if device number is assigned, if so reuse that */ + if (!slave->dev_num) { + mutex_lock(&slave->bus->bus_lock); + dev_num = sdw_get_device_num(slave); + mutex_unlock(&slave->bus->bus_lock); + if (dev_num < 0) { + dev_err(slave->bus->dev, "Get dev_num failed: %d", + dev_num); + return dev_num; + } + } else { + dev_info(slave->bus->dev, + "Slave already registered dev_num:%d", + slave->dev_num); + + /* Clear the slave->dev_num to transfer message on device 0 */ + dev_num = slave->dev_num; + slave->dev_num = 0; + + } + + ret = sdw_write(slave, SDW_SCP_DEVNUMBER, dev_num); + if (ret < 0) { + dev_err(&slave->dev, "Program device_num failed: %d", ret); + return ret; + } + + /* After xfer of msg, restore dev_num */ + slave->dev_num = dev_num; + + return 0; +} + void sdw_extract_slave_id(struct sdw_bus *bus, u64 addr, struct sdw_slave_id *id) { @@ -402,3 +489,130 @@ void sdw_extract_slave_id(struct sdw_bus *bus, id->unique_id, id->sdw_version); } + +static int sdw_program_device_num(struct sdw_bus *bus) +{ + u8 buf[SDW_NUM_DEV_ID_REGISTERS] = {0}; + struct sdw_slave *slave, *_s; + struct sdw_slave_id id; + struct sdw_msg msg; + bool found = false; + int count = 0, ret; + u64 addr; + + /* No Slave, so use raw xfer api */ + ret = sdw_fill_msg(&msg, NULL, SDW_SCP_DEVID_0, + SDW_NUM_DEV_ID_REGISTERS, 0, SDW_MSG_FLAG_READ, buf); + if (ret < 0) + return ret; + + do { + ret = sdw_transfer(bus, &msg); + if (ret == -ENODATA) { /* end of device id reads */ + ret = 0; + break; + } + if (ret < 0) { + dev_err(bus->dev, "DEVID read fail:%d\n", ret); + break; + } + + /* + * Construct the addr and extract. Cast the higher shift + * bits to avoid truncation due to size limit. + */ + addr = buf[5] | (buf[4] << 8) | (buf[3] << 16) | + (buf[2] << 24) | ((unsigned long long)buf[1] << 32) | + ((unsigned long long)buf[0] << 40); + + sdw_extract_slave_id(bus, addr, &id); + + /* Now compare with entries */ + list_for_each_entry_safe(slave, _s, &bus->slaves, node) { + if (sdw_compare_devid(slave, id) == 0) { + found = true; + + /* + * Assign a new dev_num to this Slave and + * not mark it present. It will be marked + * present after it reports ATTACHED on new + * dev_num + */ + ret = sdw_assign_device_num(slave); + if (ret) { + dev_err(slave->bus->dev, + "Assign dev_num failed:%d", + ret); + return ret; + } + + break; + } + } + + if (found == false) { + /* TODO: Park this device in Group 13 */ + dev_err(bus->dev, "Slave Entry not found"); + } + + count++; + + /* + * Check till error out or retry (count) exhausts. + * Device can drop off and rejoin during enumeration + * so count till twice the bound. + */ + + } while (ret == 0 && count < (SDW_MAX_DEVICES * 2)); + + return ret; +} + +static void sdw_modify_slave_status(struct sdw_slave *slave, + enum sdw_slave_status status) +{ + mutex_lock(&slave->bus->bus_lock); + slave->status = status; + mutex_unlock(&slave->bus->bus_lock); +} + +static int sdw_initialize_slave(struct sdw_slave *slave) +{ + struct sdw_slave_prop *prop = &slave->prop; + int ret; + u8 val; + + /* + * Set bus clash, parity and SCP implementation + * defined interrupt mask + * TODO: Read implementation defined interrupt mask + * from Slave property + */ + val = SDW_SCP_INT1_IMPL_DEF | SDW_SCP_INT1_BUS_CLASH | + SDW_SCP_INT1_PARITY; + + /* Enable SCP interrupts */ + ret = sdw_update(slave, SDW_SCP_INTMASK1, val, val); + if (ret < 0) { + dev_err(slave->bus->dev, + "SDW_SCP_INTMASK1 write failed:%d", ret); + return ret; + } + + /* No need to continue if DP0 is not present */ + if (!slave->prop.dp0_prop) + return 0; + + /* Enable DP0 interrupts */ + val = prop->dp0_prop->device_interrupts; + val |= SDW_DP0_INT_PORT_READY | SDW_DP0_INT_BRA_FAILURE; + + ret = sdw_update(slave, SDW_DP0_INTMASK, val, val); + if (ret < 0) { + dev_err(slave->bus->dev, + "SDW_DP0_INTMASK read failed:%d", ret); + return val; + } + + return 0; +} diff --git a/drivers/soundwire/bus.h b/drivers/soundwire/bus.h index 48eb7de8db7b..e8df55d761c7 100644 --- a/drivers/soundwire/bus.h +++ b/drivers/soundwire/bus.h @@ -52,4 +52,18 @@ int sdw_transfer_defer(struct sdw_bus *bus, struct sdw_msg *msg, int sdw_fill_msg(struct sdw_msg *msg, struct sdw_slave *slave, u32 addr, size_t count, u16 dev_num, u8 flags, u8 *buf); +/* Read-Modify-Write Slave register */ +static inline int +sdw_update(struct sdw_slave *slave, u32 addr, u8 mask, u8 val) +{ + int tmp; + + tmp = sdw_read(slave, addr); + if (tmp < 0) + return tmp; + + tmp = (tmp & ~mask) | val; + return sdw_write(slave, addr, tmp); +} + #endif /* __SDW_BUS_H */ diff --git a/include/linux/soundwire/sdw.h b/include/linux/soundwire/sdw.h index d4fa95efe8cd..cbadddce470d 100644 --- a/include/linux/soundwire/sdw.h +++ b/include/linux/soundwire/sdw.h @@ -22,6 +22,8 @@ struct sdw_slave; /* SDW Master Device Number, not supported yet */ #define SDW_MASTER_DEV_NUM 14 +#define SDW_NUM_DEV_ID_REGISTERS 6 + #define SDW_MAX_DEVICES 11 /** -- cgit v1.2.3-58-ga151 From b0a9c37b0178bf397393952e98789b0d4dc7fd6c Mon Sep 17 00:00:00 2001 From: Vinod Koul Date: Thu, 14 Dec 2017 11:19:40 +0530 Subject: soundwire: Add slave status handling Add status handling API sdw_handle_slave_status() to handle Slave status changes. Signed-off-by: Hardik T Shah Signed-off-by: Sanyog Kale Reviewed-by: Philippe Ombredanne Acked-By: Pierre-Louis Bossart Reviewed-by: Takashi Iwai Signed-off-by: Vinod Koul Signed-off-by: Greg Kroah-Hartman --- drivers/soundwire/bus.c | 379 ++++++++++++++++++++++++++++++++++++++++++ drivers/soundwire/bus.h | 2 + include/linux/soundwire/sdw.h | 20 +++ 3 files changed, 401 insertions(+) (limited to 'include') diff --git a/drivers/soundwire/bus.c b/drivers/soundwire/bus.c index e306bd4438bd..4c345197eb55 100644 --- a/drivers/soundwire/bus.c +++ b/drivers/soundwire/bus.c @@ -616,3 +616,382 @@ static int sdw_initialize_slave(struct sdw_slave *slave) return 0; } + +static int sdw_handle_dp0_interrupt(struct sdw_slave *slave, u8 *slave_status) +{ + u8 clear = 0, impl_int_mask; + int status, status2, ret, count = 0; + + status = sdw_read(slave, SDW_DP0_INT); + if (status < 0) { + dev_err(slave->bus->dev, + "SDW_DP0_INT read failed:%d", status); + return status; + } + + do { + + if (status & SDW_DP0_INT_TEST_FAIL) { + dev_err(&slave->dev, "Test fail for port 0"); + clear |= SDW_DP0_INT_TEST_FAIL; + } + + /* + * Assumption: PORT_READY interrupt will be received only for + * ports implementing Channel Prepare state machine (CP_SM) + */ + + if (status & SDW_DP0_INT_PORT_READY) { + complete(&slave->port_ready[0]); + clear |= SDW_DP0_INT_PORT_READY; + } + + if (status & SDW_DP0_INT_BRA_FAILURE) { + dev_err(&slave->dev, "BRA failed"); + clear |= SDW_DP0_INT_BRA_FAILURE; + } + + impl_int_mask = SDW_DP0_INT_IMPDEF1 | + SDW_DP0_INT_IMPDEF2 | SDW_DP0_INT_IMPDEF3; + + if (status & impl_int_mask) { + clear |= impl_int_mask; + *slave_status = clear; + } + + /* clear the interrupt */ + ret = sdw_write(slave, SDW_DP0_INT, clear); + if (ret < 0) { + dev_err(slave->bus->dev, + "SDW_DP0_INT write failed:%d", ret); + return ret; + } + + /* Read DP0 interrupt again */ + status2 = sdw_read(slave, SDW_DP0_INT); + if (status2 < 0) { + dev_err(slave->bus->dev, + "SDW_DP0_INT read failed:%d", status); + return status; + } + status &= status2; + + count++; + + /* we can get alerts while processing so keep retrying */ + } while (status != 0 && count < SDW_READ_INTR_CLEAR_RETRY); + + if (count == SDW_READ_INTR_CLEAR_RETRY) + dev_warn(slave->bus->dev, "Reached MAX_RETRY on DP0 read"); + + return ret; +} + +static int sdw_handle_port_interrupt(struct sdw_slave *slave, + int port, u8 *slave_status) +{ + u8 clear = 0, impl_int_mask; + int status, status2, ret, count = 0; + u32 addr; + + if (port == 0) + return sdw_handle_dp0_interrupt(slave, slave_status); + + addr = SDW_DPN_INT(port); + status = sdw_read(slave, addr); + if (status < 0) { + dev_err(slave->bus->dev, + "SDW_DPN_INT read failed:%d", status); + + return status; + } + + do { + + if (status & SDW_DPN_INT_TEST_FAIL) { + dev_err(&slave->dev, "Test fail for port:%d", port); + clear |= SDW_DPN_INT_TEST_FAIL; + } + + /* + * Assumption: PORT_READY interrupt will be received only + * for ports implementing CP_SM. + */ + if (status & SDW_DPN_INT_PORT_READY) { + complete(&slave->port_ready[port]); + clear |= SDW_DPN_INT_PORT_READY; + } + + impl_int_mask = SDW_DPN_INT_IMPDEF1 | + SDW_DPN_INT_IMPDEF2 | SDW_DPN_INT_IMPDEF3; + + + if (status & impl_int_mask) { + clear |= impl_int_mask; + *slave_status = clear; + } + + /* clear the interrupt */ + ret = sdw_write(slave, addr, clear); + if (ret < 0) { + dev_err(slave->bus->dev, + "SDW_DPN_INT write failed:%d", ret); + return ret; + } + + /* Read DPN interrupt again */ + status2 = sdw_read(slave, addr); + if (status < 0) { + dev_err(slave->bus->dev, + "SDW_DPN_INT read failed:%d", status); + return status; + } + status &= status2; + + count++; + + /* we can get alerts while processing so keep retrying */ + } while (status != 0 && count < SDW_READ_INTR_CLEAR_RETRY); + + if (count == SDW_READ_INTR_CLEAR_RETRY) + dev_warn(slave->bus->dev, "Reached MAX_RETRY on port read"); + + return ret; +} + +static int sdw_handle_slave_alerts(struct sdw_slave *slave) +{ + struct sdw_slave_intr_status slave_intr; + u8 clear = 0, bit, port_status[15]; + int port_num, stat, ret, count = 0; + unsigned long port; + bool slave_notify = false; + u8 buf, buf2[2], _buf, _buf2[2]; + + sdw_modify_slave_status(slave, SDW_SLAVE_ALERT); + + /* Read Instat 1, Instat 2 and Instat 3 registers */ + ret = buf = sdw_read(slave, SDW_SCP_INT1); + if (ret < 0) { + dev_err(slave->bus->dev, + "SDW_SCP_INT1 read failed:%d", ret); + return ret; + } + + ret = sdw_nread(slave, SDW_SCP_INTSTAT2, 2, buf2); + if (ret < 0) { + dev_err(slave->bus->dev, + "SDW_SCP_INT2/3 read failed:%d", ret); + return ret; + } + + do { + /* + * Check parity, bus clash and Slave (impl defined) + * interrupt + */ + if (buf & SDW_SCP_INT1_PARITY) { + dev_err(&slave->dev, "Parity error detected"); + clear |= SDW_SCP_INT1_PARITY; + } + + if (buf & SDW_SCP_INT1_BUS_CLASH) { + dev_err(&slave->dev, "Bus clash error detected"); + clear |= SDW_SCP_INT1_BUS_CLASH; + } + + /* + * When bus clash or parity errors are detected, such errors + * are unlikely to be recoverable errors. + * TODO: In such scenario, reset bus. Make this configurable + * via sysfs property with bus reset being the default. + */ + + if (buf & SDW_SCP_INT1_IMPL_DEF) { + dev_dbg(&slave->dev, "Slave impl defined interrupt\n"); + clear |= SDW_SCP_INT1_IMPL_DEF; + slave_notify = true; + } + + /* Check port 0 - 3 interrupts */ + port = buf & SDW_SCP_INT1_PORT0_3; + + /* To get port number corresponding to bits, shift it */ + port = port >> SDW_REG_SHIFT(SDW_SCP_INT1_PORT0_3); + for_each_set_bit(bit, &port, 8) { + sdw_handle_port_interrupt(slave, bit, + &port_status[bit]); + + } + + /* Check if cascade 2 interrupt is present */ + if (buf & SDW_SCP_INT1_SCP2_CASCADE) { + port = buf2[0] & SDW_SCP_INTSTAT2_PORT4_10; + for_each_set_bit(bit, &port, 8) { + /* scp2 ports start from 4 */ + port_num = bit + 3; + sdw_handle_port_interrupt(slave, + port_num, + &port_status[port_num]); + } + } + + /* now check last cascade */ + if (buf2[0] & SDW_SCP_INTSTAT2_SCP3_CASCADE) { + port = buf2[1] & SDW_SCP_INTSTAT3_PORT11_14; + for_each_set_bit(bit, &port, 8) { + /* scp3 ports start from 11 */ + port_num = bit + 10; + sdw_handle_port_interrupt(slave, + port_num, + &port_status[port_num]); + } + } + + /* Update the Slave driver */ + if (slave_notify && (slave->ops) && + (slave->ops->interrupt_callback)) { + slave_intr.control_port = clear; + memcpy(slave_intr.port, &port_status, + sizeof(slave_intr.port)); + + slave->ops->interrupt_callback(slave, &slave_intr); + } + + /* Ack interrupt */ + ret = sdw_write(slave, SDW_SCP_INT1, clear); + if (ret < 0) { + dev_err(slave->bus->dev, + "SDW_SCP_INT1 write failed:%d", ret); + return ret; + } + + /* + * Read status again to ensure no new interrupts arrived + * while servicing interrupts. + */ + ret = _buf = sdw_read(slave, SDW_SCP_INT1); + if (ret < 0) { + dev_err(slave->bus->dev, + "SDW_SCP_INT1 read failed:%d", ret); + return ret; + } + + ret = sdw_nread(slave, SDW_SCP_INTSTAT2, 2, _buf2); + if (ret < 0) { + dev_err(slave->bus->dev, + "SDW_SCP_INT2/3 read failed:%d", ret); + return ret; + } + + /* Make sure no interrupts are pending */ + buf &= _buf; + buf2[0] &= _buf2[0]; + buf2[1] &= _buf2[1]; + stat = buf || buf2[0] || buf2[1]; + + /* + * Exit loop if Slave is continuously in ALERT state even + * after servicing the interrupt multiple times. + */ + count++; + + /* we can get alerts while processing so keep retrying */ + } while (stat != 0 && count < SDW_READ_INTR_CLEAR_RETRY); + + if (count == SDW_READ_INTR_CLEAR_RETRY) + dev_warn(slave->bus->dev, "Reached MAX_RETRY on alert read"); + + return ret; +} + +static int sdw_update_slave_status(struct sdw_slave *slave, + enum sdw_slave_status status) +{ + if ((slave->ops) && (slave->ops->update_status)) + return slave->ops->update_status(slave, status); + + return 0; +} + +/** + * sdw_handle_slave_status() - Handle Slave status + * @bus: SDW bus instance + * @status: Status for all Slave(s) + */ +int sdw_handle_slave_status(struct sdw_bus *bus, + enum sdw_slave_status status[]) +{ + enum sdw_slave_status prev_status; + struct sdw_slave *slave; + int i, ret = 0; + + if (status[0] == SDW_SLAVE_ATTACHED) { + ret = sdw_program_device_num(bus); + if (ret) + dev_err(bus->dev, "Slave attach failed: %d", ret); + } + + /* Continue to check other slave statuses */ + for (i = 1; i <= SDW_MAX_DEVICES; i++) { + mutex_lock(&bus->bus_lock); + if (test_bit(i, bus->assigned) == false) { + mutex_unlock(&bus->bus_lock); + continue; + } + mutex_unlock(&bus->bus_lock); + + slave = sdw_get_slave(bus, i); + if (!slave) + continue; + + switch (status[i]) { + case SDW_SLAVE_UNATTACHED: + if (slave->status == SDW_SLAVE_UNATTACHED) + break; + + sdw_modify_slave_status(slave, SDW_SLAVE_UNATTACHED); + break; + + case SDW_SLAVE_ALERT: + ret = sdw_handle_slave_alerts(slave); + if (ret) + dev_err(bus->dev, + "Slave %d alert handling failed: %d", + i, ret); + break; + + case SDW_SLAVE_ATTACHED: + if (slave->status == SDW_SLAVE_ATTACHED) + break; + + prev_status = slave->status; + sdw_modify_slave_status(slave, SDW_SLAVE_ATTACHED); + + if (prev_status == SDW_SLAVE_ALERT) + break; + + ret = sdw_initialize_slave(slave); + if (ret) + dev_err(bus->dev, + "Slave %d initialization failed: %d", + i, ret); + + break; + + default: + dev_err(bus->dev, "Invalid slave %d status:%d", + i, status[i]); + break; + } + + ret = sdw_update_slave_status(slave, status[i]); + if (ret) + dev_err(slave->bus->dev, + "Update Slave status failed:%d", ret); + + } + + return ret; +} +EXPORT_SYMBOL(sdw_handle_slave_status); diff --git a/drivers/soundwire/bus.h b/drivers/soundwire/bus.h index e8df55d761c7..345c34d697e9 100644 --- a/drivers/soundwire/bus.h +++ b/drivers/soundwire/bus.h @@ -49,6 +49,8 @@ int sdw_transfer(struct sdw_bus *bus, struct sdw_msg *msg); int sdw_transfer_defer(struct sdw_bus *bus, struct sdw_msg *msg, struct sdw_defer *defer); +#define SDW_READ_INTR_CLEAR_RETRY 10 + int sdw_fill_msg(struct sdw_msg *msg, struct sdw_slave *slave, u32 addr, size_t count, u16 dev_num, u8 flags, u8 *buf); diff --git a/include/linux/soundwire/sdw.h b/include/linux/soundwire/sdw.h index cbadddce470d..e91fdcf41049 100644 --- a/include/linux/soundwire/sdw.h +++ b/include/linux/soundwire/sdw.h @@ -330,12 +330,29 @@ struct sdw_slave_id { __u8 sdw_version:4; }; +/** + * struct sdw_slave_intr_status - Slave interrupt status + * @control_port: control port status + * @port: data port status + */ +struct sdw_slave_intr_status { + u8 control_port; + u8 port[15]; +}; + /** * struct sdw_slave_ops - Slave driver callback ops * @read_prop: Read Slave properties + * @interrupt_callback: Device interrupt notification (invoked in thread + * context) + * @update_status: Update Slave status */ struct sdw_slave_ops { int (*read_prop)(struct sdw_slave *sdw); + int (*interrupt_callback)(struct sdw_slave *slave, + struct sdw_slave_intr_status *status); + int (*update_status)(struct sdw_slave *slave, + enum sdw_slave_status status); }; /** @@ -382,6 +399,9 @@ struct sdw_driver { { .mfg_id = (_mfg_id), .part_id = (_part_id), \ .driver_data = (unsigned long)(_drv_data) } +int sdw_handle_slave_status(struct sdw_bus *bus, + enum sdw_slave_status status[]); + /* * SDW master structures and APIs */ -- cgit v1.2.3-58-ga151 From 71bb8a1b059ecd6a070660b7d5d89a7a3c443f4a Mon Sep 17 00:00:00 2001 From: Vinod Koul Date: Thu, 14 Dec 2017 11:19:43 +0530 Subject: soundwire: intel: Add Intel Master driver Some Intel platforms have SoundWire Master, so add Intel SoundWire Master driver which uses Cadence module. This patch adds probe and initialization routines for Intel Master driver. Signed-off-by: Hardik T Shah Signed-off-by: Sanyog Kale Reviewed-by: Philippe Ombredanne Acked-By: Pierre-Louis Bossart Reviewed-by: Takashi Iwai Signed-off-by: Vinod Koul Signed-off-by: Greg Kroah-Hartman --- drivers/soundwire/Kconfig | 11 ++ drivers/soundwire/Makefile | 4 + drivers/soundwire/intel.c | 345 ++++++++++++++++++++++++++++++++++++ drivers/soundwire/intel.h | 23 +++ include/linux/soundwire/sdw_intel.h | 21 +++ 5 files changed, 404 insertions(+) create mode 100644 drivers/soundwire/intel.c create mode 100644 drivers/soundwire/intel.h create mode 100644 include/linux/soundwire/sdw_intel.h (limited to 'include') diff --git a/drivers/soundwire/Kconfig b/drivers/soundwire/Kconfig index 534e255db0bd..08aad9f52152 100644 --- a/drivers/soundwire/Kconfig +++ b/drivers/soundwire/Kconfig @@ -22,4 +22,15 @@ config SOUNDWIRE_BUS config SOUNDWIRE_CADENCE tristate +config SOUNDWIRE_INTEL + tristate "Intel SoundWire Master driver" + select SOUNDWIRE_CADENCE + select SOUNDWIRE_BUS + depends on X86 && ACPI + ---help--- + SoundWire Intel Master driver. + If you have an Intel platform which has a SoundWire Master then + enable this config option to get the SoundWire support for that + device. + endif diff --git a/drivers/soundwire/Makefile b/drivers/soundwire/Makefile index 1e8e966d32ee..befd7f8af7e1 100644 --- a/drivers/soundwire/Makefile +++ b/drivers/soundwire/Makefile @@ -9,3 +9,7 @@ obj-$(CONFIG_SOUNDWIRE_BUS) += soundwire-bus.o #Cadence Objs soundwire-cadence-objs := cadence_master.o obj-$(CONFIG_SOUNDWIRE_CADENCE) += soundwire-cadence.o + +#Intel driver +soundwire-intel-objs := intel.o +obj-$(CONFIG_SOUNDWIRE_INTEL) += soundwire-intel.o diff --git a/drivers/soundwire/intel.c b/drivers/soundwire/intel.c new file mode 100644 index 000000000000..6a9177ea6eb9 --- /dev/null +++ b/drivers/soundwire/intel.c @@ -0,0 +1,345 @@ +// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) +// Copyright(c) 2015-17 Intel Corporation. + +/* + * Soundwire Intel Master Driver + */ + +#include +#include +#include +#include +#include +#include +#include +#include "cadence_master.h" +#include "intel.h" + +/* Intel SHIM Registers Definition */ +#define SDW_SHIM_LCAP 0x0 +#define SDW_SHIM_LCTL 0x4 +#define SDW_SHIM_IPPTR 0x8 +#define SDW_SHIM_SYNC 0xC + +#define SDW_SHIM_CTLSCAP(x) (0x010 + 0x60 * x) +#define SDW_SHIM_CTLS0CM(x) (0x012 + 0x60 * x) +#define SDW_SHIM_CTLS1CM(x) (0x014 + 0x60 * x) +#define SDW_SHIM_CTLS2CM(x) (0x016 + 0x60 * x) +#define SDW_SHIM_CTLS3CM(x) (0x018 + 0x60 * x) +#define SDW_SHIM_PCMSCAP(x) (0x020 + 0x60 * x) + +#define SDW_SHIM_PCMSYCHM(x, y) (0x022 + (0x60 * x) + (0x2 * y)) +#define SDW_SHIM_PCMSYCHC(x, y) (0x042 + (0x60 * x) + (0x2 * y)) +#define SDW_SHIM_PDMSCAP(x) (0x062 + 0x60 * x) +#define SDW_SHIM_IOCTL(x) (0x06C + 0x60 * x) +#define SDW_SHIM_CTMCTL(x) (0x06E + 0x60 * x) + +#define SDW_SHIM_WAKEEN 0x190 +#define SDW_SHIM_WAKESTS 0x192 + +#define SDW_SHIM_LCTL_SPA BIT(0) +#define SDW_SHIM_LCTL_CPA BIT(8) + +#define SDW_SHIM_SYNC_SYNCPRD_VAL 0x176F +#define SDW_SHIM_SYNC_SYNCPRD GENMASK(14, 0) +#define SDW_SHIM_SYNC_SYNCCPU BIT(15) +#define SDW_SHIM_SYNC_CMDSYNC_MASK GENMASK(19, 16) +#define SDW_SHIM_SYNC_CMDSYNC BIT(16) +#define SDW_SHIM_SYNC_SYNCGO BIT(24) + +#define SDW_SHIM_PCMSCAP_ISS GENMASK(3, 0) +#define SDW_SHIM_PCMSCAP_OSS GENMASK(7, 4) +#define SDW_SHIM_PCMSCAP_BSS GENMASK(12, 8) + +#define SDW_SHIM_PCMSYCM_LCHN GENMASK(3, 0) +#define SDW_SHIM_PCMSYCM_HCHN GENMASK(7, 4) +#define SDW_SHIM_PCMSYCM_STREAM GENMASK(13, 8) +#define SDW_SHIM_PCMSYCM_DIR BIT(15) + +#define SDW_SHIM_PDMSCAP_ISS GENMASK(3, 0) +#define SDW_SHIM_PDMSCAP_OSS GENMASK(7, 4) +#define SDW_SHIM_PDMSCAP_BSS GENMASK(12, 8) +#define SDW_SHIM_PDMSCAP_CPSS GENMASK(15, 13) + +#define SDW_SHIM_IOCTL_MIF BIT(0) +#define SDW_SHIM_IOCTL_CO BIT(1) +#define SDW_SHIM_IOCTL_COE BIT(2) +#define SDW_SHIM_IOCTL_DO BIT(3) +#define SDW_SHIM_IOCTL_DOE BIT(4) +#define SDW_SHIM_IOCTL_BKE BIT(5) +#define SDW_SHIM_IOCTL_WPDD BIT(6) +#define SDW_SHIM_IOCTL_CIBD BIT(8) +#define SDW_SHIM_IOCTL_DIBD BIT(9) + +#define SDW_SHIM_CTMCTL_DACTQE BIT(0) +#define SDW_SHIM_CTMCTL_DODS BIT(1) +#define SDW_SHIM_CTMCTL_DOAIS GENMASK(4, 3) + +#define SDW_SHIM_WAKEEN_ENABLE BIT(0) +#define SDW_SHIM_WAKESTS_STATUS BIT(0) + +/* Intel ALH Register definitions */ +#define SDW_ALH_STRMZCFG(x) (0x000 + (0x4 * x)) + +#define SDW_ALH_STRMZCFG_DMAT_VAL 0x3 +#define SDW_ALH_STRMZCFG_DMAT GENMASK(7, 0) +#define SDW_ALH_STRMZCFG_CHN GENMASK(19, 16) + +struct sdw_intel { + struct sdw_cdns cdns; + int instance; + struct sdw_intel_link_res *res; +}; + +#define cdns_to_intel(_cdns) container_of(_cdns, struct sdw_intel, cdns) + +/* + * Read, write helpers for HW registers + */ +static inline int intel_readl(void __iomem *base, int offset) +{ + return readl(base + offset); +} + +static inline void intel_writel(void __iomem *base, int offset, int value) +{ + writel(value, base + offset); +} + +static inline u16 intel_readw(void __iomem *base, int offset) +{ + return readw(base + offset); +} + +static inline void intel_writew(void __iomem *base, int offset, u16 value) +{ + writew(value, base + offset); +} + +static int intel_clear_bit(void __iomem *base, int offset, u32 value, u32 mask) +{ + int timeout = 10; + u32 reg_read; + + writel(value, base + offset); + do { + reg_read = readl(base + offset); + if (!(reg_read & mask)) + return 0; + + timeout--; + udelay(50); + } while (timeout != 0); + + return -EAGAIN; +} + +static int intel_set_bit(void __iomem *base, int offset, u32 value, u32 mask) +{ + int timeout = 10; + u32 reg_read; + + writel(value, base + offset); + do { + reg_read = readl(base + offset); + if (reg_read & mask) + return 0; + + timeout--; + udelay(50); + } while (timeout != 0); + + return -EAGAIN; +} + +/* + * shim ops + */ + +static int intel_link_power_up(struct sdw_intel *sdw) +{ + unsigned int link_id = sdw->instance; + void __iomem *shim = sdw->res->shim; + int spa_mask, cpa_mask; + int link_control, ret; + + /* Link power up sequence */ + link_control = intel_readl(shim, SDW_SHIM_LCTL); + spa_mask = (SDW_SHIM_LCTL_SPA << link_id); + cpa_mask = (SDW_SHIM_LCTL_CPA << link_id); + link_control |= spa_mask; + + ret = intel_set_bit(shim, SDW_SHIM_LCTL, link_control, cpa_mask); + if (ret < 0) + return ret; + + sdw->cdns.link_up = true; + return 0; +} + +static int intel_shim_init(struct sdw_intel *sdw) +{ + void __iomem *shim = sdw->res->shim; + unsigned int link_id = sdw->instance; + int sync_reg, ret; + u16 ioctl = 0, act = 0; + + /* Initialize Shim */ + ioctl |= SDW_SHIM_IOCTL_BKE; + intel_writew(shim, SDW_SHIM_IOCTL(link_id), ioctl); + + ioctl |= SDW_SHIM_IOCTL_WPDD; + intel_writew(shim, SDW_SHIM_IOCTL(link_id), ioctl); + + ioctl |= SDW_SHIM_IOCTL_DO; + intel_writew(shim, SDW_SHIM_IOCTL(link_id), ioctl); + + ioctl |= SDW_SHIM_IOCTL_DOE; + intel_writew(shim, SDW_SHIM_IOCTL(link_id), ioctl); + + /* Switch to MIP from Glue logic */ + ioctl = intel_readw(shim, SDW_SHIM_IOCTL(link_id)); + + ioctl &= ~(SDW_SHIM_IOCTL_DOE); + intel_writew(shim, SDW_SHIM_IOCTL(link_id), ioctl); + + ioctl &= ~(SDW_SHIM_IOCTL_DO); + intel_writew(shim, SDW_SHIM_IOCTL(link_id), ioctl); + + ioctl |= (SDW_SHIM_IOCTL_MIF); + intel_writew(shim, SDW_SHIM_IOCTL(link_id), ioctl); + + ioctl &= ~(SDW_SHIM_IOCTL_BKE); + ioctl &= ~(SDW_SHIM_IOCTL_COE); + + intel_writew(shim, SDW_SHIM_IOCTL(link_id), ioctl); + + act |= 0x1 << SDW_REG_SHIFT(SDW_SHIM_CTMCTL_DOAIS); + act |= SDW_SHIM_CTMCTL_DACTQE; + act |= SDW_SHIM_CTMCTL_DODS; + intel_writew(shim, SDW_SHIM_CTMCTL(link_id), act); + + /* Now set SyncPRD period */ + sync_reg = intel_readl(shim, SDW_SHIM_SYNC); + sync_reg |= (SDW_SHIM_SYNC_SYNCPRD_VAL << + SDW_REG_SHIFT(SDW_SHIM_SYNC_SYNCPRD)); + + /* Set SyncCPU bit */ + sync_reg |= SDW_SHIM_SYNC_SYNCCPU; + ret = intel_clear_bit(shim, SDW_SHIM_SYNC, sync_reg, + SDW_SHIM_SYNC_SYNCCPU); + if (ret < 0) + dev_err(sdw->cdns.dev, "Failed to set sync period: %d", ret); + + return ret; +} + +static int intel_prop_read(struct sdw_bus *bus) +{ + /* Initialize with default handler to read all DisCo properties */ + sdw_master_read_prop(bus); + + /* BIOS is not giving some values correctly. So, lets override them */ + bus->prop.num_freq = 1; + bus->prop.freq = devm_kcalloc(bus->dev, sizeof(*bus->prop.freq), + bus->prop.num_freq, GFP_KERNEL); + if (!bus->prop.freq) + return -ENOMEM; + + bus->prop.freq[0] = bus->prop.max_freq; + bus->prop.err_threshold = 5; + + return 0; +} + +/* + * probe and init + */ +static int intel_probe(struct platform_device *pdev) +{ + struct sdw_intel *sdw; + int ret; + + sdw = devm_kzalloc(&pdev->dev, sizeof(*sdw), GFP_KERNEL); + if (!sdw) + return -ENOMEM; + + sdw->instance = pdev->id; + sdw->res = dev_get_platdata(&pdev->dev); + sdw->cdns.dev = &pdev->dev; + sdw->cdns.registers = sdw->res->registers; + sdw->cdns.instance = sdw->instance; + sdw->cdns.msg_count = 0; + sdw->cdns.bus.dev = &pdev->dev; + sdw->cdns.bus.link_id = pdev->id; + + sdw_cdns_probe(&sdw->cdns); + + /* Set property read ops */ + sdw_cdns_master_ops.read_prop = intel_prop_read; + sdw->cdns.bus.ops = &sdw_cdns_master_ops; + + platform_set_drvdata(pdev, sdw); + + ret = sdw_add_bus_master(&sdw->cdns.bus); + if (ret) { + dev_err(&pdev->dev, "sdw_add_bus_master fail: %d\n", ret); + goto err_master_reg; + } + + /* Initialize shim and controller */ + intel_link_power_up(sdw); + intel_shim_init(sdw); + + ret = sdw_cdns_init(&sdw->cdns); + if (ret) + goto err_init; + + sdw_cdns_enable_interrupt(&sdw->cdns); + if (ret) + goto err_init; + + /* Acquire IRQ */ + ret = request_threaded_irq(sdw->res->irq, sdw_cdns_irq, + sdw_cdns_thread, IRQF_SHARED, KBUILD_MODNAME, + &sdw->cdns); + if (ret < 0) { + dev_err(sdw->cdns.dev, "unable to grab IRQ %d, disabling device\n", + sdw->res->irq); + goto err_init; + } + + return 0; + +err_init: + sdw_delete_bus_master(&sdw->cdns.bus); +err_master_reg: + return ret; +} + +static int intel_remove(struct platform_device *pdev) +{ + struct sdw_intel *sdw; + + sdw = platform_get_drvdata(pdev); + + free_irq(sdw->res->irq, sdw); + sdw_delete_bus_master(&sdw->cdns.bus); + + return 0; +} + +static struct platform_driver sdw_intel_drv = { + .probe = intel_probe, + .remove = intel_remove, + .driver = { + .name = "int-sdw", + + }, +}; + +module_platform_driver(sdw_intel_drv); + +MODULE_LICENSE("Dual BSD/GPL"); +MODULE_ALIAS("platform:int-sdw"); +MODULE_DESCRIPTION("Intel Soundwire Master Driver"); diff --git a/drivers/soundwire/intel.h b/drivers/soundwire/intel.h new file mode 100644 index 000000000000..ffa30d9535a2 --- /dev/null +++ b/drivers/soundwire/intel.h @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) +// Copyright(c) 2015-17 Intel Corporation. + +#ifndef __SDW_INTEL_LOCAL_H +#define __SDW_INTEL_LOCAL_H + +/** + * struct sdw_intel_res - Soundwire link resources + * @registers: Link IO registers base + * @shim: Audio shim pointer + * @alh: ALH (Audio Link Hub) pointer + * @irq: Interrupt line + * + * This is set as pdata for each link instance. + */ +struct sdw_intel_link_res { + void __iomem *registers; + void __iomem *shim; + void __iomem *alh; + int irq; +}; + +#endif /* __SDW_INTEL_LOCAL_H */ diff --git a/include/linux/soundwire/sdw_intel.h b/include/linux/soundwire/sdw_intel.h new file mode 100644 index 000000000000..370cc52d8d1e --- /dev/null +++ b/include/linux/soundwire/sdw_intel.h @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) +// Copyright(c) 2015-17 Intel Corporation. + +#ifndef __SDW_INTEL_H +#define __SDW_INTEL_H + +/** + * struct sdw_intel_res - Soundwire Intel resource structure + * @mmio_base: mmio base of SoundWire registers + * @irq: interrupt number + * @handle: ACPI parent handle + * @parent: parent device + */ +struct sdw_intel_res { + void __iomem *mmio_base; + int irq; + acpi_handle handle; + struct device *parent; +}; + +#endif -- cgit v1.2.3-58-ga151 From d62a7d41f38e1d3f8f8a1c0db4dec7a5bb39268a Mon Sep 17 00:00:00 2001 From: Vinod Koul Date: Thu, 14 Dec 2017 11:19:44 +0530 Subject: soundwire: intel: Add Intel init module The SoundWire Master is implemented as part of Audio controller in Intel platforms. Add a init module which creates SoundWire Master platform devices based on the links supported in the hardware. Signed-off-by: Sanyog Kale Reviewed-by: Philippe Ombredanne Acked-By: Pierre-Louis Bossart Reviewed-by: Takashi Iwai Signed-off-by: Vinod Koul Signed-off-by: Greg Kroah-Hartman --- drivers/soundwire/Makefile | 3 + drivers/soundwire/intel_init.c | 198 ++++++++++++++++++++++++++++++++++++ include/linux/soundwire/sdw_intel.h | 3 + 3 files changed, 204 insertions(+) create mode 100644 drivers/soundwire/intel_init.c (limited to 'include') diff --git a/drivers/soundwire/Makefile b/drivers/soundwire/Makefile index befd7f8af7e1..e1a74c5692aa 100644 --- a/drivers/soundwire/Makefile +++ b/drivers/soundwire/Makefile @@ -13,3 +13,6 @@ obj-$(CONFIG_SOUNDWIRE_CADENCE) += soundwire-cadence.o #Intel driver soundwire-intel-objs := intel.o obj-$(CONFIG_SOUNDWIRE_INTEL) += soundwire-intel.o + +soundwire-intel-init-objs := intel_init.o +obj-$(CONFIG_SOUNDWIRE_INTEL) += soundwire-intel-init.o diff --git a/drivers/soundwire/intel_init.c b/drivers/soundwire/intel_init.c new file mode 100644 index 000000000000..6f2bb99526f2 --- /dev/null +++ b/drivers/soundwire/intel_init.c @@ -0,0 +1,198 @@ +// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) +// Copyright(c) 2015-17 Intel Corporation. + +/* + * SDW Intel Init Routines + * + * Initializes and creates SDW devices based on ACPI and Hardware values + */ + +#include +#include +#include +#include "intel.h" + +#define SDW_MAX_LINKS 4 +#define SDW_SHIM_LCAP 0x0 +#define SDW_SHIM_BASE 0x2C000 +#define SDW_ALH_BASE 0x2C800 +#define SDW_LINK_BASE 0x30000 +#define SDW_LINK_SIZE 0x10000 + +struct sdw_link_data { + struct sdw_intel_link_res res; + struct platform_device *pdev; +}; + +struct sdw_intel_ctx { + int count; + struct sdw_link_data *links; +}; + +static int sdw_intel_cleanup_pdev(struct sdw_intel_ctx *ctx) +{ + struct sdw_link_data *link = ctx->links; + int i; + + if (!link) + return 0; + + for (i = 0; i < ctx->count; i++) { + if (link->pdev) + platform_device_unregister(link->pdev); + link++; + } + + kfree(ctx->links); + ctx->links = NULL; + + return 0; +} + +static struct sdw_intel_ctx +*sdw_intel_add_controller(struct sdw_intel_res *res) +{ + struct platform_device_info pdevinfo; + struct platform_device *pdev; + struct sdw_link_data *link; + struct sdw_intel_ctx *ctx; + struct acpi_device *adev; + int ret, i; + u8 count; + u32 caps; + + if (acpi_bus_get_device(res->handle, &adev)) + return NULL; + + /* Found controller, find links supported */ + count = 0; + ret = fwnode_property_read_u8_array(acpi_fwnode_handle(adev), + "mipi-sdw-master-count", &count, 1); + + /* Don't fail on error, continue and use hw value */ + if (ret) { + dev_err(&adev->dev, + "Failed to read mipi-sdw-master-count: %d\n", ret); + count = SDW_MAX_LINKS; + } + + /* Check SNDWLCAP.LCOUNT */ + caps = ioread32(res->mmio_base + SDW_SHIM_BASE + SDW_SHIM_LCAP); + + /* Check HW supported vs property value and use min of two */ + count = min_t(u8, caps, count); + + /* Check count is within bounds */ + if (count > SDW_MAX_LINKS) { + dev_err(&adev->dev, "Link count %d exceeds max %d\n", + count, SDW_MAX_LINKS); + return NULL; + } + + dev_dbg(&adev->dev, "Creating %d SDW Link devices\n", count); + + ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return NULL; + + ctx->count = count; + ctx->links = kcalloc(ctx->count, sizeof(*ctx->links), GFP_KERNEL); + if (!ctx->links) + goto link_err; + + link = ctx->links; + + /* Create SDW Master devices */ + for (i = 0; i < count; i++) { + + link->res.irq = res->irq; + link->res.registers = res->mmio_base + SDW_LINK_BASE + + (SDW_LINK_SIZE * i); + link->res.shim = res->mmio_base + SDW_SHIM_BASE; + link->res.alh = res->mmio_base + SDW_ALH_BASE; + + memset(&pdevinfo, 0, sizeof(pdevinfo)); + + pdevinfo.parent = res->parent; + pdevinfo.name = "int-sdw"; + pdevinfo.id = i; + pdevinfo.fwnode = acpi_fwnode_handle(adev); + pdevinfo.data = &link->res; + pdevinfo.size_data = sizeof(link->res); + + pdev = platform_device_register_full(&pdevinfo); + if (IS_ERR(pdev)) { + dev_err(&adev->dev, + "platform device creation failed: %ld\n", + PTR_ERR(pdev)); + goto pdev_err; + } + + link->pdev = pdev; + link++; + } + + return ctx; + +pdev_err: + sdw_intel_cleanup_pdev(ctx); +link_err: + kfree(ctx); + return NULL; +} + +static acpi_status sdw_intel_acpi_cb(acpi_handle handle, u32 level, + void *cdata, void **return_value) +{ + struct sdw_intel_res *res = cdata; + struct acpi_device *adev; + + if (acpi_bus_get_device(handle, &adev)) { + dev_err(&adev->dev, "Couldn't find ACPI handle\n"); + return AE_NOT_FOUND; + } + + res->handle = handle; + return AE_OK; +} + +/** + * sdw_intel_init() - SoundWire Intel init routine + * @parent_handle: ACPI parent handle + * @res: resource data + * + * This scans the namespace and creates SoundWire link controller devices + * based on the info queried. + */ +void *sdw_intel_init(acpi_handle *parent_handle, struct sdw_intel_res *res) +{ + acpi_status status; + + status = acpi_walk_namespace(ACPI_TYPE_DEVICE, + parent_handle, 1, + sdw_intel_acpi_cb, + NULL, res, NULL); + if (ACPI_FAILURE(status)) + return NULL; + + return sdw_intel_add_controller(res); +} +EXPORT_SYMBOL(sdw_intel_init); + +/** + * sdw_intel_exit() - SoundWire Intel exit + * @arg: callback context + * + * Delete the controller instances created and cleanup + */ +void sdw_intel_exit(void *arg) +{ + struct sdw_intel_ctx *ctx = arg; + + sdw_intel_cleanup_pdev(ctx); + kfree(ctx); +} +EXPORT_SYMBOL(sdw_intel_exit); + +MODULE_LICENSE("Dual BSD/GPL"); +MODULE_DESCRIPTION("Intel Soundwire Init Library"); diff --git a/include/linux/soundwire/sdw_intel.h b/include/linux/soundwire/sdw_intel.h index 370cc52d8d1e..4b37528f592d 100644 --- a/include/linux/soundwire/sdw_intel.h +++ b/include/linux/soundwire/sdw_intel.h @@ -18,4 +18,7 @@ struct sdw_intel_res { struct device *parent; }; +void *sdw_intel_init(acpi_handle *parent_handle, struct sdw_intel_res *res); +void sdw_intel_exit(void *arg); + #endif -- cgit v1.2.3-58-ga151 From 337ccce6674d37566dbc48f111fe94fdf3e0f6c3 Mon Sep 17 00:00:00 2001 From: Sinan Kaya Date: Tue, 19 Dec 2017 00:38:01 -0500 Subject: i7300_idle: remove unused file i7300_idle.h is not being called by any source file and contains calls to pci_get_bus_and_slot() that we are trying to deprecate. Remove unused file. Signed-off-by: Sinan Kaya Signed-off-by: Greg Kroah-Hartman --- include/linux/i7300_idle.h | 84 ---------------------------------------------- 1 file changed, 84 deletions(-) delete mode 100644 include/linux/i7300_idle.h (limited to 'include') diff --git a/include/linux/i7300_idle.h b/include/linux/i7300_idle.h deleted file mode 100644 index 4dbe651f71f5..000000000000 --- a/include/linux/i7300_idle.h +++ /dev/null @@ -1,84 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ - -#ifndef I7300_IDLE_H -#define I7300_IDLE_H - -#include - -/* - * I/O AT controls (PCI bus 0 device 8 function 0) - * DIMM controls (PCI bus 0 device 16 function 1) - */ -#define IOAT_BUS 0 -#define IOAT_DEVFN PCI_DEVFN(8, 0) -#define MEMCTL_BUS 0 -#define MEMCTL_DEVFN PCI_DEVFN(16, 1) - -struct fbd_ioat { - unsigned int vendor; - unsigned int ioat_dev; - unsigned int enabled; -}; - -/* - * The i5000 chip-set has the same hooks as the i7300 - * but it is not enabled by default and must be manually - * manually enabled with "forceload=1" because it is - * only lightly validated. - */ - -static const struct fbd_ioat fbd_ioat_list[] = { - {PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_IOAT_CNB, 1}, - {PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_IOAT, 0}, - {0, 0} -}; - -/* table of devices that work with this driver */ -static const struct pci_device_id pci_tbl[] = { - { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_FBD_CNB) }, - { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_5000_ERR) }, - { } /* Terminating entry */ -}; - -/* Check for known platforms with I/O-AT */ -static inline int i7300_idle_platform_probe(struct pci_dev **fbd_dev, - struct pci_dev **ioat_dev, - int enable_all) -{ - int i; - struct pci_dev *memdev, *dmadev; - - memdev = pci_get_bus_and_slot(MEMCTL_BUS, MEMCTL_DEVFN); - if (!memdev) - return -ENODEV; - - for (i = 0; pci_tbl[i].vendor != 0; i++) { - if (memdev->vendor == pci_tbl[i].vendor && - memdev->device == pci_tbl[i].device) { - break; - } - } - if (pci_tbl[i].vendor == 0) - return -ENODEV; - - dmadev = pci_get_bus_and_slot(IOAT_BUS, IOAT_DEVFN); - if (!dmadev) - return -ENODEV; - - for (i = 0; fbd_ioat_list[i].vendor != 0; i++) { - if (dmadev->vendor == fbd_ioat_list[i].vendor && - dmadev->device == fbd_ioat_list[i].ioat_dev) { - if (!(fbd_ioat_list[i].enabled || enable_all)) - continue; - if (fbd_dev) - *fbd_dev = memdev; - if (ioat_dev) - *ioat_dev = dmadev; - - return 0; - } - } - return -ENODEV; -} - -#endif -- cgit v1.2.3-58-ga151 From 0edff03d44009f5a459a1d3c6a7915b9952107d1 Mon Sep 17 00:00:00 2001 From: Peter Rosin Date: Fri, 29 Dec 2017 00:22:55 +0100 Subject: mux: add SPDX identifiers to all mux source files Remove all free-text license texts. This is done on a quest to remove the 700+ different ways that files in the kernel describe the GPL license text. No copyright headers or other non-license-description text was removed. Reviewed-by: Philippe Ombredanne Acked-by: Philipp Zabel Signed-off-by: Peter Rosin Signed-off-by: Greg Kroah-Hartman --- drivers/mux/Kconfig | 1 + drivers/mux/Makefile | 1 + drivers/mux/adg792a.c | 5 +---- drivers/mux/core.c | 5 +---- drivers/mux/gpio.c | 5 +---- drivers/mux/mmio.c | 5 +---- include/linux/mux/consumer.h | 5 +---- include/linux/mux/driver.h | 5 +---- 8 files changed, 8 insertions(+), 24 deletions(-) (limited to 'include') diff --git a/drivers/mux/Kconfig b/drivers/mux/Kconfig index 19e4e904c9bf..6241678e99af 100644 --- a/drivers/mux/Kconfig +++ b/drivers/mux/Kconfig @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0 # # Multiplexer devices # diff --git a/drivers/mux/Makefile b/drivers/mux/Makefile index 0e1e59760e3f..c3d883955fd5 100644 --- a/drivers/mux/Makefile +++ b/drivers/mux/Makefile @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0 # # Makefile for multiplexer devices. # diff --git a/drivers/mux/adg792a.c b/drivers/mux/adg792a.c index 12aa221ab90d..6a8725cf3d71 100644 --- a/drivers/mux/adg792a.c +++ b/drivers/mux/adg792a.c @@ -1,13 +1,10 @@ +// SPDX-License-Identifier: GPL-2.0 /* * Multiplexer driver for Analog Devices ADG792A/G Triple 4:1 mux * * Copyright (C) 2017 Axentia Technologies AB * * Author: Peter Rosin - * - * 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 diff --git a/drivers/mux/core.c b/drivers/mux/core.c index 2260063b0ea8..959d22aaa063 100644 --- a/drivers/mux/core.c +++ b/drivers/mux/core.c @@ -1,13 +1,10 @@ +// SPDX-License-Identifier: GPL-2.0 /* * Multiplexer subsystem * * Copyright (C) 2017 Axentia Technologies AB * * Author: Peter Rosin - * - * 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. */ #define pr_fmt(fmt) "mux-core: " fmt diff --git a/drivers/mux/gpio.c b/drivers/mux/gpio.c index 468bf1709606..6fdd9316db8b 100644 --- a/drivers/mux/gpio.c +++ b/drivers/mux/gpio.c @@ -1,13 +1,10 @@ +// SPDX-License-Identifier: GPL-2.0 /* * GPIO-controlled multiplexer driver * * Copyright (C) 2017 Axentia Technologies AB * * Author: Peter Rosin - * - * 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 diff --git a/drivers/mux/mmio.c b/drivers/mux/mmio.c index 37c1de359a70..935ac44aa209 100644 --- a/drivers/mux/mmio.c +++ b/drivers/mux/mmio.c @@ -1,11 +1,8 @@ +// SPDX-License-Identifier: GPL-2.0 /* * MMIO register bitfield-controlled multiplexer driver * * Copyright (C) 2017 Pengutronix, Philipp Zabel - * - * 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 diff --git a/include/linux/mux/consumer.h b/include/linux/mux/consumer.h index ea96d4c82be7..5fc6bb2fefad 100644 --- a/include/linux/mux/consumer.h +++ b/include/linux/mux/consumer.h @@ -1,13 +1,10 @@ +/* SPDX-License-Identifier: GPL-2.0 */ /* * mux/consumer.h - definitions for the multiplexer consumer interface * * Copyright (C) 2017 Axentia Technologies AB * * Author: Peter Rosin - * - * 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. */ #ifndef _LINUX_MUX_CONSUMER_H diff --git a/include/linux/mux/driver.h b/include/linux/mux/driver.h index 35c3579c3304..627a2c6bc02d 100644 --- a/include/linux/mux/driver.h +++ b/include/linux/mux/driver.h @@ -1,13 +1,10 @@ +/* SPDX-License-Identifier: GPL-2.0 */ /* * mux/driver.h - definitions for the multiplexer driver interface * * Copyright (C) 2017 Axentia Technologies AB * * Author: Peter Rosin - * - * 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. */ #ifndef _LINUX_MUX_DRIVER_H -- cgit v1.2.3-58-ga151