diff options
Diffstat (limited to 'drivers/misc')
41 files changed, 2622 insertions, 1346 deletions
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index 42c38525904b..ccccc2943f2f 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -271,6 +271,16 @@ config HP_ILO To compile this driver as a module, choose M here: the module will be called hpilo. +config QCOM_COINCELL + tristate "Qualcomm coincell charger support" + depends on MFD_SPMI_PMIC || COMPILE_TEST + help + This driver supports the coincell block found inside of + Qualcomm PMICs. The coincell charger provides a means to + charge a coincell battery or backup capacitor which is used + to maintain PMIC register and RTC state in the absence of + external power. + config SGI_GRU tristate "SGI GRU driver" depends on X86_UV && SMP diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index d056fb7186fe..537d7f3b78da 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -18,6 +18,7 @@ obj-$(CONFIG_LKDTM) += lkdtm.o obj-$(CONFIG_TIFM_CORE) += tifm_core.o obj-$(CONFIG_TIFM_7XX1) += tifm_7xx1.o obj-$(CONFIG_PHANTOM) += phantom.o +obj-$(CONFIG_QCOM_COINCELL) += qcom-coincell.o obj-$(CONFIG_SENSORS_BH1780) += bh1780gli.o obj-$(CONFIG_SENSORS_BH1770) += bh1770glc.o obj-$(CONFIG_SENSORS_APDS990X) += apds990x.o diff --git a/drivers/misc/ad525x_dpot-i2c.c b/drivers/misc/ad525x_dpot-i2c.c index 705b881e186d..d11187d36ddd 100644 --- a/drivers/misc/ad525x_dpot-i2c.c +++ b/drivers/misc/ad525x_dpot-i2c.c @@ -106,7 +106,6 @@ MODULE_DEVICE_TABLE(i2c, ad_dpot_id); static struct i2c_driver ad_dpot_i2c_driver = { .driver = { .name = "ad_dpot", - .owner = THIS_MODULE, }, .probe = ad_dpot_i2c_probe, .remove = ad_dpot_i2c_remove, diff --git a/drivers/misc/apds990x.c b/drivers/misc/apds990x.c index 3739ffa9cdf1..a3e789b85cc8 100644 --- a/drivers/misc/apds990x.c +++ b/drivers/misc/apds990x.c @@ -1275,7 +1275,6 @@ static const struct dev_pm_ops apds990x_pm_ops = { static struct i2c_driver apds990x_driver = { .driver = { .name = "apds990x", - .owner = THIS_MODULE, .pm = &apds990x_pm_ops, }, .probe = apds990x_probe, diff --git a/drivers/misc/bh1770glc.c b/drivers/misc/bh1770glc.c index b756381b8250..753d7ecdadaa 100644 --- a/drivers/misc/bh1770glc.c +++ b/drivers/misc/bh1770glc.c @@ -1396,7 +1396,6 @@ static const struct dev_pm_ops bh1770_pm_ops = { static struct i2c_driver bh1770_driver = { .driver = { .name = "bh1770glc", - .owner = THIS_MODULE, .pm = &bh1770_pm_ops, }, .probe = bh1770_probe, diff --git a/drivers/misc/bmp085-i2c.c b/drivers/misc/bmp085-i2c.c index a7c16295b816..f35c218aaa1a 100644 --- a/drivers/misc/bmp085-i2c.c +++ b/drivers/misc/bmp085-i2c.c @@ -66,7 +66,6 @@ MODULE_DEVICE_TABLE(i2c, bmp085_id); static struct i2c_driver bmp085_i2c_driver = { .driver = { - .owner = THIS_MODULE, .name = BMP085_NAME, }, .id_table = bmp085_id, diff --git a/drivers/misc/cxl/sysfs.c b/drivers/misc/cxl/sysfs.c index 31f38bc71a3d..87cd747bb511 100644 --- a/drivers/misc/cxl/sysfs.c +++ b/drivers/misc/cxl/sysfs.c @@ -443,12 +443,7 @@ static ssize_t afu_read_config(struct file *filp, struct kobject *kobj, struct afu_config_record *cr = to_cr(kobj); struct cxl_afu *afu = to_cxl_afu(container_of(kobj->parent, struct device, kobj)); - u64 i, j, val, size = afu->crs_len; - - if (off > size) - return 0; - if (off + count > size) - count = size - off; + u64 i, j, val; for (i = 0; i < count;) { val = cxl_afu_cr_read64(afu, cr->cr, off & ~0x7); diff --git a/drivers/misc/ds1682.c b/drivers/misc/ds1682.c index b909fb30232a..c7112276a039 100644 --- a/drivers/misc/ds1682.c +++ b/drivers/misc/ds1682.c @@ -148,12 +148,6 @@ static ssize_t ds1682_eeprom_read(struct file *filp, struct kobject *kobj, dev_dbg(&client->dev, "ds1682_eeprom_read(p=%p, off=%lli, c=%zi)\n", buf, off, count); - if (off >= DS1682_EEPROM_SIZE) - return 0; - - if (off + count > DS1682_EEPROM_SIZE) - count = DS1682_EEPROM_SIZE - off; - rc = i2c_smbus_read_i2c_block_data(client, DS1682_REG_EEPROM + off, count, buf); if (rc < 0) @@ -171,12 +165,6 @@ static ssize_t ds1682_eeprom_write(struct file *filp, struct kobject *kobj, dev_dbg(&client->dev, "ds1682_eeprom_write(p=%p, off=%lli, c=%zi)\n", buf, off, count); - if (off >= DS1682_EEPROM_SIZE) - return -ENOSPC; - - if (off + count > DS1682_EEPROM_SIZE) - count = DS1682_EEPROM_SIZE - off; - /* Write out to the device */ if (i2c_smbus_write_i2c_block_data(client, DS1682_REG_EEPROM + off, count, buf) < 0) diff --git a/drivers/misc/eeprom/Kconfig b/drivers/misc/eeprom/Kconfig index 9536852fd4c6..04f2e1fa9dd1 100644 --- a/drivers/misc/eeprom/Kconfig +++ b/drivers/misc/eeprom/Kconfig @@ -96,17 +96,4 @@ config EEPROM_DIGSY_MTC_CFG If unsure, say N. -config EEPROM_SUNXI_SID - tristate "Allwinner sunxi security ID support" - depends on ARCH_SUNXI && SYSFS - help - This is a driver for the 'security ID' available on various Allwinner - devices. - - Due to the potential risks involved with changing e-fuses, - this driver is read-only. - - This driver can also be built as a module. If so, the module - will be called sunxi_sid. - endmenu diff --git a/drivers/misc/eeprom/Makefile b/drivers/misc/eeprom/Makefile index 9507aec95e94..fc1e81d29267 100644 --- a/drivers/misc/eeprom/Makefile +++ b/drivers/misc/eeprom/Makefile @@ -4,5 +4,4 @@ obj-$(CONFIG_EEPROM_LEGACY) += eeprom.o obj-$(CONFIG_EEPROM_MAX6875) += max6875.o obj-$(CONFIG_EEPROM_93CX6) += eeprom_93cx6.o obj-$(CONFIG_EEPROM_93XX46) += eeprom_93xx46.o -obj-$(CONFIG_EEPROM_SUNXI_SID) += sunxi_sid.o obj-$(CONFIG_EEPROM_DIGSY_MTC_CFG) += digsy_mtc_eeprom.o diff --git a/drivers/misc/eeprom/at24.c b/drivers/misc/eeprom/at24.c index 6ded3dc36644..2b254f3a1154 100644 --- a/drivers/misc/eeprom/at24.c +++ b/drivers/misc/eeprom/at24.c @@ -686,7 +686,6 @@ static int at24_remove(struct i2c_client *client) static struct i2c_driver at24_driver = { .driver = { .name = "at24", - .owner = THIS_MODULE, }, .probe = at24_probe, .remove = at24_remove, diff --git a/drivers/misc/eeprom/eeprom.c b/drivers/misc/eeprom/eeprom.c index b432873def96..7342fd637031 100644 --- a/drivers/misc/eeprom/eeprom.c +++ b/drivers/misc/eeprom/eeprom.c @@ -88,11 +88,6 @@ static ssize_t eeprom_read(struct file *filp, struct kobject *kobj, struct eeprom_data *data = i2c_get_clientdata(client); u8 slice; - if (off > EEPROM_SIZE) - return 0; - if (off + count > EEPROM_SIZE) - count = EEPROM_SIZE - off; - /* Only refresh slices which contain requested bytes */ for (slice = off >> 5; slice <= (off + count - 1) >> 5; slice++) eeprom_update_client(client, slice); diff --git a/drivers/misc/eeprom/eeprom_93xx46.c b/drivers/misc/eeprom/eeprom_93xx46.c index 9ebeacdb8ec4..a6bd9e3fe9d3 100644 --- a/drivers/misc/eeprom/eeprom_93xx46.c +++ b/drivers/misc/eeprom/eeprom_93xx46.c @@ -48,13 +48,6 @@ eeprom_93xx46_bin_read(struct file *filp, struct kobject *kobj, dev = container_of(kobj, struct device, kobj); edev = dev_get_drvdata(dev); - if (unlikely(off >= edev->bin.size)) - return 0; - if ((off + count) > edev->bin.size) - count = edev->bin.size - off; - if (unlikely(!count)) - return count; - cmd_addr = OP_READ << edev->addrlen; if (edev->addrlen == 7) { @@ -200,13 +193,6 @@ eeprom_93xx46_bin_write(struct file *filp, struct kobject *kobj, dev = container_of(kobj, struct device, kobj); edev = dev_get_drvdata(dev); - if (unlikely(off >= edev->bin.size)) - return -EFBIG; - if ((off + count) > edev->bin.size) - count = edev->bin.size - off; - if (unlikely(!count)) - return count; - /* only write even number of bytes on 16-bit devices */ if (edev->addrlen == 6) { step = 2; diff --git a/drivers/misc/eeprom/max6875.c b/drivers/misc/eeprom/max6875.c index 580ff9df5529..9aa4332a6b04 100644 --- a/drivers/misc/eeprom/max6875.c +++ b/drivers/misc/eeprom/max6875.c @@ -114,12 +114,6 @@ static ssize_t max6875_read(struct file *filp, struct kobject *kobj, struct max6875_data *data = i2c_get_clientdata(client); int slice, max_slice; - if (off > USER_EEPROM_SIZE) - return 0; - - if (off + count > USER_EEPROM_SIZE) - count = USER_EEPROM_SIZE - off; - /* refresh slices which contain requested bytes */ max_slice = (off + count - 1) >> SLICE_BITS; for (slice = (off >> SLICE_BITS); slice <= max_slice; slice++) diff --git a/drivers/misc/eeprom/sunxi_sid.c b/drivers/misc/eeprom/sunxi_sid.c deleted file mode 100644 index 8385177ff32b..000000000000 --- a/drivers/misc/eeprom/sunxi_sid.c +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Copyright (c) 2013 Oliver Schinagl <oliver@schinagl.nl> - * http://www.linux-sunxi.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * This driver exposes the Allwinner security ID, efuses exported in byte- - * sized chunks. - */ - -#include <linux/compiler.h> -#include <linux/device.h> -#include <linux/err.h> -#include <linux/export.h> -#include <linux/fs.h> -#include <linux/io.h> -#include <linux/kernel.h> -#include <linux/kobject.h> -#include <linux/module.h> -#include <linux/of_device.h> -#include <linux/platform_device.h> -#include <linux/random.h> -#include <linux/slab.h> -#include <linux/stat.h> -#include <linux/sysfs.h> -#include <linux/types.h> - -#define DRV_NAME "sunxi-sid" - -struct sunxi_sid_data { - void __iomem *reg_base; - unsigned int keysize; -}; - -/* We read the entire key, due to a 32 bit read alignment requirement. Since we - * want to return the requested byte, this results in somewhat slower code and - * uses 4 times more reads as needed but keeps code simpler. Since the SID is - * only very rarely probed, this is not really an issue. - */ -static u8 sunxi_sid_read_byte(const struct sunxi_sid_data *sid_data, - const unsigned int offset) -{ - u32 sid_key; - - if (offset >= sid_data->keysize) - return 0; - - sid_key = ioread32be(sid_data->reg_base + round_down(offset, 4)); - sid_key >>= (offset % 4) * 8; - - return sid_key; /* Only return the last byte */ -} - -static ssize_t sid_read(struct file *fd, struct kobject *kobj, - struct bin_attribute *attr, char *buf, - loff_t pos, size_t size) -{ - struct platform_device *pdev; - struct sunxi_sid_data *sid_data; - int i; - - pdev = to_platform_device(kobj_to_dev(kobj)); - sid_data = platform_get_drvdata(pdev); - - if (pos < 0 || pos >= sid_data->keysize) - return 0; - if (size > sid_data->keysize - pos) - size = sid_data->keysize - pos; - - for (i = 0; i < size; i++) - buf[i] = sunxi_sid_read_byte(sid_data, pos + i); - - return i; -} - -static struct bin_attribute sid_bin_attr = { - .attr = { .name = "eeprom", .mode = S_IRUGO, }, - .read = sid_read, -}; - -static int sunxi_sid_remove(struct platform_device *pdev) -{ - device_remove_bin_file(&pdev->dev, &sid_bin_attr); - dev_dbg(&pdev->dev, "driver unloaded\n"); - - return 0; -} - -static const struct of_device_id sunxi_sid_of_match[] = { - { .compatible = "allwinner,sun4i-a10-sid", .data = (void *)16}, - { .compatible = "allwinner,sun7i-a20-sid", .data = (void *)512}, - {/* sentinel */}, -}; -MODULE_DEVICE_TABLE(of, sunxi_sid_of_match); - -static int sunxi_sid_probe(struct platform_device *pdev) -{ - struct sunxi_sid_data *sid_data; - struct resource *res; - const struct of_device_id *of_dev_id; - u8 *entropy; - unsigned int i; - - sid_data = devm_kzalloc(&pdev->dev, sizeof(struct sunxi_sid_data), - GFP_KERNEL); - if (!sid_data) - return -ENOMEM; - - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - sid_data->reg_base = devm_ioremap_resource(&pdev->dev, res); - if (IS_ERR(sid_data->reg_base)) - return PTR_ERR(sid_data->reg_base); - - of_dev_id = of_match_device(sunxi_sid_of_match, &pdev->dev); - if (!of_dev_id) - return -ENODEV; - sid_data->keysize = (int)of_dev_id->data; - - platform_set_drvdata(pdev, sid_data); - - sid_bin_attr.size = sid_data->keysize; - if (device_create_bin_file(&pdev->dev, &sid_bin_attr)) - return -ENODEV; - - entropy = kzalloc(sizeof(u8) * sid_data->keysize, GFP_KERNEL); - for (i = 0; i < sid_data->keysize; i++) - entropy[i] = sunxi_sid_read_byte(sid_data, i); - add_device_randomness(entropy, sid_data->keysize); - kfree(entropy); - - dev_dbg(&pdev->dev, "loaded\n"); - - return 0; -} - -static struct platform_driver sunxi_sid_driver = { - .probe = sunxi_sid_probe, - .remove = sunxi_sid_remove, - .driver = { - .name = DRV_NAME, - .of_match_table = sunxi_sid_of_match, - }, -}; -module_platform_driver(sunxi_sid_driver); - -MODULE_AUTHOR("Oliver Schinagl <oliver@schinagl.nl>"); -MODULE_DESCRIPTION("Allwinner sunxi security id driver"); -MODULE_LICENSE("GPL"); diff --git a/drivers/misc/isl29003.c b/drivers/misc/isl29003.c index 12c30b486b27..976df0013633 100644 --- a/drivers/misc/isl29003.c +++ b/drivers/misc/isl29003.c @@ -465,7 +465,6 @@ MODULE_DEVICE_TABLE(i2c, isl29003_id); static struct i2c_driver isl29003_driver = { .driver = { .name = ISL29003_DRV_NAME, - .owner = THIS_MODULE, .pm = ISL29003_PM_OPS, }, .probe = isl29003_probe, diff --git a/drivers/misc/lis3lv02d/lis3lv02d_i2c.c b/drivers/misc/lis3lv02d/lis3lv02d_i2c.c index e3e7f1dc27ba..0c3bb7e3ee80 100644 --- a/drivers/misc/lis3lv02d/lis3lv02d_i2c.c +++ b/drivers/misc/lis3lv02d/lis3lv02d_i2c.c @@ -274,7 +274,6 @@ static const struct dev_pm_ops lis3_pm_ops = { static struct i2c_driver lis3lv02d_i2c_driver = { .driver = { .name = DRV_NAME, - .owner = THIS_MODULE, .pm = &lis3_pm_ops, .of_match_table = of_match_ptr(lis3lv02d_i2c_dt_ids), }, diff --git a/drivers/misc/mei/Makefile b/drivers/misc/mei/Makefile index 518914a82b83..01447ca21c26 100644 --- a/drivers/misc/mei/Makefile +++ b/drivers/misc/mei/Makefile @@ -11,7 +11,7 @@ mei-objs += main.o mei-objs += amthif.o mei-objs += wd.o mei-objs += bus.o -mei-objs += nfc.o +mei-objs += bus-fixup.o mei-$(CONFIG_DEBUG_FS) += debugfs.o obj-$(CONFIG_INTEL_MEI_ME) += mei-me.o diff --git a/drivers/misc/mei/bus-fixup.c b/drivers/misc/mei/bus-fixup.c new file mode 100644 index 000000000000..3e536ca85f7d --- /dev/null +++ b/drivers/misc/mei/bus-fixup.c @@ -0,0 +1,306 @@ +/* + * + * Intel Management Engine Interface (Intel MEI) Linux driver + * Copyright (c) 2003-2013, 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, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + */ + +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/device.h> +#include <linux/slab.h> +#include <linux/uuid.h> + +#include <linux/mei_cl_bus.h> + +#include "mei_dev.h" +#include "client.h" + +#define MEI_UUID_NFC_INFO UUID_LE(0xd2de1625, 0x382d, 0x417d, \ + 0x48, 0xa4, 0xef, 0xab, 0xba, 0x8a, 0x12, 0x06) + +static const uuid_le mei_nfc_info_guid = MEI_UUID_NFC_INFO; + +#define MEI_UUID_NFC_HCI UUID_LE(0x0bb17a78, 0x2a8e, 0x4c50, \ + 0x94, 0xd4, 0x50, 0x26, 0x67, 0x23, 0x77, 0x5c) + +#define MEI_UUID_ANY NULL_UUID_LE + +/** + * number_of_connections - determine whether an client be on the bus + * according number of connections + * We support only clients: + * 1. with single connection + * 2. and fixed clients (max_number_of_connections == 0) + * + * @cldev: me clients device + */ +static void number_of_connections(struct mei_cl_device *cldev) +{ + dev_dbg(&cldev->dev, "running hook %s on %pUl\n", + __func__, mei_me_cl_uuid(cldev->me_cl)); + + if (cldev->me_cl->props.max_number_of_connections > 1) + cldev->do_match = 0; +} + +/** + * blacklist - blacklist a client from the bus + * + * @cldev: me clients device + */ +static void blacklist(struct mei_cl_device *cldev) +{ + dev_dbg(&cldev->dev, "running hook %s on %pUl\n", + __func__, mei_me_cl_uuid(cldev->me_cl)); + cldev->do_match = 0; +} + +struct mei_nfc_cmd { + u8 command; + u8 status; + u16 req_id; + u32 reserved; + u16 data_size; + u8 sub_command; + u8 data[]; +} __packed; + +struct mei_nfc_reply { + u8 command; + u8 status; + u16 req_id; + u32 reserved; + u16 data_size; + u8 sub_command; + u8 reply_status; + u8 data[]; +} __packed; + +struct mei_nfc_if_version { + u8 radio_version_sw[3]; + u8 reserved[3]; + u8 radio_version_hw[3]; + u8 i2c_addr; + u8 fw_ivn; + u8 vendor_id; + u8 radio_type; +} __packed; + + +#define MEI_NFC_CMD_MAINTENANCE 0x00 +#define MEI_NFC_SUBCMD_IF_VERSION 0x01 + +/* Vendors */ +#define MEI_NFC_VENDOR_INSIDE 0x00 +#define MEI_NFC_VENDOR_NXP 0x01 + +/* Radio types */ +#define MEI_NFC_VENDOR_INSIDE_UREAD 0x00 +#define MEI_NFC_VENDOR_NXP_PN544 0x01 + +/** + * mei_nfc_if_version - get NFC interface version + * + * @cl: host client (nfc info) + * @ver: NFC interface version to be filled in + * + * Return: 0 on success; < 0 otherwise + */ +static int mei_nfc_if_version(struct mei_cl *cl, + struct mei_nfc_if_version *ver) +{ + struct mei_device *bus; + struct mei_nfc_cmd cmd = { + .command = MEI_NFC_CMD_MAINTENANCE, + .data_size = 1, + .sub_command = MEI_NFC_SUBCMD_IF_VERSION, + }; + struct mei_nfc_reply *reply = NULL; + size_t if_version_length; + int bytes_recv, ret; + + bus = cl->dev; + + WARN_ON(mutex_is_locked(&bus->device_lock)); + + ret = __mei_cl_send(cl, (u8 *)&cmd, sizeof(struct mei_nfc_cmd), 1); + if (ret < 0) { + dev_err(bus->dev, "Could not send IF version cmd\n"); + return ret; + } + + /* to be sure on the stack we alloc memory */ + if_version_length = sizeof(struct mei_nfc_reply) + + sizeof(struct mei_nfc_if_version); + + reply = kzalloc(if_version_length, GFP_KERNEL); + if (!reply) + return -ENOMEM; + + ret = 0; + bytes_recv = __mei_cl_recv(cl, (u8 *)reply, if_version_length); + if (bytes_recv < 0 || bytes_recv < sizeof(struct mei_nfc_reply)) { + dev_err(bus->dev, "Could not read IF version\n"); + ret = -EIO; + goto err; + } + + memcpy(ver, reply->data, sizeof(struct mei_nfc_if_version)); + + dev_info(bus->dev, "NFC MEI VERSION: IVN 0x%x Vendor ID 0x%x Type 0x%x\n", + ver->fw_ivn, ver->vendor_id, ver->radio_type); + +err: + kfree(reply); + return ret; +} + +/** + * mei_nfc_radio_name - derive nfc radio name from the interface version + * + * @ver: NFC radio version + * + * Return: radio name string + */ +static const char *mei_nfc_radio_name(struct mei_nfc_if_version *ver) +{ + + if (ver->vendor_id == MEI_NFC_VENDOR_INSIDE) { + if (ver->radio_type == MEI_NFC_VENDOR_INSIDE_UREAD) + return "microread"; + } + + if (ver->vendor_id == MEI_NFC_VENDOR_NXP) { + if (ver->radio_type == MEI_NFC_VENDOR_NXP_PN544) + return "pn544"; + } + + return NULL; +} + +/** + * mei_nfc - The nfc fixup function. The function retrieves nfc radio + * name and set is as device attribute so we can load + * the proper device driver for it + * + * @cldev: me client device (nfc) + */ +static void mei_nfc(struct mei_cl_device *cldev) +{ + struct mei_device *bus; + struct mei_cl *cl; + struct mei_me_client *me_cl = NULL; + struct mei_nfc_if_version ver; + const char *radio_name = NULL; + int ret; + + bus = cldev->bus; + + dev_dbg(bus->dev, "running hook %s: %pUl match=%d\n", + __func__, mei_me_cl_uuid(cldev->me_cl), cldev->do_match); + + mutex_lock(&bus->device_lock); + /* we need to connect to INFO GUID */ + cl = mei_cl_alloc_linked(bus, MEI_HOST_CLIENT_ID_ANY); + if (IS_ERR(cl)) { + ret = PTR_ERR(cl); + cl = NULL; + dev_err(bus->dev, "nfc hook alloc failed %d\n", ret); + goto out; + } + + me_cl = mei_me_cl_by_uuid(bus, &mei_nfc_info_guid); + if (!me_cl) { + ret = -ENOTTY; + dev_err(bus->dev, "Cannot find nfc info %d\n", ret); + goto out; + } + + ret = mei_cl_connect(cl, me_cl, NULL); + if (ret < 0) { + dev_err(&cldev->dev, "Can't connect to the NFC INFO ME ret = %d\n", + ret); + goto out; + } + + mutex_unlock(&bus->device_lock); + + ret = mei_nfc_if_version(cl, &ver); + if (ret) + goto disconnect; + + radio_name = mei_nfc_radio_name(&ver); + + if (!radio_name) { + ret = -ENOENT; + dev_err(&cldev->dev, "Can't get the NFC interface version ret = %d\n", + ret); + goto disconnect; + } + + dev_dbg(bus->dev, "nfc radio %s\n", radio_name); + strlcpy(cldev->name, radio_name, sizeof(cldev->name)); + +disconnect: + mutex_lock(&bus->device_lock); + if (mei_cl_disconnect(cl) < 0) + dev_err(bus->dev, "Can't disconnect the NFC INFO ME\n"); + + mei_cl_flush_queues(cl, NULL); + +out: + mei_cl_unlink(cl); + mutex_unlock(&bus->device_lock); + mei_me_cl_put(me_cl); + kfree(cl); + + if (ret) + cldev->do_match = 0; + + dev_dbg(bus->dev, "end of fixup match = %d\n", cldev->do_match); +} + +#define MEI_FIXUP(_uuid, _hook) { _uuid, _hook } + +static struct mei_fixup { + + const uuid_le uuid; + void (*hook)(struct mei_cl_device *cldev); +} mei_fixups[] = { + MEI_FIXUP(MEI_UUID_ANY, number_of_connections), + MEI_FIXUP(MEI_UUID_NFC_INFO, blacklist), + MEI_FIXUP(MEI_UUID_NFC_HCI, mei_nfc), +}; + +/** + * mei_cl_dev_fixup - run fixup handlers + * + * @cldev: me client device + */ +void mei_cl_dev_fixup(struct mei_cl_device *cldev) +{ + struct mei_fixup *f; + const uuid_le *uuid = mei_me_cl_uuid(cldev->me_cl); + int i; + + for (i = 0; i < ARRAY_SIZE(mei_fixups); i++) { + + f = &mei_fixups[i]; + if (uuid_le_cmp(f->uuid, MEI_UUID_ANY) == 0 || + uuid_le_cmp(f->uuid, *uuid) == 0) + f->hook(cldev); + } +} + diff --git a/drivers/misc/mei/bus.c b/drivers/misc/mei/bus.c index 458aa5a09c52..eef1c6b46ad8 100644 --- a/drivers/misc/mei/bus.c +++ b/drivers/misc/mei/bus.c @@ -30,276 +30,29 @@ #define to_mei_cl_driver(d) container_of(d, struct mei_cl_driver, driver) #define to_mei_cl_device(d) container_of(d, struct mei_cl_device, dev) -static int mei_cl_device_match(struct device *dev, struct device_driver *drv) -{ - struct mei_cl_device *device = to_mei_cl_device(dev); - struct mei_cl_driver *driver = to_mei_cl_driver(drv); - const struct mei_cl_device_id *id; - const uuid_le *uuid; - const char *name; - - if (!device) - return 0; - - uuid = mei_me_cl_uuid(device->me_cl); - name = device->name; - - if (!driver || !driver->id_table) - return 0; - - id = driver->id_table; - - while (uuid_le_cmp(NULL_UUID_LE, id->uuid)) { - - if (!uuid_le_cmp(*uuid, id->uuid)) { - if (id->name[0]) { - if (!strncmp(name, id->name, sizeof(id->name))) - return 1; - } else { - return 1; - } - } - - id++; - } - - return 0; -} - -static int mei_cl_device_probe(struct device *dev) -{ - struct mei_cl_device *device = to_mei_cl_device(dev); - struct mei_cl_driver *driver; - struct mei_cl_device_id id; - - if (!device) - return 0; - - driver = to_mei_cl_driver(dev->driver); - if (!driver || !driver->probe) - return -ENODEV; - - dev_dbg(dev, "Device probe\n"); - - strlcpy(id.name, device->name, sizeof(id.name)); - - return driver->probe(device, &id); -} - -static int mei_cl_device_remove(struct device *dev) -{ - struct mei_cl_device *device = to_mei_cl_device(dev); - struct mei_cl_driver *driver; - - if (!device || !dev->driver) - return 0; - - if (device->event_cb) { - device->event_cb = NULL; - cancel_work_sync(&device->event_work); - } - - driver = to_mei_cl_driver(dev->driver); - if (!driver->remove) { - dev->driver = NULL; - - return 0; - } - - return driver->remove(device); -} - -static ssize_t name_show(struct device *dev, struct device_attribute *a, - char *buf) -{ - struct mei_cl_device *device = to_mei_cl_device(dev); - size_t len; - - len = snprintf(buf, PAGE_SIZE, "%s", device->name); - - return (len >= PAGE_SIZE) ? (PAGE_SIZE - 1) : len; -} -static DEVICE_ATTR_RO(name); - -static ssize_t uuid_show(struct device *dev, struct device_attribute *a, - char *buf) -{ - struct mei_cl_device *device = to_mei_cl_device(dev); - const uuid_le *uuid = mei_me_cl_uuid(device->me_cl); - size_t len; - - len = snprintf(buf, PAGE_SIZE, "%pUl", uuid); - - return (len >= PAGE_SIZE) ? (PAGE_SIZE - 1) : len; -} -static DEVICE_ATTR_RO(uuid); - -static ssize_t modalias_show(struct device *dev, struct device_attribute *a, - char *buf) -{ - struct mei_cl_device *device = to_mei_cl_device(dev); - const uuid_le *uuid = mei_me_cl_uuid(device->me_cl); - size_t len; - - len = snprintf(buf, PAGE_SIZE, "mei:%s:" MEI_CL_UUID_FMT ":", - device->name, MEI_CL_UUID_ARGS(uuid->b)); - - return (len >= PAGE_SIZE) ? (PAGE_SIZE - 1) : len; -} -static DEVICE_ATTR_RO(modalias); - -static struct attribute *mei_cl_dev_attrs[] = { - &dev_attr_name.attr, - &dev_attr_uuid.attr, - &dev_attr_modalias.attr, - NULL, -}; -ATTRIBUTE_GROUPS(mei_cl_dev); - -static int mei_cl_uevent(struct device *dev, struct kobj_uevent_env *env) -{ - struct mei_cl_device *device = to_mei_cl_device(dev); - const uuid_le *uuid = mei_me_cl_uuid(device->me_cl); - - if (add_uevent_var(env, "MEI_CL_UUID=%pUl", uuid)) - return -ENOMEM; - - if (add_uevent_var(env, "MEI_CL_NAME=%s", device->name)) - return -ENOMEM; - - if (add_uevent_var(env, "MODALIAS=mei:%s:" MEI_CL_UUID_FMT ":", - device->name, MEI_CL_UUID_ARGS(uuid->b))) - return -ENOMEM; - - return 0; -} - -static struct bus_type mei_cl_bus_type = { - .name = "mei", - .dev_groups = mei_cl_dev_groups, - .match = mei_cl_device_match, - .probe = mei_cl_device_probe, - .remove = mei_cl_device_remove, - .uevent = mei_cl_uevent, -}; - -static void mei_cl_dev_release(struct device *dev) -{ - struct mei_cl_device *device = to_mei_cl_device(dev); - - if (!device) - return; - - mei_me_cl_put(device->me_cl); - kfree(device); -} - -static struct device_type mei_cl_device_type = { - .release = mei_cl_dev_release, -}; - -struct mei_cl *mei_cl_bus_find_cl_by_uuid(struct mei_device *dev, - uuid_le uuid) -{ - struct mei_cl *cl; - - list_for_each_entry(cl, &dev->device_list, device_link) { - if (cl->device && cl->device->me_cl && - !uuid_le_cmp(uuid, *mei_me_cl_uuid(cl->device->me_cl))) - return cl; - } - - return NULL; -} - -struct mei_cl_device *mei_cl_add_device(struct mei_device *dev, - struct mei_me_client *me_cl, - struct mei_cl *cl, - char *name) -{ - struct mei_cl_device *device; - int status; - - device = kzalloc(sizeof(struct mei_cl_device), GFP_KERNEL); - if (!device) - return NULL; - - device->me_cl = mei_me_cl_get(me_cl); - if (!device->me_cl) { - kfree(device); - return NULL; - } - - device->cl = cl; - device->dev.parent = dev->dev; - device->dev.bus = &mei_cl_bus_type; - device->dev.type = &mei_cl_device_type; - - strlcpy(device->name, name, sizeof(device->name)); - - dev_set_name(&device->dev, "mei:%s:%pUl", name, mei_me_cl_uuid(me_cl)); - - status = device_register(&device->dev); - if (status) { - dev_err(dev->dev, "Failed to register MEI device\n"); - mei_me_cl_put(device->me_cl); - kfree(device); - return NULL; - } - - cl->device = device; - - dev_dbg(&device->dev, "client %s registered\n", name); - - return device; -} -EXPORT_SYMBOL_GPL(mei_cl_add_device); - -void mei_cl_remove_device(struct mei_cl_device *device) -{ - device_unregister(&device->dev); -} -EXPORT_SYMBOL_GPL(mei_cl_remove_device); - -int __mei_cl_driver_register(struct mei_cl_driver *driver, struct module *owner) -{ - int err; - - driver->driver.name = driver->name; - driver->driver.owner = owner; - driver->driver.bus = &mei_cl_bus_type; - - err = driver_register(&driver->driver); - if (err) - return err; - - pr_debug("mei: driver [%s] registered\n", driver->driver.name); - - return 0; -} -EXPORT_SYMBOL_GPL(__mei_cl_driver_register); - -void mei_cl_driver_unregister(struct mei_cl_driver *driver) -{ - driver_unregister(&driver->driver); - - pr_debug("mei: driver [%s] unregistered\n", driver->driver.name); -} -EXPORT_SYMBOL_GPL(mei_cl_driver_unregister); - +/** + * __mei_cl_send - internal client send (write) + * + * @cl: host client + * @buf: buffer to send + * @length: buffer length + * @blocking: wait for write completion + * + * Return: written size bytes or < 0 on error + */ ssize_t __mei_cl_send(struct mei_cl *cl, u8 *buf, size_t length, bool blocking) { - struct mei_device *dev; + struct mei_device *bus; struct mei_cl_cb *cb = NULL; ssize_t rets; if (WARN_ON(!cl || !cl->dev)) return -ENODEV; - dev = cl->dev; + bus = cl->dev; - mutex_lock(&dev->device_lock); + mutex_lock(&bus->device_lock); if (!mei_cl_is_connected(cl)) { rets = -ENODEV; goto out; @@ -327,16 +80,25 @@ ssize_t __mei_cl_send(struct mei_cl *cl, u8 *buf, size_t length, rets = mei_cl_write(cl, cb, blocking); out: - mutex_unlock(&dev->device_lock); + mutex_unlock(&bus->device_lock); if (rets < 0) mei_io_cb_free(cb); return rets; } +/** + * __mei_cl_recv - internal client receive (read) + * + * @cl: host client + * @buf: buffer to send + * @length: buffer length + * + * Return: read size in bytes of < 0 on error + */ ssize_t __mei_cl_recv(struct mei_cl *cl, u8 *buf, size_t length) { - struct mei_device *dev; + struct mei_device *bus; struct mei_cl_cb *cb; size_t r_length; ssize_t rets; @@ -344,9 +106,9 @@ ssize_t __mei_cl_recv(struct mei_cl *cl, u8 *buf, size_t length) if (WARN_ON(!cl || !cl->dev)) return -ENODEV; - dev = cl->dev; + bus = cl->dev; - mutex_lock(&dev->device_lock); + mutex_lock(&bus->device_lock); cb = mei_cl_read_cb(cl, NULL); if (cb) @@ -356,9 +118,10 @@ ssize_t __mei_cl_recv(struct mei_cl *cl, u8 *buf, size_t length) if (rets && rets != -EBUSY) goto out; + /* wait on event only if there is no other waiter */ if (list_empty(&cl->rd_completed) && !waitqueue_active(&cl->rx_wait)) { - mutex_unlock(&dev->device_lock); + mutex_unlock(&bus->device_lock); if (wait_event_interruptible(cl->rx_wait, (!list_empty(&cl->rd_completed)) || @@ -369,7 +132,7 @@ ssize_t __mei_cl_recv(struct mei_cl *cl, u8 *buf, size_t length) return -ERESTARTSYS; } - mutex_lock(&dev->device_lock); + mutex_lock(&bus->device_lock); if (!mei_cl_is_connected(cl)) { rets = -EBUSY; @@ -396,14 +159,23 @@ copy: free: mei_io_cb_free(cb); out: - mutex_unlock(&dev->device_lock); + mutex_unlock(&bus->device_lock); return rets; } -ssize_t mei_cl_send(struct mei_cl_device *device, u8 *buf, size_t length) +/** + * mei_cl_send - me device send (write) + * + * @cldev: me client device + * @buf: buffer to send + * @length: buffer length + * + * Return: written size in bytes or < 0 on error + */ +ssize_t mei_cl_send(struct mei_cl_device *cldev, u8 *buf, size_t length) { - struct mei_cl *cl = device->cl; + struct mei_cl *cl = cldev->cl; if (cl == NULL) return -ENODEV; @@ -412,9 +184,18 @@ ssize_t mei_cl_send(struct mei_cl_device *device, u8 *buf, size_t length) } EXPORT_SYMBOL_GPL(mei_cl_send); -ssize_t mei_cl_recv(struct mei_cl_device *device, u8 *buf, size_t length) +/** + * mei_cl_recv - client receive (read) + * + * @cldev: me client device + * @buf: buffer to send + * @length: buffer length + * + * Return: read size in bytes of < 0 on error + */ +ssize_t mei_cl_recv(struct mei_cl_device *cldev, u8 *buf, size_t length) { - struct mei_cl *cl = device->cl; + struct mei_cl *cl = cldev->cl; if (cl == NULL) return -ENODEV; @@ -423,134 +204,697 @@ ssize_t mei_cl_recv(struct mei_cl_device *device, u8 *buf, size_t length) } EXPORT_SYMBOL_GPL(mei_cl_recv); +/** + * mei_bus_event_work - dispatch rx event for a bus device + * and schedule new work + * + * @work: work + */ static void mei_bus_event_work(struct work_struct *work) { - struct mei_cl_device *device; + struct mei_cl_device *cldev; - device = container_of(work, struct mei_cl_device, event_work); + cldev = container_of(work, struct mei_cl_device, event_work); - if (device->event_cb) - device->event_cb(device, device->events, device->event_context); + if (cldev->event_cb) + cldev->event_cb(cldev, cldev->events, cldev->event_context); - device->events = 0; + cldev->events = 0; /* Prepare for the next read */ - mei_cl_read_start(device->cl, 0, NULL); + if (cldev->events_mask & BIT(MEI_CL_EVENT_RX)) + mei_cl_read_start(cldev->cl, 0, NULL); } -int mei_cl_register_event_cb(struct mei_cl_device *device, +/** + * mei_cl_bus_notify_event - schedule notify cb on bus client + * + * @cl: host client + */ +void mei_cl_bus_notify_event(struct mei_cl *cl) +{ + struct mei_cl_device *cldev = cl->cldev; + + if (!cldev || !cldev->event_cb) + return; + + if (!(cldev->events_mask & BIT(MEI_CL_EVENT_NOTIF))) + return; + + if (!cl->notify_ev) + return; + + set_bit(MEI_CL_EVENT_NOTIF, &cldev->events); + + schedule_work(&cldev->event_work); + + cl->notify_ev = false; +} + +/** + * mei_cl_bus_rx_event - schedule rx evenet + * + * @cl: host client + */ +void mei_cl_bus_rx_event(struct mei_cl *cl) +{ + struct mei_cl_device *cldev = cl->cldev; + + if (!cldev || !cldev->event_cb) + return; + + if (!(cldev->events_mask & BIT(MEI_CL_EVENT_RX))) + return; + + set_bit(MEI_CL_EVENT_RX, &cldev->events); + + schedule_work(&cldev->event_work); +} + +/** + * mei_cl_register_event_cb - register event callback + * + * @cldev: me client devices + * @event_cb: callback function + * @events_mask: requested events bitmask + * @context: driver context data + * + * Return: 0 on success + * -EALREADY if an callback is already registered + * <0 on other errors + */ +int mei_cl_register_event_cb(struct mei_cl_device *cldev, + unsigned long events_mask, mei_cl_event_cb_t event_cb, void *context) { - if (device->event_cb) + int ret; + + if (cldev->event_cb) return -EALREADY; - device->events = 0; - device->event_cb = event_cb; - device->event_context = context; - INIT_WORK(&device->event_work, mei_bus_event_work); + cldev->events = 0; + cldev->events_mask = events_mask; + cldev->event_cb = event_cb; + cldev->event_context = context; + INIT_WORK(&cldev->event_work, mei_bus_event_work); - mei_cl_read_start(device->cl, 0, NULL); + if (cldev->events_mask & BIT(MEI_CL_EVENT_RX)) { + ret = mei_cl_read_start(cldev->cl, 0, NULL); + if (ret && ret != -EBUSY) + return ret; + } + + if (cldev->events_mask & BIT(MEI_CL_EVENT_NOTIF)) { + mutex_lock(&cldev->cl->dev->device_lock); + ret = mei_cl_notify_request(cldev->cl, NULL, event_cb ? 1 : 0); + mutex_unlock(&cldev->cl->dev->device_lock); + if (ret) + return ret; + } return 0; } EXPORT_SYMBOL_GPL(mei_cl_register_event_cb); -void *mei_cl_get_drvdata(const struct mei_cl_device *device) +/** + * mei_cl_get_drvdata - driver data getter + * + * @cldev: mei client device + * + * Return: driver private data + */ +void *mei_cl_get_drvdata(const struct mei_cl_device *cldev) { - return dev_get_drvdata(&device->dev); + return dev_get_drvdata(&cldev->dev); } EXPORT_SYMBOL_GPL(mei_cl_get_drvdata); -void mei_cl_set_drvdata(struct mei_cl_device *device, void *data) +/** + * mei_cl_set_drvdata - driver data setter + * + * @cldev: mei client device + * @data: data to store + */ +void mei_cl_set_drvdata(struct mei_cl_device *cldev, void *data) { - dev_set_drvdata(&device->dev, data); + dev_set_drvdata(&cldev->dev, data); } EXPORT_SYMBOL_GPL(mei_cl_set_drvdata); -int mei_cl_enable_device(struct mei_cl_device *device) +/** + * mei_cl_enable_device - enable me client device + * create connection with me client + * + * @cldev: me client device + * + * Return: 0 on success and < 0 on error + */ +int mei_cl_enable_device(struct mei_cl_device *cldev) { - int err; - struct mei_device *dev; - struct mei_cl *cl = device->cl; - - if (cl == NULL) - return -ENODEV; - - dev = cl->dev; - - mutex_lock(&dev->device_lock); + struct mei_device *bus = cldev->bus; + struct mei_cl *cl; + int ret; + + cl = cldev->cl; + + if (!cl) { + mutex_lock(&bus->device_lock); + cl = mei_cl_alloc_linked(bus, MEI_HOST_CLIENT_ID_ANY); + mutex_unlock(&bus->device_lock); + if (IS_ERR(cl)) + return PTR_ERR(cl); + /* update pointers */ + cldev->cl = cl; + cl->cldev = cldev; + } + mutex_lock(&bus->device_lock); if (mei_cl_is_connected(cl)) { - mutex_unlock(&dev->device_lock); - dev_warn(dev->dev, "Already connected"); - return -EBUSY; + ret = 0; + goto out; } - err = mei_cl_connect(cl, device->me_cl, NULL); - if (err < 0) { - mutex_unlock(&dev->device_lock); - dev_err(dev->dev, "Could not connect to the ME client"); - - return err; + if (!mei_me_cl_is_active(cldev->me_cl)) { + dev_err(&cldev->dev, "me client is not active\n"); + ret = -ENOTTY; + goto out; } - mutex_unlock(&dev->device_lock); + ret = mei_cl_connect(cl, cldev->me_cl, NULL); + if (ret < 0) + dev_err(&cldev->dev, "cannot connect\n"); - if (device->event_cb) - mei_cl_read_start(device->cl, 0, NULL); +out: + mutex_unlock(&bus->device_lock); - return 0; + return ret; } EXPORT_SYMBOL_GPL(mei_cl_enable_device); -int mei_cl_disable_device(struct mei_cl_device *device) +/** + * mei_cl_disable_device - disable me client device + * disconnect form the me client + * + * @cldev: me client device + * + * Return: 0 on success and < 0 on error + */ +int mei_cl_disable_device(struct mei_cl_device *cldev) { + struct mei_device *bus; + struct mei_cl *cl; int err; - struct mei_device *dev; - struct mei_cl *cl = device->cl; - if (cl == NULL) + if (!cldev || !cldev->cl) return -ENODEV; - dev = cl->dev; + cl = cldev->cl; - device->event_cb = NULL; + bus = cldev->bus; - mutex_lock(&dev->device_lock); + cldev->event_cb = NULL; + + mutex_lock(&bus->device_lock); if (!mei_cl_is_connected(cl)) { - dev_err(dev->dev, "Already disconnected"); + dev_err(bus->dev, "Already disconnected"); err = 0; goto out; } err = mei_cl_disconnect(cl); - if (err < 0) { - dev_err(dev->dev, "Could not disconnect from the ME client"); - goto out; - } + if (err < 0) + dev_err(bus->dev, "Could not disconnect from the ME client"); +out: /* Flush queues and remove any pending read */ mei_cl_flush_queues(cl, NULL); + mei_cl_unlink(cl); -out: - mutex_unlock(&dev->device_lock); - return err; + kfree(cl); + cldev->cl = NULL; + mutex_unlock(&bus->device_lock); + return err; } EXPORT_SYMBOL_GPL(mei_cl_disable_device); -void mei_cl_bus_rx_event(struct mei_cl *cl) +/** + * mei_cl_device_find - find matching entry in the driver id table + * + * @cldev: me client device + * @cldrv: me client driver + * + * Return: id on success; NULL if no id is matching + */ +static const +struct mei_cl_device_id *mei_cl_device_find(struct mei_cl_device *cldev, + struct mei_cl_driver *cldrv) { - struct mei_cl_device *device = cl->device; + const struct mei_cl_device_id *id; + const uuid_le *uuid; + + uuid = mei_me_cl_uuid(cldev->me_cl); + + id = cldrv->id_table; + while (uuid_le_cmp(NULL_UUID_LE, id->uuid)) { + if (!uuid_le_cmp(*uuid, id->uuid)) { + + if (!cldev->name[0]) + return id; + + if (!strncmp(cldev->name, id->name, sizeof(id->name))) + return id; + } + + id++; + } + + return NULL; +} + +/** + * mei_cl_device_match - device match function + * + * @dev: device + * @drv: driver + * + * Return: 1 if matching device was found 0 otherwise + */ +static int mei_cl_device_match(struct device *dev, struct device_driver *drv) +{ + struct mei_cl_device *cldev = to_mei_cl_device(dev); + struct mei_cl_driver *cldrv = to_mei_cl_driver(drv); + const struct mei_cl_device_id *found_id; + + if (!cldev) + return 0; + + if (!cldev->do_match) + return 0; + + if (!cldrv || !cldrv->id_table) + return 0; + + found_id = mei_cl_device_find(cldev, cldrv); + if (found_id) + return 1; + + return 0; +} + +/** + * mei_cl_device_probe - bus probe function + * + * @dev: device + * + * Return: 0 on success; < 0 otherwise + */ +static int mei_cl_device_probe(struct device *dev) +{ + struct mei_cl_device *cldev; + struct mei_cl_driver *cldrv; + const struct mei_cl_device_id *id; + + cldev = to_mei_cl_device(dev); + cldrv = to_mei_cl_driver(dev->driver); + + if (!cldev) + return 0; + + if (!cldrv || !cldrv->probe) + return -ENODEV; + + id = mei_cl_device_find(cldev, cldrv); + if (!id) + return -ENODEV; + + __module_get(THIS_MODULE); + + return cldrv->probe(cldev, id); +} + +/** + * mei_cl_device_remove - remove device from the bus + * + * @dev: device + * + * Return: 0 on success; < 0 otherwise + */ +static int mei_cl_device_remove(struct device *dev) +{ + struct mei_cl_device *cldev = to_mei_cl_device(dev); + struct mei_cl_driver *cldrv; + int ret = 0; + + if (!cldev || !dev->driver) + return 0; + + if (cldev->event_cb) { + cldev->event_cb = NULL; + cancel_work_sync(&cldev->event_work); + } + + cldrv = to_mei_cl_driver(dev->driver); + if (cldrv->remove) + ret = cldrv->remove(cldev); + + module_put(THIS_MODULE); + dev->driver = NULL; + return ret; + +} + +static ssize_t name_show(struct device *dev, struct device_attribute *a, + char *buf) +{ + struct mei_cl_device *cldev = to_mei_cl_device(dev); + size_t len; + + len = snprintf(buf, PAGE_SIZE, "%s", cldev->name); + + return (len >= PAGE_SIZE) ? (PAGE_SIZE - 1) : len; +} +static DEVICE_ATTR_RO(name); + +static ssize_t uuid_show(struct device *dev, struct device_attribute *a, + char *buf) +{ + struct mei_cl_device *cldev = to_mei_cl_device(dev); + const uuid_le *uuid = mei_me_cl_uuid(cldev->me_cl); + size_t len; + + len = snprintf(buf, PAGE_SIZE, "%pUl", uuid); + + return (len >= PAGE_SIZE) ? (PAGE_SIZE - 1) : len; +} +static DEVICE_ATTR_RO(uuid); + +static ssize_t modalias_show(struct device *dev, struct device_attribute *a, + char *buf) +{ + struct mei_cl_device *cldev = to_mei_cl_device(dev); + const uuid_le *uuid = mei_me_cl_uuid(cldev->me_cl); + size_t len; + + len = snprintf(buf, PAGE_SIZE, "mei:%s:" MEI_CL_UUID_FMT ":", + cldev->name, MEI_CL_UUID_ARGS(uuid->b)); + + return (len >= PAGE_SIZE) ? (PAGE_SIZE - 1) : len; +} +static DEVICE_ATTR_RO(modalias); + +static struct attribute *mei_cl_dev_attrs[] = { + &dev_attr_name.attr, + &dev_attr_uuid.attr, + &dev_attr_modalias.attr, + NULL, +}; +ATTRIBUTE_GROUPS(mei_cl_dev); + +/** + * mei_cl_device_uevent - me client bus uevent handler + * + * @dev: device + * @env: uevent kobject + * + * Return: 0 on success -ENOMEM on when add_uevent_var fails + */ +static int mei_cl_device_uevent(struct device *dev, struct kobj_uevent_env *env) +{ + struct mei_cl_device *cldev = to_mei_cl_device(dev); + const uuid_le *uuid = mei_me_cl_uuid(cldev->me_cl); + + if (add_uevent_var(env, "MEI_CL_UUID=%pUl", uuid)) + return -ENOMEM; + + if (add_uevent_var(env, "MEI_CL_NAME=%s", cldev->name)) + return -ENOMEM; + + if (add_uevent_var(env, "MODALIAS=mei:%s:" MEI_CL_UUID_FMT ":", + cldev->name, MEI_CL_UUID_ARGS(uuid->b))) + return -ENOMEM; - if (!device || !device->event_cb) + return 0; +} + +static struct bus_type mei_cl_bus_type = { + .name = "mei", + .dev_groups = mei_cl_dev_groups, + .match = mei_cl_device_match, + .probe = mei_cl_device_probe, + .remove = mei_cl_device_remove, + .uevent = mei_cl_device_uevent, +}; + +static struct mei_device *mei_dev_bus_get(struct mei_device *bus) +{ + if (bus) + get_device(bus->dev); + + return bus; +} + +static void mei_dev_bus_put(struct mei_device *bus) +{ + if (bus) + put_device(bus->dev); +} + +static void mei_cl_dev_release(struct device *dev) +{ + struct mei_cl_device *cldev = to_mei_cl_device(dev); + + if (!cldev) + return; + + mei_me_cl_put(cldev->me_cl); + mei_dev_bus_put(cldev->bus); + kfree(cldev); +} + +static struct device_type mei_cl_device_type = { + .release = mei_cl_dev_release, +}; + +/** + * mei_cl_dev_alloc - initialize and allocate mei client device + * + * @bus: mei device + * @me_cl: me client + * + * Return: allocated device structur or NULL on allocation failure + */ +static struct mei_cl_device *mei_cl_dev_alloc(struct mei_device *bus, + struct mei_me_client *me_cl) +{ + struct mei_cl_device *cldev; + + cldev = kzalloc(sizeof(struct mei_cl_device), GFP_KERNEL); + if (!cldev) + return NULL; + + device_initialize(&cldev->dev); + cldev->dev.parent = bus->dev; + cldev->dev.bus = &mei_cl_bus_type; + cldev->dev.type = &mei_cl_device_type; + cldev->bus = mei_dev_bus_get(bus); + cldev->me_cl = mei_me_cl_get(me_cl); + cldev->is_added = 0; + INIT_LIST_HEAD(&cldev->bus_list); + + return cldev; +} + +/** + * mei_cl_dev_setup - setup me client device + * run fix up routines and set the device name + * + * @bus: mei device + * @cldev: me client device + * + * Return: true if the device is eligible for enumeration + */ +static bool mei_cl_dev_setup(struct mei_device *bus, + struct mei_cl_device *cldev) +{ + cldev->do_match = 1; + mei_cl_dev_fixup(cldev); + + if (cldev->do_match) + dev_set_name(&cldev->dev, "mei:%s:%pUl", + cldev->name, mei_me_cl_uuid(cldev->me_cl)); + + return cldev->do_match == 1; +} + +/** + * mei_cl_bus_dev_add - add me client devices + * + * @cldev: me client device + * + * Return: 0 on success; < 0 on failre + */ +static int mei_cl_bus_dev_add(struct mei_cl_device *cldev) +{ + int ret; + + dev_dbg(cldev->bus->dev, "adding %pUL\n", mei_me_cl_uuid(cldev->me_cl)); + ret = device_add(&cldev->dev); + if (!ret) + cldev->is_added = 1; + + return ret; +} + +/** + * mei_cl_bus_dev_stop - stop the driver + * + * @cldev: me client device + */ +static void mei_cl_bus_dev_stop(struct mei_cl_device *cldev) +{ + if (cldev->is_added) + device_release_driver(&cldev->dev); +} + +/** + * mei_cl_bus_dev_destroy - destroy me client devices object + * + * @cldev: me client device + */ +static void mei_cl_bus_dev_destroy(struct mei_cl_device *cldev) +{ + if (!cldev->is_added) + return; + + device_del(&cldev->dev); + + mutex_lock(&cldev->bus->cl_bus_lock); + list_del_init(&cldev->bus_list); + mutex_unlock(&cldev->bus->cl_bus_lock); + + cldev->is_added = 0; + put_device(&cldev->dev); +} + +/** + * mei_cl_bus_remove_device - remove a devices form the bus + * + * @cldev: me client device + */ +static void mei_cl_bus_remove_device(struct mei_cl_device *cldev) +{ + mei_cl_bus_dev_stop(cldev); + mei_cl_bus_dev_destroy(cldev); +} + +/** + * mei_cl_bus_remove_devices - remove all devices form the bus + * + * @bus: mei device + */ +void mei_cl_bus_remove_devices(struct mei_device *bus) +{ + struct mei_cl_device *cldev, *next; + + list_for_each_entry_safe(cldev, next, &bus->device_list, bus_list) + mei_cl_bus_remove_device(cldev); +} + + +/** + * mei_cl_dev_init - allocate and initializes an mei client devices + * based on me client + * + * @bus: mei device + * @me_cl: me client + */ +static void mei_cl_dev_init(struct mei_device *bus, struct mei_me_client *me_cl) +{ + struct mei_cl_device *cldev; + + dev_dbg(bus->dev, "initializing %pUl", mei_me_cl_uuid(me_cl)); + + if (me_cl->bus_added) return; - set_bit(MEI_CL_EVENT_RX, &device->events); + cldev = mei_cl_dev_alloc(bus, me_cl); + if (!cldev) + return; + + mutex_lock(&cldev->bus->cl_bus_lock); + me_cl->bus_added = true; + list_add_tail(&cldev->bus_list, &bus->device_list); + mutex_unlock(&cldev->bus->cl_bus_lock); + +} + +/** + * mei_cl_bus_rescan - scan me clients list and add create + * devices for eligible clients + * + * @bus: mei device + */ +void mei_cl_bus_rescan(struct mei_device *bus) +{ + struct mei_cl_device *cldev, *n; + struct mei_me_client *me_cl; + + down_read(&bus->me_clients_rwsem); + list_for_each_entry(me_cl, &bus->me_clients, list) + mei_cl_dev_init(bus, me_cl); + up_read(&bus->me_clients_rwsem); + + mutex_lock(&bus->cl_bus_lock); + list_for_each_entry_safe(cldev, n, &bus->device_list, bus_list) { + + if (!mei_me_cl_is_active(cldev->me_cl)) { + mei_cl_bus_remove_device(cldev); + continue; + } + + if (cldev->is_added) + continue; + + if (mei_cl_dev_setup(bus, cldev)) + mei_cl_bus_dev_add(cldev); + else { + list_del_init(&cldev->bus_list); + put_device(&cldev->dev); + } + } + mutex_unlock(&bus->cl_bus_lock); + + dev_dbg(bus->dev, "rescan end"); +} + +int __mei_cl_driver_register(struct mei_cl_driver *cldrv, struct module *owner) +{ + int err; + + cldrv->driver.name = cldrv->name; + cldrv->driver.owner = owner; + cldrv->driver.bus = &mei_cl_bus_type; + + err = driver_register(&cldrv->driver); + if (err) + return err; - schedule_work(&device->event_work); + pr_debug("mei: driver [%s] registered\n", cldrv->driver.name); + + return 0; } +EXPORT_SYMBOL_GPL(__mei_cl_driver_register); + +void mei_cl_driver_unregister(struct mei_cl_driver *cldrv) +{ + driver_unregister(&cldrv->driver); + + pr_debug("mei: driver [%s] unregistered\n", cldrv->driver.name); +} +EXPORT_SYMBOL_GPL(mei_cl_driver_unregister); + int __init mei_cl_bus_init(void) { diff --git a/drivers/misc/mei/client.c b/drivers/misc/mei/client.c index 6decbe136ea7..a6c87c713193 100644 --- a/drivers/misc/mei/client.c +++ b/drivers/misc/mei/client.c @@ -555,10 +555,10 @@ void mei_cl_init(struct mei_cl *cl, struct mei_device *dev) init_waitqueue_head(&cl->wait); init_waitqueue_head(&cl->rx_wait); init_waitqueue_head(&cl->tx_wait); + init_waitqueue_head(&cl->ev_wait); INIT_LIST_HEAD(&cl->rd_completed); INIT_LIST_HEAD(&cl->rd_pending); INIT_LIST_HEAD(&cl->link); - INIT_LIST_HEAD(&cl->device_link); cl->writing_state = MEI_IDLE; cl->state = MEI_FILE_INITIALIZING; cl->dev = dev; @@ -690,16 +690,12 @@ void mei_host_client_init(struct work_struct *work) mei_wd_host_init(dev, me_cl); mei_me_cl_put(me_cl); - me_cl = mei_me_cl_by_uuid(dev, &mei_nfc_guid); - if (me_cl) - mei_nfc_host_init(dev, me_cl); - mei_me_cl_put(me_cl); - - dev->dev_state = MEI_DEV_ENABLED; dev->reset_count = 0; mutex_unlock(&dev->device_lock); + mei_cl_bus_rescan(dev); + pm_runtime_mark_last_busy(dev->dev); dev_dbg(dev->dev, "rpm: autosuspend\n"); pm_runtime_autosuspend(dev->dev); @@ -841,45 +837,22 @@ int mei_cl_irq_disconnect(struct mei_cl *cl, struct mei_cl_cb *cb, return ret; } - - /** - * mei_cl_disconnect - disconnect host client from the me one + * __mei_cl_disconnect - disconnect host client from the me one + * internal function runtime pm has to be already acquired * * @cl: host client * - * Locking: called under "dev->device_lock" lock - * * Return: 0 on success, <0 on failure. */ -int mei_cl_disconnect(struct mei_cl *cl) +static int __mei_cl_disconnect(struct mei_cl *cl) { struct mei_device *dev; struct mei_cl_cb *cb; int rets; - if (WARN_ON(!cl || !cl->dev)) - return -ENODEV; - dev = cl->dev; - cl_dbg(dev, cl, "disconnecting"); - - if (!mei_cl_is_connected(cl)) - return 0; - - if (mei_cl_is_fixed_address(cl)) { - mei_cl_set_disconnected(cl); - return 0; - } - - rets = pm_runtime_get(dev->dev); - if (rets < 0 && rets != -EINPROGRESS) { - pm_runtime_put_noidle(dev->dev); - cl_err(dev, cl, "rpm: get failed %d\n", rets); - return rets; - } - cl->state = MEI_FILE_DISCONNECTING; cb = mei_io_cb_init(cl, MEI_FOP_DISCONNECT, NULL); @@ -915,11 +888,52 @@ out: if (!rets) cl_dbg(dev, cl, "successfully disconnected from FW client.\n"); + mei_io_cb_free(cb); + return rets; +} + +/** + * mei_cl_disconnect - disconnect host client from the me one + * + * @cl: host client + * + * Locking: called under "dev->device_lock" lock + * + * Return: 0 on success, <0 on failure. + */ +int mei_cl_disconnect(struct mei_cl *cl) +{ + struct mei_device *dev; + int rets; + + if (WARN_ON(!cl || !cl->dev)) + return -ENODEV; + + dev = cl->dev; + + cl_dbg(dev, cl, "disconnecting"); + + if (!mei_cl_is_connected(cl)) + return 0; + + if (mei_cl_is_fixed_address(cl)) { + mei_cl_set_disconnected(cl); + return 0; + } + + rets = pm_runtime_get(dev->dev); + if (rets < 0 && rets != -EINPROGRESS) { + pm_runtime_put_noidle(dev->dev); + cl_err(dev, cl, "rpm: get failed %d\n", rets); + return rets; + } + + rets = __mei_cl_disconnect(cl); + cl_dbg(dev, cl, "rpm: autosuspend\n"); pm_runtime_mark_last_busy(dev->dev); pm_runtime_put_autosuspend(dev->dev); - mei_io_cb_free(cb); return rets; } @@ -1064,11 +1078,23 @@ int mei_cl_connect(struct mei_cl *cl, struct mei_me_client *me_cl, mutex_unlock(&dev->device_lock); wait_event_timeout(cl->wait, (cl->state == MEI_FILE_CONNECTED || + cl->state == MEI_FILE_DISCONNECT_REQUIRED || cl->state == MEI_FILE_DISCONNECT_REPLY), mei_secs_to_jiffies(MEI_CL_CONNECT_TIMEOUT)); mutex_lock(&dev->device_lock); if (!mei_cl_is_connected(cl)) { + if (cl->state == MEI_FILE_DISCONNECT_REQUIRED) { + mei_io_list_flush(&dev->ctrl_rd_list, cl); + mei_io_list_flush(&dev->ctrl_wr_list, cl); + /* ignore disconnect return valuue; + * in case of failure reset will be invoked + */ + __mei_cl_disconnect(cl); + rets = -EFAULT; + goto out; + } + /* timeout or something went really wrong */ if (!cl->status) cl->status = -EFAULT; @@ -1181,6 +1207,221 @@ int mei_cl_flow_ctrl_reduce(struct mei_cl *cl) } /** + * mei_cl_notify_fop2req - convert fop to proper request + * + * @fop: client notification start response command + * + * Return: MEI_HBM_NOTIFICATION_START/STOP + */ +u8 mei_cl_notify_fop2req(enum mei_cb_file_ops fop) +{ + if (fop == MEI_FOP_NOTIFY_START) + return MEI_HBM_NOTIFICATION_START; + else + return MEI_HBM_NOTIFICATION_STOP; +} + +/** + * mei_cl_notify_req2fop - convert notification request top file operation type + * + * @req: hbm notification request type + * + * Return: MEI_FOP_NOTIFY_START/STOP + */ +enum mei_cb_file_ops mei_cl_notify_req2fop(u8 req) +{ + if (req == MEI_HBM_NOTIFICATION_START) + return MEI_FOP_NOTIFY_START; + else + return MEI_FOP_NOTIFY_STOP; +} + +/** + * mei_cl_irq_notify - send notification request in irq_thread context + * + * @cl: client + * @cb: callback block. + * @cmpl_list: complete list. + * + * Return: 0 on such and error otherwise. + */ +int mei_cl_irq_notify(struct mei_cl *cl, struct mei_cl_cb *cb, + struct mei_cl_cb *cmpl_list) +{ + struct mei_device *dev = cl->dev; + u32 msg_slots; + int slots; + int ret; + bool request; + + msg_slots = mei_data2slots(sizeof(struct hbm_client_connect_request)); + slots = mei_hbuf_empty_slots(dev); + + if (slots < msg_slots) + return -EMSGSIZE; + + request = mei_cl_notify_fop2req(cb->fop_type); + ret = mei_hbm_cl_notify_req(dev, cl, request); + if (ret) { + cl->status = ret; + list_move_tail(&cb->list, &cmpl_list->list); + return ret; + } + + list_move_tail(&cb->list, &dev->ctrl_rd_list.list); + return 0; +} + +/** + * mei_cl_notify_request - send notification stop/start request + * + * @cl: host client + * @file: associate request with file + * @request: 1 for start or 0 for stop + * + * Locking: called under "dev->device_lock" lock + * + * Return: 0 on such and error otherwise. + */ +int mei_cl_notify_request(struct mei_cl *cl, struct file *file, u8 request) +{ + struct mei_device *dev; + struct mei_cl_cb *cb; + enum mei_cb_file_ops fop_type; + int rets; + + if (WARN_ON(!cl || !cl->dev)) + return -ENODEV; + + dev = cl->dev; + + if (!dev->hbm_f_ev_supported) { + cl_dbg(dev, cl, "notifications not supported\n"); + return -EOPNOTSUPP; + } + + rets = pm_runtime_get(dev->dev); + if (rets < 0 && rets != -EINPROGRESS) { + pm_runtime_put_noidle(dev->dev); + cl_err(dev, cl, "rpm: get failed %d\n", rets); + return rets; + } + + fop_type = mei_cl_notify_req2fop(request); + cb = mei_io_cb_init(cl, fop_type, file); + if (!cb) { + rets = -ENOMEM; + goto out; + } + + if (mei_hbuf_acquire(dev)) { + if (mei_hbm_cl_notify_req(dev, cl, request)) { + rets = -ENODEV; + goto out; + } + list_add_tail(&cb->list, &dev->ctrl_rd_list.list); + } else { + list_add_tail(&cb->list, &dev->ctrl_wr_list.list); + } + + mutex_unlock(&dev->device_lock); + wait_event_timeout(cl->wait, cl->notify_en == request, + mei_secs_to_jiffies(MEI_CL_CONNECT_TIMEOUT)); + mutex_lock(&dev->device_lock); + + if (cl->notify_en != request) { + mei_io_list_flush(&dev->ctrl_rd_list, cl); + mei_io_list_flush(&dev->ctrl_wr_list, cl); + if (!cl->status) + cl->status = -EFAULT; + } + + rets = cl->status; + +out: + cl_dbg(dev, cl, "rpm: autosuspend\n"); + pm_runtime_mark_last_busy(dev->dev); + pm_runtime_put_autosuspend(dev->dev); + + mei_io_cb_free(cb); + return rets; +} + +/** + * mei_cl_notify - raise notification + * + * @cl: host client + * + * Locking: called under "dev->device_lock" lock + */ +void mei_cl_notify(struct mei_cl *cl) +{ + struct mei_device *dev; + + if (!cl || !cl->dev) + return; + + dev = cl->dev; + + if (!cl->notify_en) + return; + + cl_dbg(dev, cl, "notify event"); + cl->notify_ev = true; + wake_up_interruptible_all(&cl->ev_wait); + + if (cl->ev_async) + kill_fasync(&cl->ev_async, SIGIO, POLL_PRI); + + mei_cl_bus_notify_event(cl); +} + +/** + * mei_cl_notify_get - get or wait for notification event + * + * @cl: host client + * @block: this request is blocking + * @notify_ev: true if notification event was received + * + * Locking: called under "dev->device_lock" lock + * + * Return: 0 on such and error otherwise. + */ +int mei_cl_notify_get(struct mei_cl *cl, bool block, bool *notify_ev) +{ + struct mei_device *dev; + int rets; + + *notify_ev = false; + + if (WARN_ON(!cl || !cl->dev)) + return -ENODEV; + + dev = cl->dev; + + if (!mei_cl_is_connected(cl)) + return -ENODEV; + + if (cl->notify_ev) + goto out; + + if (!block) + return -EAGAIN; + + mutex_unlock(&dev->device_lock); + rets = wait_event_interruptible(cl->ev_wait, cl->notify_ev); + mutex_lock(&dev->device_lock); + + if (rets < 0) + return rets; + +out: + *notify_ev = cl->notify_ev; + cl->notify_ev = false; + return 0; +} + +/** * mei_cl_read_start - the start read client message function. * * @cl: host client @@ -1356,6 +1597,7 @@ int mei_cl_write(struct mei_cl *cl, struct mei_cl_cb *cb, bool blocking) struct mei_device *dev; struct mei_msg_data *buf; struct mei_msg_hdr mei_hdr; + int size; int rets; @@ -1367,10 +1609,10 @@ int mei_cl_write(struct mei_cl *cl, struct mei_cl_cb *cb, bool blocking) dev = cl->dev; - buf = &cb->buf; + size = buf->size; - cl_dbg(dev, cl, "size=%d\n", buf->size); + cl_dbg(dev, cl, "size=%d\n", size); rets = pm_runtime_get(dev->dev); if (rets < 0 && rets != -EINPROGRESS) { @@ -1394,21 +1636,21 @@ int mei_cl_write(struct mei_cl *cl, struct mei_cl_cb *cb, bool blocking) if (rets == 0) { cl_dbg(dev, cl, "No flow control credentials: not sending.\n"); - rets = buf->size; + rets = size; goto out; } if (!mei_hbuf_acquire(dev)) { cl_dbg(dev, cl, "Cannot acquire the host buffer: not sending.\n"); - rets = buf->size; + rets = size; goto out; } /* Check for a maximum length */ - if (buf->size > mei_hbuf_max_len(dev)) { + if (size > mei_hbuf_max_len(dev)) { mei_hdr.length = mei_hbuf_max_len(dev); mei_hdr.msg_complete = 0; } else { - mei_hdr.length = buf->size; + mei_hdr.length = size; mei_hdr.msg_complete = 1; } @@ -1430,6 +1672,7 @@ out: else list_add_tail(&cb->list, &dev->write_list.list); + cb = NULL; if (blocking && cl->writing_state != MEI_WRITE_COMPLETE) { mutex_unlock(&dev->device_lock); @@ -1444,7 +1687,7 @@ out: } } - rets = buf->size; + rets = size; err: cl_dbg(dev, cl, "rpm: autosuspend\n"); pm_runtime_mark_last_busy(dev->dev); @@ -1486,6 +1729,8 @@ void mei_cl_complete(struct mei_cl *cl, struct mei_cl_cb *cb) case MEI_FOP_CONNECT: case MEI_FOP_DISCONNECT: + case MEI_FOP_NOTIFY_STOP: + case MEI_FOP_NOTIFY_START: if (waitqueue_active(&cl->wait)) wake_up(&cl->wait); @@ -1528,6 +1773,12 @@ void mei_cl_all_wakeup(struct mei_device *dev) cl_dbg(dev, cl, "Waking up writing client!\n"); wake_up_interruptible(&cl->tx_wait); } + + /* synchronized under device mutex */ + if (waitqueue_active(&cl->ev_wait)) { + cl_dbg(dev, cl, "Waking up waiting for event clients!\n"); + wake_up_interruptible(&cl->ev_wait); + } } } diff --git a/drivers/misc/mei/client.h b/drivers/misc/mei/client.h index 8d7f057f1045..1c7cad07d731 100644 --- a/drivers/misc/mei/client.h +++ b/drivers/misc/mei/client.h @@ -219,6 +219,14 @@ void mei_cl_complete(struct mei_cl *cl, struct mei_cl_cb *cb); void mei_host_client_init(struct work_struct *work); +u8 mei_cl_notify_fop2req(enum mei_cb_file_ops fop); +enum mei_cb_file_ops mei_cl_notify_req2fop(u8 request); +int mei_cl_notify_request(struct mei_cl *cl, struct file *file, u8 request); +int mei_cl_irq_notify(struct mei_cl *cl, struct mei_cl_cb *cb, + struct mei_cl_cb *cmpl_list); +int mei_cl_notify_get(struct mei_cl *cl, bool block, bool *notify_ev); +void mei_cl_notify(struct mei_cl *cl); + void mei_cl_all_disconnect(struct mei_device *dev); void mei_cl_all_wakeup(struct mei_device *dev); void mei_cl_all_write_clear(struct mei_device *dev); diff --git a/drivers/misc/mei/debugfs.c b/drivers/misc/mei/debugfs.c index eb868341247f..4b469cf9e60f 100644 --- a/drivers/misc/mei/debugfs.c +++ b/drivers/misc/mei/debugfs.c @@ -154,6 +154,12 @@ static ssize_t mei_dbgfs_read_devstate(struct file *fp, char __user *ubuf, pos += scnprintf(buf + pos, bufsz - pos, "hbm features:\n"); pos += scnprintf(buf + pos, bufsz - pos, "\tPG: %01d\n", dev->hbm_f_pg_supported); + pos += scnprintf(buf + pos, bufsz - pos, "\tDC: %01d\n", + dev->hbm_f_dc_supported); + pos += scnprintf(buf + pos, bufsz - pos, "\tDOT: %01d\n", + dev->hbm_f_dot_supported); + pos += scnprintf(buf + pos, bufsz - pos, "\tEV: %01d\n", + dev->hbm_f_ev_supported); } pos += scnprintf(buf + pos, bufsz - pos, "pg: %s, %s\n", diff --git a/drivers/misc/mei/hbm.c b/drivers/misc/mei/hbm.c index a4f283165a33..8eec887c8f70 100644 --- a/drivers/misc/mei/hbm.c +++ b/drivers/misc/mei/hbm.c @@ -52,6 +52,7 @@ static const char *mei_cl_conn_status_str(enum mei_cl_connect_status status) MEI_CL_CS(ALREADY_STARTED); MEI_CL_CS(OUT_OF_RESOURCES); MEI_CL_CS(MESSAGE_SMALL); + MEI_CL_CS(NOT_ALLOWED); default: return "unknown"; } #undef MEI_CL_CCS @@ -89,6 +90,7 @@ static int mei_cl_conn_status_to_errno(enum mei_cl_connect_status status) case MEI_CL_CONN_ALREADY_STARTED: return -EBUSY; case MEI_CL_CONN_OUT_OF_RESOURCES: return -EBUSY; case MEI_CL_CONN_MESSAGE_SMALL: return -EINVAL; + case MEI_CL_CONN_NOT_ALLOWED: return -EBUSY; default: return -EINVAL; } } @@ -299,6 +301,7 @@ static int mei_hbm_enum_clients_req(struct mei_device *dev) enum_req = (struct hbm_host_enum_request *)dev->wr_msg.data; memset(enum_req, 0, len); enum_req->hbm_cmd = HOST_ENUM_REQ_CMD; + enum_req->allow_add = dev->hbm_f_dc_supported; ret = mei_write_message(dev, mei_hdr, dev->wr_msg.data); if (ret) { @@ -344,6 +347,180 @@ static int mei_hbm_me_cl_add(struct mei_device *dev, } /** + * mei_hbm_add_cl_resp - send response to fw on client add request + * + * @dev: the device structure + * @addr: me address + * @status: response status + * + * Return: 0 on success and < 0 on failure + */ +static int mei_hbm_add_cl_resp(struct mei_device *dev, u8 addr, u8 status) +{ + struct mei_msg_hdr *mei_hdr = &dev->wr_msg.hdr; + struct hbm_add_client_response *resp; + const size_t len = sizeof(struct hbm_add_client_response); + int ret; + + dev_dbg(dev->dev, "adding client response\n"); + + resp = (struct hbm_add_client_response *)dev->wr_msg.data; + + mei_hbm_hdr(mei_hdr, len); + memset(resp, 0, sizeof(struct hbm_add_client_response)); + + resp->hbm_cmd = MEI_HBM_ADD_CLIENT_RES_CMD; + resp->me_addr = addr; + resp->status = status; + + ret = mei_write_message(dev, mei_hdr, dev->wr_msg.data); + if (ret) + dev_err(dev->dev, "add client response write failed: ret = %d\n", + ret); + return ret; +} + +/** + * mei_hbm_fw_add_cl_req - request from the fw to add a client + * + * @dev: the device structure + * @req: add client request + * + * Return: 0 on success and < 0 on failure + */ +static int mei_hbm_fw_add_cl_req(struct mei_device *dev, + struct hbm_add_client_request *req) +{ + int ret; + u8 status = MEI_HBMS_SUCCESS; + + BUILD_BUG_ON(sizeof(struct hbm_add_client_request) != + sizeof(struct hbm_props_response)); + + ret = mei_hbm_me_cl_add(dev, (struct hbm_props_response *)req); + if (ret) + status = !MEI_HBMS_SUCCESS; + + return mei_hbm_add_cl_resp(dev, req->me_addr, status); +} + +/** + * mei_hbm_cl_notify_req - send notification request + * + * @dev: the device structure + * @cl: a client to disconnect from + * @start: true for start false for stop + * + * Return: 0 on success and -EIO on write failure + */ +int mei_hbm_cl_notify_req(struct mei_device *dev, + struct mei_cl *cl, u8 start) +{ + + struct mei_msg_hdr *mei_hdr = &dev->wr_msg.hdr; + struct hbm_notification_request *req; + const size_t len = sizeof(struct hbm_notification_request); + int ret; + + mei_hbm_hdr(mei_hdr, len); + mei_hbm_cl_hdr(cl, MEI_HBM_NOTIFY_REQ_CMD, dev->wr_msg.data, len); + + req = (struct hbm_notification_request *)dev->wr_msg.data; + req->start = start; + + ret = mei_write_message(dev, mei_hdr, dev->wr_msg.data); + if (ret) + dev_err(dev->dev, "notify request failed: ret = %d\n", ret); + + return ret; +} + +/** + * notify_res_to_fop - convert notification response to the proper + * notification FOP + * + * @cmd: client notification start response command + * + * Return: MEI_FOP_NOTIFY_START or MEI_FOP_NOTIFY_STOP; + */ +static inline enum mei_cb_file_ops notify_res_to_fop(struct mei_hbm_cl_cmd *cmd) +{ + struct hbm_notification_response *rs = + (struct hbm_notification_response *)cmd; + + return mei_cl_notify_req2fop(rs->start); +} + +/** + * mei_hbm_cl_notify_start_res - update the client state according + * notify start response + * + * @dev: the device structure + * @cl: mei host client + * @cmd: client notification start response command + */ +static void mei_hbm_cl_notify_start_res(struct mei_device *dev, + struct mei_cl *cl, + struct mei_hbm_cl_cmd *cmd) +{ + struct hbm_notification_response *rs = + (struct hbm_notification_response *)cmd; + + cl_dbg(dev, cl, "hbm: notify start response status=%d\n", rs->status); + + if (rs->status == MEI_HBMS_SUCCESS || + rs->status == MEI_HBMS_ALREADY_STARTED) { + cl->notify_en = true; + cl->status = 0; + } else { + cl->status = -EINVAL; + } +} + +/** + * mei_hbm_cl_notify_stop_res - update the client state according + * notify stop response + * + * @dev: the device structure + * @cl: mei host client + * @cmd: client notification stop response command + */ +static void mei_hbm_cl_notify_stop_res(struct mei_device *dev, + struct mei_cl *cl, + struct mei_hbm_cl_cmd *cmd) +{ + struct hbm_notification_response *rs = + (struct hbm_notification_response *)cmd; + + cl_dbg(dev, cl, "hbm: notify stop response status=%d\n", rs->status); + + if (rs->status == MEI_HBMS_SUCCESS || + rs->status == MEI_HBMS_NOT_STARTED) { + cl->notify_en = false; + cl->status = 0; + } else { + /* TODO: spec is not clear yet about other possible issues */ + cl->status = -EINVAL; + } +} + +/** + * mei_hbm_cl_notify - signal notification event + * + * @dev: the device structure + * @cmd: notification client message + */ +static void mei_hbm_cl_notify(struct mei_device *dev, + struct mei_hbm_cl_cmd *cmd) +{ + struct mei_cl *cl; + + cl = mei_hbm_cl_find_by_cmd(dev, cmd); + if (cl) + mei_cl_notify(cl); +} + +/** * mei_hbm_prop_req - request property for a single client * * @dev: the device structure @@ -610,8 +787,11 @@ static void mei_hbm_cl_connect_res(struct mei_device *dev, struct mei_cl *cl, if (rs->status == MEI_CL_CONN_SUCCESS) cl->state = MEI_FILE_CONNECTED; - else + else { cl->state = MEI_FILE_DISCONNECT_REPLY; + if (rs->status == MEI_CL_CONN_NOT_FOUND) + mei_me_cl_del(dev, cl->me_cl); + } cl->status = mei_cl_conn_status_to_errno(rs->status); } @@ -654,6 +834,12 @@ static void mei_hbm_cl_res(struct mei_device *dev, case MEI_FOP_DISCONNECT: mei_hbm_cl_disconnect_res(dev, cl, rs); break; + case MEI_FOP_NOTIFY_START: + mei_hbm_cl_notify_start_res(dev, cl, rs); + break; + case MEI_FOP_NOTIFY_STOP: + mei_hbm_cl_notify_stop_res(dev, cl, rs); + break; default: return; } @@ -694,6 +880,79 @@ static int mei_hbm_fw_disconnect_req(struct mei_device *dev, } /** + * mei_hbm_pg_enter_res - PG enter response received + * + * @dev: the device structure. + * + * Return: 0 on success, -EPROTO on state mismatch + */ +static int mei_hbm_pg_enter_res(struct mei_device *dev) +{ + if (mei_pg_state(dev) != MEI_PG_OFF || + dev->pg_event != MEI_PG_EVENT_WAIT) { + dev_err(dev->dev, "hbm: pg entry response: state mismatch [%s, %d]\n", + mei_pg_state_str(mei_pg_state(dev)), dev->pg_event); + return -EPROTO; + } + + dev->pg_event = MEI_PG_EVENT_RECEIVED; + wake_up(&dev->wait_pg); + + return 0; +} + +/** + * mei_hbm_pg_resume - process with PG resume + * + * @dev: the device structure. + */ +void mei_hbm_pg_resume(struct mei_device *dev) +{ + pm_request_resume(dev->dev); +} +EXPORT_SYMBOL_GPL(mei_hbm_pg_resume); + +/** + * mei_hbm_pg_exit_res - PG exit response received + * + * @dev: the device structure. + * + * Return: 0 on success, -EPROTO on state mismatch + */ +static int mei_hbm_pg_exit_res(struct mei_device *dev) +{ + if (mei_pg_state(dev) != MEI_PG_ON || + (dev->pg_event != MEI_PG_EVENT_WAIT && + dev->pg_event != MEI_PG_EVENT_IDLE)) { + dev_err(dev->dev, "hbm: pg exit response: state mismatch [%s, %d]\n", + mei_pg_state_str(mei_pg_state(dev)), dev->pg_event); + return -EPROTO; + } + + switch (dev->pg_event) { + case MEI_PG_EVENT_WAIT: + dev->pg_event = MEI_PG_EVENT_RECEIVED; + wake_up(&dev->wait_pg); + break; + case MEI_PG_EVENT_IDLE: + /* + * If the driver is not waiting on this then + * this is HW initiated exit from PG. + * Start runtime pm resume sequence to exit from PG. + */ + dev->pg_event = MEI_PG_EVENT_RECEIVED; + mei_hbm_pg_resume(dev); + break; + default: + WARN(1, "hbm: pg exit response: unexpected pg event = %d\n", + dev->pg_event); + return -EPROTO; + } + + return 0; +} + +/** * mei_hbm_config_features - check what hbm features and commands * are supported by the fw * @@ -709,6 +968,17 @@ static void mei_hbm_config_features(struct mei_device *dev) if (dev->version.major_version == HBM_MAJOR_VERSION_PGI && dev->version.minor_version >= HBM_MINOR_VERSION_PGI) dev->hbm_f_pg_supported = 1; + + if (dev->version.major_version >= HBM_MAJOR_VERSION_DC) + dev->hbm_f_dc_supported = 1; + + /* disconnect on connect timeout instead of link reset */ + if (dev->version.major_version >= HBM_MAJOR_VERSION_DOT) + dev->hbm_f_dot_supported = 1; + + /* Notification Event Support */ + if (dev->version.major_version >= HBM_MAJOR_VERSION_EV) + dev->hbm_f_ev_supported = 1; } /** @@ -740,6 +1010,8 @@ int mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr) struct hbm_host_version_response *version_res; struct hbm_props_response *props_res; struct hbm_host_enum_response *enum_res; + struct hbm_add_client_request *add_cl_req; + int ret; struct mei_hbm_cl_cmd *cl_cmd; struct hbm_client_connect_request *disconnect_req; @@ -828,24 +1100,17 @@ int mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr) break; case MEI_PG_ISOLATION_ENTRY_RES_CMD: - dev_dbg(dev->dev, "power gate isolation entry response received\n"); - dev->pg_event = MEI_PG_EVENT_RECEIVED; - if (waitqueue_active(&dev->wait_pg)) - wake_up(&dev->wait_pg); + dev_dbg(dev->dev, "hbm: power gate isolation entry response received\n"); + ret = mei_hbm_pg_enter_res(dev); + if (ret) + return ret; break; case MEI_PG_ISOLATION_EXIT_REQ_CMD: - dev_dbg(dev->dev, "power gate isolation exit request received\n"); - dev->pg_event = MEI_PG_EVENT_RECEIVED; - if (waitqueue_active(&dev->wait_pg)) - wake_up(&dev->wait_pg); - else - /* - * If the driver is not waiting on this then - * this is HW initiated exit from PG. - * Start runtime pm resume sequence to exit from PG. - */ - pm_request_resume(dev->dev); + dev_dbg(dev->dev, "hbm: power gate isolation exit request received\n"); + ret = mei_hbm_pg_exit_res(dev); + if (ret) + return ret; break; case HOST_CLIENT_PROPERTIES_RES_CMD: @@ -937,6 +1202,39 @@ int mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr) return -EIO; } break; + + case MEI_HBM_ADD_CLIENT_REQ_CMD: + dev_dbg(dev->dev, "hbm: add client request received\n"); + /* + * after the host receives the enum_resp + * message clients may be added or removed + */ + if (dev->hbm_state <= MEI_HBM_ENUM_CLIENTS && + dev->hbm_state >= MEI_HBM_STOPPED) { + dev_err(dev->dev, "hbm: add client: state mismatch, [%d, %d]\n", + dev->dev_state, dev->hbm_state); + return -EPROTO; + } + add_cl_req = (struct hbm_add_client_request *)mei_msg; + ret = mei_hbm_fw_add_cl_req(dev, add_cl_req); + if (ret) { + dev_err(dev->dev, "hbm: add client: failed to send response %d\n", + ret); + return -EIO; + } + dev_dbg(dev->dev, "hbm: add client request processed\n"); + break; + + case MEI_HBM_NOTIFY_RES_CMD: + dev_dbg(dev->dev, "hbm: notify response received\n"); + mei_hbm_cl_res(dev, cl_cmd, notify_res_to_fop(cl_cmd)); + break; + + case MEI_HBM_NOTIFICATION_CMD: + dev_dbg(dev->dev, "hbm: notification\n"); + mei_hbm_cl_notify(dev, cl_cmd); + break; + default: BUG(); break; diff --git a/drivers/misc/mei/hbm.h b/drivers/misc/mei/hbm.h index 2544db7d1649..a2025a5083a3 100644 --- a/drivers/misc/mei/hbm.h +++ b/drivers/misc/mei/hbm.h @@ -54,6 +54,9 @@ int mei_hbm_cl_disconnect_rsp(struct mei_device *dev, struct mei_cl *cl); int mei_hbm_cl_connect_req(struct mei_device *dev, struct mei_cl *cl); bool mei_hbm_version_is_supported(struct mei_device *dev); int mei_hbm_pg(struct mei_device *dev, u8 pg_cmd); +void mei_hbm_pg_resume(struct mei_device *dev); +int mei_hbm_cl_notify_req(struct mei_device *dev, + struct mei_cl *cl, u8 request); #endif /* _MEI_HBM_H_ */ diff --git a/drivers/misc/mei/hw-me-regs.h b/drivers/misc/mei/hw-me-regs.h index 9eb7ed70ace2..a8a68acd3267 100644 --- a/drivers/misc/mei/hw-me-regs.h +++ b/drivers/misc/mei/hw-me-regs.h @@ -117,12 +117,17 @@ #define MEI_DEV_ID_WPT_LP 0x9CBA /* Wildcat Point LP */ #define MEI_DEV_ID_WPT_LP_2 0x9CBB /* Wildcat Point LP 2 */ +#define MEI_DEV_ID_SPT 0x9D3A /* Sunrise Point */ +#define MEI_DEV_ID_SPT_2 0x9D3B /* Sunrise Point 2 */ +#define MEI_DEV_ID_SPT_H 0xA13A /* Sunrise Point H */ +#define MEI_DEV_ID_SPT_H_2 0xA13B /* Sunrise Point H 2 */ /* * MEI HW Section */ /* Host Firmware Status Registers in PCI Config Space */ #define PCI_CFG_HFS_1 0x40 +# define PCI_CFG_HFS_1_D0I3_MSK 0x80000000 #define PCI_CFG_HFS_2 0x48 #define PCI_CFG_HFS_3 0x60 #define PCI_CFG_HFS_4 0x64 @@ -140,7 +145,8 @@ #define ME_CSR_HA 0xC /* H_HGC_CSR - PGI register */ #define H_HPG_CSR 0x10 - +/* H_D0I3C - D0I3 Control */ +#define H_D0I3C 0x800 /* register bits of H_CSR (Host Control Status register) */ /* Host Circular Buffer Depth - maximum number of 32-bit entries in CB */ @@ -159,7 +165,14 @@ #define H_IS 0x00000002 /* Host Interrupt Enable */ #define H_IE 0x00000001 +/* Host D0I3 Interrupt Enable */ +#define H_D0I3C_IE 0x00000020 +/* Host D0I3 Interrupt Status */ +#define H_D0I3C_IS 0x00000040 +/* H_CSR masks */ +#define H_CSR_IE_MASK (H_IE | H_D0I3C_IE) +#define H_CSR_IS_MASK (H_IS | H_D0I3C_IS) /* register bits of ME_CSR_HA (ME Control Status Host Access register) */ /* ME CB (Circular Buffer) Depth HRA (Host Read Access) - host read only @@ -183,8 +196,14 @@ access to ME_CBD */ #define ME_IE_HRA 0x00000001 -/* register bits - H_HPG_CSR */ -#define H_HPG_CSR_PGIHEXR 0x00000001 -#define H_HPG_CSR_PGI 0x00000002 +/* H_HPG_CSR register bits */ +#define H_HPG_CSR_PGIHEXR 0x00000001 +#define H_HPG_CSR_PGI 0x00000002 + +/* H_D0I3C register bits */ +#define H_D0I3C_CIP 0x00000001 +#define H_D0I3C_IR 0x00000002 +#define H_D0I3C_I3 0x00000004 +#define H_D0I3C_RR 0x00000008 #endif /* _MEI_HW_MEI_REGS_H_ */ diff --git a/drivers/misc/mei/hw-me.c b/drivers/misc/mei/hw-me.c index 43d7101ff993..65511d39d89b 100644 --- a/drivers/misc/mei/hw-me.c +++ b/drivers/misc/mei/hw-me.c @@ -134,11 +134,40 @@ static inline void mei_hcsr_write(struct mei_device *dev, u32 reg) */ static inline void mei_hcsr_set(struct mei_device *dev, u32 reg) { - reg &= ~H_IS; + reg &= ~H_CSR_IS_MASK; mei_hcsr_write(dev, reg); } /** + * mei_me_d0i3c_read - Reads 32bit data from the D0I3C register + * + * @dev: the device structure + * + * Return: H_D0I3C register value (u32) + */ +static inline u32 mei_me_d0i3c_read(const struct mei_device *dev) +{ + u32 reg; + + reg = mei_me_reg_read(to_me_hw(dev), H_D0I3C); + trace_mei_reg_read(dev->dev, "H_D0I3C", H_CSR, reg); + + return reg; +} + +/** + * mei_me_d0i3c_write - writes H_D0I3C register to device + * + * @dev: the device structure + * @reg: new register value + */ +static inline void mei_me_d0i3c_write(struct mei_device *dev, u32 reg) +{ + trace_mei_reg_write(dev->dev, "H_D0I3C", H_CSR, reg); + mei_me_reg_write(to_me_hw(dev), H_D0I3C, reg); +} + +/** * mei_me_fw_status - read fw status register from pci config space * * @dev: mei device @@ -176,12 +205,25 @@ static int mei_me_fw_status(struct mei_device *dev, */ static void mei_me_hw_config(struct mei_device *dev) { + struct pci_dev *pdev = to_pci_dev(dev->dev); struct mei_me_hw *hw = to_me_hw(dev); - u32 hcsr = mei_hcsr_read(dev); + u32 hcsr, reg; + /* Doesn't change in runtime */ + hcsr = mei_hcsr_read(dev); dev->hbuf_depth = (hcsr & H_CBD) >> 24; + reg = 0; + pci_read_config_dword(pdev, PCI_CFG_HFS_1, ®); + hw->d0i3_supported = + ((reg & PCI_CFG_HFS_1_D0I3_MSK) == PCI_CFG_HFS_1_D0I3_MSK); + hw->pg_state = MEI_PG_OFF; + if (hw->d0i3_supported) { + reg = mei_me_d0i3c_read(dev); + if (reg & H_D0I3C_I3) + hw->pg_state = MEI_PG_ON; + } } /** @@ -208,7 +250,7 @@ static void mei_me_intr_clear(struct mei_device *dev) { u32 hcsr = mei_hcsr_read(dev); - if ((hcsr & H_IS) == H_IS) + if (hcsr & H_CSR_IS_MASK) mei_hcsr_write(dev, hcsr); } /** @@ -220,7 +262,7 @@ static void mei_me_intr_enable(struct mei_device *dev) { u32 hcsr = mei_hcsr_read(dev); - hcsr |= H_IE; + hcsr |= H_CSR_IE_MASK; mei_hcsr_set(dev, hcsr); } @@ -233,7 +275,7 @@ static void mei_me_intr_disable(struct mei_device *dev) { u32 hcsr = mei_hcsr_read(dev); - hcsr &= ~H_IE; + hcsr &= ~H_CSR_IE_MASK; mei_hcsr_set(dev, hcsr); } @@ -253,57 +295,6 @@ static void mei_me_hw_reset_release(struct mei_device *dev) /* complete this write before we set host ready on another CPU */ mmiowb(); } -/** - * mei_me_hw_reset - resets fw via mei csr register. - * - * @dev: the device structure - * @intr_enable: if interrupt should be enabled after reset. - * - * Return: always 0 - */ -static int mei_me_hw_reset(struct mei_device *dev, bool intr_enable) -{ - u32 hcsr = mei_hcsr_read(dev); - - /* H_RST may be found lit before reset is started, - * for example if preceding reset flow hasn't completed. - * In that case asserting H_RST will be ignored, therefore - * we need to clean H_RST bit to start a successful reset sequence. - */ - if ((hcsr & H_RST) == H_RST) { - dev_warn(dev->dev, "H_RST is set = 0x%08X", hcsr); - hcsr &= ~H_RST; - mei_hcsr_set(dev, hcsr); - hcsr = mei_hcsr_read(dev); - } - - hcsr |= H_RST | H_IG | H_IS; - - if (intr_enable) - hcsr |= H_IE; - else - hcsr &= ~H_IE; - - dev->recvd_hw_ready = false; - mei_hcsr_write(dev, hcsr); - - /* - * Host reads the H_CSR once to ensure that the - * posted write to H_CSR completes. - */ - hcsr = mei_hcsr_read(dev); - - if ((hcsr & H_RST) == 0) - dev_warn(dev->dev, "H_RST is not set = 0x%08X", hcsr); - - if ((hcsr & H_RDY) == H_RDY) - dev_warn(dev->dev, "H_RDY is not cleared 0x%08X", hcsr); - - if (intr_enable == false) - mei_me_hw_reset_release(dev); - - return 0; -} /** * mei_me_host_set_ready - enable device @@ -314,7 +305,7 @@ static void mei_me_host_set_ready(struct mei_device *dev) { u32 hcsr = mei_hcsr_read(dev); - hcsr |= H_IE | H_IG | H_RDY; + hcsr |= H_CSR_IE_MASK | H_IG | H_RDY; mei_hcsr_set(dev, hcsr); } @@ -601,13 +592,13 @@ static void mei_me_pg_unset(struct mei_device *dev) } /** - * mei_me_pg_enter_sync - perform pg entry procedure + * mei_me_pg_legacy_enter_sync - perform legacy pg entry procedure * * @dev: the device structure * * Return: 0 on success an error code otherwise */ -int mei_me_pg_enter_sync(struct mei_device *dev) +static int mei_me_pg_legacy_enter_sync(struct mei_device *dev) { struct mei_me_hw *hw = to_me_hw(dev); unsigned long timeout = mei_secs_to_jiffies(MEI_PGI_TIMEOUT); @@ -638,13 +629,13 @@ int mei_me_pg_enter_sync(struct mei_device *dev) } /** - * mei_me_pg_exit_sync - perform pg exit procedure + * mei_me_pg_legacy_exit_sync - perform legacy pg exit procedure * * @dev: the device structure * * Return: 0 on success an error code otherwise */ -int mei_me_pg_exit_sync(struct mei_device *dev) +static int mei_me_pg_legacy_exit_sync(struct mei_device *dev) { struct mei_me_hw *hw = to_me_hw(dev); unsigned long timeout = mei_secs_to_jiffies(MEI_PGI_TIMEOUT); @@ -712,8 +703,12 @@ static bool mei_me_pg_in_transition(struct mei_device *dev) */ static bool mei_me_pg_is_enabled(struct mei_device *dev) { + struct mei_me_hw *hw = to_me_hw(dev); u32 reg = mei_me_mecsr_read(dev); + if (hw->d0i3_supported) + return true; + if ((reg & ME_PGIC_HRA) == 0) goto notsupported; @@ -723,7 +718,8 @@ static bool mei_me_pg_is_enabled(struct mei_device *dev) return true; notsupported: - dev_dbg(dev->dev, "pg: not supported: HGP = %d hbm version %d.%d ?= %d.%d\n", + dev_dbg(dev->dev, "pg: not supported: d0i3 = %d HGP = %d hbm version %d.%d ?= %d.%d\n", + hw->d0i3_supported, !!(reg & ME_PGIC_HRA), dev->version.major_version, dev->version.minor_version, @@ -734,11 +730,211 @@ notsupported: } /** - * mei_me_pg_intr - perform pg processing in interrupt thread handler + * mei_me_d0i3_set - write d0i3 register bit on mei device. * * @dev: the device structure + * @intr: ask for interrupt + * + * Return: D0I3C register value */ -static void mei_me_pg_intr(struct mei_device *dev) +static u32 mei_me_d0i3_set(struct mei_device *dev, bool intr) +{ + u32 reg = mei_me_d0i3c_read(dev); + + reg |= H_D0I3C_I3; + if (intr) + reg |= H_D0I3C_IR; + else + reg &= ~H_D0I3C_IR; + mei_me_d0i3c_write(dev, reg); + /* read it to ensure HW consistency */ + reg = mei_me_d0i3c_read(dev); + return reg; +} + +/** + * mei_me_d0i3_unset - clean d0i3 register bit on mei device. + * + * @dev: the device structure + * + * Return: D0I3C register value + */ +static u32 mei_me_d0i3_unset(struct mei_device *dev) +{ + u32 reg = mei_me_d0i3c_read(dev); + + reg &= ~H_D0I3C_I3; + reg |= H_D0I3C_IR; + mei_me_d0i3c_write(dev, reg); + /* read it to ensure HW consistency */ + reg = mei_me_d0i3c_read(dev); + return reg; +} + +/** + * mei_me_d0i3_enter_sync - perform d0i3 entry procedure + * + * @dev: the device structure + * + * Return: 0 on success an error code otherwise + */ +static int mei_me_d0i3_enter_sync(struct mei_device *dev) +{ + struct mei_me_hw *hw = to_me_hw(dev); + unsigned long d0i3_timeout = mei_secs_to_jiffies(MEI_D0I3_TIMEOUT); + unsigned long pgi_timeout = mei_secs_to_jiffies(MEI_PGI_TIMEOUT); + int ret; + u32 reg; + + reg = mei_me_d0i3c_read(dev); + if (reg & H_D0I3C_I3) { + /* we are in d0i3, nothing to do */ + dev_dbg(dev->dev, "d0i3 set not needed\n"); + ret = 0; + goto on; + } + + /* PGI entry procedure */ + dev->pg_event = MEI_PG_EVENT_WAIT; + + ret = mei_hbm_pg(dev, MEI_PG_ISOLATION_ENTRY_REQ_CMD); + if (ret) + /* FIXME: should we reset here? */ + goto out; + + mutex_unlock(&dev->device_lock); + wait_event_timeout(dev->wait_pg, + dev->pg_event == MEI_PG_EVENT_RECEIVED, pgi_timeout); + mutex_lock(&dev->device_lock); + + if (dev->pg_event != MEI_PG_EVENT_RECEIVED) { + ret = -ETIME; + goto out; + } + /* end PGI entry procedure */ + + dev->pg_event = MEI_PG_EVENT_INTR_WAIT; + + reg = mei_me_d0i3_set(dev, true); + if (!(reg & H_D0I3C_CIP)) { + dev_dbg(dev->dev, "d0i3 enter wait not needed\n"); + ret = 0; + goto on; + } + + mutex_unlock(&dev->device_lock); + wait_event_timeout(dev->wait_pg, + dev->pg_event == MEI_PG_EVENT_INTR_RECEIVED, d0i3_timeout); + mutex_lock(&dev->device_lock); + + if (dev->pg_event != MEI_PG_EVENT_INTR_RECEIVED) { + reg = mei_me_d0i3c_read(dev); + if (!(reg & H_D0I3C_I3)) { + ret = -ETIME; + goto out; + } + } + + ret = 0; +on: + hw->pg_state = MEI_PG_ON; +out: + dev->pg_event = MEI_PG_EVENT_IDLE; + dev_dbg(dev->dev, "d0i3 enter ret = %d\n", ret); + return ret; +} + +/** + * mei_me_d0i3_enter - perform d0i3 entry procedure + * no hbm PG handshake + * no waiting for confirmation; runs with interrupts + * disabled + * + * @dev: the device structure + * + * Return: 0 on success an error code otherwise + */ +static int mei_me_d0i3_enter(struct mei_device *dev) +{ + struct mei_me_hw *hw = to_me_hw(dev); + u32 reg; + + reg = mei_me_d0i3c_read(dev); + if (reg & H_D0I3C_I3) { + /* we are in d0i3, nothing to do */ + dev_dbg(dev->dev, "already d0i3 : set not needed\n"); + goto on; + } + + mei_me_d0i3_set(dev, false); +on: + hw->pg_state = MEI_PG_ON; + dev->pg_event = MEI_PG_EVENT_IDLE; + dev_dbg(dev->dev, "d0i3 enter\n"); + return 0; +} + +/** + * mei_me_d0i3_exit_sync - perform d0i3 exit procedure + * + * @dev: the device structure + * + * Return: 0 on success an error code otherwise + */ +static int mei_me_d0i3_exit_sync(struct mei_device *dev) +{ + struct mei_me_hw *hw = to_me_hw(dev); + unsigned long timeout = mei_secs_to_jiffies(MEI_D0I3_TIMEOUT); + int ret; + u32 reg; + + dev->pg_event = MEI_PG_EVENT_INTR_WAIT; + + reg = mei_me_d0i3c_read(dev); + if (!(reg & H_D0I3C_I3)) { + /* we are not in d0i3, nothing to do */ + dev_dbg(dev->dev, "d0i3 exit not needed\n"); + ret = 0; + goto off; + } + + reg = mei_me_d0i3_unset(dev); + if (!(reg & H_D0I3C_CIP)) { + dev_dbg(dev->dev, "d0i3 exit wait not needed\n"); + ret = 0; + goto off; + } + + mutex_unlock(&dev->device_lock); + wait_event_timeout(dev->wait_pg, + dev->pg_event == MEI_PG_EVENT_INTR_RECEIVED, timeout); + mutex_lock(&dev->device_lock); + + if (dev->pg_event != MEI_PG_EVENT_INTR_RECEIVED) { + reg = mei_me_d0i3c_read(dev); + if (reg & H_D0I3C_I3) { + ret = -ETIME; + goto out; + } + } + + ret = 0; +off: + hw->pg_state = MEI_PG_OFF; +out: + dev->pg_event = MEI_PG_EVENT_IDLE; + + dev_dbg(dev->dev, "d0i3 exit ret = %d\n", ret); + return ret; +} + +/** + * mei_me_pg_legacy_intr - perform legacy pg processing + * in interrupt thread handler + * + * @dev: the device structure + */ +static void mei_me_pg_legacy_intr(struct mei_device *dev) { struct mei_me_hw *hw = to_me_hw(dev); @@ -752,6 +948,162 @@ static void mei_me_pg_intr(struct mei_device *dev) } /** + * mei_me_d0i3_intr - perform d0i3 processing in interrupt thread handler + * + * @dev: the device structure + */ +static void mei_me_d0i3_intr(struct mei_device *dev) +{ + struct mei_me_hw *hw = to_me_hw(dev); + + if (dev->pg_event == MEI_PG_EVENT_INTR_WAIT && + (hw->intr_source & H_D0I3C_IS)) { + dev->pg_event = MEI_PG_EVENT_INTR_RECEIVED; + if (hw->pg_state == MEI_PG_ON) { + hw->pg_state = MEI_PG_OFF; + if (dev->hbm_state != MEI_HBM_IDLE) { + /* + * force H_RDY because it could be + * wiped off during PG + */ + dev_dbg(dev->dev, "d0i3 set host ready\n"); + mei_me_host_set_ready(dev); + } + } else { + hw->pg_state = MEI_PG_ON; + } + + wake_up(&dev->wait_pg); + } + + if (hw->pg_state == MEI_PG_ON && (hw->intr_source & H_IS)) { + /* + * HW sent some data and we are in D0i3, so + * we got here because of HW initiated exit from D0i3. + * Start runtime pm resume sequence to exit low power state. + */ + dev_dbg(dev->dev, "d0i3 want resume\n"); + mei_hbm_pg_resume(dev); + } +} + +/** + * mei_me_pg_intr - perform pg processing in interrupt thread handler + * + * @dev: the device structure + */ +static void mei_me_pg_intr(struct mei_device *dev) +{ + struct mei_me_hw *hw = to_me_hw(dev); + + if (hw->d0i3_supported) + mei_me_d0i3_intr(dev); + else + mei_me_pg_legacy_intr(dev); +} + +/** + * mei_me_pg_enter_sync - perform runtime pm entry procedure + * + * @dev: the device structure + * + * Return: 0 on success an error code otherwise + */ +int mei_me_pg_enter_sync(struct mei_device *dev) +{ + struct mei_me_hw *hw = to_me_hw(dev); + + if (hw->d0i3_supported) + return mei_me_d0i3_enter_sync(dev); + else + return mei_me_pg_legacy_enter_sync(dev); +} + +/** + * mei_me_pg_exit_sync - perform runtime pm exit procedure + * + * @dev: the device structure + * + * Return: 0 on success an error code otherwise + */ +int mei_me_pg_exit_sync(struct mei_device *dev) +{ + struct mei_me_hw *hw = to_me_hw(dev); + + if (hw->d0i3_supported) + return mei_me_d0i3_exit_sync(dev); + else + return mei_me_pg_legacy_exit_sync(dev); +} + +/** + * mei_me_hw_reset - resets fw via mei csr register. + * + * @dev: the device structure + * @intr_enable: if interrupt should be enabled after reset. + * + * Return: 0 on success an error code otherwise + */ +static int mei_me_hw_reset(struct mei_device *dev, bool intr_enable) +{ + struct mei_me_hw *hw = to_me_hw(dev); + int ret; + u32 hcsr; + + if (intr_enable) { + mei_me_intr_enable(dev); + if (hw->d0i3_supported) { + ret = mei_me_d0i3_exit_sync(dev); + if (ret) + return ret; + } + } + + hcsr = mei_hcsr_read(dev); + /* H_RST may be found lit before reset is started, + * for example if preceding reset flow hasn't completed. + * In that case asserting H_RST will be ignored, therefore + * we need to clean H_RST bit to start a successful reset sequence. + */ + if ((hcsr & H_RST) == H_RST) { + dev_warn(dev->dev, "H_RST is set = 0x%08X", hcsr); + hcsr &= ~H_RST; + mei_hcsr_set(dev, hcsr); + hcsr = mei_hcsr_read(dev); + } + + hcsr |= H_RST | H_IG | H_CSR_IS_MASK; + + if (!intr_enable) + hcsr &= ~H_CSR_IE_MASK; + + dev->recvd_hw_ready = false; + mei_hcsr_write(dev, hcsr); + + /* + * Host reads the H_CSR once to ensure that the + * posted write to H_CSR completes. + */ + hcsr = mei_hcsr_read(dev); + + if ((hcsr & H_RST) == 0) + dev_warn(dev->dev, "H_RST is not set = 0x%08X", hcsr); + + if ((hcsr & H_RDY) == H_RDY) + dev_warn(dev->dev, "H_RDY is not cleared 0x%08X", hcsr); + + if (!intr_enable) { + mei_me_hw_reset_release(dev); + if (hw->d0i3_supported) { + ret = mei_me_d0i3_enter(dev); + if (ret) + return ret; + } + } + return 0; +} + +/** * mei_me_irq_quick_handler - The ISR of the MEI device * * @irq: The irq number @@ -759,16 +1111,20 @@ static void mei_me_pg_intr(struct mei_device *dev) * * Return: irqreturn_t */ - irqreturn_t mei_me_irq_quick_handler(int irq, void *dev_id) { - struct mei_device *dev = (struct mei_device *) dev_id; - u32 hcsr = mei_hcsr_read(dev); + struct mei_device *dev = (struct mei_device *)dev_id; + struct mei_me_hw *hw = to_me_hw(dev); + u32 hcsr; - if ((hcsr & H_IS) != H_IS) + hcsr = mei_hcsr_read(dev); + if (!(hcsr & H_CSR_IS_MASK)) return IRQ_NONE; - /* clear H_IS bit in H_CSR */ + hw->intr_source = hcsr & H_CSR_IS_MASK; + dev_dbg(dev->dev, "interrupt source 0x%08X.\n", hw->intr_source); + + /* clear H_IS and H_D0I3C_IS bits in H_CSR to clear the interrupts */ mei_hcsr_write(dev, hcsr); return IRQ_WAKE_THREAD; @@ -796,11 +1152,6 @@ irqreturn_t mei_me_irq_thread_handler(int irq, void *dev_id) mutex_lock(&dev->device_lock); mei_io_list_init(&complete_list); - /* Ack the interrupt here - * In case of MSI we don't go through the quick handler */ - if (pci_dev_msi_enabled(to_pci_dev(dev->dev))) - mei_clear_interrupts(dev); - /* check if ME wants a reset */ if (!mei_hw_is_ready(dev) && dev->dev_state != MEI_DEV_RESETTING) { dev_warn(dev->dev, "FW not ready: resetting.\n"); diff --git a/drivers/misc/mei/hw-me.h b/drivers/misc/mei/hw-me.h index 6022d52af6f6..2ee14dc1b2ea 100644 --- a/drivers/misc/mei/hw-me.h +++ b/drivers/misc/mei/hw-me.h @@ -50,13 +50,17 @@ struct mei_cfg { * struct mei_me_hw - me hw specific data * * @cfg: per device generation config and ops - * @mem_addr: io memory address - * @pg_state: power gating state + * @mem_addr: io memory address + * @intr_source: interrupt source + * @pg_state: power gating state + * @d0i3_supported: di03 support */ struct mei_me_hw { const struct mei_cfg *cfg; void __iomem *mem_addr; + u32 intr_source; enum mei_pg_state pg_state; + bool d0i3_supported; }; #define to_me_hw(dev) (struct mei_me_hw *)((dev)->hw) diff --git a/drivers/misc/mei/hw.h b/drivers/misc/mei/hw.h index 16fef6dc4dd7..4cebde85924f 100644 --- a/drivers/misc/mei/hw.h +++ b/drivers/misc/mei/hw.h @@ -31,14 +31,15 @@ #define MEI_IAMTHIF_STALL_TIMER 12 /* HPS */ #define MEI_IAMTHIF_READ_TIMER 10 /* HPS */ -#define MEI_PGI_TIMEOUT 1 /* PG Isolation time response 1 sec */ -#define MEI_HBM_TIMEOUT 1 /* 1 second */ +#define MEI_PGI_TIMEOUT 1 /* PG Isolation time response 1 sec */ +#define MEI_D0I3_TIMEOUT 5 /* D0i3 set/unset max response time */ +#define MEI_HBM_TIMEOUT 1 /* 1 second */ /* * MEI Version */ -#define HBM_MINOR_VERSION 1 -#define HBM_MAJOR_VERSION 1 +#define HBM_MINOR_VERSION 0 +#define HBM_MAJOR_VERSION 2 /* * MEI version with PGI support @@ -46,6 +47,24 @@ #define HBM_MINOR_VERSION_PGI 1 #define HBM_MAJOR_VERSION_PGI 1 +/* + * MEI version with Dynamic clients support + */ +#define HBM_MINOR_VERSION_DC 0 +#define HBM_MAJOR_VERSION_DC 2 + +/* + * MEI version with disconnect on connection timeout support + */ +#define HBM_MINOR_VERSION_DOT 0 +#define HBM_MAJOR_VERSION_DOT 2 + +/* + * MEI version with notifcation support + */ +#define HBM_MINOR_VERSION_EV 0 +#define HBM_MAJOR_VERSION_EV 2 + /* Host bus message command opcode */ #define MEI_HBM_CMD_OP_MSK 0x7f /* Host bus message command RESPONSE */ @@ -81,6 +100,13 @@ #define MEI_PG_ISOLATION_EXIT_REQ_CMD 0x0b #define MEI_PG_ISOLATION_EXIT_RES_CMD 0x8b +#define MEI_HBM_ADD_CLIENT_REQ_CMD 0x0f +#define MEI_HBM_ADD_CLIENT_RES_CMD 0x8f + +#define MEI_HBM_NOTIFY_REQ_CMD 0x10 +#define MEI_HBM_NOTIFY_RES_CMD 0x90 +#define MEI_HBM_NOTIFICATION_CMD 0x11 + /* * MEI Stop Reason * used by hbm_host_stop_request.reason @@ -136,6 +162,7 @@ enum mei_cl_connect_status { MEI_CL_CONN_ALREADY_STARTED = MEI_HBMS_ALREADY_EXISTS, MEI_CL_CONN_OUT_OF_RESOURCES = MEI_HBMS_REJECTED, MEI_CL_CONN_MESSAGE_SMALL = MEI_HBMS_INVALID_PARAMETER, + MEI_CL_CONN_NOT_ALLOWED = MEI_HBMS_NOT_ALLOWED, }; /* @@ -213,9 +240,17 @@ struct hbm_me_stop_request { u8 reserved[2]; } __packed; +/** + * struct hbm_host_enum_request - enumeration request from host to fw + * + * @hbm_cmd: bus message command header + * @allow_add: allow dynamic clients add HBM version >= 2.0 + * @reserved: reserved + */ struct hbm_host_enum_request { u8 hbm_cmd; - u8 reserved[3]; + u8 allow_add; + u8 reserved[2]; } __packed; struct hbm_host_enum_response { @@ -248,6 +283,38 @@ struct hbm_props_response { } __packed; /** + * struct hbm_add_client_request - request to add a client + * might be sent by fw after enumeration has already completed + * + * @hbm_cmd: bus message command header + * @me_addr: address of the client in ME + * @reserved: reserved + * @client_properties: client properties + */ +struct hbm_add_client_request { + u8 hbm_cmd; + u8 me_addr; + u8 reserved[2]; + struct mei_client_properties client_properties; +} __packed; + +/** + * struct hbm_add_client_response - response to add a client + * sent by the host to report client addition status to fw + * + * @hbm_cmd: bus message command header + * @me_addr: address of the client in ME + * @status: if HBMS_SUCCESS then the client can now accept connections. + * @reserved: reserved + */ +struct hbm_add_client_response { + u8 hbm_cmd; + u8 me_addr; + u8 status; + u8 reserved[1]; +} __packed; + +/** * struct hbm_power_gate - power gate request/response * * @hbm_cmd: bus message command header @@ -298,5 +365,62 @@ struct hbm_flow_control { u8 reserved[MEI_FC_MESSAGE_RESERVED_LENGTH]; } __packed; +#define MEI_HBM_NOTIFICATION_START 1 +#define MEI_HBM_NOTIFICATION_STOP 0 +/** + * struct hbm_notification_request - start/stop notification request + * + * @hbm_cmd: bus message command header + * @me_addr: address of the client in ME + * @host_addr: address of the client in the driver + * @start: start = 1 or stop = 0 asynchronous notifications + */ +struct hbm_notification_request { + u8 hbm_cmd; + u8 me_addr; + u8 host_addr; + u8 start; +} __packed; + +/** + * struct hbm_notification_response - start/stop notification response + * + * @hbm_cmd: bus message command header + * @me_addr: address of the client in ME + * @host_addr: - address of the client in the driver + * @status: (mei_hbm_status) response status for the request + * - MEI_HBMS_SUCCESS: successful stop/start + * - MEI_HBMS_CLIENT_NOT_FOUND: if the connection could not be found. + * - MEI_HBMS_ALREADY_STARTED: for start requests for a previously + * started notification. + * - MEI_HBMS_NOT_STARTED: for stop request for a connected client for whom + * asynchronous notifications are currently disabled. + * + * @start: start = 1 or stop = 0 asynchronous notifications + * @reserved: reserved + */ +struct hbm_notification_response { + u8 hbm_cmd; + u8 me_addr; + u8 host_addr; + u8 status; + u8 start; + u8 reserved[3]; +} __packed; + +/** + * struct hbm_notification - notification event + * + * @hbm_cmd: bus message command header + * @me_addr: address of the client in ME + * @host_addr: address of the client in the driver + * @reserved: reserved for alignment + */ +struct hbm_notification { + u8 hbm_cmd; + u8 me_addr; + u8 host_addr; + u8 reserved[1]; +} __packed; #endif diff --git a/drivers/misc/mei/init.c b/drivers/misc/mei/init.c index 00c3865ca3b1..e374661652cd 100644 --- a/drivers/misc/mei/init.c +++ b/drivers/misc/mei/init.c @@ -331,7 +331,7 @@ void mei_stop(struct mei_device *dev) mei_cancel_work(dev); - mei_nfc_host_exit(dev); + mei_cl_bus_remove_devices(dev); mutex_lock(&dev->device_lock); @@ -390,6 +390,7 @@ void mei_device_init(struct mei_device *dev, INIT_LIST_HEAD(&dev->me_clients); mutex_init(&dev->device_lock); init_rwsem(&dev->me_clients_rwsem); + mutex_init(&dev->cl_bus_lock); init_waitqueue_head(&dev->wait_hw_ready); init_waitqueue_head(&dev->wait_pg); init_waitqueue_head(&dev->wait_hbm_start); diff --git a/drivers/misc/mei/interrupt.c b/drivers/misc/mei/interrupt.c index 3f3405269c39..c418d7888994 100644 --- a/drivers/misc/mei/interrupt.c +++ b/drivers/misc/mei/interrupt.c @@ -403,6 +403,13 @@ int mei_irq_write_handler(struct mei_device *dev, struct mei_cl_cb *cmpl_list) if (ret) return ret; break; + + case MEI_FOP_NOTIFY_START: + case MEI_FOP_NOTIFY_STOP: + ret = mei_cl_irq_notify(cl, cb, cmpl_list); + if (ret) + return ret; + break; default: BUG(); } @@ -424,6 +431,24 @@ int mei_irq_write_handler(struct mei_device *dev, struct mei_cl_cb *cmpl_list) EXPORT_SYMBOL_GPL(mei_irq_write_handler); +/** + * mei_connect_timeout - connect/disconnect timeouts + * + * @cl: host client + */ +static void mei_connect_timeout(struct mei_cl *cl) +{ + struct mei_device *dev = cl->dev; + + if (cl->state == MEI_FILE_CONNECTING) { + if (dev->hbm_f_dot_supported) { + cl->state = MEI_FILE_DISCONNECT_REQUIRED; + wake_up(&cl->wait); + return; + } + } + mei_reset(dev); +} /** * mei_timer - timer function. @@ -464,7 +489,7 @@ void mei_timer(struct work_struct *work) if (cl->timer_count) { if (--cl->timer_count == 0) { dev_err(dev->dev, "timer: connect/disconnect timeout.\n"); - mei_reset(dev); + mei_connect_timeout(cl); goto out; } } diff --git a/drivers/misc/mei/main.c b/drivers/misc/mei/main.c index e9513d651cd3..b2f2486b3d75 100644 --- a/drivers/misc/mei/main.c +++ b/drivers/misc/mei/main.c @@ -446,6 +446,45 @@ end: } /** + * mei_ioctl_client_notify_request - + * propagate event notification request to client + * + * @file: pointer to file structure + * @request: 0 - disable, 1 - enable + * + * Return: 0 on success , <0 on error + */ +static int mei_ioctl_client_notify_request(struct file *file, u32 request) +{ + struct mei_cl *cl = file->private_data; + + return mei_cl_notify_request(cl, file, request); +} + +/** + * mei_ioctl_client_notify_get - wait for notification request + * + * @file: pointer to file structure + * @notify_get: 0 - disable, 1 - enable + * + * Return: 0 on success , <0 on error + */ +static int mei_ioctl_client_notify_get(struct file *file, u32 *notify_get) +{ + struct mei_cl *cl = file->private_data; + bool notify_ev; + bool block = (file->f_flags & O_NONBLOCK) == 0; + int rets; + + rets = mei_cl_notify_get(cl, block, ¬ify_ev); + if (rets) + return rets; + + *notify_get = notify_ev ? 1 : 0; + return 0; +} + +/** * mei_ioctl - the IOCTL function * * @file: pointer to file structure @@ -459,6 +498,7 @@ static long mei_ioctl(struct file *file, unsigned int cmd, unsigned long data) struct mei_device *dev; struct mei_cl *cl = file->private_data; struct mei_connect_client_data connect_data; + u32 notify_get, notify_req; int rets; @@ -499,6 +539,33 @@ static long mei_ioctl(struct file *file, unsigned int cmd, unsigned long data) break; + case IOCTL_MEI_NOTIFY_SET: + dev_dbg(dev->dev, ": IOCTL_MEI_NOTIFY_SET.\n"); + if (copy_from_user(¬ify_req, + (char __user *)data, sizeof(notify_req))) { + dev_dbg(dev->dev, "failed to copy data from userland\n"); + rets = -EFAULT; + goto out; + } + rets = mei_ioctl_client_notify_request(file, notify_req); + break; + + case IOCTL_MEI_NOTIFY_GET: + dev_dbg(dev->dev, ": IOCTL_MEI_NOTIFY_GET.\n"); + rets = mei_ioctl_client_notify_get(file, ¬ify_get); + if (rets) + goto out; + + dev_dbg(dev->dev, "copy connect data to user\n"); + if (copy_to_user((char __user *)data, + ¬ify_get, sizeof(notify_get))) { + dev_dbg(dev->dev, "failed to copy data to userland\n"); + rets = -EFAULT; + goto out; + + } + break; + default: dev_err(dev->dev, ": unsupported ioctl %d.\n", cmd); rets = -ENOIOCTLCMD; @@ -541,6 +608,7 @@ static unsigned int mei_poll(struct file *file, poll_table *wait) struct mei_cl *cl = file->private_data; struct mei_device *dev; unsigned int mask = 0; + bool notify_en; if (WARN_ON(!cl || !cl->dev)) return POLLERR; @@ -549,6 +617,7 @@ static unsigned int mei_poll(struct file *file, poll_table *wait) mutex_lock(&dev->device_lock); + notify_en = cl->notify_en && (req_events & POLLPRI); if (dev->dev_state != MEI_DEV_ENABLED || !mei_cl_is_connected(cl)) { @@ -561,6 +630,12 @@ static unsigned int mei_poll(struct file *file, poll_table *wait) goto out; } + if (notify_en) { + poll_wait(file, &cl->ev_wait, wait); + if (cl->notify_ev) + mask |= POLLPRI; + } + if (req_events & (POLLIN | POLLRDNORM)) { poll_wait(file, &cl->rx_wait, wait); @@ -576,6 +651,26 @@ out: } /** + * mei_fasync - asynchronous io support + * + * @fd: file descriptor + * @file: pointer to file structure + * @band: band bitmap + * + * Return: poll mask + */ +static int mei_fasync(int fd, struct file *file, int band) +{ + + struct mei_cl *cl = file->private_data; + + if (!mei_cl_is_connected(cl)) + return POLLERR; + + return fasync_helper(fd, file, band, &cl->ev_async); +} + +/** * fw_status_show - mei device attribute show method * * @device: device pointer @@ -627,6 +722,7 @@ static const struct file_operations mei_fops = { .release = mei_release, .write = mei_write, .poll = mei_poll, + .fasync = mei_fasync, .llseek = no_llseek }; diff --git a/drivers/misc/mei/mei_dev.h b/drivers/misc/mei/mei_dev.h index 453f6a333b42..e25ee16c658e 100644 --- a/drivers/misc/mei/mei_dev.h +++ b/drivers/misc/mei/mei_dev.h @@ -89,6 +89,7 @@ enum file_state { MEI_FILE_CONNECTED, MEI_FILE_DISCONNECTING, MEI_FILE_DISCONNECT_REPLY, + MEI_FILE_DISCONNECT_REQUIRED, MEI_FILE_DISCONNECTED, }; @@ -135,6 +136,8 @@ enum mei_wd_states { * @MEI_FOP_CONNECT: connect * @MEI_FOP_DISCONNECT: disconnect * @MEI_FOP_DISCONNECT_RSP: disconnect response + * @MEI_FOP_NOTIFY_START: start notification + * @MEI_FOP_NOTIFY_STOP: stop notification */ enum mei_cb_file_ops { MEI_FOP_READ = 0, @@ -142,6 +145,8 @@ enum mei_cb_file_ops { MEI_FOP_CONNECT, MEI_FOP_DISCONNECT, MEI_FOP_DISCONNECT_RSP, + MEI_FOP_NOTIFY_START, + MEI_FOP_NOTIFY_STOP, }; /* @@ -178,7 +183,7 @@ struct mei_fw_status { * @client_id: me client id * @mei_flow_ctrl_creds: flow control credits * @connect_count: number connections to this client - * @reserved: reserved + * @bus_added: added to bus */ struct mei_me_client { struct list_head list; @@ -187,7 +192,7 @@ struct mei_me_client { u8 client_id; u8 mei_flow_ctrl_creds; u8 connect_count; - u8 reserved; + u8 bus_added; }; @@ -230,18 +235,21 @@ struct mei_cl_cb { * @tx_wait: wait queue for tx completion * @rx_wait: wait queue for rx completion * @wait: wait queue for management operation + * @ev_wait: notification wait queue + * @ev_async: event async notification * @status: connection status * @me_cl: fw client connected * @host_client_id: host id * @mei_flow_ctrl_creds: transmit flow credentials * @timer_count: watchdog timer for operation completion * @reserved: reserved for alignment + * @notify_en: notification - enabled/disabled + * @notify_ev: pending notification event * @writing_state: state of the tx * @rd_pending: pending read credits * @rd_completed: completed read * - * @device: device on the mei client bus - * @device_link: link to bus clients + * @cldev: device on the mei client bus */ struct mei_cl { struct list_head link; @@ -250,19 +258,21 @@ struct mei_cl { wait_queue_head_t tx_wait; wait_queue_head_t rx_wait; wait_queue_head_t wait; + wait_queue_head_t ev_wait; + struct fasync_struct *ev_async; int status; struct mei_me_client *me_cl; u8 host_client_id; u8 mei_flow_ctrl_creds; u8 timer_count; u8 reserved; + u8 notify_en; + u8 notify_ev; enum mei_file_transaction_states writing_state; struct list_head rd_pending; struct list_head rd_completed; - /* MEI CL bus data */ - struct mei_cl_device *device; - struct list_head device_link; + struct mei_cl_device *cldev; }; /** struct mei_hw_ops @@ -329,21 +339,16 @@ struct mei_hw_ops { }; /* MEI bus API*/ - -struct mei_cl_device *mei_cl_add_device(struct mei_device *dev, - struct mei_me_client *me_cl, - struct mei_cl *cl, - char *name); -void mei_cl_remove_device(struct mei_cl_device *device); - +void mei_cl_bus_rescan(struct mei_device *bus); +void mei_cl_dev_fixup(struct mei_cl_device *dev); ssize_t __mei_cl_send(struct mei_cl *cl, u8 *buf, size_t length, bool blocking); ssize_t __mei_cl_recv(struct mei_cl *cl, u8 *buf, size_t length); void mei_cl_bus_rx_event(struct mei_cl *cl); -void mei_cl_bus_remove_devices(struct mei_device *dev); +void mei_cl_bus_notify_event(struct mei_cl *cl); +void mei_cl_bus_remove_devices(struct mei_device *bus); int mei_cl_bus_init(void); void mei_cl_bus_exit(void); -struct mei_cl *mei_cl_bus_find_cl_by_uuid(struct mei_device *dev, uuid_le uuid); /** * enum mei_pg_event - power gating transition events @@ -416,7 +421,10 @@ const char *mei_pg_state_str(enum mei_pg_state state); * @wr_msg : the buffer for hbm control messages * * @version : HBM protocol version in use - * @hbm_f_pg_supported : hbm feature pgi protocol + * @hbm_f_pg_supported : hbm feature pgi protocol + * @hbm_f_dc_supported : hbm feature dynamic clients + * @hbm_f_dot_supported : hbm feature disconnect on timeout + * @hbm_f_ev_supported : hbm feature event notification * * @me_clients_rwsem: rw lock over me_clients list * @me_clients : list of FW clients @@ -447,6 +455,7 @@ const char *mei_pg_state_str(enum mei_pg_state state); * @reset_work : work item for the device reset * * @device_list : mei client bus list + * @cl_bus_lock : client bus list lock * * @dbgfs_dir : debugfs mei root directory * @@ -509,6 +518,9 @@ struct mei_device { struct hbm_version version; unsigned int hbm_f_pg_supported:1; + unsigned int hbm_f_dc_supported:1; + unsigned int hbm_f_dot_supported:1; + unsigned int hbm_f_ev_supported:1; struct rw_semaphore me_clients_rwsem; struct list_head me_clients; @@ -543,6 +555,7 @@ struct mei_device { /* List of bus devices */ struct list_head device_list; + struct mutex cl_bus_lock; #if IS_ENABLED(CONFIG_DEBUG_FS) struct dentry *dbgfs_dir; diff --git a/drivers/misc/mei/nfc.c b/drivers/misc/mei/nfc.c deleted file mode 100644 index 290ef3037437..000000000000 --- a/drivers/misc/mei/nfc.c +++ /dev/null @@ -1,415 +0,0 @@ -/* - * - * Intel Management Engine Interface (Intel MEI) Linux driver - * Copyright (c) 2003-2013, 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, - * version 2, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - */ - -#include <linux/kernel.h> -#include <linux/sched.h> -#include <linux/module.h> -#include <linux/moduleparam.h> -#include <linux/device.h> -#include <linux/slab.h> - -#include <linux/mei_cl_bus.h> - -#include "mei_dev.h" -#include "client.h" - -struct mei_nfc_cmd { - u8 command; - u8 status; - u16 req_id; - u32 reserved; - u16 data_size; - u8 sub_command; - u8 data[]; -} __packed; - -struct mei_nfc_reply { - u8 command; - u8 status; - u16 req_id; - u32 reserved; - u16 data_size; - u8 sub_command; - u8 reply_status; - u8 data[]; -} __packed; - -struct mei_nfc_if_version { - u8 radio_version_sw[3]; - u8 reserved[3]; - u8 radio_version_hw[3]; - u8 i2c_addr; - u8 fw_ivn; - u8 vendor_id; - u8 radio_type; -} __packed; - -struct mei_nfc_connect { - u8 fw_ivn; - u8 vendor_id; -} __packed; - -struct mei_nfc_connect_resp { - u8 fw_ivn; - u8 vendor_id; - u16 me_major; - u16 me_minor; - u16 me_hotfix; - u16 me_build; -} __packed; - -struct mei_nfc_hci_hdr { - u8 cmd; - u8 status; - u16 req_id; - u32 reserved; - u16 data_size; -} __packed; - -#define MEI_NFC_CMD_MAINTENANCE 0x00 -#define MEI_NFC_CMD_HCI_SEND 0x01 -#define MEI_NFC_CMD_HCI_RECV 0x02 - -#define MEI_NFC_SUBCMD_CONNECT 0x00 -#define MEI_NFC_SUBCMD_IF_VERSION 0x01 - -#define MEI_NFC_HEADER_SIZE 10 - -/** - * struct mei_nfc_dev - NFC mei device - * - * @me_cl: NFC me client - * @cl: NFC host client - * @cl_info: NFC info host client - * @init_work: perform connection to the info client - * @fw_ivn: NFC Interface Version Number - * @vendor_id: NFC manufacturer ID - * @radio_type: NFC radio type - * @bus_name: bus name - * - */ -struct mei_nfc_dev { - struct mei_me_client *me_cl; - struct mei_cl *cl; - struct mei_cl *cl_info; - struct work_struct init_work; - u8 fw_ivn; - u8 vendor_id; - u8 radio_type; - char *bus_name; -}; - -/* UUIDs for NFC F/W clients */ -const uuid_le mei_nfc_guid = UUID_LE(0x0bb17a78, 0x2a8e, 0x4c50, - 0x94, 0xd4, 0x50, 0x26, - 0x67, 0x23, 0x77, 0x5c); - -static const uuid_le mei_nfc_info_guid = UUID_LE(0xd2de1625, 0x382d, 0x417d, - 0x48, 0xa4, 0xef, 0xab, - 0xba, 0x8a, 0x12, 0x06); - -/* Vendors */ -#define MEI_NFC_VENDOR_INSIDE 0x00 -#define MEI_NFC_VENDOR_NXP 0x01 - -/* Radio types */ -#define MEI_NFC_VENDOR_INSIDE_UREAD 0x00 -#define MEI_NFC_VENDOR_NXP_PN544 0x01 - -static void mei_nfc_free(struct mei_nfc_dev *ndev) -{ - if (!ndev) - return; - - if (ndev->cl) { - list_del(&ndev->cl->device_link); - mei_cl_unlink(ndev->cl); - kfree(ndev->cl); - } - - if (ndev->cl_info) { - list_del(&ndev->cl_info->device_link); - mei_cl_unlink(ndev->cl_info); - kfree(ndev->cl_info); - } - - mei_me_cl_put(ndev->me_cl); - kfree(ndev); -} - -static int mei_nfc_build_bus_name(struct mei_nfc_dev *ndev) -{ - struct mei_device *dev; - - if (!ndev->cl) - return -ENODEV; - - dev = ndev->cl->dev; - - switch (ndev->vendor_id) { - case MEI_NFC_VENDOR_INSIDE: - switch (ndev->radio_type) { - case MEI_NFC_VENDOR_INSIDE_UREAD: - ndev->bus_name = "microread"; - return 0; - - default: - dev_err(dev->dev, "Unknown radio type 0x%x\n", - ndev->radio_type); - - return -EINVAL; - } - - case MEI_NFC_VENDOR_NXP: - switch (ndev->radio_type) { - case MEI_NFC_VENDOR_NXP_PN544: - ndev->bus_name = "pn544"; - return 0; - default: - dev_err(dev->dev, "Unknown radio type 0x%x\n", - ndev->radio_type); - - return -EINVAL; - } - - default: - dev_err(dev->dev, "Unknown vendor ID 0x%x\n", - ndev->vendor_id); - - return -EINVAL; - } - - return 0; -} - -static int mei_nfc_if_version(struct mei_nfc_dev *ndev) -{ - struct mei_device *dev; - struct mei_cl *cl; - - struct mei_nfc_cmd cmd; - struct mei_nfc_reply *reply = NULL; - struct mei_nfc_if_version *version; - size_t if_version_length; - int bytes_recv, ret; - - cl = ndev->cl_info; - dev = cl->dev; - - memset(&cmd, 0, sizeof(struct mei_nfc_cmd)); - cmd.command = MEI_NFC_CMD_MAINTENANCE; - cmd.data_size = 1; - cmd.sub_command = MEI_NFC_SUBCMD_IF_VERSION; - - ret = __mei_cl_send(cl, (u8 *)&cmd, sizeof(struct mei_nfc_cmd), 1); - if (ret < 0) { - dev_err(dev->dev, "Could not send IF version cmd\n"); - return ret; - } - - /* to be sure on the stack we alloc memory */ - if_version_length = sizeof(struct mei_nfc_reply) + - sizeof(struct mei_nfc_if_version); - - reply = kzalloc(if_version_length, GFP_KERNEL); - if (!reply) - return -ENOMEM; - - bytes_recv = __mei_cl_recv(cl, (u8 *)reply, if_version_length); - if (bytes_recv < 0 || bytes_recv < sizeof(struct mei_nfc_reply)) { - dev_err(dev->dev, "Could not read IF version\n"); - ret = -EIO; - goto err; - } - - version = (struct mei_nfc_if_version *)reply->data; - - ndev->fw_ivn = version->fw_ivn; - ndev->vendor_id = version->vendor_id; - ndev->radio_type = version->radio_type; - -err: - kfree(reply); - return ret; -} - -static void mei_nfc_init(struct work_struct *work) -{ - struct mei_device *dev; - struct mei_cl_device *cldev; - struct mei_nfc_dev *ndev; - struct mei_cl *cl_info; - struct mei_me_client *me_cl_info; - - ndev = container_of(work, struct mei_nfc_dev, init_work); - - cl_info = ndev->cl_info; - dev = cl_info->dev; - - mutex_lock(&dev->device_lock); - - /* check for valid client id */ - me_cl_info = mei_me_cl_by_uuid(dev, &mei_nfc_info_guid); - if (!me_cl_info) { - mutex_unlock(&dev->device_lock); - dev_info(dev->dev, "nfc: failed to find the info client\n"); - goto err; - } - - if (mei_cl_connect(cl_info, me_cl_info, NULL) < 0) { - mei_me_cl_put(me_cl_info); - mutex_unlock(&dev->device_lock); - dev_err(dev->dev, "Could not connect to the NFC INFO ME client"); - - goto err; - } - mei_me_cl_put(me_cl_info); - mutex_unlock(&dev->device_lock); - - if (mei_nfc_if_version(ndev) < 0) { - dev_err(dev->dev, "Could not get the NFC interface version"); - - goto err; - } - - dev_info(dev->dev, "NFC MEI VERSION: IVN 0x%x Vendor ID 0x%x Type 0x%x\n", - ndev->fw_ivn, ndev->vendor_id, ndev->radio_type); - - mutex_lock(&dev->device_lock); - - if (mei_cl_disconnect(cl_info) < 0) { - mutex_unlock(&dev->device_lock); - dev_err(dev->dev, "Could not disconnect the NFC INFO ME client"); - - goto err; - } - - mutex_unlock(&dev->device_lock); - - if (mei_nfc_build_bus_name(ndev) < 0) { - dev_err(dev->dev, "Could not build the bus ID name\n"); - return; - } - - cldev = mei_cl_add_device(dev, ndev->me_cl, ndev->cl, - ndev->bus_name); - if (!cldev) { - dev_err(dev->dev, "Could not add the NFC device to the MEI bus\n"); - - goto err; - } - - cldev->priv_data = ndev; - - - return; - -err: - mutex_lock(&dev->device_lock); - mei_nfc_free(ndev); - mutex_unlock(&dev->device_lock); - -} - - -int mei_nfc_host_init(struct mei_device *dev, struct mei_me_client *me_cl) -{ - struct mei_nfc_dev *ndev; - struct mei_cl *cl_info, *cl; - int ret; - - - /* in case of internal reset bail out - * as the device is already setup - */ - cl = mei_cl_bus_find_cl_by_uuid(dev, mei_nfc_guid); - if (cl) - return 0; - - ndev = kzalloc(sizeof(struct mei_nfc_dev), GFP_KERNEL); - if (!ndev) { - ret = -ENOMEM; - goto err; - } - - ndev->me_cl = mei_me_cl_get(me_cl); - if (!ndev->me_cl) { - ret = -ENODEV; - goto err; - } - - cl_info = mei_cl_alloc_linked(dev, MEI_HOST_CLIENT_ID_ANY); - if (IS_ERR(cl_info)) { - ret = PTR_ERR(cl_info); - goto err; - } - - list_add_tail(&cl_info->device_link, &dev->device_list); - - ndev->cl_info = cl_info; - - cl = mei_cl_alloc_linked(dev, MEI_HOST_CLIENT_ID_ANY); - if (IS_ERR(cl)) { - ret = PTR_ERR(cl); - goto err; - } - - list_add_tail(&cl->device_link, &dev->device_list); - - ndev->cl = cl; - - INIT_WORK(&ndev->init_work, mei_nfc_init); - schedule_work(&ndev->init_work); - - return 0; - -err: - mei_nfc_free(ndev); - - return ret; -} - -void mei_nfc_host_exit(struct mei_device *dev) -{ - struct mei_nfc_dev *ndev; - struct mei_cl *cl; - struct mei_cl_device *cldev; - - cl = mei_cl_bus_find_cl_by_uuid(dev, mei_nfc_guid); - if (!cl) - return; - - cldev = cl->device; - if (!cldev) - return; - - ndev = (struct mei_nfc_dev *)cldev->priv_data; - if (ndev) - cancel_work_sync(&ndev->init_work); - - cldev->priv_data = NULL; - - /* Need to remove the device here - * since mei_nfc_free will unlink the clients - */ - mei_cl_remove_device(cldev); - - mutex_lock(&dev->device_lock); - mei_nfc_free(ndev); - mutex_unlock(&dev->device_lock); -} - - diff --git a/drivers/misc/mei/pci-me.c b/drivers/misc/mei/pci-me.c index 23f71f5ce4fb..27678d8154e0 100644 --- a/drivers/misc/mei/pci-me.c +++ b/drivers/misc/mei/pci-me.c @@ -82,6 +82,11 @@ static const struct pci_device_id mei_me_pci_tbl[] = { {MEI_PCI_DEVICE(MEI_DEV_ID_WPT_LP, mei_me_pch8_cfg)}, {MEI_PCI_DEVICE(MEI_DEV_ID_WPT_LP_2, mei_me_pch8_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_SPT, mei_me_pch8_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_SPT_2, mei_me_pch8_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_SPT_H, mei_me_pch8_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_SPT_H_2, mei_me_pch8_cfg)}, + /* required last entry */ {0, } }; @@ -128,6 +133,7 @@ static int mei_me_probe(struct pci_dev *pdev, const struct pci_device_id *ent) const struct mei_cfg *cfg = (struct mei_cfg *)(ent->driver_data); struct mei_device *dev; struct mei_me_hw *hw; + unsigned int irqflags; int err; @@ -180,17 +186,12 @@ static int mei_me_probe(struct pci_dev *pdev, const struct pci_device_id *ent) pci_enable_msi(pdev); /* request and enable interrupt */ - if (pci_dev_msi_enabled(pdev)) - err = request_threaded_irq(pdev->irq, - NULL, - mei_me_irq_thread_handler, - IRQF_ONESHOT, KBUILD_MODNAME, dev); - else - err = request_threaded_irq(pdev->irq, + irqflags = pci_dev_msi_enabled(pdev) ? IRQF_ONESHOT : IRQF_SHARED; + + err = request_threaded_irq(pdev->irq, mei_me_irq_quick_handler, mei_me_irq_thread_handler, - IRQF_SHARED, KBUILD_MODNAME, dev); - + irqflags, KBUILD_MODNAME, dev); if (err) { dev_err(&pdev->dev, "request_threaded_irq failure. irq = %d\n", pdev->irq); @@ -319,6 +320,7 @@ static int mei_me_pci_resume(struct device *device) { struct pci_dev *pdev = to_pci_dev(device); struct mei_device *dev; + unsigned int irqflags; int err; dev = pci_get_drvdata(pdev); @@ -327,17 +329,13 @@ static int mei_me_pci_resume(struct device *device) pci_enable_msi(pdev); + irqflags = pci_dev_msi_enabled(pdev) ? IRQF_ONESHOT : IRQF_SHARED; + /* request and enable interrupt */ - if (pci_dev_msi_enabled(pdev)) - err = request_threaded_irq(pdev->irq, - NULL, - mei_me_irq_thread_handler, - IRQF_ONESHOT, KBUILD_MODNAME, dev); - else - err = request_threaded_irq(pdev->irq, + err = request_threaded_irq(pdev->irq, mei_me_irq_quick_handler, mei_me_irq_thread_handler, - IRQF_SHARED, KBUILD_MODNAME, dev); + irqflags, KBUILD_MODNAME, dev); if (err) { dev_err(&pdev->dev, "request_threaded_irq failed: irq = %d.\n", diff --git a/drivers/misc/qcom-coincell.c b/drivers/misc/qcom-coincell.c new file mode 100644 index 000000000000..7b4a2da487a5 --- /dev/null +++ b/drivers/misc/qcom-coincell.c @@ -0,0 +1,152 @@ +/* Copyright (c) 2013, The Linux Foundation. All rights reserved. + * Copyright (c) 2015, Sony Mobile Communications Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/of.h> +#include <linux/regmap.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> + +struct qcom_coincell { + struct device *dev; + struct regmap *regmap; + u32 base_addr; +}; + +#define QCOM_COINCELL_REG_RSET 0x44 +#define QCOM_COINCELL_REG_VSET 0x45 +#define QCOM_COINCELL_REG_ENABLE 0x46 + +#define QCOM_COINCELL_ENABLE BIT(7) + +static const int qcom_rset_map[] = { 2100, 1700, 1200, 800 }; +static const int qcom_vset_map[] = { 2500, 3200, 3100, 3000 }; +/* NOTE: for pm8921 and others, voltage of 2500 is 16 (10000b), not 0 */ + +/* if enable==0, rset and vset are ignored */ +static int qcom_coincell_chgr_config(struct qcom_coincell *chgr, int rset, + int vset, bool enable) +{ + int i, j, rc; + + /* if disabling, just do that and skip other operations */ + if (!enable) + return regmap_write(chgr->regmap, + chgr->base_addr + QCOM_COINCELL_REG_ENABLE, 0); + + /* find index for current-limiting resistor */ + for (i = 0; i < ARRAY_SIZE(qcom_rset_map); i++) + if (rset == qcom_rset_map[i]) + break; + + if (i >= ARRAY_SIZE(qcom_rset_map)) { + dev_err(chgr->dev, "invalid rset-ohms value %d\n", rset); + return -EINVAL; + } + + /* find index for charge voltage */ + for (j = 0; j < ARRAY_SIZE(qcom_vset_map); j++) + if (vset == qcom_vset_map[j]) + break; + + if (j >= ARRAY_SIZE(qcom_vset_map)) { + dev_err(chgr->dev, "invalid vset-millivolts value %d\n", vset); + return -EINVAL; + } + + rc = regmap_write(chgr->regmap, + chgr->base_addr + QCOM_COINCELL_REG_RSET, i); + if (rc) { + /* + * This is mainly to flag a bad base_addr (reg) from dts. + * Other failures writing to the registers should be + * extremely rare, or indicative of problems that + * should be reported elsewhere (eg. spmi failure). + */ + dev_err(chgr->dev, "could not write to RSET register\n"); + return rc; + } + + rc = regmap_write(chgr->regmap, + chgr->base_addr + QCOM_COINCELL_REG_VSET, j); + if (rc) + return rc; + + /* set 'enable' register */ + return regmap_write(chgr->regmap, + chgr->base_addr + QCOM_COINCELL_REG_ENABLE, + QCOM_COINCELL_ENABLE); +} + +static int qcom_coincell_probe(struct platform_device *pdev) +{ + struct device_node *node = pdev->dev.of_node; + struct qcom_coincell chgr; + u32 rset, vset; + bool enable; + int rc; + + chgr.dev = &pdev->dev; + + chgr.regmap = dev_get_regmap(pdev->dev.parent, NULL); + if (!chgr.regmap) { + dev_err(chgr.dev, "Unable to get regmap\n"); + return -EINVAL; + } + + rc = of_property_read_u32(node, "reg", &chgr.base_addr); + if (rc) + return rc; + + enable = !of_property_read_bool(node, "qcom,charger-disable"); + + if (enable) { + rc = of_property_read_u32(node, "qcom,rset-ohms", &rset); + if (rc) { + dev_err(chgr.dev, + "can't find 'qcom,rset-ohms' in DT block"); + return rc; + } + + rc = of_property_read_u32(node, "qcom,vset-millivolts", &vset); + if (rc) { + dev_err(chgr.dev, + "can't find 'qcom,vset-millivolts' in DT block"); + return rc; + } + } + + return qcom_coincell_chgr_config(&chgr, rset, vset, enable); +} + +static const struct of_device_id qcom_coincell_match_table[] = { + { .compatible = "qcom,pm8941-coincell", }, + {} +}; + +MODULE_DEVICE_TABLE(of, qcom_coincell_match_table); + +static struct platform_driver qcom_coincell_driver = { + .driver = { + .name = "qcom-spmi-coincell", + .of_match_table = qcom_coincell_match_table, + }, + .probe = qcom_coincell_probe, +}; + +module_platform_driver(qcom_coincell_driver); + +MODULE_DESCRIPTION("Qualcomm PMIC coincell charger driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/misc/ti-st/st_kim.c b/drivers/misc/ti-st/st_kim.c index 5027b8ffae43..71b64550b591 100644 --- a/drivers/misc/ti-st/st_kim.c +++ b/drivers/misc/ti-st/st_kim.c @@ -36,8 +36,6 @@ #include <linux/skbuff.h> #include <linux/ti_wilink_st.h> #include <linux/module.h> -#include <linux/of.h> -#include <linux/of_device.h> #define MAX_ST_DEVICES 3 /* Imagine 1 on each UART for now */ static struct platform_device *st_kim_devices[MAX_ST_DEVICES]; @@ -45,9 +43,6 @@ static struct platform_device *st_kim_devices[MAX_ST_DEVICES]; /**********************************************************************/ /* internal functions */ -struct ti_st_plat_data *dt_pdata; -static struct ti_st_plat_data *get_platform_data(struct device *dev); - /** * st_get_plat_device - * function which returns the reference to the platform device @@ -469,12 +464,7 @@ long st_kim_start(void *kim_data) struct kim_data_s *kim_gdata = (struct kim_data_s *)kim_data; pr_info(" %s", __func__); - if (kim_gdata->kim_pdev->dev.of_node) { - pr_debug("use device tree data"); - pdata = dt_pdata; - } else { - pdata = kim_gdata->kim_pdev->dev.platform_data; - } + pdata = kim_gdata->kim_pdev->dev.platform_data; do { /* platform specific enabling code here */ @@ -482,9 +472,9 @@ long st_kim_start(void *kim_data) pdata->chip_enable(kim_gdata); /* Configure BT nShutdown to HIGH state */ - gpio_set_value(kim_gdata->nshutdown, GPIO_LOW); + gpio_set_value_cansleep(kim_gdata->nshutdown, GPIO_LOW); mdelay(5); /* FIXME: a proper toggle */ - gpio_set_value(kim_gdata->nshutdown, GPIO_HIGH); + gpio_set_value_cansleep(kim_gdata->nshutdown, GPIO_HIGH); mdelay(100); /* re-initialize the completion */ reinit_completion(&kim_gdata->ldisc_installed); @@ -534,18 +524,12 @@ long st_kim_stop(void *kim_data) { long err = 0; struct kim_data_s *kim_gdata = (struct kim_data_s *)kim_data; - struct ti_st_plat_data *pdata; + struct ti_st_plat_data *pdata = + kim_gdata->kim_pdev->dev.platform_data; struct tty_struct *tty = kim_gdata->core_data->tty; reinit_completion(&kim_gdata->ldisc_installed); - if (kim_gdata->kim_pdev->dev.of_node) { - pr_debug("use device tree data"); - pdata = dt_pdata; - } else - pdata = kim_gdata->kim_pdev->dev.platform_data; - - if (tty) { /* can be called before ldisc is installed */ /* Flush any pending characters in the driver and discipline. */ tty_ldisc_flush(tty); @@ -566,11 +550,11 @@ long st_kim_stop(void *kim_data) } /* By default configure BT nShutdown to LOW state */ - gpio_set_value(kim_gdata->nshutdown, GPIO_LOW); + gpio_set_value_cansleep(kim_gdata->nshutdown, GPIO_LOW); mdelay(1); - gpio_set_value(kim_gdata->nshutdown, GPIO_HIGH); + gpio_set_value_cansleep(kim_gdata->nshutdown, GPIO_HIGH); mdelay(1); - gpio_set_value(kim_gdata->nshutdown, GPIO_LOW); + gpio_set_value_cansleep(kim_gdata->nshutdown, GPIO_LOW); /* platform specific disable */ if (pdata->chip_disable) @@ -737,52 +721,13 @@ static const struct file_operations list_debugfs_fops = { * board-*.c file */ -static const struct of_device_id kim_of_match[] = { -{ - .compatible = "kim", - }, - {} -}; -MODULE_DEVICE_TABLE(of, kim_of_match); - -static struct ti_st_plat_data *get_platform_data(struct device *dev) -{ - struct device_node *np = dev->of_node; - const u32 *dt_property; - int len; - - dt_pdata = kzalloc(sizeof(*dt_pdata), GFP_KERNEL); - if (!dt_pdata) - return NULL; - - dt_property = of_get_property(np, "dev_name", &len); - if (dt_property) - memcpy(&dt_pdata->dev_name, dt_property, len); - of_property_read_u32(np, "nshutdown_gpio", - &dt_pdata->nshutdown_gpio); - of_property_read_u32(np, "flow_cntrl", &dt_pdata->flow_cntrl); - of_property_read_u32(np, "baud_rate", &dt_pdata->baud_rate); - - return dt_pdata; -} - static struct dentry *kim_debugfs_dir; static int kim_probe(struct platform_device *pdev) { struct kim_data_s *kim_gdata; - struct ti_st_plat_data *pdata; + struct ti_st_plat_data *pdata = pdev->dev.platform_data; int err; - if (pdev->dev.of_node) - pdata = get_platform_data(&pdev->dev); - else - pdata = pdev->dev.platform_data; - - if (pdata == NULL) { - dev_err(&pdev->dev, "Platform Data is missing\n"); - return -ENXIO; - } - if ((pdev->id != -1) && (pdev->id < MAX_ST_DEVICES)) { /* multiple devices could exist */ st_kim_devices[pdev->id] = pdev; @@ -863,16 +808,9 @@ err_core_init: static int kim_remove(struct platform_device *pdev) { /* free the GPIOs requested */ - struct ti_st_plat_data *pdata; + struct ti_st_plat_data *pdata = pdev->dev.platform_data; struct kim_data_s *kim_gdata; - if (pdev->dev.of_node) { - pr_debug("use device tree data"); - pdata = dt_pdata; - } else { - pdata = pdev->dev.platform_data; - } - kim_gdata = platform_get_drvdata(pdev); /* Free the Bluetooth/FM/GPIO @@ -890,22 +828,12 @@ static int kim_remove(struct platform_device *pdev) kfree(kim_gdata); kim_gdata = NULL; - kfree(dt_pdata); - dt_pdata = NULL; - return 0; } static int kim_suspend(struct platform_device *pdev, pm_message_t state) { - struct ti_st_plat_data *pdata; - - if (pdev->dev.of_node) { - pr_debug("use device tree data"); - pdata = dt_pdata; - } else { - pdata = pdev->dev.platform_data; - } + struct ti_st_plat_data *pdata = pdev->dev.platform_data; if (pdata->suspend) return pdata->suspend(pdev, state); @@ -915,14 +843,7 @@ static int kim_suspend(struct platform_device *pdev, pm_message_t state) static int kim_resume(struct platform_device *pdev) { - struct ti_st_plat_data *pdata; - - if (pdev->dev.of_node) { - pr_debug("use device tree data"); - pdata = dt_pdata; - } else { - pdata = pdev->dev.platform_data; - } + struct ti_st_plat_data *pdata = pdev->dev.platform_data; if (pdata->resume) return pdata->resume(pdev); @@ -939,8 +860,6 @@ static struct platform_driver kim_platform_driver = { .resume = kim_resume, .driver = { .name = "kim", - .owner = THIS_MODULE, - .of_match_table = of_match_ptr(kim_of_match), }, }; diff --git a/drivers/misc/ti-st/st_ll.c b/drivers/misc/ti-st/st_ll.c index 518e1b7f2f95..93b4d67cc4a3 100644 --- a/drivers/misc/ti-st/st_ll.c +++ b/drivers/misc/ti-st/st_ll.c @@ -26,7 +26,6 @@ #include <linux/ti_wilink_st.h> /**********************************************************************/ - /* internal functions */ static void send_ll_cmd(struct st_data_s *st_data, unsigned char cmd) @@ -54,13 +53,7 @@ static void ll_device_want_to_sleep(struct st_data_s *st_data) /* communicate to platform about chip asleep */ kim_data = st_data->kim_data; - if (kim_data->kim_pdev->dev.of_node) { - pr_debug("use device tree data"); - pdata = dt_pdata; - } else { - pdata = kim_data->kim_pdev->dev.platform_data; - } - + pdata = kim_data->kim_pdev->dev.platform_data; if (pdata->chip_asleep) pdata->chip_asleep(NULL); } @@ -93,13 +86,7 @@ static void ll_device_want_to_wakeup(struct st_data_s *st_data) /* communicate to platform about chip wakeup */ kim_data = st_data->kim_data; - if (kim_data->kim_pdev->dev.of_node) { - pr_debug("use device tree data"); - pdata = dt_pdata; - } else { - pdata = kim_data->kim_pdev->dev.platform_data; - } - + pdata = kim_data->kim_pdev->dev.platform_data; if (pdata->chip_awake) pdata->chip_awake(NULL); } diff --git a/drivers/misc/tsl2550.c b/drivers/misc/tsl2550.c index b00335652e52..87a13374fdc0 100644 --- a/drivers/misc/tsl2550.c +++ b/drivers/misc/tsl2550.c @@ -446,7 +446,6 @@ MODULE_DEVICE_TABLE(i2c, tsl2550_id); static struct i2c_driver tsl2550_driver = { .driver = { .name = TSL2550_DRV_NAME, - .owner = THIS_MODULE, .pm = TSL2550_PM_OPS, }, .probe = tsl2550_probe, diff --git a/drivers/misc/vmw_balloon.c b/drivers/misc/vmw_balloon.c index 191617492181..ffb56340d0c7 100644 --- a/drivers/misc/vmw_balloon.c +++ b/drivers/misc/vmw_balloon.c @@ -46,7 +46,7 @@ MODULE_AUTHOR("VMware, Inc."); MODULE_DESCRIPTION("VMware Memory Control (Balloon) Driver"); -MODULE_VERSION("1.2.1.3-k"); +MODULE_VERSION("1.3.0.0-k"); MODULE_ALIAS("dmi:*:svnVMware*:*"); MODULE_ALIAS("vmware_vmmemctl"); MODULE_LICENSE("GPL"); @@ -110,9 +110,18 @@ MODULE_LICENSE("GPL"); */ #define VMW_BALLOON_HV_PORT 0x5670 #define VMW_BALLOON_HV_MAGIC 0x456c6d6f -#define VMW_BALLOON_PROTOCOL_VERSION 2 #define VMW_BALLOON_GUEST_ID 1 /* Linux */ +enum vmwballoon_capabilities { + /* + * Bit 0 is reserved and not associated to any capability. + */ + VMW_BALLOON_BASIC_CMDS = (1 << 1), + VMW_BALLOON_BATCHED_CMDS = (1 << 2) +}; + +#define VMW_BALLOON_CAPABILITIES (VMW_BALLOON_BASIC_CMDS) + #define VMW_BALLOON_CMD_START 0 #define VMW_BALLOON_CMD_GET_TARGET 1 #define VMW_BALLOON_CMD_LOCK 2 @@ -120,32 +129,36 @@ MODULE_LICENSE("GPL"); #define VMW_BALLOON_CMD_GUEST_ID 4 /* error codes */ -#define VMW_BALLOON_SUCCESS 0 -#define VMW_BALLOON_FAILURE -1 -#define VMW_BALLOON_ERROR_CMD_INVALID 1 -#define VMW_BALLOON_ERROR_PPN_INVALID 2 -#define VMW_BALLOON_ERROR_PPN_LOCKED 3 -#define VMW_BALLOON_ERROR_PPN_UNLOCKED 4 -#define VMW_BALLOON_ERROR_PPN_PINNED 5 -#define VMW_BALLOON_ERROR_PPN_NOTNEEDED 6 -#define VMW_BALLOON_ERROR_RESET 7 -#define VMW_BALLOON_ERROR_BUSY 8 - -#define VMWARE_BALLOON_CMD(cmd, data, result) \ -({ \ - unsigned long __stat, __dummy1, __dummy2; \ - __asm__ __volatile__ ("inl %%dx" : \ - "=a"(__stat), \ - "=c"(__dummy1), \ - "=d"(__dummy2), \ - "=b"(result) : \ - "0"(VMW_BALLOON_HV_MAGIC), \ - "1"(VMW_BALLOON_CMD_##cmd), \ - "2"(VMW_BALLOON_HV_PORT), \ - "3"(data) : \ - "memory"); \ - result &= -1UL; \ - __stat & -1UL; \ +#define VMW_BALLOON_SUCCESS 0 +#define VMW_BALLOON_FAILURE -1 +#define VMW_BALLOON_ERROR_CMD_INVALID 1 +#define VMW_BALLOON_ERROR_PPN_INVALID 2 +#define VMW_BALLOON_ERROR_PPN_LOCKED 3 +#define VMW_BALLOON_ERROR_PPN_UNLOCKED 4 +#define VMW_BALLOON_ERROR_PPN_PINNED 5 +#define VMW_BALLOON_ERROR_PPN_NOTNEEDED 6 +#define VMW_BALLOON_ERROR_RESET 7 +#define VMW_BALLOON_ERROR_BUSY 8 + +#define VMW_BALLOON_SUCCESS_WITH_CAPABILITIES (0x03000000) + +#define VMWARE_BALLOON_CMD(cmd, data, result) \ +({ \ + unsigned long __status, __dummy1, __dummy2; \ + __asm__ __volatile__ ("inl %%dx" : \ + "=a"(__status), \ + "=c"(__dummy1), \ + "=d"(__dummy2), \ + "=b"(result) : \ + "0"(VMW_BALLOON_HV_MAGIC), \ + "1"(VMW_BALLOON_CMD_##cmd), \ + "2"(VMW_BALLOON_HV_PORT), \ + "3"(data) : \ + "memory"); \ + if (VMW_BALLOON_CMD_##cmd == VMW_BALLOON_CMD_START) \ + result = __dummy1; \ + result &= -1UL; \ + __status & -1UL; \ }) #ifdef CONFIG_DEBUG_FS @@ -223,11 +236,12 @@ static struct vmballoon balloon; */ static bool vmballoon_send_start(struct vmballoon *b) { - unsigned long status, dummy; + unsigned long status, capabilities; STATS_INC(b->stats.start); - status = VMWARE_BALLOON_CMD(START, VMW_BALLOON_PROTOCOL_VERSION, dummy); + status = VMWARE_BALLOON_CMD(START, VMW_BALLOON_CAPABILITIES, + capabilities); if (status == VMW_BALLOON_SUCCESS) return true; @@ -402,55 +416,37 @@ static void vmballoon_reset(struct vmballoon *b) } /* - * Allocate (or reserve) a page for the balloon and notify the host. If host - * refuses the page put it on "refuse" list and allocate another one until host - * is satisfied. "Refused" pages are released at the end of inflation cycle - * (when we allocate b->rate_alloc pages). + * Notify the host of a ballooned page. If host rejects the page put it on the + * refuse list, those refused page are then released at the end of the + * inflation cycle. */ -static int vmballoon_reserve_page(struct vmballoon *b, bool can_sleep) +static int vmballoon_lock_page(struct vmballoon *b, struct page *page) { - struct page *page; - gfp_t flags; - unsigned int hv_status; - int locked; - flags = can_sleep ? VMW_PAGE_ALLOC_CANSLEEP : VMW_PAGE_ALLOC_NOSLEEP; - - do { - if (!can_sleep) - STATS_INC(b->stats.alloc); - else - STATS_INC(b->stats.sleep_alloc); + int locked, hv_status; - page = alloc_page(flags); - if (!page) { - if (!can_sleep) - STATS_INC(b->stats.alloc_fail); - else - STATS_INC(b->stats.sleep_alloc_fail); - return -ENOMEM; - } + locked = vmballoon_send_lock_page(b, page_to_pfn(page), &hv_status); + if (locked > 0) { + STATS_INC(b->stats.refused_alloc); - /* inform monitor */ - locked = vmballoon_send_lock_page(b, page_to_pfn(page), &hv_status); - if (locked > 0) { - STATS_INC(b->stats.refused_alloc); - - if (hv_status == VMW_BALLOON_ERROR_RESET || - hv_status == VMW_BALLOON_ERROR_PPN_NOTNEEDED) { - __free_page(page); - return -EIO; - } + if (hv_status == VMW_BALLOON_ERROR_RESET || + hv_status == VMW_BALLOON_ERROR_PPN_NOTNEEDED) { + __free_page(page); + return -EIO; + } - /* - * Place page on the list of non-balloonable pages - * and retry allocation, unless we already accumulated - * too many of them, in which case take a breather. - */ + /* + * Place page on the list of non-balloonable pages + * and retry allocation, unless we already accumulated + * too many of them, in which case take a breather. + */ + if (b->n_refused_pages < VMW_BALLOON_MAX_REFUSED) { + b->n_refused_pages++; list_add(&page->lru, &b->refused_pages); - if (++b->n_refused_pages >= VMW_BALLOON_MAX_REFUSED) - return -EIO; + } else { + __free_page(page); } - } while (locked != 0); + return -EIO; + } /* track allocated page */ list_add(&page->lru, &b->pages); @@ -512,7 +508,7 @@ static void vmballoon_inflate(struct vmballoon *b) unsigned int i; unsigned int allocations = 0; int error = 0; - bool alloc_can_sleep = false; + gfp_t flags = VMW_PAGE_ALLOC_NOSLEEP; pr_debug("%s - size: %d, target %d\n", __func__, b->size, b->target); @@ -543,19 +539,16 @@ static void vmballoon_inflate(struct vmballoon *b) __func__, goal, rate, b->rate_alloc); for (i = 0; i < goal; i++) { + struct page *page; - error = vmballoon_reserve_page(b, alloc_can_sleep); - if (error) { - if (error != -ENOMEM) { - /* - * Not a page allocation failure, stop this - * cycle. Maybe we'll get new target from - * the host soon. - */ - break; - } + if (flags == VMW_PAGE_ALLOC_NOSLEEP) + STATS_INC(b->stats.alloc); + else + STATS_INC(b->stats.sleep_alloc); - if (alloc_can_sleep) { + page = alloc_page(flags); + if (!page) { + if (flags == VMW_PAGE_ALLOC_CANSLEEP) { /* * CANSLEEP page allocation failed, so guest * is under severe memory pressure. Quickly @@ -563,8 +556,10 @@ static void vmballoon_inflate(struct vmballoon *b) */ b->rate_alloc = max(b->rate_alloc / 2, VMW_BALLOON_RATE_ALLOC_MIN); + STATS_INC(b->stats.sleep_alloc_fail); break; } + STATS_INC(b->stats.alloc_fail); /* * NOSLEEP page allocation failed, so the guest is @@ -579,11 +574,16 @@ static void vmballoon_inflate(struct vmballoon *b) if (i >= b->rate_alloc) break; - alloc_can_sleep = true; + flags = VMW_PAGE_ALLOC_CANSLEEP; /* Lower rate for sleeping allocations. */ rate = b->rate_alloc; + continue; } + error = vmballoon_lock_page(b, page); + if (error) + break; + if (++allocations > VMW_BALLOON_YIELD_THRESHOLD) { cond_resched(); allocations = 0; diff --git a/drivers/misc/vmw_vmci/vmci_host.c b/drivers/misc/vmw_vmci/vmci_host.c index a721b5d8a9da..9ec262a52656 100644 --- a/drivers/misc/vmw_vmci/vmci_host.c +++ b/drivers/misc/vmw_vmci/vmci_host.c @@ -1031,14 +1031,9 @@ int __init vmci_host_init(void) void __exit vmci_host_exit(void) { - int error; - vmci_host_device_initialized = false; - error = misc_deregister(&vmci_host_miscdev); - if (error) - pr_warn("Error unregistering character device: %d\n", error); - + misc_deregister(&vmci_host_miscdev); vmci_ctx_destroy(host_context); vmci_qp_broker_exit(); |