summaryrefslogtreecommitdiff
path: root/drivers/misc
diff options
context:
space:
mode:
authorAndré Fabian Silva Delgado <emulatorman@parabola.nu>2015-12-15 14:52:16 -0300
committerAndré Fabian Silva Delgado <emulatorman@parabola.nu>2015-12-15 14:52:16 -0300
commit8d91c1e411f55d7ea91b1183a2e9f8088fb4d5be (patch)
treee9891aa6c295060d065adffd610c4f49ecf884f3 /drivers/misc
parenta71852147516bc1cb5b0b3cbd13639bfd4022dc8 (diff)
Linux-libre 4.3.2-gnu
Diffstat (limited to 'drivers/misc')
-rw-r--r--drivers/misc/Kconfig10
-rw-r--r--drivers/misc/Makefile1
-rw-r--r--drivers/misc/ad525x_dpot-i2c.c1
-rw-r--r--drivers/misc/apds990x.c1
-rw-r--r--drivers/misc/bh1770glc.c1
-rw-r--r--drivers/misc/bmp085-i2c.c1
-rw-r--r--drivers/misc/cxl/Kconfig7
-rw-r--r--drivers/misc/cxl/Makefile2
-rw-r--r--drivers/misc/cxl/api.c58
-rw-r--r--drivers/misc/cxl/context.c25
-rw-r--r--drivers/misc/cxl/cxl.h95
-rw-r--r--drivers/misc/cxl/debugfs.c2
-rw-r--r--drivers/misc/cxl/file.c40
-rw-r--r--drivers/misc/cxl/irq.c52
-rw-r--r--drivers/misc/cxl/main.c1
-rw-r--r--drivers/misc/cxl/native.c121
-rw-r--r--drivers/misc/cxl/pci.c618
-rw-r--r--drivers/misc/cxl/sysfs.c35
-rw-r--r--drivers/misc/cxl/trace.h10
-rw-r--r--drivers/misc/cxl/vphb.c40
-rw-r--r--drivers/misc/ds1682.c12
-rw-r--r--drivers/misc/eeprom/Kconfig13
-rw-r--r--drivers/misc/eeprom/Makefile1
-rw-r--r--drivers/misc/eeprom/at24.c38
-rw-r--r--drivers/misc/eeprom/eeprom.c5
-rw-r--r--drivers/misc/eeprom/eeprom_93xx46.c14
-rw-r--r--drivers/misc/eeprom/max6875.c7
-rw-r--r--drivers/misc/eeprom/sunxi_sid.c156
-rw-r--r--drivers/misc/genwqe/card_dev.c2
-rw-r--r--drivers/misc/isl29003.c1
-rw-r--r--drivers/misc/lis3lv02d/lis3lv02d_i2c.c1
-rw-r--r--drivers/misc/mei/Makefile2
-rw-r--r--drivers/misc/mei/bus-fixup.c306
-rw-r--r--drivers/misc/mei/bus.c1008
-rw-r--r--drivers/misc/mei/client.c333
-rw-r--r--drivers/misc/mei/client.h8
-rw-r--r--drivers/misc/mei/debugfs.c9
-rw-r--r--drivers/misc/mei/hbm.c330
-rw-r--r--drivers/misc/mei/hbm.h3
-rw-r--r--drivers/misc/mei/hw-me-regs.h27
-rw-r--r--drivers/misc/mei/hw-me.c499
-rw-r--r--drivers/misc/mei/hw-me.h8
-rw-r--r--drivers/misc/mei/hw.h134
-rw-r--r--drivers/misc/mei/init.c3
-rw-r--r--drivers/misc/mei/interrupt.c27
-rw-r--r--drivers/misc/mei/main.c96
-rw-r--r--drivers/misc/mei/mei_dev.h47
-rw-r--r--drivers/misc/mei/nfc.c415
-rw-r--r--drivers/misc/mei/pci-me.c32
-rw-r--r--drivers/misc/mei/wd.c1
-rw-r--r--drivers/misc/qcom-coincell.c152
-rw-r--r--drivers/misc/sgi-xp/xpc_uv.c2
-rw-r--r--drivers/misc/sram.c8
-rw-r--r--drivers/misc/ti-st/st_kim.c105
-rw-r--r--drivers/misc/ti-st/st_ll.c17
-rw-r--r--drivers/misc/tsl2550.c1
-rw-r--r--drivers/misc/vmw_balloon.c170
-rw-r--r--drivers/misc/vmw_vmci/vmci_host.c7
58 files changed, 3551 insertions, 1570 deletions
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index 42c385259..ccccc2943 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 d056fb718..537d7f3b7 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 705b881e1..d11187d36 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 3739ffa9c..a3e789b85 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 b756381b8..753d7ecda 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 a7c16295b..f35c218aa 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/Kconfig b/drivers/misc/cxl/Kconfig
index b6db9ebd5..8756d06e2 100644
--- a/drivers/misc/cxl/Kconfig
+++ b/drivers/misc/cxl/Kconfig
@@ -11,11 +11,16 @@ config CXL_KERNEL_API
bool
default n
+config CXL_EEH
+ bool
+ default n
+
config CXL
tristate "Support for IBM Coherent Accelerators (CXL)"
- depends on PPC_POWERNV && PCI_MSI
+ depends on PPC_POWERNV && PCI_MSI && EEH
select CXL_BASE
select CXL_KERNEL_API
+ select CXL_EEH
default m
help
Select this option to enable driver support for IBM Coherent
diff --git a/drivers/misc/cxl/Makefile b/drivers/misc/cxl/Makefile
index 14e3f8219..6982f603f 100644
--- a/drivers/misc/cxl/Makefile
+++ b/drivers/misc/cxl/Makefile
@@ -1,3 +1,5 @@
+ccflags-y := -Werror -Wno-unused-const-variable
+
cxl-y += main.o file.o irq.o fault.o native.o
cxl-y += context.o sysfs.o debugfs.o pci.o trace.o
cxl-y += vphb.o api.o
diff --git a/drivers/misc/cxl/api.c b/drivers/misc/cxl/api.c
index 4224a6acf..103baf0e0 100644
--- a/drivers/misc/cxl/api.c
+++ b/drivers/misc/cxl/api.c
@@ -12,11 +12,13 @@
#include <linux/anon_inodes.h>
#include <linux/file.h>
#include <misc/cxl.h>
+#include <linux/fs.h>
#include "cxl.h"
struct cxl_context *cxl_dev_context_init(struct pci_dev *dev)
{
+ struct address_space *mapping;
struct cxl_afu *afu;
struct cxl_context *ctx;
int rc;
@@ -25,19 +27,42 @@ struct cxl_context *cxl_dev_context_init(struct pci_dev *dev)
get_device(&afu->dev);
ctx = cxl_context_alloc();
- if (IS_ERR(ctx))
- return ctx;
+ if (IS_ERR(ctx)) {
+ rc = PTR_ERR(ctx);
+ goto err_dev;
+ }
- /* Make it a slave context. We can promote it later? */
- rc = cxl_context_init(ctx, afu, false, NULL);
- if (rc) {
- kfree(ctx);
- put_device(&afu->dev);
- return ERR_PTR(-ENOMEM);
+ ctx->kernelapi = true;
+
+ /*
+ * Make our own address space since we won't have one from the
+ * filesystem like the user api has, and even if we do associate a file
+ * with this context we don't want to use the global anonymous inode's
+ * address space as that can invalidate unrelated users:
+ */
+ mapping = kmalloc(sizeof(struct address_space), GFP_KERNEL);
+ if (!mapping) {
+ rc = -ENOMEM;
+ goto err_ctx;
}
+ address_space_init_once(mapping);
+
+ /* Make it a slave context. We can promote it later? */
+ rc = cxl_context_init(ctx, afu, false, mapping);
+ if (rc)
+ goto err_mapping;
+
cxl_assign_psn_space(ctx);
return ctx;
+
+err_mapping:
+ kfree(mapping);
+err_ctx:
+ kfree(ctx);
+err_dev:
+ put_device(&afu->dev);
+ return ERR_PTR(rc);
}
EXPORT_SYMBOL_GPL(cxl_dev_context_init);
@@ -80,6 +105,7 @@ EXPORT_SYMBOL_GPL(cxl_allocate_afu_irqs);
void cxl_free_afu_irqs(struct cxl_context *ctx)
{
+ afu_irq_name_free(ctx);
cxl_release_irq_ranges(&ctx->irqs, ctx->afu->adapter);
}
EXPORT_SYMBOL_GPL(cxl_free_afu_irqs);
@@ -255,9 +281,16 @@ struct file *cxl_get_fd(struct cxl_context *ctx, struct file_operations *fops,
file = anon_inode_getfile("cxl", fops, ctx, flags);
if (IS_ERR(file))
- put_unused_fd(fdtmp);
+ goto err_fd;
+
+ file->f_mapping = ctx->mapping;
+
*fd = fdtmp;
return file;
+
+err_fd:
+ put_unused_fd(fdtmp);
+ return NULL;
}
EXPORT_SYMBOL_GPL(cxl_get_fd);
@@ -327,3 +360,10 @@ int cxl_afu_reset(struct cxl_context *ctx)
return cxl_afu_check_and_enable(afu);
}
EXPORT_SYMBOL_GPL(cxl_afu_reset);
+
+void cxl_perst_reloads_same_image(struct cxl_afu *afu,
+ bool perst_reloads_same_image)
+{
+ afu->adapter->perst_same_image = perst_reloads_same_image;
+}
+EXPORT_SYMBOL_GPL(cxl_perst_reloads_same_image);
diff --git a/drivers/misc/cxl/context.c b/drivers/misc/cxl/context.c
index 128714862..2faa1270d 100644
--- a/drivers/misc/cxl/context.c
+++ b/drivers/misc/cxl/context.c
@@ -126,6 +126,18 @@ static int cxl_mmap_fault(struct vm_area_struct *vma, struct vm_fault *vmf)
if (ctx->status != STARTED) {
mutex_unlock(&ctx->status_mutex);
pr_devel("%s: Context not started, failing problem state access\n", __func__);
+ if (ctx->mmio_err_ff) {
+ if (!ctx->ff_page) {
+ ctx->ff_page = alloc_page(GFP_USER);
+ if (!ctx->ff_page)
+ return VM_FAULT_OOM;
+ memset(page_address(ctx->ff_page), 0xff, PAGE_SIZE);
+ }
+ get_page(ctx->ff_page);
+ vmf->page = ctx->ff_page;
+ vma->vm_page_prot = pgprot_cached(vma->vm_page_prot);
+ return 0;
+ }
return VM_FAULT_SIGBUS;
}
@@ -193,7 +205,11 @@ int __detach_context(struct cxl_context *ctx)
if (status != STARTED)
return -EBUSY;
- WARN_ON(cxl_detach_process(ctx));
+ /* Only warn if we detached while the link was OK.
+ * If detach fails when hw is down, we don't care.
+ */
+ WARN_ON(cxl_detach_process(ctx) &&
+ cxl_adapter_link_ok(ctx->afu->adapter));
flush_work(&ctx->fault_work); /* Only needed for dedicated process */
put_pid(ctx->pid);
cxl_ctx_put();
@@ -253,7 +269,14 @@ static void reclaim_ctx(struct rcu_head *rcu)
struct cxl_context *ctx = container_of(rcu, struct cxl_context, rcu);
free_page((u64)ctx->sstp);
+ if (ctx->ff_page)
+ __free_page(ctx->ff_page);
ctx->sstp = NULL;
+ if (ctx->kernelapi)
+ kfree(ctx->mapping);
+
+ if (ctx->irq_bitmap)
+ kfree(ctx->irq_bitmap);
kfree(ctx);
}
diff --git a/drivers/misc/cxl/cxl.h b/drivers/misc/cxl/cxl.h
index 4fd66cabd..0cfb9c129 100644
--- a/drivers/misc/cxl/cxl.h
+++ b/drivers/misc/cxl/cxl.h
@@ -34,7 +34,7 @@ extern uint cxl_verbose;
* Bump version each time a user API change is made, whether it is
* backwards compatible ot not.
*/
-#define CXL_API_VERSION 1
+#define CXL_API_VERSION 2
#define CXL_API_VERSION_COMPATIBLE 1
/*
@@ -83,8 +83,10 @@ static const cxl_p1_reg_t CXL_PSL_AFUSEL = {0x00B0};
/* 0x00C0:7EFF Implementation dependent area */
static const cxl_p1_reg_t CXL_PSL_FIR1 = {0x0100};
static const cxl_p1_reg_t CXL_PSL_FIR2 = {0x0108};
+static const cxl_p1_reg_t CXL_PSL_Timebase = {0x0110};
static const cxl_p1_reg_t CXL_PSL_VERSION = {0x0118};
static const cxl_p1_reg_t CXL_PSL_RESLCKTO = {0x0128};
+static const cxl_p1_reg_t CXL_PSL_TB_CTLSTAT = {0x0140};
static const cxl_p1_reg_t CXL_PSL_FIR_CNTL = {0x0148};
static const cxl_p1_reg_t CXL_PSL_DSNDCTL = {0x0150};
static const cxl_p1_reg_t CXL_PSL_SNWRALLOC = {0x0158};
@@ -152,6 +154,9 @@ static const cxl_p2n_reg_t CXL_PSL_WED_An = {0x0A0};
#define CXL_PSL_SPAP_Size_Shift 4
#define CXL_PSL_SPAP_V 0x0000000000000001ULL
+/****** CXL_PSL_Control ****************************************************/
+#define CXL_PSL_Control_tb 0x0000000000000001ULL
+
/****** CXL_PSL_DLCNTL *****************************************************/
#define CXL_PSL_DLCNTL_D (0x1ull << (63-28))
#define CXL_PSL_DLCNTL_C (0x1ull << (63-29))
@@ -418,6 +423,9 @@ struct cxl_context {
/* Used to unmap any mmaps when force detaching */
struct address_space *mapping;
struct mutex mapping_lock;
+ struct page *ff_page;
+ bool mmio_err_ff;
+ bool kernelapi;
spinlock_t sste_lock; /* Protects segment table entries */
struct cxl_sste *sstp;
@@ -493,6 +501,7 @@ struct cxl {
bool user_image_loaded;
bool perst_loads_image;
bool perst_select_user;
+ bool perst_same_image;
};
int cxl_alloc_one_irq(struct cxl *adapter);
@@ -531,16 +540,33 @@ struct cxl_process_element {
__be32 software_state;
} __packed;
+static inline bool cxl_adapter_link_ok(struct cxl *cxl)
+{
+ struct pci_dev *pdev;
+
+ pdev = to_pci_dev(cxl->dev.parent);
+ return !pci_channel_offline(pdev);
+}
+
static inline void __iomem *_cxl_p1_addr(struct cxl *cxl, cxl_p1_reg_t reg)
{
WARN_ON(!cpu_has_feature(CPU_FTR_HVMODE));
return cxl->p1_mmio + cxl_reg_off(reg);
}
-#define cxl_p1_write(cxl, reg, val) \
- out_be64(_cxl_p1_addr(cxl, reg), val)
-#define cxl_p1_read(cxl, reg) \
- in_be64(_cxl_p1_addr(cxl, reg))
+static inline void cxl_p1_write(struct cxl *cxl, cxl_p1_reg_t reg, u64 val)
+{
+ if (likely(cxl_adapter_link_ok(cxl)))
+ out_be64(_cxl_p1_addr(cxl, reg), val);
+}
+
+static inline u64 cxl_p1_read(struct cxl *cxl, cxl_p1_reg_t reg)
+{
+ if (likely(cxl_adapter_link_ok(cxl)))
+ return in_be64(_cxl_p1_addr(cxl, reg));
+ else
+ return ~0ULL;
+}
static inline void __iomem *_cxl_p1n_addr(struct cxl_afu *afu, cxl_p1n_reg_t reg)
{
@@ -548,26 +574,56 @@ static inline void __iomem *_cxl_p1n_addr(struct cxl_afu *afu, cxl_p1n_reg_t reg
return afu->p1n_mmio + cxl_reg_off(reg);
}
-#define cxl_p1n_write(afu, reg, val) \
- out_be64(_cxl_p1n_addr(afu, reg), val)
-#define cxl_p1n_read(afu, reg) \
- in_be64(_cxl_p1n_addr(afu, reg))
+static inline void cxl_p1n_write(struct cxl_afu *afu, cxl_p1n_reg_t reg, u64 val)
+{
+ if (likely(cxl_adapter_link_ok(afu->adapter)))
+ out_be64(_cxl_p1n_addr(afu, reg), val);
+}
+
+static inline u64 cxl_p1n_read(struct cxl_afu *afu, cxl_p1n_reg_t reg)
+{
+ if (likely(cxl_adapter_link_ok(afu->adapter)))
+ return in_be64(_cxl_p1n_addr(afu, reg));
+ else
+ return ~0ULL;
+}
static inline void __iomem *_cxl_p2n_addr(struct cxl_afu *afu, cxl_p2n_reg_t reg)
{
return afu->p2n_mmio + cxl_reg_off(reg);
}
-#define cxl_p2n_write(afu, reg, val) \
- out_be64(_cxl_p2n_addr(afu, reg), val)
-#define cxl_p2n_read(afu, reg) \
- in_be64(_cxl_p2n_addr(afu, reg))
+static inline void cxl_p2n_write(struct cxl_afu *afu, cxl_p2n_reg_t reg, u64 val)
+{
+ if (likely(cxl_adapter_link_ok(afu->adapter)))
+ out_be64(_cxl_p2n_addr(afu, reg), val);
+}
+
+static inline u64 cxl_p2n_read(struct cxl_afu *afu, cxl_p2n_reg_t reg)
+{
+ if (likely(cxl_adapter_link_ok(afu->adapter)))
+ return in_be64(_cxl_p2n_addr(afu, reg));
+ else
+ return ~0ULL;
+}
+static inline u64 cxl_afu_cr_read64(struct cxl_afu *afu, int cr, u64 off)
+{
+ if (likely(cxl_adapter_link_ok(afu->adapter)))
+ return in_le64((afu)->afu_desc_mmio + (afu)->crs_offset +
+ ((cr) * (afu)->crs_len) + (off));
+ else
+ return ~0ULL;
+}
-#define cxl_afu_cr_read64(afu, cr, off) \
- in_le64((afu)->afu_desc_mmio + (afu)->crs_offset + ((cr) * (afu)->crs_len) + (off))
-#define cxl_afu_cr_read32(afu, cr, off) \
- in_le32((afu)->afu_desc_mmio + (afu)->crs_offset + ((cr) * (afu)->crs_len) + (off))
+static inline u32 cxl_afu_cr_read32(struct cxl_afu *afu, int cr, u64 off)
+{
+ if (likely(cxl_adapter_link_ok(afu->adapter)))
+ return in_le32((afu)->afu_desc_mmio + (afu)->crs_offset +
+ ((cr) * (afu)->crs_len) + (off));
+ else
+ return 0xffffffff;
+}
u16 cxl_afu_cr_read16(struct cxl_afu *afu, int cr, u64 off);
u8 cxl_afu_cr_read8(struct cxl_afu *afu, int cr, u64 off);
@@ -585,6 +641,9 @@ void unregister_cxl_calls(struct cxl_calls *calls);
int cxl_alloc_adapter_nr(struct cxl *adapter);
void cxl_remove_adapter_nr(struct cxl *adapter);
+int cxl_alloc_spa(struct cxl_afu *afu);
+void cxl_release_spa(struct cxl_afu *afu);
+
int cxl_file_init(void);
void cxl_file_exit(void);
int cxl_register_adapter(struct cxl *adapter);
@@ -618,6 +677,7 @@ int cxl_register_serr_irq(struct cxl_afu *afu);
void cxl_release_serr_irq(struct cxl_afu *afu);
int afu_register_irqs(struct cxl_context *ctx, u32 count);
void afu_release_irqs(struct cxl_context *ctx, void *cookie);
+void afu_irq_name_free(struct cxl_context *ctx);
irqreturn_t cxl_slice_irq_err(int irq, void *data);
int cxl_debugfs_init(void);
@@ -675,6 +735,7 @@ int cxl_psl_purge(struct cxl_afu *afu);
void cxl_stop_trace(struct cxl *cxl);
int cxl_pci_vphb_add(struct cxl_afu *afu);
+void cxl_pci_vphb_reconfigure(struct cxl_afu *afu);
void cxl_pci_vphb_remove(struct cxl_afu *afu);
extern struct pci_driver cxl_pci_driver;
diff --git a/drivers/misc/cxl/debugfs.c b/drivers/misc/cxl/debugfs.c
index 825c41258..18df6f44a 100644
--- a/drivers/misc/cxl/debugfs.c
+++ b/drivers/misc/cxl/debugfs.c
@@ -48,7 +48,7 @@ DEFINE_SIMPLE_ATTRIBUTE(fops_io_x64, debugfs_io_u64_get, debugfs_io_u64_set, "0x
static struct dentry *debugfs_create_io_x64(const char *name, umode_t mode,
struct dentry *parent, u64 __iomem *value)
{
- return debugfs_create_file(name, mode, parent, (void *)value, &fops_io_x64);
+ return debugfs_create_file(name, mode, parent, (void __force *)value, &fops_io_x64);
}
int cxl_debugfs_adapter_add(struct cxl *adapter)
diff --git a/drivers/misc/cxl/file.c b/drivers/misc/cxl/file.c
index e3f4b6952..7ccd2998b 100644
--- a/drivers/misc/cxl/file.c
+++ b/drivers/misc/cxl/file.c
@@ -73,6 +73,11 @@ static int __afu_open(struct inode *inode, struct file *file, bool master)
if (!afu->current_mode)
goto err_put_afu;
+ if (!cxl_adapter_link_ok(adapter)) {
+ rc = -EIO;
+ goto err_put_afu;
+ }
+
if (!(ctx = cxl_context_alloc())) {
rc = -ENOMEM;
goto err_put_afu;
@@ -115,9 +120,16 @@ int afu_release(struct inode *inode, struct file *file)
__func__, ctx->pe);
cxl_context_detach(ctx);
- mutex_lock(&ctx->mapping_lock);
- ctx->mapping = NULL;
- mutex_unlock(&ctx->mapping_lock);
+
+ /*
+ * Delete the context's mapping pointer, unless it's created by the
+ * kernel API, in which case leave it so it can be freed by reclaim_ctx()
+ */
+ if (!ctx->kernelapi) {
+ mutex_lock(&ctx->mapping_lock);
+ ctx->mapping = NULL;
+ mutex_unlock(&ctx->mapping_lock);
+ }
put_device(&ctx->afu->dev);
@@ -179,6 +191,8 @@ static long afu_ioctl_start_work(struct cxl_context *ctx,
if (work.flags & CXL_START_WORK_AMR)
amr = work.amr & mfspr(SPRN_UAMOR);
+ ctx->mmio_err_ff = !!(work.flags & CXL_START_WORK_ERR_FF);
+
/*
* We grab the PID here and not in the file open to allow for the case
* where a process (master, some daemon, etc) has opened the chardev on
@@ -238,6 +252,9 @@ long afu_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
if (ctx->status == CLOSED)
return -EIO;
+ if (!cxl_adapter_link_ok(ctx->afu->adapter))
+ return -EIO;
+
pr_devel("afu_ioctl\n");
switch (cmd) {
case CXL_IOCTL_START_WORK:
@@ -251,7 +268,7 @@ long afu_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
return -EINVAL;
}
-long afu_compat_ioctl(struct file *file, unsigned int cmd,
+static long afu_compat_ioctl(struct file *file, unsigned int cmd,
unsigned long arg)
{
return afu_ioctl(file, cmd, arg);
@@ -265,6 +282,9 @@ int afu_mmap(struct file *file, struct vm_area_struct *vm)
if (ctx->status != STARTED)
return -EIO;
+ if (!cxl_adapter_link_ok(ctx->afu->adapter))
+ return -EIO;
+
return cxl_context_iomap(ctx, vm);
}
@@ -309,6 +329,9 @@ ssize_t afu_read(struct file *file, char __user *buf, size_t count,
int rc;
DEFINE_WAIT(wait);
+ if (!cxl_adapter_link_ok(ctx->afu->adapter))
+ return -EIO;
+
if (count < CXL_READ_MIN_SIZE)
return -EINVAL;
@@ -319,6 +342,11 @@ ssize_t afu_read(struct file *file, char __user *buf, size_t count,
if (ctx_event_pending(ctx))
break;
+ if (!cxl_adapter_link_ok(ctx->afu->adapter)) {
+ rc = -EIO;
+ goto out;
+ }
+
if (file->f_flags & O_NONBLOCK) {
rc = -EAGAIN;
goto out;
@@ -396,7 +424,7 @@ const struct file_operations afu_fops = {
.mmap = afu_mmap,
};
-const struct file_operations afu_master_fops = {
+static const struct file_operations afu_master_fops = {
.owner = THIS_MODULE,
.open = afu_master_open,
.poll = afu_poll,
@@ -519,7 +547,7 @@ int __init cxl_file_init(void)
* If these change we really need to update API. Either change some
* flags or update API version number CXL_API_VERSION.
*/
- BUILD_BUG_ON(CXL_API_VERSION != 1);
+ BUILD_BUG_ON(CXL_API_VERSION != 2);
BUILD_BUG_ON(sizeof(struct cxl_ioctl_start_work) != 64);
BUILD_BUG_ON(sizeof(struct cxl_event_header) != 8);
BUILD_BUG_ON(sizeof(struct cxl_event_afu_interrupt) != 8);
diff --git a/drivers/misc/cxl/irq.c b/drivers/misc/cxl/irq.c
index 680cd2634..09a406058 100644
--- a/drivers/misc/cxl/irq.c
+++ b/drivers/misc/cxl/irq.c
@@ -30,12 +30,12 @@ static irqreturn_t handle_psl_slice_error(struct cxl_context *ctx, u64 dsisr, u6
serr = cxl_p1n_read(ctx->afu, CXL_PSL_SERR_An);
afu_debug = cxl_p1n_read(ctx->afu, CXL_AFU_DEBUG_An);
- dev_crit(&ctx->afu->dev, "PSL ERROR STATUS: 0x%.16llx\n", errstat);
- dev_crit(&ctx->afu->dev, "PSL_FIR1: 0x%.16llx\n", fir1);
- dev_crit(&ctx->afu->dev, "PSL_FIR2: 0x%.16llx\n", fir2);
- dev_crit(&ctx->afu->dev, "PSL_SERR_An: 0x%.16llx\n", serr);
- dev_crit(&ctx->afu->dev, "PSL_FIR_SLICE_An: 0x%.16llx\n", fir_slice);
- dev_crit(&ctx->afu->dev, "CXL_PSL_AFU_DEBUG_An: 0x%.16llx\n", afu_debug);
+ dev_crit(&ctx->afu->dev, "PSL ERROR STATUS: 0x%016llx\n", errstat);
+ dev_crit(&ctx->afu->dev, "PSL_FIR1: 0x%016llx\n", fir1);
+ dev_crit(&ctx->afu->dev, "PSL_FIR2: 0x%016llx\n", fir2);
+ dev_crit(&ctx->afu->dev, "PSL_SERR_An: 0x%016llx\n", serr);
+ dev_crit(&ctx->afu->dev, "PSL_FIR_SLICE_An: 0x%016llx\n", fir_slice);
+ dev_crit(&ctx->afu->dev, "CXL_PSL_AFU_DEBUG_An: 0x%016llx\n", afu_debug);
dev_crit(&ctx->afu->dev, "STOPPING CXL TRACE\n");
cxl_stop_trace(ctx->afu->adapter);
@@ -54,10 +54,10 @@ irqreturn_t cxl_slice_irq_err(int irq, void *data)
fir_slice = cxl_p1n_read(afu, CXL_PSL_FIR_SLICE_An);
errstat = cxl_p2n_read(afu, CXL_PSL_ErrStat_An);
afu_debug = cxl_p1n_read(afu, CXL_AFU_DEBUG_An);
- dev_crit(&afu->dev, "PSL_SERR_An: 0x%.16llx\n", serr);
- dev_crit(&afu->dev, "PSL_FIR_SLICE_An: 0x%.16llx\n", fir_slice);
- dev_crit(&afu->dev, "CXL_PSL_ErrStat_An: 0x%.16llx\n", errstat);
- dev_crit(&afu->dev, "CXL_PSL_AFU_DEBUG_An: 0x%.16llx\n", afu_debug);
+ dev_crit(&afu->dev, "PSL_SERR_An: 0x%016llx\n", serr);
+ dev_crit(&afu->dev, "PSL_FIR_SLICE_An: 0x%016llx\n", fir_slice);
+ dev_crit(&afu->dev, "CXL_PSL_ErrStat_An: 0x%016llx\n", errstat);
+ dev_crit(&afu->dev, "CXL_PSL_AFU_DEBUG_An: 0x%016llx\n", afu_debug);
cxl_p1n_write(afu, CXL_PSL_SERR_An, serr);
@@ -72,7 +72,7 @@ static irqreturn_t cxl_irq_err(int irq, void *data)
WARN(1, "CXL ERROR interrupt %i\n", irq);
err_ivte = cxl_p1_read(adapter, CXL_PSL_ErrIVTE);
- dev_crit(&adapter->dev, "PSL_ErrIVTE: 0x%.16llx\n", err_ivte);
+ dev_crit(&adapter->dev, "PSL_ErrIVTE: 0x%016llx\n", err_ivte);
dev_crit(&adapter->dev, "STOPPING CXL TRACE\n");
cxl_stop_trace(adapter);
@@ -80,7 +80,7 @@ static irqreturn_t cxl_irq_err(int irq, void *data)
fir1 = cxl_p1_read(adapter, CXL_PSL_FIR1);
fir2 = cxl_p1_read(adapter, CXL_PSL_FIR2);
- dev_crit(&adapter->dev, "PSL_FIR1: 0x%.16llx\nPSL_FIR2: 0x%.16llx\n", fir1, fir2);
+ dev_crit(&adapter->dev, "PSL_FIR1: 0x%016llx\nPSL_FIR2: 0x%016llx\n", fir1, fir2);
return IRQ_HANDLED;
}
@@ -147,7 +147,7 @@ static irqreturn_t cxl_irq(int irq, void *data, struct cxl_irq_info *irq_info)
if (dsisr & CXL_PSL_DSISR_An_PE)
return handle_psl_slice_error(ctx, dsisr, irq_info->errstat);
if (dsisr & CXL_PSL_DSISR_An_AE) {
- pr_devel("CXL interrupt: AFU Error %.llx\n", irq_info->afu_err);
+ pr_devel("CXL interrupt: AFU Error 0x%016llx\n", irq_info->afu_err);
if (ctx->pending_afu_err) {
/*
@@ -158,7 +158,7 @@ static irqreturn_t cxl_irq(int irq, void *data, struct cxl_irq_info *irq_info)
* probably best that we log them somewhere:
*/
dev_err_ratelimited(&ctx->afu->dev, "CXL AFU Error "
- "undelivered to pe %i: %.llx\n",
+ "undelivered to pe %i: 0x%016llx\n",
ctx->pe, irq_info->afu_err);
} else {
spin_lock(&ctx->lock);
@@ -211,8 +211,8 @@ static irqreturn_t cxl_irq_multiplexed(int irq, void *data)
}
rcu_read_unlock();
- WARN(1, "Unable to demultiplex CXL PSL IRQ for PE %i DSISR %.16llx DAR"
- " %.16llx\n(Possible AFU HW issue - was a term/remove acked"
+ WARN(1, "Unable to demultiplex CXL PSL IRQ for PE %i DSISR %016llx DAR"
+ " %016llx\n(Possible AFU HW issue - was a term/remove acked"
" with outstanding transactions?)\n", ph, irq_info.dsisr,
irq_info.dar);
return fail_psl_irq(afu, &irq_info);
@@ -341,6 +341,9 @@ int cxl_register_psl_err_irq(struct cxl *adapter)
void cxl_release_psl_err_irq(struct cxl *adapter)
{
+ if (adapter->err_virq != irq_find_mapping(NULL, adapter->err_hwirq))
+ return;
+
cxl_p1_write(adapter, CXL_PSL_ErrIVTE, 0x0000000000000000);
cxl_unmap_irq(adapter->err_virq, adapter);
cxl_release_one_irq(adapter, adapter->err_hwirq);
@@ -374,6 +377,9 @@ int cxl_register_serr_irq(struct cxl_afu *afu)
void cxl_release_serr_irq(struct cxl_afu *afu)
{
+ if (afu->serr_virq != irq_find_mapping(NULL, afu->serr_hwirq))
+ return;
+
cxl_p1n_write(afu, CXL_PSL_SERR_An, 0x0000000000000000);
cxl_unmap_irq(afu->serr_virq, afu);
cxl_release_one_irq(afu->adapter, afu->serr_hwirq);
@@ -400,6 +406,9 @@ int cxl_register_psl_irq(struct cxl_afu *afu)
void cxl_release_psl_irq(struct cxl_afu *afu)
{
+ if (afu->psl_virq != irq_find_mapping(NULL, afu->psl_hwirq))
+ return;
+
cxl_unmap_irq(afu->psl_virq, afu);
cxl_release_one_irq(afu->adapter, afu->psl_hwirq);
kfree(afu->psl_irq_name);
@@ -421,6 +430,9 @@ int afu_allocate_irqs(struct cxl_context *ctx, u32 count)
int rc, r, i, j = 1;
struct cxl_irq_name *irq_name;
+ /* Initialize the list head to hold irq names */
+ INIT_LIST_HEAD(&ctx->irq_names);
+
if ((rc = cxl_alloc_irq_ranges(&ctx->irqs, ctx->afu->adapter, count)))
return rc;
@@ -432,13 +444,12 @@ int afu_allocate_irqs(struct cxl_context *ctx, u32 count)
ctx->irq_bitmap = kcalloc(BITS_TO_LONGS(count),
sizeof(*ctx->irq_bitmap), GFP_KERNEL);
if (!ctx->irq_bitmap)
- return -ENOMEM;
+ goto out;
/*
* Allocate names first. If any fail, bail out before allocating
* actual hardware IRQs.
*/
- INIT_LIST_HEAD(&ctx->irq_names);
for (r = 1; r < CXL_IRQ_RANGES; r++) {
for (i = 0; i < ctx->irqs.range[r]; i++) {
irq_name = kmalloc(sizeof(struct cxl_irq_name),
@@ -460,11 +471,12 @@ int afu_allocate_irqs(struct cxl_context *ctx, u32 count)
return 0;
out:
+ cxl_release_irq_ranges(&ctx->irqs, ctx->afu->adapter);
afu_irq_name_free(ctx);
return -ENOMEM;
}
-void afu_register_hwirqs(struct cxl_context *ctx)
+static void afu_register_hwirqs(struct cxl_context *ctx)
{
irq_hw_number_t hwirq;
struct cxl_irq_name *irq_name;
@@ -511,4 +523,6 @@ void afu_release_irqs(struct cxl_context *ctx, void *cookie)
afu_irq_name_free(ctx);
cxl_release_irq_ranges(&ctx->irqs, ctx->afu->adapter);
+
+ ctx->irq_count = 0;
}
diff --git a/drivers/misc/cxl/main.c b/drivers/misc/cxl/main.c
index 4a164ab8b..9fde75ed4 100644
--- a/drivers/misc/cxl/main.c
+++ b/drivers/misc/cxl/main.c
@@ -222,6 +222,7 @@ static void exit_cxl(void)
cxl_debugfs_exit();
cxl_file_exit();
unregister_cxl_calls(&cxl_calls);
+ idr_destroy(&cxl_adapter_idr);
}
module_init(init_cxl);
diff --git a/drivers/misc/cxl/native.c b/drivers/misc/cxl/native.c
index 10567f245..d2e75c88f 100644
--- a/drivers/misc/cxl/native.c
+++ b/drivers/misc/cxl/native.c
@@ -41,7 +41,14 @@ static int afu_control(struct cxl_afu *afu, u64 command,
rc = -EBUSY;
goto out;
}
- pr_devel_ratelimited("AFU control... (0x%.16llx)\n",
+
+ if (!cxl_adapter_link_ok(afu->adapter)) {
+ afu->enabled = enabled;
+ rc = -EIO;
+ goto out;
+ }
+
+ pr_devel_ratelimited("AFU control... (0x%016llx)\n",
AFU_Cntl | command);
cpu_relax();
AFU_Cntl = cxl_p2n_read(afu, CXL_AFU_Cntl_An);
@@ -85,6 +92,10 @@ int __cxl_afu_reset(struct cxl_afu *afu)
int cxl_afu_check_and_enable(struct cxl_afu *afu)
{
+ if (!cxl_adapter_link_ok(afu->adapter)) {
+ WARN(1, "Refusing to enable afu while link down!\n");
+ return -EIO;
+ }
if (afu->enabled)
return 0;
return afu_enable(afu);
@@ -103,6 +114,12 @@ int cxl_psl_purge(struct cxl_afu *afu)
pr_devel("PSL purge request\n");
+ if (!cxl_adapter_link_ok(afu->adapter)) {
+ dev_warn(&afu->dev, "PSL Purge called with link down, ignoring\n");
+ rc = -EIO;
+ goto out;
+ }
+
if ((AFU_Cntl & CXL_AFU_Cntl_An_ES_MASK) != CXL_AFU_Cntl_An_ES_Disabled) {
WARN(1, "psl_purge request while AFU not disabled!\n");
cxl_afu_disable(afu);
@@ -119,14 +136,19 @@ int cxl_psl_purge(struct cxl_afu *afu)
rc = -EBUSY;
goto out;
}
+ if (!cxl_adapter_link_ok(afu->adapter)) {
+ rc = -EIO;
+ goto out;
+ }
+
dsisr = cxl_p2n_read(afu, CXL_PSL_DSISR_An);
- pr_devel_ratelimited("PSL purging... PSL_CNTL: 0x%.16llx PSL_DSISR: 0x%.16llx\n", PSL_CNTL, dsisr);
+ pr_devel_ratelimited("PSL purging... PSL_CNTL: 0x%016llx PSL_DSISR: 0x%016llx\n", PSL_CNTL, dsisr);
if (dsisr & CXL_PSL_DSISR_TRANS) {
dar = cxl_p2n_read(afu, CXL_PSL_DAR_An);
- dev_notice(&afu->dev, "PSL purge terminating pending translation, DSISR: 0x%.16llx, DAR: 0x%.16llx\n", dsisr, dar);
+ dev_notice(&afu->dev, "PSL purge terminating pending translation, DSISR: 0x%016llx, DAR: 0x%016llx\n", dsisr, dar);
cxl_p2n_write(afu, CXL_PSL_TFC_An, CXL_PSL_TFC_An_AE);
} else if (dsisr) {
- dev_notice(&afu->dev, "PSL purge acknowledging pending non-translation fault, DSISR: 0x%.16llx\n", dsisr);
+ dev_notice(&afu->dev, "PSL purge acknowledging pending non-translation fault, DSISR: 0x%016llx\n", dsisr);
cxl_p2n_write(afu, CXL_PSL_TFC_An, CXL_PSL_TFC_An_A);
} else {
cpu_relax();
@@ -161,10 +183,8 @@ static int spa_max_procs(int spa_size)
return ((spa_size / 8) - 96) / 17;
}
-static int alloc_spa(struct cxl_afu *afu)
+int cxl_alloc_spa(struct cxl_afu *afu)
{
- u64 spap;
-
/* Work out how many pages to allocate */
afu->spa_order = 0;
do {
@@ -183,6 +203,13 @@ static int alloc_spa(struct cxl_afu *afu)
pr_devel("spa pages: %i afu->spa_max_procs: %i afu->num_procs: %i\n",
1<<afu->spa_order, afu->spa_max_procs, afu->num_procs);
+ return 0;
+}
+
+static void attach_spa(struct cxl_afu *afu)
+{
+ u64 spap;
+
afu->sw_command_status = (__be64 *)((char *)afu->spa +
((afu->spa_max_procs + 3) * 128));
@@ -191,14 +218,19 @@ static int alloc_spa(struct cxl_afu *afu)
spap |= CXL_PSL_SPAP_V;
pr_devel("cxl: SPA allocated at 0x%p. Max processes: %i, sw_command_status: 0x%p CXL_PSL_SPAP_An=0x%016llx\n", afu->spa, afu->spa_max_procs, afu->sw_command_status, spap);
cxl_p1n_write(afu, CXL_PSL_SPAP_An, spap);
-
- return 0;
}
-static void release_spa(struct cxl_afu *afu)
+static inline void detach_spa(struct cxl_afu *afu)
{
cxl_p1n_write(afu, CXL_PSL_SPAP_An, 0);
- free_pages((unsigned long) afu->spa, afu->spa_order);
+}
+
+void cxl_release_spa(struct cxl_afu *afu)
+{
+ if (afu->spa) {
+ free_pages((unsigned long) afu->spa, afu->spa_order);
+ afu->spa = NULL;
+ }
}
int cxl_tlb_slb_invalidate(struct cxl *adapter)
@@ -215,6 +247,8 @@ int cxl_tlb_slb_invalidate(struct cxl *adapter)
dev_warn(&adapter->dev, "WARNING: CXL adapter wide TLBIA timed out!\n");
return -EBUSY;
}
+ if (!cxl_adapter_link_ok(adapter))
+ return -EIO;
cpu_relax();
}
@@ -224,6 +258,8 @@ int cxl_tlb_slb_invalidate(struct cxl *adapter)
dev_warn(&adapter->dev, "WARNING: CXL adapter wide SLBIA timed out!\n");
return -EBUSY;
}
+ if (!cxl_adapter_link_ok(adapter))
+ return -EIO;
cpu_relax();
}
return 0;
@@ -240,6 +276,11 @@ int cxl_afu_slbia(struct cxl_afu *afu)
dev_warn(&afu->dev, "WARNING: CXL AFU SLBIA timed out!\n");
return -EBUSY;
}
+ /* If the adapter has gone down, we can assume that we
+ * will PERST it and that will invalidate everything.
+ */
+ if (!cxl_adapter_link_ok(afu->adapter))
+ return -EIO;
cpu_relax();
}
return 0;
@@ -279,6 +320,8 @@ static void slb_invalid(struct cxl_context *ctx)
cxl_p1_write(adapter, CXL_PSL_SLBIA, CXL_TLB_SLB_IQ_LPIDPID);
while (1) {
+ if (!cxl_adapter_link_ok(adapter))
+ break;
slbia = cxl_p1_read(adapter, CXL_PSL_SLBIA);
if (!(slbia & CXL_TLB_SLB_P))
break;
@@ -308,6 +351,11 @@ static int do_process_element_cmd(struct cxl_context *ctx,
rc = -EBUSY;
goto out;
}
+ if (!cxl_adapter_link_ok(ctx->afu->adapter)) {
+ dev_warn(&ctx->afu->dev, "WARNING: Device link down, aborting Process Element Command!\n");
+ rc = -EIO;
+ goto out;
+ }
state = be64_to_cpup(ctx->afu->sw_command_status);
if (state == ~0ULL) {
pr_err("cxl: Error adding process element to AFU\n");
@@ -355,8 +403,13 @@ static int terminate_process_element(struct cxl_context *ctx)
mutex_lock(&ctx->afu->spa_mutex);
pr_devel("%s Terminate pe: %i started\n", __func__, ctx->pe);
- rc = do_process_element_cmd(ctx, CXL_SPA_SW_CMD_TERMINATE,
- CXL_PE_SOFTWARE_STATE_V | CXL_PE_SOFTWARE_STATE_T);
+ /* We could be asked to terminate when the hw is down. That
+ * should always succeed: it's not running if the hw has gone
+ * away and is being reset.
+ */
+ if (cxl_adapter_link_ok(ctx->afu->adapter))
+ rc = do_process_element_cmd(ctx, CXL_SPA_SW_CMD_TERMINATE,
+ CXL_PE_SOFTWARE_STATE_V | CXL_PE_SOFTWARE_STATE_T);
ctx->elem->software_state = 0; /* Remove Valid bit */
pr_devel("%s Terminate pe: %i finished\n", __func__, ctx->pe);
mutex_unlock(&ctx->afu->spa_mutex);
@@ -369,7 +422,14 @@ static int remove_process_element(struct cxl_context *ctx)
mutex_lock(&ctx->afu->spa_mutex);
pr_devel("%s Remove pe: %i started\n", __func__, ctx->pe);
- if (!(rc = do_process_element_cmd(ctx, CXL_SPA_SW_CMD_REMOVE, 0)))
+
+ /* We could be asked to remove when the hw is down. Again, if
+ * the hw is down, the PE is gone, so we succeed.
+ */
+ if (cxl_adapter_link_ok(ctx->afu->adapter))
+ rc = do_process_element_cmd(ctx, CXL_SPA_SW_CMD_REMOVE, 0);
+
+ if (!rc)
ctx->pe_inserted = false;
slb_invalid(ctx);
pr_devel("%s Remove pe: %i finished\n", __func__, ctx->pe);
@@ -397,15 +457,18 @@ static int activate_afu_directed(struct cxl_afu *afu)
dev_info(&afu->dev, "Activating AFU directed mode\n");
- if (alloc_spa(afu))
- return -ENOMEM;
+ afu->num_procs = afu->max_procs_virtualised;
+ if (afu->spa == NULL) {
+ if (cxl_alloc_spa(afu))
+ return -ENOMEM;
+ }
+ attach_spa(afu);
cxl_p1n_write(afu, CXL_PSL_SCNTL_An, CXL_PSL_SCNTL_An_PM_AFU);
cxl_p1n_write(afu, CXL_PSL_AMOR_An, 0xFFFFFFFFFFFFFFFFULL);
cxl_p1n_write(afu, CXL_PSL_ID_An, CXL_PSL_ID_An_F | CXL_PSL_ID_An_L);
afu->current_mode = CXL_MODE_DIRECTED;
- afu->num_procs = afu->max_procs_virtualised;
if ((rc = cxl_chardev_m_afu_add(afu)))
return rc;
@@ -492,9 +555,7 @@ static int attach_afu_directed(struct cxl_context *ctx, u64 wed, u64 amr)
if ((result = cxl_afu_check_and_enable(ctx->afu)))
return result;
- add_process_element(ctx);
-
- return 0;
+ return add_process_element(ctx);
}
static int deactivate_afu_directed(struct cxl_afu *afu)
@@ -511,8 +572,6 @@ static int deactivate_afu_directed(struct cxl_afu *afu)
cxl_afu_disable(afu);
cxl_psl_purge(afu);
- release_spa(afu);
-
return 0;
}
@@ -614,6 +673,11 @@ int cxl_afu_activate_mode(struct cxl_afu *afu, int mode)
if (!(mode & afu->modes_supported))
return -EINVAL;
+ if (!cxl_adapter_link_ok(afu->adapter)) {
+ WARN(1, "Device link is down, refusing to activate!\n");
+ return -EIO;
+ }
+
if (mode == CXL_MODE_DIRECTED)
return activate_afu_directed(afu);
if (mode == CXL_MODE_DEDICATED)
@@ -624,6 +688,11 @@ int cxl_afu_activate_mode(struct cxl_afu *afu, int mode)
int cxl_attach_process(struct cxl_context *ctx, bool kernel, u64 wed, u64 amr)
{
+ if (!cxl_adapter_link_ok(ctx->afu->adapter)) {
+ WARN(1, "Device link is down, refusing to attach process!\n");
+ return -EIO;
+ }
+
ctx->kernel = kernel;
if (ctx->afu->current_mode == CXL_MODE_DIRECTED)
return attach_afu_directed(ctx, wed, amr);
@@ -668,6 +737,12 @@ int cxl_get_irq(struct cxl_afu *afu, struct cxl_irq_info *info)
{
u64 pidtid;
+ /* If the adapter has gone away, we can't get any meaningful
+ * information.
+ */
+ if (!cxl_adapter_link_ok(afu->adapter))
+ return -EIO;
+
info->dsisr = cxl_p2n_read(afu, CXL_PSL_DSISR_An);
info->dar = cxl_p2n_read(afu, CXL_PSL_DAR_An);
info->dsr = cxl_p2n_read(afu, CXL_PSL_DSR_An);
@@ -684,7 +759,7 @@ static void recover_psl_err(struct cxl_afu *afu, u64 errstat)
{
u64 dsisr;
- pr_devel("RECOVERING FROM PSL ERROR... (0x%.16llx)\n", errstat);
+ pr_devel("RECOVERING FROM PSL ERROR... (0x%016llx)\n", errstat);
/* Clear PSL_DSISR[PE] */
dsisr = cxl_p2n_read(afu, CXL_PSL_DSISR_An);
diff --git a/drivers/misc/cxl/pci.c b/drivers/misc/cxl/pci.c
index dc836071c..85761d7eb 100644
--- a/drivers/misc/cxl/pci.c
+++ b/drivers/misc/cxl/pci.c
@@ -24,6 +24,7 @@
#include <asm/io.h>
#include "cxl.h"
+#include <misc/cxl.h>
#define CXL_PCI_VSEC_ID 0x1280
@@ -133,7 +134,7 @@ u8 cxl_afu_cr_read8(struct cxl_afu *afu, int cr, u64 off)
return (val >> ((off & 0x3) * 8)) & 0xff;
}
-static DEFINE_PCI_DEVICE_TABLE(cxl_pci_tbl) = {
+static const struct pci_device_id cxl_pci_tbl[] = {
{ PCI_DEVICE(PCI_VENDOR_ID_IBM, 0x0477), },
{ PCI_DEVICE(PCI_VENDOR_ID_IBM, 0x044b), },
{ PCI_DEVICE(PCI_VENDOR_ID_IBM, 0x04cf), },
@@ -369,6 +370,55 @@ static int init_implementation_adapter_regs(struct cxl *adapter, struct pci_dev
return 0;
}
+#define TBSYNC_CNT(n) (((u64)n & 0x7) << (63-6))
+#define _2048_250MHZ_CYCLES 1
+
+static int cxl_setup_psl_timebase(struct cxl *adapter, struct pci_dev *dev)
+{
+ u64 psl_tb;
+ int delta;
+ unsigned int retry = 0;
+ struct device_node *np;
+
+ if (!(np = pnv_pci_get_phb_node(dev)))
+ return -ENODEV;
+
+ /* Do not fail when CAPP timebase sync is not supported by OPAL */
+ of_node_get(np);
+ if (! of_get_property(np, "ibm,capp-timebase-sync", NULL)) {
+ of_node_put(np);
+ pr_err("PSL: Timebase sync: OPAL support missing\n");
+ return 0;
+ }
+ of_node_put(np);
+
+ /*
+ * Setup PSL Timebase Control and Status register
+ * with the recommended Timebase Sync Count value
+ */
+ cxl_p1_write(adapter, CXL_PSL_TB_CTLSTAT,
+ TBSYNC_CNT(2 * _2048_250MHZ_CYCLES));
+
+ /* Enable PSL Timebase */
+ cxl_p1_write(adapter, CXL_PSL_Control, 0x0000000000000000);
+ cxl_p1_write(adapter, CXL_PSL_Control, CXL_PSL_Control_tb);
+
+ /* Wait until CORE TB and PSL TB difference <= 16usecs */
+ do {
+ msleep(1);
+ if (retry++ > 5) {
+ pr_err("PSL: Timebase sync: giving up!\n");
+ return -EIO;
+ }
+ psl_tb = cxl_p1_read(adapter, CXL_PSL_Timebase);
+ delta = mftb() - psl_tb;
+ if (delta < 0)
+ delta = -delta;
+ } while (cputime_to_usecs(delta) > 16);
+
+ return 0;
+}
+
static int init_implementation_afu_regs(struct cxl_afu *afu)
{
/* read/write masks for this slice */
@@ -539,10 +589,18 @@ err:
static void cxl_unmap_slice_regs(struct cxl_afu *afu)
{
- if (afu->p2n_mmio)
+ if (afu->p2n_mmio) {
iounmap(afu->p2n_mmio);
- if (afu->p1n_mmio)
+ afu->p2n_mmio = NULL;
+ }
+ if (afu->p1n_mmio) {
iounmap(afu->p1n_mmio);
+ afu->p1n_mmio = NULL;
+ }
+ if (afu->afu_desc_mmio) {
+ iounmap(afu->afu_desc_mmio);
+ afu->afu_desc_mmio = NULL;
+ }
}
static void cxl_release_afu(struct device *dev)
@@ -551,6 +609,9 @@ static void cxl_release_afu(struct device *dev)
pr_devel("cxl_release_afu\n");
+ idr_destroy(&afu->contexts_idr);
+ cxl_release_spa(afu);
+
kfree(afu);
}
@@ -656,7 +717,7 @@ static int sanitise_afu_regs(struct cxl_afu *afu)
*/
reg = cxl_p2n_read(afu, CXL_AFU_Cntl_An);
if ((reg & CXL_AFU_Cntl_An_ES_MASK) != CXL_AFU_Cntl_An_ES_Disabled) {
- dev_warn(&afu->dev, "WARNING: AFU was not disabled: %#.16llx\n", reg);
+ dev_warn(&afu->dev, "WARNING: AFU was not disabled: %#016llx\n", reg);
if (__cxl_afu_reset(afu))
return -EIO;
if (cxl_afu_disable(afu))
@@ -677,7 +738,7 @@ static int sanitise_afu_regs(struct cxl_afu *afu)
cxl_p2n_write(afu, CXL_SSTP0_An, 0x0000000000000000);
reg = cxl_p2n_read(afu, CXL_PSL_DSISR_An);
if (reg) {
- dev_warn(&afu->dev, "AFU had pending DSISR: %#.16llx\n", reg);
+ dev_warn(&afu->dev, "AFU had pending DSISR: %#016llx\n", reg);
if (reg & CXL_PSL_DSISR_TRANS)
cxl_p2n_write(afu, CXL_PSL_TFC_An, CXL_PSL_TFC_An_AE);
else
@@ -686,12 +747,12 @@ static int sanitise_afu_regs(struct cxl_afu *afu)
reg = cxl_p1n_read(afu, CXL_PSL_SERR_An);
if (reg) {
if (reg & ~0xffff)
- dev_warn(&afu->dev, "AFU had pending SERR: %#.16llx\n", reg);
+ dev_warn(&afu->dev, "AFU had pending SERR: %#016llx\n", reg);
cxl_p1n_write(afu, CXL_PSL_SERR_An, reg & ~0xffff);
}
reg = cxl_p2n_read(afu, CXL_PSL_ErrStat_An);
if (reg) {
- dev_warn(&afu->dev, "AFU had pending error status: %#.16llx\n", reg);
+ dev_warn(&afu->dev, "AFU had pending error status: %#016llx\n", reg);
cxl_p2n_write(afu, CXL_PSL_ErrStat_An, reg);
}
@@ -742,45 +803,70 @@ ssize_t cxl_afu_read_err_buffer(struct cxl_afu *afu, char *buf,
return count;
}
-static int cxl_init_afu(struct cxl *adapter, int slice, struct pci_dev *dev)
+static int cxl_configure_afu(struct cxl_afu *afu, struct cxl *adapter, struct pci_dev *dev)
{
- struct cxl_afu *afu;
- bool free = true;
int rc;
- if (!(afu = cxl_alloc_afu(adapter, slice)))
- return -ENOMEM;
-
- if ((rc = dev_set_name(&afu->dev, "afu%i.%i", adapter->adapter_num, slice)))
- goto err1;
-
if ((rc = cxl_map_slice_regs(afu, adapter, dev)))
- goto err1;
+ return rc;
if ((rc = sanitise_afu_regs(afu)))
- goto err2;
+ goto err1;
/* We need to reset the AFU before we can read the AFU descriptor */
if ((rc = __cxl_afu_reset(afu)))
- goto err2;
+ goto err1;
if (cxl_verbose)
dump_afu_descriptor(afu);
if ((rc = cxl_read_afu_descriptor(afu)))
- goto err2;
+ goto err1;
if ((rc = cxl_afu_descriptor_looks_ok(afu)))
- goto err2;
+ goto err1;
if ((rc = init_implementation_afu_regs(afu)))
- goto err2;
+ goto err1;
if ((rc = cxl_register_serr_irq(afu)))
- goto err2;
+ goto err1;
if ((rc = cxl_register_psl_irq(afu)))
- goto err3;
+ goto err2;
+
+ return 0;
+
+err2:
+ cxl_release_serr_irq(afu);
+err1:
+ cxl_unmap_slice_regs(afu);
+ return rc;
+}
+
+static void cxl_deconfigure_afu(struct cxl_afu *afu)
+{
+ cxl_release_psl_irq(afu);
+ cxl_release_serr_irq(afu);
+ cxl_unmap_slice_regs(afu);
+}
+
+static int cxl_init_afu(struct cxl *adapter, int slice, struct pci_dev *dev)
+{
+ struct cxl_afu *afu;
+ int rc;
+
+ afu = cxl_alloc_afu(adapter, slice);
+ if (!afu)
+ return -ENOMEM;
+
+ rc = dev_set_name(&afu->dev, "afu%i.%i", adapter->adapter_num, slice);
+ if (rc)
+ goto err_free;
+
+ rc = cxl_configure_afu(afu, adapter, dev);
+ if (rc)
+ goto err_free;
/* Don't care if this fails */
cxl_debugfs_afu_add(afu);
@@ -795,10 +881,6 @@ static int cxl_init_afu(struct cxl *adapter, int slice, struct pci_dev *dev)
if ((rc = cxl_sysfs_afu_add(afu)))
goto err_put1;
-
- if ((rc = cxl_afu_select_best_mode(afu)))
- goto err_put2;
-
adapter->afu[afu->slice] = afu;
if ((rc = cxl_pci_vphb_add(afu)))
@@ -806,21 +888,16 @@ static int cxl_init_afu(struct cxl *adapter, int slice, struct pci_dev *dev)
return 0;
-err_put2:
- cxl_sysfs_afu_remove(afu);
err_put1:
- device_unregister(&afu->dev);
- free = false;
+ cxl_deconfigure_afu(afu);
cxl_debugfs_afu_remove(afu);
- cxl_release_psl_irq(afu);
-err3:
- cxl_release_serr_irq(afu);
-err2:
- cxl_unmap_slice_regs(afu);
-err1:
- if (free)
- kfree(afu);
+ device_unregister(&afu->dev);
return rc;
+
+err_free:
+ kfree(afu);
+ return rc;
+
}
static void cxl_remove_afu(struct cxl_afu *afu)
@@ -840,10 +917,7 @@ static void cxl_remove_afu(struct cxl_afu *afu)
cxl_context_detach_all(afu);
cxl_afu_deactivate_mode(afu);
- cxl_release_psl_irq(afu);
- cxl_release_serr_irq(afu);
- cxl_unmap_slice_regs(afu);
-
+ cxl_deconfigure_afu(afu);
device_unregister(&afu->dev);
}
@@ -852,6 +926,12 @@ int cxl_reset(struct cxl *adapter)
struct pci_dev *dev = to_pci_dev(adapter->dev.parent);
int rc;
+ if (adapter->perst_same_image) {
+ dev_warn(&dev->dev,
+ "cxl: refusing to reset/reflash when perst_reloads_same_image is set.\n");
+ return -EINVAL;
+ }
+
dev_info(&dev->dev, "CXL reset\n");
/* pcie_warm_reset requests a fundamental pci reset which includes a
@@ -872,7 +952,7 @@ static int cxl_map_adapter_regs(struct cxl *adapter, struct pci_dev *dev)
if (pci_request_region(dev, 0, "priv 1 regs"))
goto err2;
- pr_devel("cxl_map_adapter_regs: p1: %#.16llx %#llx, p2: %#.16llx %#llx",
+ pr_devel("cxl_map_adapter_regs: p1: %#016llx %#llx, p2: %#016llx %#llx",
p1_base(dev), p1_size(dev), p2_base(dev), p2_size(dev));
if (!(adapter->p1_mmio = ioremap(p1_base(dev), p1_size(dev))))
@@ -896,10 +976,16 @@ err1:
static void cxl_unmap_adapter_regs(struct cxl *adapter)
{
- if (adapter->p1_mmio)
+ if (adapter->p1_mmio) {
iounmap(adapter->p1_mmio);
- if (adapter->p2_mmio)
+ adapter->p1_mmio = NULL;
+ pci_release_region(to_pci_dev(adapter->dev.parent), 2);
+ }
+ if (adapter->p2_mmio) {
iounmap(adapter->p2_mmio);
+ adapter->p2_mmio = NULL;
+ pci_release_region(to_pci_dev(adapter->dev.parent), 0);
+ }
}
static int cxl_read_vsec(struct cxl *adapter, struct pci_dev *dev)
@@ -928,7 +1014,6 @@ static int cxl_read_vsec(struct cxl *adapter, struct pci_dev *dev)
CXL_READ_VSEC_BASE_IMAGE(dev, vsec, &adapter->base_image);
CXL_READ_VSEC_IMAGE_STATE(dev, vsec, &image_state);
adapter->user_image_loaded = !!(image_state & CXL_VSEC_USER_IMAGE_LOADED);
- adapter->perst_loads_image = true;
adapter->perst_select_user = !!(image_state & CXL_VSEC_USER_IMAGE_LOADED);
CXL_READ_VSEC_NAFUS(dev, vsec, &adapter->slices);
@@ -950,6 +1035,32 @@ static int cxl_read_vsec(struct cxl *adapter, struct pci_dev *dev)
return 0;
}
+/*
+ * Workaround a PCIe Host Bridge defect on some cards, that can cause
+ * malformed Transaction Layer Packet (TLP) errors to be erroneously
+ * reported. Mask this error in the Uncorrectable Error Mask Register.
+ *
+ * The upper nibble of the PSL revision is used to distinguish between
+ * different cards. The affected ones have it set to 0.
+ */
+static void cxl_fixup_malformed_tlp(struct cxl *adapter, struct pci_dev *dev)
+{
+ int aer;
+ u32 data;
+
+ if (adapter->psl_rev & 0xf000)
+ return;
+ if (!(aer = pci_find_ext_capability(dev, PCI_EXT_CAP_ID_ERR)))
+ return;
+ pci_read_config_dword(dev, aer + PCI_ERR_UNCOR_MASK, &data);
+ if (data & PCI_ERR_UNC_MALF_TLP)
+ if (data & PCI_ERR_UNC_INTN)
+ return;
+ data |= PCI_ERR_UNC_MALF_TLP;
+ data |= PCI_ERR_UNC_INTN;
+ pci_write_config_dword(dev, aer + PCI_ERR_UNCOR_MASK, data);
+}
+
static int cxl_vsec_looks_ok(struct cxl *adapter, struct pci_dev *dev)
{
if (adapter->vsec_status & CXL_STATUS_SECOND_PORT)
@@ -988,81 +1099,140 @@ static void cxl_release_adapter(struct device *dev)
pr_devel("cxl_release_adapter\n");
+ cxl_remove_adapter_nr(adapter);
+
kfree(adapter);
}
-static struct cxl *cxl_alloc_adapter(struct pci_dev *dev)
+static struct cxl *cxl_alloc_adapter(void)
{
struct cxl *adapter;
if (!(adapter = kzalloc(sizeof(struct cxl), GFP_KERNEL)))
return NULL;
- adapter->dev.parent = &dev->dev;
- adapter->dev.release = cxl_release_adapter;
- pci_set_drvdata(dev, adapter);
spin_lock_init(&adapter->afu_list_lock);
+ if (cxl_alloc_adapter_nr(adapter))
+ goto err1;
+
+ if (dev_set_name(&adapter->dev, "card%i", adapter->adapter_num))
+ goto err2;
+
return adapter;
+
+err2:
+ cxl_remove_adapter_nr(adapter);
+err1:
+ kfree(adapter);
+ return NULL;
}
+#define CXL_PSL_ErrIVTE_tberror (0x1ull << (63-31))
+
static int sanitise_adapter_regs(struct cxl *adapter)
{
- cxl_p1_write(adapter, CXL_PSL_ErrIVTE, 0x0000000000000000);
+ /* Clear PSL tberror bit by writing 1 to it */
+ cxl_p1_write(adapter, CXL_PSL_ErrIVTE, CXL_PSL_ErrIVTE_tberror);
return cxl_tlb_slb_invalidate(adapter);
}
-static struct cxl *cxl_init_adapter(struct pci_dev *dev)
+/* This should contain *only* operations that can safely be done in
+ * both creation and recovery.
+ */
+static int cxl_configure_adapter(struct cxl *adapter, struct pci_dev *dev)
{
- struct cxl *adapter;
- bool free = true;
int rc;
+ adapter->dev.parent = &dev->dev;
+ adapter->dev.release = cxl_release_adapter;
+ pci_set_drvdata(dev, adapter);
- if (!(adapter = cxl_alloc_adapter(dev)))
- return ERR_PTR(-ENOMEM);
+ rc = pci_enable_device(dev);
+ if (rc) {
+ dev_err(&dev->dev, "pci_enable_device failed: %i\n", rc);
+ return rc;
+ }
if ((rc = cxl_read_vsec(adapter, dev)))
- goto err1;
+ return rc;
if ((rc = cxl_vsec_looks_ok(adapter, dev)))
- goto err1;
+ return rc;
+
+ cxl_fixup_malformed_tlp(adapter, dev);
if ((rc = setup_cxl_bars(dev)))
- goto err1;
+ return rc;
if ((rc = switch_card_to_cxl(dev)))
- goto err1;
-
- if ((rc = cxl_alloc_adapter_nr(adapter)))
- goto err1;
-
- if ((rc = dev_set_name(&adapter->dev, "card%i", adapter->adapter_num)))
- goto err2;
+ return rc;
if ((rc = cxl_update_image_control(adapter)))
- goto err2;
+ return rc;
if ((rc = cxl_map_adapter_regs(adapter, dev)))
- goto err2;
+ return rc;
if ((rc = sanitise_adapter_regs(adapter)))
- goto err2;
+ goto err;
if ((rc = init_implementation_adapter_regs(adapter, dev)))
- goto err3;
+ goto err;
if ((rc = pnv_phb_to_cxl_mode(dev, OPAL_PHB_CAPI_MODE_CAPI)))
- goto err3;
+ goto err;
/* If recovery happened, the last step is to turn on snooping.
* In the non-recovery case this has no effect */
- if ((rc = pnv_phb_to_cxl_mode(dev, OPAL_PHB_CAPI_MODE_SNOOP_ON))) {
- goto err3;
- }
+ if ((rc = pnv_phb_to_cxl_mode(dev, OPAL_PHB_CAPI_MODE_SNOOP_ON)))
+ goto err;
+
+ if ((rc = cxl_setup_psl_timebase(adapter, dev)))
+ goto err;
if ((rc = cxl_register_psl_err_irq(adapter)))
- goto err3;
+ goto err;
+
+ return 0;
+
+err:
+ cxl_unmap_adapter_regs(adapter);
+ return rc;
+
+}
+
+static void cxl_deconfigure_adapter(struct cxl *adapter)
+{
+ struct pci_dev *pdev = to_pci_dev(adapter->dev.parent);
+
+ cxl_release_psl_err_irq(adapter);
+ cxl_unmap_adapter_regs(adapter);
+
+ pci_disable_device(pdev);
+}
+
+static struct cxl *cxl_init_adapter(struct pci_dev *dev)
+{
+ struct cxl *adapter;
+ int rc;
+
+ adapter = cxl_alloc_adapter();
+ if (!adapter)
+ return ERR_PTR(-ENOMEM);
+
+ /* Set defaults for parameters which need to persist over
+ * configure/reconfigure
+ */
+ adapter->perst_loads_image = true;
+ adapter->perst_same_image = false;
+
+ rc = cxl_configure_adapter(adapter, dev);
+ if (rc) {
+ pci_disable_device(dev);
+ cxl_release_adapter(&adapter->dev);
+ return ERR_PTR(rc);
+ }
/* Don't care if this one fails: */
cxl_debugfs_adapter_add(adapter);
@@ -1080,37 +1250,25 @@ static struct cxl *cxl_init_adapter(struct pci_dev *dev)
return adapter;
err_put1:
- device_unregister(&adapter->dev);
- free = false;
+ /* This should mirror cxl_remove_adapter, except without the
+ * sysfs parts
+ */
cxl_debugfs_adapter_remove(adapter);
- cxl_release_psl_err_irq(adapter);
-err3:
- cxl_unmap_adapter_regs(adapter);
-err2:
- cxl_remove_adapter_nr(adapter);
-err1:
- if (free)
- kfree(adapter);
+ cxl_deconfigure_adapter(adapter);
+ device_unregister(&adapter->dev);
return ERR_PTR(rc);
}
static void cxl_remove_adapter(struct cxl *adapter)
{
- struct pci_dev *pdev = to_pci_dev(adapter->dev.parent);
-
- pr_devel("cxl_release_adapter\n");
+ pr_devel("cxl_remove_adapter\n");
cxl_sysfs_adapter_remove(adapter);
cxl_debugfs_adapter_remove(adapter);
- cxl_release_psl_err_irq(adapter);
- cxl_unmap_adapter_regs(adapter);
- cxl_remove_adapter_nr(adapter);
- device_unregister(&adapter->dev);
+ cxl_deconfigure_adapter(adapter);
- pci_release_region(pdev, 0);
- pci_release_region(pdev, 2);
- pci_disable_device(pdev);
+ device_unregister(&adapter->dev);
}
static int cxl_probe(struct pci_dev *dev, const struct pci_device_id *id)
@@ -1122,21 +1280,21 @@ static int cxl_probe(struct pci_dev *dev, const struct pci_device_id *id)
if (cxl_verbose)
dump_cxl_config_space(dev);
- if ((rc = pci_enable_device(dev))) {
- dev_err(&dev->dev, "pci_enable_device failed: %i\n", rc);
- return rc;
- }
-
adapter = cxl_init_adapter(dev);
if (IS_ERR(adapter)) {
dev_err(&dev->dev, "cxl_init_adapter failed: %li\n", PTR_ERR(adapter));
- pci_disable_device(dev);
return PTR_ERR(adapter);
}
for (slice = 0; slice < adapter->slices; slice++) {
- if ((rc = cxl_init_afu(adapter, slice, dev)))
+ if ((rc = cxl_init_afu(adapter, slice, dev))) {
dev_err(&dev->dev, "AFU %i failed to initialise: %i\n", slice, rc);
+ continue;
+ }
+
+ rc = cxl_afu_select_best_mode(adapter->afu[slice]);
+ if (rc)
+ dev_err(&dev->dev, "AFU %i failed to start: %i\n", slice, rc);
}
return 0;
@@ -1160,10 +1318,262 @@ static void cxl_remove(struct pci_dev *dev)
cxl_remove_adapter(adapter);
}
+static pci_ers_result_t cxl_vphb_error_detected(struct cxl_afu *afu,
+ pci_channel_state_t state)
+{
+ struct pci_dev *afu_dev;
+ pci_ers_result_t result = PCI_ERS_RESULT_NEED_RESET;
+ pci_ers_result_t afu_result = PCI_ERS_RESULT_NEED_RESET;
+
+ /* There should only be one entry, but go through the list
+ * anyway
+ */
+ list_for_each_entry(afu_dev, &afu->phb->bus->devices, bus_list) {
+ if (!afu_dev->driver)
+ continue;
+
+ afu_dev->error_state = state;
+
+ if (afu_dev->driver->err_handler)
+ afu_result = afu_dev->driver->err_handler->error_detected(afu_dev,
+ state);
+ /* Disconnect trumps all, NONE trumps NEED_RESET */
+ if (afu_result == PCI_ERS_RESULT_DISCONNECT)
+ result = PCI_ERS_RESULT_DISCONNECT;
+ else if ((afu_result == PCI_ERS_RESULT_NONE) &&
+ (result == PCI_ERS_RESULT_NEED_RESET))
+ result = PCI_ERS_RESULT_NONE;
+ }
+ return result;
+}
+
+static pci_ers_result_t cxl_pci_error_detected(struct pci_dev *pdev,
+ pci_channel_state_t state)
+{
+ struct cxl *adapter = pci_get_drvdata(pdev);
+ struct cxl_afu *afu;
+ pci_ers_result_t result = PCI_ERS_RESULT_NEED_RESET;
+ int i;
+
+ /* At this point, we could still have an interrupt pending.
+ * Let's try to get them out of the way before they do
+ * anything we don't like.
+ */
+ schedule();
+
+ /* If we're permanently dead, give up. */
+ if (state == pci_channel_io_perm_failure) {
+ /* Tell the AFU drivers; but we don't care what they
+ * say, we're going away.
+ */
+ for (i = 0; i < adapter->slices; i++) {
+ afu = adapter->afu[i];
+ cxl_vphb_error_detected(afu, state);
+ }
+ return PCI_ERS_RESULT_DISCONNECT;
+ }
+
+ /* Are we reflashing?
+ *
+ * If we reflash, we could come back as something entirely
+ * different, including a non-CAPI card. As such, by default
+ * we don't participate in the process. We'll be unbound and
+ * the slot re-probed. (TODO: check EEH doesn't blindly rebind
+ * us!)
+ *
+ * However, this isn't the entire story: for reliablity
+ * reasons, we usually want to reflash the FPGA on PERST in
+ * order to get back to a more reliable known-good state.
+ *
+ * This causes us a bit of a problem: if we reflash we can't
+ * trust that we'll come back the same - we could have a new
+ * image and been PERSTed in order to load that
+ * image. However, most of the time we actually *will* come
+ * back the same - for example a regular EEH event.
+ *
+ * Therefore, we allow the user to assert that the image is
+ * indeed the same and that we should continue on into EEH
+ * anyway.
+ */
+ if (adapter->perst_loads_image && !adapter->perst_same_image) {
+ /* TODO take the PHB out of CXL mode */
+ dev_info(&pdev->dev, "reflashing, so opting out of EEH!\n");
+ return PCI_ERS_RESULT_NONE;
+ }
+
+ /*
+ * At this point, we want to try to recover. We'll always
+ * need a complete slot reset: we don't trust any other reset.
+ *
+ * Now, we go through each AFU:
+ * - We send the driver, if bound, an error_detected callback.
+ * We expect it to clean up, but it can also tell us to give
+ * up and permanently detach the card. To simplify things, if
+ * any bound AFU driver doesn't support EEH, we give up on EEH.
+ *
+ * - We detach all contexts associated with the AFU. This
+ * does not free them, but puts them into a CLOSED state
+ * which causes any the associated files to return useful
+ * errors to userland. It also unmaps, but does not free,
+ * any IRQs.
+ *
+ * - We clean up our side: releasing and unmapping resources we hold
+ * so we can wire them up again when the hardware comes back up.
+ *
+ * Driver authors should note:
+ *
+ * - Any contexts you create in your kernel driver (except
+ * those associated with anonymous file descriptors) are
+ * your responsibility to free and recreate. Likewise with
+ * any attached resources.
+ *
+ * - We will take responsibility for re-initialising the
+ * device context (the one set up for you in
+ * cxl_pci_enable_device_hook and accessed through
+ * cxl_get_context). If you've attached IRQs or other
+ * resources to it, they remains yours to free.
+ *
+ * You can call the same functions to release resources as you
+ * normally would: we make sure that these functions continue
+ * to work when the hardware is down.
+ *
+ * Two examples:
+ *
+ * 1) If you normally free all your resources at the end of
+ * each request, or if you use anonymous FDs, your
+ * error_detected callback can simply set a flag to tell
+ * your driver not to start any new calls. You can then
+ * clear the flag in the resume callback.
+ *
+ * 2) If you normally allocate your resources on startup:
+ * * Set a flag in error_detected as above.
+ * * Let CXL detach your contexts.
+ * * In slot_reset, free the old resources and allocate new ones.
+ * * In resume, clear the flag to allow things to start.
+ */
+ for (i = 0; i < adapter->slices; i++) {
+ afu = adapter->afu[i];
+
+ result = cxl_vphb_error_detected(afu, state);
+
+ /* Only continue if everyone agrees on NEED_RESET */
+ if (result != PCI_ERS_RESULT_NEED_RESET)
+ return result;
+
+ cxl_context_detach_all(afu);
+ cxl_afu_deactivate_mode(afu);
+ cxl_deconfigure_afu(afu);
+ }
+ cxl_deconfigure_adapter(adapter);
+
+ return result;
+}
+
+static pci_ers_result_t cxl_pci_slot_reset(struct pci_dev *pdev)
+{
+ struct cxl *adapter = pci_get_drvdata(pdev);
+ struct cxl_afu *afu;
+ struct cxl_context *ctx;
+ struct pci_dev *afu_dev;
+ pci_ers_result_t afu_result = PCI_ERS_RESULT_RECOVERED;
+ pci_ers_result_t result = PCI_ERS_RESULT_RECOVERED;
+ int i;
+
+ if (cxl_configure_adapter(adapter, pdev))
+ goto err;
+
+ for (i = 0; i < adapter->slices; i++) {
+ afu = adapter->afu[i];
+
+ if (cxl_configure_afu(afu, adapter, pdev))
+ goto err;
+
+ if (cxl_afu_select_best_mode(afu))
+ goto err;
+
+ cxl_pci_vphb_reconfigure(afu);
+
+ list_for_each_entry(afu_dev, &afu->phb->bus->devices, bus_list) {
+ /* Reset the device context.
+ * TODO: make this less disruptive
+ */
+ ctx = cxl_get_context(afu_dev);
+
+ if (ctx && cxl_release_context(ctx))
+ goto err;
+
+ ctx = cxl_dev_context_init(afu_dev);
+ if (!ctx)
+ goto err;
+
+ afu_dev->dev.archdata.cxl_ctx = ctx;
+
+ if (cxl_afu_check_and_enable(afu))
+ goto err;
+
+ afu_dev->error_state = pci_channel_io_normal;
+
+ /* If there's a driver attached, allow it to
+ * chime in on recovery. Drivers should check
+ * if everything has come back OK, but
+ * shouldn't start new work until we call
+ * their resume function.
+ */
+ if (!afu_dev->driver)
+ continue;
+
+ if (afu_dev->driver->err_handler &&
+ afu_dev->driver->err_handler->slot_reset)
+ afu_result = afu_dev->driver->err_handler->slot_reset(afu_dev);
+
+ if (afu_result == PCI_ERS_RESULT_DISCONNECT)
+ result = PCI_ERS_RESULT_DISCONNECT;
+ }
+ }
+ return result;
+
+err:
+ /* All the bits that happen in both error_detected and cxl_remove
+ * should be idempotent, so we don't need to worry about leaving a mix
+ * of unconfigured and reconfigured resources.
+ */
+ dev_err(&pdev->dev, "EEH recovery failed. Asking to be disconnected.\n");
+ return PCI_ERS_RESULT_DISCONNECT;
+}
+
+static void cxl_pci_resume(struct pci_dev *pdev)
+{
+ struct cxl *adapter = pci_get_drvdata(pdev);
+ struct cxl_afu *afu;
+ struct pci_dev *afu_dev;
+ int i;
+
+ /* Everything is back now. Drivers should restart work now.
+ * This is not the place to be checking if everything came back up
+ * properly, because there's no return value: do that in slot_reset.
+ */
+ for (i = 0; i < adapter->slices; i++) {
+ afu = adapter->afu[i];
+
+ list_for_each_entry(afu_dev, &afu->phb->bus->devices, bus_list) {
+ if (afu_dev->driver && afu_dev->driver->err_handler &&
+ afu_dev->driver->err_handler->resume)
+ afu_dev->driver->err_handler->resume(afu_dev);
+ }
+ }
+}
+
+static const struct pci_error_handlers cxl_err_handler = {
+ .error_detected = cxl_pci_error_detected,
+ .slot_reset = cxl_pci_slot_reset,
+ .resume = cxl_pci_resume,
+};
+
struct pci_driver cxl_pci_driver = {
.name = "cxl-pci",
.id_table = cxl_pci_tbl,
.probe = cxl_probe,
.remove = cxl_remove,
.shutdown = cxl_remove,
+ .err_handler = &cxl_err_handler,
};
diff --git a/drivers/misc/cxl/sysfs.c b/drivers/misc/cxl/sysfs.c
index 31f38bc71..02006f710 100644
--- a/drivers/misc/cxl/sysfs.c
+++ b/drivers/misc/cxl/sysfs.c
@@ -112,12 +112,38 @@ static ssize_t load_image_on_perst_store(struct device *device,
return count;
}
+static ssize_t perst_reloads_same_image_show(struct device *device,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct cxl *adapter = to_cxl_adapter(device);
+
+ return scnprintf(buf, PAGE_SIZE, "%i\n", adapter->perst_same_image);
+}
+
+static ssize_t perst_reloads_same_image_store(struct device *device,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct cxl *adapter = to_cxl_adapter(device);
+ int rc;
+ int val;
+
+ rc = sscanf(buf, "%i", &val);
+ if ((rc != 1) || !(val == 1 || val == 0))
+ return -EINVAL;
+
+ adapter->perst_same_image = (val == 1 ? true : false);
+ return count;
+}
+
static struct device_attribute adapter_attrs[] = {
__ATTR_RO(caia_version),
__ATTR_RO(psl_revision),
__ATTR_RO(base_image),
__ATTR_RO(image_loaded),
__ATTR_RW(load_image_on_perst),
+ __ATTR_RW(perst_reloads_same_image),
__ATTR(reset, S_IWUSR, NULL, reset_adapter_store),
};
@@ -443,12 +469,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);
@@ -571,6 +592,8 @@ int cxl_sysfs_afu_add(struct cxl_afu *afu)
/* conditionally create the add the binary file for error info buffer */
if (afu->eb_len) {
+ sysfs_attr_init(&afu->attr_eb.attr);
+
afu->attr_eb.attr.name = "afu_err_buff";
afu->attr_eb.attr.mode = S_IRUGO;
afu->attr_eb.size = afu->eb_len;
diff --git a/drivers/misc/cxl/trace.h b/drivers/misc/cxl/trace.h
index ae434d878..6e1e2adfb 100644
--- a/drivers/misc/cxl/trace.h
+++ b/drivers/misc/cxl/trace.h
@@ -105,7 +105,7 @@ TRACE_EVENT(cxl_attach,
__entry->num_interrupts = num_interrupts;
),
- TP_printk("afu%i.%i pid=%i pe=%i wed=0x%.16llx irqs=%i amr=0x%llx",
+ TP_printk("afu%i.%i pid=%i pe=%i wed=0x%016llx irqs=%i amr=0x%llx",
__entry->card,
__entry->afu,
__entry->pid,
@@ -177,7 +177,7 @@ TRACE_EVENT(cxl_psl_irq,
__entry->dar = dar;
),
- TP_printk("afu%i.%i pe=%i irq=%i dsisr=%s dar=0x%.16llx",
+ TP_printk("afu%i.%i pe=%i irq=%i dsisr=%s dar=0x%016llx",
__entry->card,
__entry->afu,
__entry->pe,
@@ -233,7 +233,7 @@ TRACE_EVENT(cxl_ste_miss,
__entry->dar = dar;
),
- TP_printk("afu%i.%i pe=%i dar=0x%.16llx",
+ TP_printk("afu%i.%i pe=%i dar=0x%016llx",
__entry->card,
__entry->afu,
__entry->pe,
@@ -264,7 +264,7 @@ TRACE_EVENT(cxl_ste_write,
__entry->v = v;
),
- TP_printk("afu%i.%i pe=%i SSTE[%i] E=0x%.16llx V=0x%.16llx",
+ TP_printk("afu%i.%i pe=%i SSTE[%i] E=0x%016llx V=0x%016llx",
__entry->card,
__entry->afu,
__entry->pe,
@@ -295,7 +295,7 @@ TRACE_EVENT(cxl_pte_miss,
__entry->dar = dar;
),
- TP_printk("afu%i.%i pe=%i dsisr=%s dar=0x%.16llx",
+ TP_printk("afu%i.%i pe=%i dsisr=%s dar=0x%016llx",
__entry->card,
__entry->afu,
__entry->pe,
diff --git a/drivers/misc/cxl/vphb.c b/drivers/misc/cxl/vphb.c
index 2eba002b5..94b520896 100644
--- a/drivers/misc/cxl/vphb.c
+++ b/drivers/misc/cxl/vphb.c
@@ -48,6 +48,12 @@ static bool cxl_pci_enable_device_hook(struct pci_dev *dev)
phb = pci_bus_to_host(dev->bus);
afu = (struct cxl_afu *)phb->private_data;
+
+ if (!cxl_adapter_link_ok(afu->adapter)) {
+ dev_warn(&dev->dev, "%s: Device link is down, refusing to enable AFU\n", __func__);
+ return false;
+ }
+
set_dma_ops(&dev->dev, &dma_direct_ops);
set_dma_offset(&dev->dev, PAGE_OFFSET);
@@ -138,6 +144,26 @@ static int cxl_pcie_config_info(struct pci_bus *bus, unsigned int devfn,
return 0;
}
+
+static inline bool cxl_config_link_ok(struct pci_bus *bus)
+{
+ struct pci_controller *phb;
+ struct cxl_afu *afu;
+
+ /* Config space IO is based on phb->cfg_addr, which is based on
+ * afu_desc_mmio. This isn't safe to read/write when the link
+ * goes down, as EEH tears down MMIO space.
+ *
+ * Check if the link is OK before proceeding.
+ */
+
+ phb = pci_bus_to_host(bus);
+ if (phb == NULL)
+ return false;
+ afu = (struct cxl_afu *)phb->private_data;
+ return cxl_adapter_link_ok(afu->adapter);
+}
+
static int cxl_pcie_read_config(struct pci_bus *bus, unsigned int devfn,
int offset, int len, u32 *val)
{
@@ -150,6 +176,9 @@ static int cxl_pcie_read_config(struct pci_bus *bus, unsigned int devfn,
if (rc)
return rc;
+ if (!cxl_config_link_ok(bus))
+ return PCIBIOS_DEVICE_NOT_FOUND;
+
/* Can only read 32 bits */
*val = (in_le32(ioaddr) >> shift) & mask;
return PCIBIOS_SUCCESSFUL;
@@ -167,6 +196,9 @@ static int cxl_pcie_write_config(struct pci_bus *bus, unsigned int devfn,
if (rc)
return rc;
+ if (!cxl_config_link_ok(bus))
+ return PCIBIOS_DEVICE_NOT_FOUND;
+
/* Can only write 32 bits so do read-modify-write */
mask <<= shift;
val <<= shift;
@@ -240,6 +272,14 @@ int cxl_pci_vphb_add(struct cxl_afu *afu)
return 0;
}
+void cxl_pci_vphb_reconfigure(struct cxl_afu *afu)
+{
+ /* When we are reconfigured, the AFU's MMIO space is unmapped
+ * and remapped. We need to reflect this in the PHB's view of
+ * the world.
+ */
+ afu->phb->cfg_addr = afu->afu_desc_mmio + afu->crs_offset;
+}
void cxl_pci_vphb_remove(struct cxl_afu *afu)
{
diff --git a/drivers/misc/ds1682.c b/drivers/misc/ds1682.c
index b909fb302..c7112276a 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 9536852fd..04f2e1fa9 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 9507aec95..fc1e81d29 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 6ded3dc36..c6cb7f8f3 100644
--- a/drivers/misc/eeprom/at24.c
+++ b/drivers/misc/eeprom/at24.c
@@ -186,19 +186,11 @@ static ssize_t at24_eeprom_read(struct at24_data *at24, char *buf,
if (count > io_limit)
count = io_limit;
- switch (at24->use_smbus) {
- case I2C_SMBUS_I2C_BLOCK_DATA:
+ if (at24->use_smbus) {
/* Smaller eeproms can work given some SMBus extension calls */
if (count > I2C_SMBUS_BLOCK_MAX)
count = I2C_SMBUS_BLOCK_MAX;
- break;
- case I2C_SMBUS_WORD_DATA:
- count = 2;
- break;
- case I2C_SMBUS_BYTE_DATA:
- count = 1;
- break;
- default:
+ } else {
/*
* When we have a better choice than SMBus calls, use a
* combined I2C message. Write address; then read up to
@@ -229,27 +221,10 @@ static ssize_t at24_eeprom_read(struct at24_data *at24, char *buf,
timeout = jiffies + msecs_to_jiffies(write_timeout);
do {
read_time = jiffies;
- switch (at24->use_smbus) {
- case I2C_SMBUS_I2C_BLOCK_DATA:
- status = i2c_smbus_read_i2c_block_data(client, offset,
- count, buf);
- break;
- case I2C_SMBUS_WORD_DATA:
- status = i2c_smbus_read_word_data(client, offset);
- if (status >= 0) {
- buf[0] = status & 0xff;
- buf[1] = status >> 8;
- status = count;
- }
- break;
- case I2C_SMBUS_BYTE_DATA:
- status = i2c_smbus_read_byte_data(client, offset);
- if (status >= 0) {
- buf[0] = status;
- status = count;
- }
- break;
- default:
+ if (at24->use_smbus) {
+ status = i2c_smbus_read_i2c_block_data_or_emulated(client, offset,
+ count, buf);
+ } else {
status = i2c_transfer(client->adapter, msg, 2);
if (status == 2)
status = count;
@@ -686,7 +661,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 b432873de..7342fd637 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 9ebeacdb8..a6bd9e3fe 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 580ff9df5..e4dd93b25 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++)
@@ -197,6 +191,7 @@ static const struct i2c_device_id max6875_id[] = {
{ "max6875", 0 },
{ }
};
+MODULE_DEVICE_TABLE(i2c, max6875_id);
static struct i2c_driver max6875_driver = {
.driver = {
diff --git a/drivers/misc/eeprom/sunxi_sid.c b/drivers/misc/eeprom/sunxi_sid.c
deleted file mode 100644
index 8385177ff..000000000
--- 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/genwqe/card_dev.c b/drivers/misc/genwqe/card_dev.c
index c49d24426..70e62d6a3 100644
--- a/drivers/misc/genwqe/card_dev.c
+++ b/drivers/misc/genwqe/card_dev.c
@@ -418,7 +418,7 @@ static void genwqe_vma_close(struct vm_area_struct *vma)
kfree(dma_map);
}
-static struct vm_operations_struct genwqe_vma_ops = {
+static const struct vm_operations_struct genwqe_vma_ops = {
.open = genwqe_vma_open,
.close = genwqe_vma_close,
};
diff --git a/drivers/misc/isl29003.c b/drivers/misc/isl29003.c
index 12c30b486..976df0013 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 e3e7f1dc2..0c3bb7e3e 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 518914a82..01447ca21 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 000000000..3e536ca85
--- /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 458aa5a09..eef1c6b46 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 6decbe136..a6c87c713 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 8d7f057f1..1c7cad07d 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 eb8683412..8504dbeac 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",
@@ -198,6 +204,8 @@ int mei_dbgfs_register(struct mei_device *dev, const char *name)
if (!dir)
return -ENOMEM;
+ dev->dbgfs_dir = dir;
+
f = debugfs_create_file("meclients", S_IRUSR, dir,
dev, &mei_dbgfs_fops_meclients);
if (!f) {
@@ -222,7 +230,6 @@ int mei_dbgfs_register(struct mei_device *dev, const char *name)
dev_err(dev->dev, "allow_fixed_address: registration failed\n");
goto err;
}
- dev->dbgfs_dir = dir;
return 0;
err:
mei_dbgfs_deregister(dev);
diff --git a/drivers/misc/mei/hbm.c b/drivers/misc/mei/hbm.c
index a4f283165..6d7c188fb 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 2544db7d1..a2025a508 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 9eb7ed70a..a8a68acd3 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 43d7101ff..65511d39d 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, &reg);
+ 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 6022d52af..2ee14dc1b 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 16fef6dc4..4cebde859 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 00c3865ca..e37466165 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 3f3405269..c418d7888 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 e9513d651..b2f2486b3 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, &notify_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(&notify_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, &notify_get);
+ if (rets)
+ goto out;
+
+ dev_dbg(dev->dev, "copy connect data to user\n");
+ if (copy_to_user((char __user *)data,
+ &notify_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 453f6a333..e25ee16c6 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 290ef3037..000000000
--- 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 23f71f5ce..27678d815 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/mei/wd.c b/drivers/misc/mei/wd.c
index 2bc0f5089..b34663883 100644
--- a/drivers/misc/mei/wd.c
+++ b/drivers/misc/mei/wd.c
@@ -364,6 +364,7 @@ int mei_watchdog_register(struct mei_device *dev)
int ret;
+ amt_wd_dev.parent = dev->dev;
/* unlock to perserve correct locking order */
mutex_unlock(&dev->device_lock);
ret = watchdog_register_device(&amt_wd_dev);
diff --git a/drivers/misc/qcom-coincell.c b/drivers/misc/qcom-coincell.c
new file mode 100644
index 000000000..7b4a2da48
--- /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/sgi-xp/xpc_uv.c b/drivers/misc/sgi-xp/xpc_uv.c
index 95c894482..340b44d9e 100644
--- a/drivers/misc/sgi-xp/xpc_uv.c
+++ b/drivers/misc/sgi-xp/xpc_uv.c
@@ -239,7 +239,7 @@ xpc_create_gru_mq_uv(unsigned int mq_size, int cpu, char *irq_name,
mq->mmr_blade = uv_cpu_to_blade_id(cpu);
nid = cpu_to_node(cpu);
- page = alloc_pages_exact_node(nid,
+ page = __alloc_pages_node(nid,
GFP_KERNEL | __GFP_ZERO | __GFP_THISNODE,
pg_order);
if (page == NULL) {
diff --git a/drivers/misc/sram.c b/drivers/misc/sram.c
index 15c33cc34..431e1dd52 100644
--- a/drivers/misc/sram.c
+++ b/drivers/misc/sram.c
@@ -186,10 +186,10 @@ static int sram_probe(struct platform_device *pdev)
if (IS_ERR(sram->virt_base))
return PTR_ERR(sram->virt_base);
- sram->pool = devm_gen_pool_create(sram->dev,
- ilog2(SRAM_GRANULARITY), -1);
- if (!sram->pool)
- return -ENOMEM;
+ sram->pool = devm_gen_pool_create(sram->dev, ilog2(SRAM_GRANULARITY),
+ NUMA_NO_NODE, NULL);
+ if (IS_ERR(sram->pool))
+ return PTR_ERR(sram->pool);
ret = sram_reserve_regions(sram, res);
if (ret)
diff --git a/drivers/misc/ti-st/st_kim.c b/drivers/misc/ti-st/st_kim.c
index de985ce28..c976d961b 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 518e1b7f2..93b4d67cc 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 b00335652..87a13374f 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 191617492..ffb56340d 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 a721b5d8a..9ec262a52 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();